diff --git a/.clang-tidy b/.clang-tidy index f1530e39578a..a62f35ce9b4a 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,6 +7,7 @@ Checks: > -misc-const-correctness, -misc-unused-parameters, -misc-non-private-member-variables-in-classes, + -misc-use-anonymous-namespace, readability-identifier-naming, bugprone-argument-comment, bugprone-assert-side-effect, diff --git a/.github/workflows/assign-reviewers.yml b/.github/workflows/assign-reviewers.yml deleted file mode 100644 index 21ccf9dc8aa8..000000000000 --- a/.github/workflows/assign-reviewers.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "Request review from code owner" -on: - pull_request_target: - branches: [ main ] - -jobs: - # Automatically request reviews from the code owner identified in a set of - # JSON files in codeowners/. - request_reviewer: - name: "Request review from code owner" - runs-on: ubuntu-latest - steps: - - name: Get CIRCT - uses: actions/checkout@v2 - with: - submodules: 'false' - - name: apply-herald-rules - id: herald - uses: gagoar/use-herald-action@master - continue-on-error: true - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - rulesLocation: codeowners/*.json - DEBUG: '*' - # Output the decisions - - name: Store response payload to file - run: echo '${{ steps.herald.outputs.appliedRules }}' diff --git a/.github/workflows/buildAndTest.yml b/.github/workflows/buildAndTest.yml index ce07c10fa955..ff272e6daa8f 100644 --- a/.github/workflows/buildAndTest.yml +++ b/.github/workflows/buildAndTest.yml @@ -19,7 +19,7 @@ jobs: # Clone the CIRCT repo and its submodules. Do shallow clone to save clone # time. - name: Get CIRCT - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 submodules: "false" @@ -89,7 +89,7 @@ jobs: if: ${{ always() }} shell: bash run: | - files=$(git diff --name-only $DIFF_COMMIT | grep -e '\.py$' || echo -n) + files=$(git diff --name-only --diff-filter=d $DIFF_COMMIT | grep -e '\.py$' || echo -n) if [[ ! -z $files ]]; then yapf --diff $files fi @@ -97,7 +97,7 @@ jobs: # Upload the format patches to an artifact (zip'd) associated # with the workflow run. Only run this on a failure. - name: Upload format patches - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 continue-on-error: true if: ${{ failure() }} with: @@ -120,41 +120,23 @@ jobs: # --- end of sanity-check job. - # Configure CIRCT using LLVM's build system ("Unified" build). We do not actually build this configuration since it isn't as easy to cache LLVM artifacts in this mode. - configure-circt-unified: - name: Configure Unified Build - runs-on: ubuntu-20.04 - steps: - # Clone the CIRCT repo and its submodules. Do shallow clone to save clone - # time. - - name: Get CIRCT - uses: actions/checkout@v2 - with: - fetch-depth: 2 - submodules: "true" - - - name: Configure Unified Build - run: | - mkdir configure_unified - cd configure_unified - cmake ../llvm/llvm \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS=ON \ - -DLLVM_ENABLE_PROJECTS=mlir \ - -DLLVM_TARGETS_TO_BUILD=host \ - -DLLVM_ENABLE_ASSERTIONS=ON \ - -DLLVM_EXTERNAL_PROJECTS=circt \ - -DLLVM_EXTERNAL_CIRCT_SOURCE_DIR=$PWD/.. - # --- end of configure-circt-unified job. # Build CIRCT and run its tests. build-circt: name: Build and Test needs: sanity-check - runs-on: ubuntu-latest + # Run on an internal MSFT subscription. Please DO NOT use this for any other + # workflows without talking to John Demme (john.demme@microsoft.com, GH + # teqdruid) first. We may lose funding for this if it ends up costing too + # much. + # If individual jobs fail due to timeouts or disconnects, please report to + # John and re-run the job. + runs-on: ["self-hosted", "1ES.Pool=1ES-CIRCT-builds", "linux"] container: image: ghcr.io/circt/images/circt-ci-build:20230126201226 + volumes: + - /mnt:/__w/circt strategy: matrix: compiler: @@ -179,7 +161,7 @@ jobs: # Clone the CIRCT repo and its submodules. Do shallow clone to save clone # time. - name: Get CIRCT - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 2 submodules: "true" @@ -195,16 +177,16 @@ jobs: # Extract the LLVM submodule hash for use in the cache key. - name: Get LLVM Hash id: get-llvm-hash - run: echo "::set-output name=hash::$(git rev-parse @:./llvm)" + run: echo "hash=$(git rev-parse @:./llvm)" >> $GITHUB_OUTPUT - name: Get workflow spec hash id: get-workflow-hash - run: echo "::set-output name=hash::$(md5sum $GITHUB_WORKSPACE/.github/workflows/buildAndTest.yml | awk '{print $1}')" + run: echo "hash=$(md5sum $GITHUB_WORKSPACE/.github/workflows/buildAndTest.yml | awk '{print $1}')" >> $GITHUB_OUTPUT # Try to fetch LLVM from the cache. - name: Cache LLVM id: cache-llvm - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | llvm/build/bin/llvm-lit @@ -302,7 +284,7 @@ jobs: # Upload the tidy patches to an artifact (zip'd) associated # with the workflow run. Only run this on a failure. - name: Upload tidy patches - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 continue-on-error: true if: ${{ failure() }} with: diff --git a/.github/workflows/buildAndTestWindows.yml b/.github/workflows/buildAndTestWindows.yml index 420ab9c1ac25..eba550b4e7a6 100644 --- a/.github/workflows/buildAndTestWindows.yml +++ b/.github/workflows/buildAndTestWindows.yml @@ -1,6 +1,7 @@ name: Windows build and test -# Run on request and every day at 12 noon UTC +# Run on main (after PRs land) to ensure that they aren't heinously breaking the +# Windows build. on: push: branches: @@ -11,76 +12,13 @@ jobs: # Build CIRCT and run its tests. build-circt: name: Build and Test - runs-on: windows-2019 - steps: - - name: Configure Environment - run: echo "$GITHUB_WORKSPACE/llvm/install/bin" >> $GITHUB_PATH - - # Clone the CIRCT repo and its submodules. Do shallow clone to save clone - # time. - - name: Get CIRCT - uses: actions/checkout@v2 - with: - fetch-depth: 2 - submodules: "true" - - # -------- - # Restore LLVM from cache and build if it's not in there. - # -------- - - # Extract the LLVM submodule hash for use in the cache key. - - name: Get LLVM Hash - id: get-llvm-hash - run: echo "::set-output name=hash::$(git rev-parse @:./llvm)" - shell: bash - - # Try to fetch LLVM from the cache. - - name: Cache LLVM - id: cache-llvm - uses: actions/cache@v2 - with: - path: | - llvm/build/bin/llvm-lit.py - llvm/install - key: ${{ runner.os }}-llvm-relobj-${{ steps.get-llvm-hash.outputs.hash }} - - # Build LLVM if we didn't hit in the cache. Even though we build it in - # the previous job, there is a low chance that it'll have been evicted by - # the time we get here. - - name: Rebuild and Install LLVM - if: steps.cache-llvm.outputs.cache-hit != 'true' - shell: pwsh - run: | - ./utils/find-vs.ps1 - mkdir llvm/build - cd llvm/build - cmake ..\llvm -GNinja ` - -DLLVM_ENABLE_PROJECTS=mlir -DLLVM_BUILD_EXAMPLES=OFF ` - -DLLVM_TARGETS_TO_BUILD="host" ` - -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=ON ` - -DLLVM_INSTALL_UTILS=ON -DCMAKE_INSTALL_PREFIX="$(pwd)/../install" - cmake --build . --target install --config Release - - # -------- - # Build and test CIRCT in both debug and release mode. - # -------- - - # Build the CIRCT test target in release mode. The cmake scripts on - # Windows only support building with the same configuration as MLIR. - - name: Build and test CIRCT (release) - shell: pwsh - run: | - ./utils/find-vs.ps1 - mkdir build_release - cd build_release - cmake ../ -GNinja ` - -DLLVM_ENABLE_ASSERTIONS=ON ` - -DMLIR_DIR="$(pwd)/../llvm/install/lib/cmake/mlir/" ` - -DLLVM_DIR="$(pwd)/../llvm/install/lib/cmake/llvm/" ` - -DLLVM_EXTERNAL_LIT="$(pwd)/../llvm/build/bin/llvm-lit.py" ` - -DCMAKE_BUILD_TYPE=RelWithDebInfo ` - -DLLVM_LIT_ARGS="-v" - cmake --build . --config RelWithDebInfo - cmake --build . --target check-circt - - # --- end of build-circt job. + uses: ./.github/workflows/unifiedBuildTestAndInstall.yml + with: + runner: windows-2019 + cmake_build_type: release + llvm_enable_assertions: ON + build_shared_libs: OFF + llvm_force_enable_stats: ON + runTests: true + cmake_c_compiler: cl + cmake_cxx_compiler: cl diff --git a/.github/workflows/nightlyIntegrationTests.yml b/.github/workflows/nightlyIntegrationTests.yml index b6dd99ad1f09..3b3f952d2490 100644 --- a/.github/workflows/nightlyIntegrationTests.yml +++ b/.github/workflows/nightlyIntegrationTests.yml @@ -11,12 +11,23 @@ jobs: # integration testing prerequisite installed. build-circt: name: Build and Test - runs-on: ubuntu-latest + # Run on an internal MSFT subscription. Please DO NOT use this for any other + # workflows without talking to John Demme (john.demme@microsoft.com, GH + # teqdruid) first. We may lose funding for this if it ends up costing too + # much. + # If individual jobs fail due to timeouts or disconnects, please report to + # John and re-run the job. + runs-on: ["self-hosted", "1ES.Pool=1ES-CIRCT-builds", "linux"] container: - image: ghcr.io/circt/images/circt-integration-test:v11.3 + image: ghcr.io/circt/images/circt-integration-test:v12.2 + volumes: + - /mnt:/__w/circt strategy: fail-fast: false matrix: + # Please clear all new builds with John first. Any changes to this + # matrix could result in a drastically increased number of builds, which + # in turn costs more. Changes which don't result in new builds are OK. build-assert: [ON, OFF] build-shared: [ON, OFF] build-type: [Debug, Release] @@ -25,36 +36,35 @@ jobs: cxx: clang++ - cc: gcc cxx: g++ + lit-flags: ['', '--vg'] exclude: - build-type: Debug + lit-flags: --vg + - build-type: Release + lit-flags: --vg + build-assert: OFF + - build-type: Release + lit-flags: --vg + build-shared: ON + # TODO: This corner is failing and has been for some time. #5253. + - build-type: Release compiler: {cc: gcc, cxx: g++} + lit-flags: --vg steps: # Clone the CIRCT repo and its submodules. Do shallow clone to save clone # time. - name: Get CIRCT - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 1 submodules: true - # Setup Ccache - - name: Ccache setup - run: | - mkdir -p $GITHUB_WORKSPACE/${{ matrix.compiler.cc }}-${{ matrix.build-type }}-${{ matrix.build-shared }}-${{ matrix.build-assert }}/.ccache - ccache --set-config=cache_dir=$GITHUB_WORKSPACE/${{ matrix.compiler.cc }}-${{ matrix.build-type }}-${{ matrix.build-shared }}-${{ matrix.build-assert }}/.ccache - ccache --set-config=max_size=300M - ccache --set-config=compression=true - ccache -p - ccache -z - - - name: Cache Ccache directory - uses: actions/cache@v2 + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 with: - path: ${{ github.workspace }}/${{ matrix.compiler.cc }}-${{ matrix.build-type }}-${{ matrix.build-shared }}-${{ matrix.build-assert }}/.ccache - key: ${{ matrix.compiler.cc }}-${{ matrix.build-type }}-${{ matrix.build-shared }}-${{ matrix.build-assert }}-${{ github.sha }} - restore-keys: | - ${{ matrix.compiler.cc }}-${{ matrix.build-type }}-${{ matrix.build-shared }}-${{ matrix.build-assert }}- + max-size: 300M + key: nightly-${{ matrix.compiler.cc }}-${{ matrix.build-type }}-${{ matrix.build-shared }}-${{ matrix.build-assert }} # -------- # Build and test CIRCT @@ -82,9 +92,11 @@ jobs: -DLLVM_EXTERNAL_CIRCT_SOURCE_DIR=.. \ -DLLVM_TARGETS_TO_BUILD="host" \ -DLLVM_USE_LINKER=lld \ + -DLLVM_USE_SPLIT_DWARF=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DCIRCT_BINDINGS_PYTHON_ENABLED=ON \ - -DLLVM_LIT_ARGS="-v --show-unsupported" + -DESI_RUNTIME=ON \ + -DLLVM_LIT_ARGS="-v --show-unsupported ${{ matrix.lit-flags }}" - name: Test CIRCT run: | ninja -C build check-circt -j$(nproc) @@ -92,9 +104,6 @@ jobs: run: | ninja -C build check-circt-unit -j$(nproc) - name: Integration Test CIRCT + if: ${{ matrix.lit-flags == '' }} run: | ninja -C build check-circt-integration -j$(nproc) - - - name: Ccache stats - run: | - ccache -s diff --git a/.github/workflows/shortIntegrationTests.yml b/.github/workflows/shortIntegrationTests.yml index 19117c757629..c3866b74dc62 100644 --- a/.github/workflows/shortIntegrationTests.yml +++ b/.github/workflows/shortIntegrationTests.yml @@ -21,9 +21,17 @@ jobs: # integration testing prerequisite installed. build-circt: name: Build and Test - runs-on: ubuntu-latest + # Run on an internal MSFT subscription. Please DO NOT use this for any other + # workflows without talking to John Demme (john.demme@microsoft.com, GH + # teqdruid) first. We may lose funding for this if it ends up costing too + # much. + # If individual jobs fail due to timeouts or disconnects, please report to + # John and re-run the job. + runs-on: ["self-hosted", "1ES.Pool=1ES-CIRCT-builds", "linux"] container: - image: ghcr.io/circt/images/circt-integration-test:v11.3 + image: ghcr.io/circt/images/circt-integration-test:v12.2 + volumes: + - /mnt:/__w/circt strategy: # Keep the 'matrix' strategy with one data point to make it obvious that # this is one point in the overall matrix. @@ -39,15 +47,16 @@ jobs: # Clone the CIRCT repo and its submodules. Do shallow clone to save clone # time. - name: Get CIRCT - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: fetch-depth: 1 submodules: true - name: ccache - uses: hendrikmuhs/ccache-action@v1 + uses: hendrikmuhs/ccache-action@v1.2 with: - max-size: 1G + key: short-${{ matrix.compiler.cc }}-${{ matrix.build-type }}-${{ matrix.build-shared }}-${{ matrix.build-assert }} + max-size: 500M # -------- # Build and test CIRCT @@ -63,6 +72,7 @@ jobs: run: | export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" mkdir build && cd build + # In order for ccache to be effective, these flags should be kept in sync with nighly. cmake -GNinja ../llvm/llvm \ -DBUILD_SHARED_LIBS=$BUILD_SHARED \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ @@ -73,9 +83,12 @@ jobs: -DLLVM_ENABLE_PROJECTS=mlir \ -DLLVM_EXTERNAL_PROJECTS=circt \ -DLLVM_EXTERNAL_CIRCT_SOURCE_DIR=.. \ + -DLLVM_TARGETS_TO_BUILD="host" \ -DLLVM_USE_LINKER=lld \ + -DLLVM_USE_SPLIT_DWARF=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DCIRCT_BINDINGS_PYTHON_ENABLED=ON \ + -DESI_RUNTIME=ON \ -DLLVM_LIT_ARGS="-v --show-unsupported" - name: Test CIRCT run: | @@ -86,7 +99,3 @@ jobs: - name: Integration Test CIRCT run: | ninja -C build check-circt-integration -j$(nproc) - - - name: Ccache stats - run: | - ccache -s diff --git a/.github/workflows/trackLLVMChanges.yml b/.github/workflows/trackLLVMChanges.yml deleted file mode 100644 index efc05433ad67..000000000000 --- a/.github/workflows/trackLLVMChanges.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Track LLVM Changes - -on: - schedule: - # 10:00 AM UTC is 3AM Pacific Time - - cron: '0 10 * * *' - # Run this workflow on pull requests which change this workflow - pull_request: - paths: - - .github/workflows/trackLLVMChanges.yml - workflow_dispatch: - -jobs: - track-llvm-changes: - name: Track LLVM Changes - runs-on: ubuntu-20.04 - steps: - # Clone the CIRCT repo and its submodules. Do shallow clone to save clone - # time. - - name: Get CIRCT - uses: actions/checkout@v2 - with: - fetch-depth: 2 - submodules: "true" - - - name: Configure Project - run: | - mkdir build - cd build - cmake ../llvm/llvm \ - -DLLVM_USE_LINKER=lld \ - -DCMAKE_C_COMPILER=clang \ - -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_RULE_MESSAGES=OFF \ - -DCMAKE_BUILD_TYPE=Debug \ - -DBUILD_SHARED_LIBS=ON \ - -DLLVM_ENABLE_PROJECTS=mlir \ - -DLLVM_TARGETS_TO_BUILD=host \ - -DLLVM_BUILD_EXAMPLES=OFF \ - -DLLVM_INSTALL_UTILS=OFF \ - -DLLVM_ENABLE_OCAMLDOC=OFF \ - -DLLVM_ENABLE_BINDINGS=OFF \ - -DLLVM_ENABLE_ASSERTIONS=ON \ - -DLLVM_EXTERNAL_PROJECTS=circt \ - -DLLVM_EXTERNAL_CIRCT_SOURCE_DIR=$PWD/.. \ - -DLLVM_LIT_ARGS="-v" - - - name: Build current LLVM commit - id: build-current-llvm-commit - run: | - cd llvm - echo "::set-output name=sha::$(git rev-parse HEAD)" - cmake --build ../build --config Debug --target check-circt --target check-circt-unit -- -j$(nproc) - - - name: Build latest LLVM commit - id: build-latest-llvm-commit - continue-on-error: true - run: | - cd llvm - git fetch origin main - git checkout --detach origin/main - echo "::set-output name=sha::$(git rev-parse HEAD)" - cmake --build ../build --config Debug --target check-circt --target check-circt-unit -- -j$(nproc) - - - name: Bisect commits - if: steps.build-latest-llvm-commit.outcome != 'success' - run: | - cd llvm - git bisect start ${{ steps.build-latest-llvm-commit.outputs.sha }} ${{ steps.build-current-llvm-commit.outputs.sha }} -- mlir llvm - git bisect run cmake --build ../build --config Debug --target check-circt --target check-circt-unit -- -j$(nproc) - - # Summarize Results (re-run tests to make the log easier to parse) - - name: Summarize Results - if: steps.build-latest-llvm-commit.outcome != 'success' - run: | - cd llvm - git bisect log - FIRST_BAD_COMMIT=$(git bisect log | sed -n 's/# first bad commit: \[\([0-9a-f]*\)\].*/\1/p') - git checkout $FIRST_BAD_COMMIT - cmake --build ../build --config Debug --target check-circt --target check-circt-unit -- -j$(nproc) - - # --- end of track-llvm-changes job. diff --git a/.github/workflows/unifiedBuildTestAndInstall.yml b/.github/workflows/unifiedBuildTestAndInstall.yml new file mode 100644 index 000000000000..b9b2e83dd94d --- /dev/null +++ b/.github/workflows/unifiedBuildTestAndInstall.yml @@ -0,0 +1,277 @@ +name: Build, Test, and Install + +on: + workflow_dispatch: + inputs: + runner: + description: "The GitHub runner to use" + required: true + type: choice + options: + - ubuntu-22.04 + - ubuntu-20.04 + - macos-13 + - macos-12 + - macos-11 + - windows-2022 + - windows-2019 + cmake_build_type: + required: true + type: choice + options: + - release + - relwithdebinfo + - debug + default: relwithdebinfo + build_shared_libs: + required: true + type: choice + options: + - on + - off + default: off + llvm_enable_assertions: + required: true + type: choice + options: + - on + - off + llvm_force_enable_stats: + required: true + type: choice + options: + - on + - off + runTests: + description: "Run tests" + required: true + type: boolean + default: false + install: + description: "Install steps to run (empty if do not install)" + required: false + type: string + package_name_prefix: + description: "The prefix for package name (has no effect unless \"install\" is set)" + required: false + type: string + cmake_c_compiler: + description: "The C compiler to use" + required: true + type: choice + options: + - clang + - clang-10 + - clang-11 + - clang-12 + - clang-13 + - clang-14 + - gcc + - cl + default: clang + cmake_cxx_compiler: + description: "The C++ compiler to use" + required: true + type: choice + options: + - clang++ + - clang++-10 + - clang++-11 + - clang++-12 + - clang++-13 + - clang++-14 + - gcc++ + - cl + default: clang++ + workflow_call: + inputs: + runner: + description: "The GitHub runner to use" + required: true + type: string + cmake_build_type: + required: true + type: string + build_shared_libs: + required: true + type: string + llvm_enable_assertions: + required: true + type: string + llvm_force_enable_stats: + required: true + type: string + runTests: + description: "If true, then run tests, otherwise skip tests" + required: true + type: boolean + install: + description: "Install steps to run" + required: false + type: string + package_name_prefix: + description: "The prefix for package name" + required: false + type: string + cmake_c_compiler: + description: "The C compiler to use" + required: false + type: string + default: clang + cmake_cxx_compiler: + description: "The C++ compiler to use" + required: false + type: string + default: clang++ + +jobs: + build-test-and-install: + runs-on: ${{ inputs.runner }} + steps: + - name: Clone llvm/circt + uses: actions/checkout@v3 + with: + fetch-depth: 2 + submodules: "true" + - name: Unshallow llvm/circt + run: | + git fetch --unshallow --no-recurse-submodules +# Per-operating system setup + - name: Setup (linux) + if: runner.os == 'Linux' + run: | + sudo apt-get install ninja-build + - name: Setup (macos) + if: runner.os == 'macOS' + run: | + brew install ninja gnu-tar + - name: Setup (windows) + id: setup-windows + if: runner.os == 'Windows' + shell: bash + run: | + echo setup=./utils/find-vs.ps1 >> "$GITHUB_OUTPUT" +# Setup Caching +# +# Use sccache as it works on Windows. Disable caching for non-release Windows +# builds due to a bug between cmake and sccache. See: +# - https://gitlab.kitware.com/cmake/cmake/-/issues/22529 + - name: sccache + if: runner.os != 'Windows' || inputs.cmake_build_type == 'release' + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ runner.os }}-${{ inputs.cmake_c_compiler }}-${{ inputs.cmake_cxx_compiler }}-${{ inputs.cmake_build_type }}-${{ inputs.build_shared_libs }}-${{ inputs.llvm_enable_assertions }}-${{ inputs.llvm_force_enable_stats}} + max-size: 500M + variant: sccache + - name: Configure sccache + id: configure-sccache + if: runner.os != 'Windows' || inputs.cmake_build_type == 'release' + shell: bash + run: + echo enable_sccache="-DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache" >> $GITHUB_OUTPUT +# Configure + - name: Configure CIRCT + run: | + ${{ steps.setup-windows.outputs.setup }} + mkdir build + cd build + cmake -G Ninja -S "$(pwd)/../llvm/llvm" $EXTRA_CMAKE_ARGS -DCMAKE_BUILD_TYPE=${{ inputs.cmake_build_type }} -DBUILD_SHARED_LIBS=${{ inputs.build_shared_libs }} -DLLVM_BUILD_TOOLS=ON -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_ENABLE_ASSERTIONS=${{ inputs.llvm_enable_assertions }} -DLLVM_ENABLE_PROJECTS=mlir -DLLVM_EXTERNAL_PROJECTS=circt -DLLVM_EXTERNAL_CIRCT_SOURCE_DIR=".." -DLLVM_STATIC_LINK_CXX_STDLIB=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_PARALLEL_LINK_JOBS=1 -DLLVM_TARGETS_TO_BUILD="host" -DLLVM_FORCE_ENABLE_STATS=${{ inputs.llvm_force_enable_stats }} -DLLVM_ENABLE_ZSTD=OFF -DVERILATOR_DISABLE=ON -DLLVM_PARALLEL_LINK_JOBS=1 -DCIRCT_RELEASE_TAG_ENABLED=ON -DCIRCT_RELEASE_TAG=firtool -DCMAKE_EXPORT_COMPILE_COMMANDS=OFF -DCMAKE_C_COMPILER=${{ inputs.cmake_c_compiler }} -DCMAKE_CXX_COMPILER=${{ inputs.cmake_cxx_compiler }} ${{ steps.configure-sccache.outputs.enable_sccache }} -DCMAKE_INSTALL_PREFIX="$(pwd)/../install" -DLLVM_INSTALL_UTILS=ON +# Optionally test + - name: Test CIRCT + if: inputs.runTests + run: | + ${{ steps.setup-windows.outputs.setup }} + ninja -C build check-circt check-circt-unit + - name: Install + if: inputs.install + run: | + ${{ steps.setup-windows.outputs.setup }} + ninja -C build ${{ inputs.install }} + file install/* + file install/bin/* + - name: Name Install Directory + id: name_dir + if: inputs.install + shell: bash + run: | + BASE=$(git describe --tag) + SANITIZED=$(echo -n $BASE | tr '/' '-') + echo "value=$SANITIZED" >> "$GITHUB_OUTPUT" + + ARCH=$(echo ${{ runner.arch }} | tr '[:upper:]' '[:lower:]') + echo arch="$ARCH" >> $GITHUB_OUTPUT + + OS=$(echo ${{ runner.os }} | tr '[:upper:]' '[:lower:]') + echo os="$OS" >> $GITHUB_OUTPUT + + ARCHIVE= + if [[ ${{ runner.os }} == 'Windows' ]]; then + ARCHIVE="zip" + else + ARCHIVE="tar.gz" + fi + echo archive="$ARCHIVE" >> $GITHUB_OUTPUT + + TAR= + if [[ ${{ runner.os }} == 'macOS' ]]; then + TAR="gtar czf" + else + TAR="tar czf" + fi + echo tar="$TAR" >> $GITHUB_OUTPUT + + SHA256= + if [[ ${{ runner.os }} == 'Windows' ]]; then + SHA256="sha256sum" + else + SHA256="shasum -a 256" + fi + echo sha256="$SHA256" >> $GITHUB_OUTPUT + - name: Name Archive + id: name_archive + if: inputs.install + shell: bash + run: | + NAME=${{ inputs.package_name_prefix }}-${{ steps.name_dir.outputs.os }}-${{ steps.name_dir.outputs.arch }}.${{ steps.name_dir.outputs.archive }} + echo "name=$NAME" >> "$GITHUB_OUTPUT" + - name: Package Binaries Linux and MacOS + if: inputs.install && ( runner.os == 'macOS' || runner.os == 'Linux' ) + run: | + mv install ${{ steps.name_dir.outputs.value }} + ${{ steps.name_dir.outputs.tar }} ${{ steps.name_archive.outputs.name }} ${{ steps.name_dir.outputs.value }} + - name: Package Binaries Windows + if: inputs.install && runner.os == 'Windows' + shell: pwsh + run: | + mv install ${{ steps.name_dir.outputs.value }} + Compress-Archive -Path ${{ steps.name_dir.outputs.value }} -DestinationPath ${{ steps.name_archive.outputs.name }} + - name: Show Tarball + if: inputs.install + shell: bash + run: | + ls -l ${{ steps.name_archive.outputs.name }} + ${{ steps.name_dir.outputs.sha256 }} ${{ steps.name_archive.outputs.name }} | cut -d ' ' -f1 > ${{ steps.name_archive.outputs.name }}.sha256 + +# Upload build artifacts + - name: Upload Binary (Non-Tag) + uses: actions/upload-artifact@v3 + if: inputs.install && github.ref_type != 'tag' + with: + name: ${{ steps.name_archive.outputs.name }} + path: ${{ steps.name_archive.outputs.name }} + retention-days: 7 + - name: Upload SHA256 (Non-Tag) + uses: actions/upload-artifact@v3 + if: inputs.install && github.ref_type != 'tag' + with: + name: ${{ steps.name_archive.outputs.name }}.sha256 + path: ${{ steps.name_archive.outputs.name }}.sha256 + retention-days: 7 + + - name: Upload Binaries (Tag) + uses: AButler/upload-release-assets@v2.0 + if: inputs.install && github.ref_type == 'tag' + with: + # The * will grab the .sha256 as well + files: ${{ steps.name_archive.outputs.name }}* + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/uploadFirrtlBinaries.yml b/.github/workflows/uploadFirrtlBinaries.yml deleted file mode 100644 index aedeca29ef37..000000000000 --- a/.github/workflows/uploadFirrtlBinaries.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: Upload Firrtl Binaries - -on: - release: - types: [created] - workflow_dispatch: - -jobs: - publish: - strategy: - matrix: - build_config: - - mode: release - assert: OFF - shared: OFF - stats: ON - cmake-args: '' - runner: [ubuntu-20.04, ubuntu-18.04, macos-11] - include: - - runner: ubuntu-20.04 - os: linux - tar: tar - # Default clang (11) is broken, see LLVM issue 59622. - cc: clang-12 - cxx: clang++-12 - - runner: ubuntu-18.04 - os: linux - tar: tar - cc: clang - cxx: clang++ - - runner: macos-11 - os: macos - tar: gtar - cc: clang - cxx: clang++ - runs-on: ${{ matrix.runner }} - steps: - # Clone the CIRCT repo and its submodules. Do shallow clone to save clone - # time. - - name: Get CIRCT - uses: actions/checkout@v2 - with: - fetch-depth: 2 - submodules: "true" - - # We need unshallow CIRCT for later "git describe" - - name: Unshallow CIRCT (but not LLVM) - run: | - git fetch --unshallow --no-recurse-submodules - - - name: Setup Ninja Linux - if: matrix.os == 'linux' - run: sudo apt-get install ninja-build - - - name: Setup Ninja and GNU Tar Mac - if: matrix.os == 'macos' - run: brew install ninja gnu-tar - - - name: Build LLVM - run: | - mkdir -p llvm/build - cd llvm/build - cmake -G Ninja ../llvm \ - ${{ matrix.build_config.cmake-args }} \ - -DCMAKE_BUILD_TYPE=${{ matrix.build_config.mode }} \ - -DCMAKE_C_COMPILER=${{ matrix.cc }} \ - -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \ - -DBUILD_SHARED_LIBS=${{ matrix.build_config.shared }} \ - -DLLVM_BUILD_TOOLS=OFF \ - -DLLVM_BUILD_EXAMPLES=OFF \ - -DLLVM_ENABLE_ASSERTIONS=${{ matrix.build_config.assert }} \ - -DLLVM_ENABLE_BINDINGS=OFF \ - -DLLVM_ENABLE_OCAMLDOC=OFF \ - -DLLVM_ENABLE_PROJECTS='mlir' \ - -DLLVM_OPTIMIZED_TABLEGEN=ON \ - -DLLVM_STATIC_LINK_CXX_STDLIB=ON \ - -DLLVM_ENABLE_TERMINFO=OFF \ - -DLLVM_PARALLEL_LINK_JOBS=1 \ - -DLLVM_TARGETS_TO_BUILD="host" \ - -DLLVM_FORCE_ENABLE_STATS=${{ matrix.build_config.stats }} \ - -DLLVM_ENABLE_ZSTD=OFF - ninja - #Checks temporarily disabled because current LLVM commit (9305b63d6) fails checks - #ninja check-llvm check-mlir - - # -------- - # Build and test CIRCT - # -------- - - - name: Build and Test CIRCT - run: | - mkdir build - cd build - cmake -G Ninja .. \ - ${{ matrix.build_config.cmake-args }} \ - -DBUILD_SHARED_LIBS=${{ matrix.build_config.shared }} \ - -DCMAKE_BUILD_TYPE=${{ matrix.build_config.mode }} \ - -DLLVM_ENABLE_ASSERTIONS=${{ matrix.build_config.assert }} \ - -DMLIR_DIR=`pwd`/../llvm/build/lib/cmake/mlir \ - -DLLVM_DIR=`pwd`/../llvm/build/lib/cmake/llvm \ - -DCMAKE_C_COMPILER=${{ matrix.cc }} \ - -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \ - -DVERILATOR_DISABLE=ON \ - -DLLVM_ENABLE_TERMINFO=OFF \ - -DLLVM_STATIC_LINK_CXX_STDLIB=ON \ - -DLLVM_PARALLEL_LINK_JOBS=1 \ - -DLLVM_FORCE_ENABLE_STATS=${{ matrix.build_config.stats }} \ - -DCIRCT_RELEASE_TAG_ENABLED=ON \ - -DCIRCT_RELEASE_TAG=firtool \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=OFF \ - -DCMAKE_INSTALL_PREFIX=`pwd`/../install - ninja - ninja check-circt check-circt-unit - ninja install-firtool - cd .. - - - name: Display Files - run: | - file install/* - file install/bin/* - - - name: Name Install Directory - id: name_dir - run: | - BASE=$(git describe --tag) - SANITIZED=$(echo -n $BASE | tr '/' '-') - echo "value=$SANITIZED" >> "$GITHUB_OUTPUT" - - - name: Package Binaries - run: | - mv install ${{ steps.name_dir.outputs.value }} - ${{ matrix.tar }} czf firrtl-bin-${{ matrix.runner }}.tar.gz ${{ steps.name_dir.outputs.value }} - - name: Show Tarball - run: | - ls -l firrtl-bin-${{ matrix.runner }}.tar.gz - shasum -a 256 firrtl-bin-${{ matrix.runner }}.tar.gz - - name: Upload Binaries (Non-Tag) - uses: actions/upload-artifact@v3 - if: github.ref_type != 'tag' - with: - name: firrtl-bin-${{ matrix.runner }}.tar.gz - path: firrtl-bin-${{ matrix.runner }}.tar.gz - retention-days: 7 - - name: Upload Binaries (Tag) - uses: AButler/upload-release-assets@v2.0 - if: startsWith(github.ref, 'refs/tags/') - with: - files: firrtl-bin-${{ matrix.runner }}.tar.gz - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/uploadReleaseArtifacts.yml b/.github/workflows/uploadReleaseArtifacts.yml new file mode 100644 index 000000000000..43c5e0858cd1 --- /dev/null +++ b/.github/workflows/uploadReleaseArtifacts.yml @@ -0,0 +1,188 @@ +name: Upload Release Artifacts + +on: + release: + types: [created] + workflow_dispatch: + inputs: + os: + type: choice + description: Operating System target + default: linux + options: + - linux + - macos + - windows + runTests: + type: choice + description: Run CIRCT tests + default: false + options: + - true + - false + # Run every day at 0700 UTC which is: + # - 0000 PDT / 2300 PST + # - 0300 EDT / 0200 EST + schedule: + - cron: '0 7 * * *' + +jobs: + publish-sources: + if: github.ref_type == 'tag' + runs-on: ubuntu-20.04 + steps: + # Clone the CIRCT repo and its submodules. Do shallow clone to save clone + # time. + - name: Get CIRCT and LLVM + uses: actions/checkout@v3 + with: + fetch-depth: 2 + submodules: "true" + + # Package up sources for distribution, as the default source bundles from GitHub don't include LLVM. + - name: Create Source Archive + run: | + touch circt-full-sources.tar.gz + tar \ + --exclude-vcs \ + --exclude=circt-full-sources.tar.gz \ + -czf \ + circt-full-sources.tar.gz . + shasum -a 256 circt-full-sources.tar.gz | cut -d ' ' -f1 > circt-full-sources.tar.gz.sha256 + + - name: Upload Source Archive + uses: AButler/upload-release-assets@v2.0 + with: + # The * will grab the .sha256 as well + files: circt-full-sources.tar.gz* + repo-token: ${{ secrets.GITHUB_TOKEN }} + + # This job sets up the build matrix. + choose-matrix: + runs-on: ubuntu-20.04 + steps: + - name: Add Linux + id: add-linux + if: github.event_name != 'workflow_dispatch' || inputs.os == 'linux' + env: + os: linux + runner: ubuntu-20.04 + arch: x64 + tar: tar czf + archive: tar.gz + sha256: shasum -a 256 + cont: '\' + setup: + cmake_c_compiler: clang-12 + cmake_cxx_compiler: clang++-12 + run: | + json=$(echo '${{ toJSON(env) }}' | jq -c) + echo $json + echo "out=$json" >> $GITHUB_OUTPUT + - name: Add macOS + id: add-macos + if: github.event_name == 'release' || ( github.event_name == 'workflow_dispatch' && inputs.os == 'macos' ) + env: + os: macos + runner: macos-11 + arch: x64 + tar: gtar czf + archive: tar.gz + sha256: shasum -a 256 + cont: '\' + setup: + cmake-args: + cmake_c_compiler: clang + cmake_cxx_compiler: clang++ + run: | + json=$(echo '${{ toJSON(env) }}' | jq -c) + echo $json + echo "out=$json" >> $GITHUB_OUTPUT + - name: Add Windows + id: add-windows + if: github.event_name == 'release' || ( github.event_name == 'workflow_dispatch' && inputs.os == 'windows' ) + env: + os: windows + runner: windows-2019 + arch: x64 + tar: tar czf + archive: zip + sha256: sha256sum + cont: '`' + setup: ./utils/find-vs.ps1 + cmake-args: + cmake_c_compiler: cl + cmake_cxx_compiler: cl + run: | + json=$(echo '${{ toJSON(env) }}' | jq -c) + echo $json + echo "out=$json" >> $GITHUB_OUTPUT + - name: Add Build Config for firtool and om-linker + id: add-build-config-firtool + run: | + json='{"name":"firtool","install_target":"install-firtool install-om-linker","package_name_prefix":"firrtl-bin","mode":"release","assert":"OFF","shared":"OFF","stats":"ON"}' + if [[ ${{ github.event_name }} == 'schedule' ]] || [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then + json=$(echo $json | jq -c '.assert = "ON" | .mode = "relwithdebinfo"') + fi + echo "out=$json" >> $GITHUB_OUTPUT + - name: Add Build Config for CIRCT-full (shared) + id: add-build-config-circt-full-shared + run: | + json='{"name":"CIRCT-full shared","install_target":"install","package_name_prefix":"circt-full-shared","mode":"release","assert":"OFF","shared":"ON","stats":"ON"}' + echo "out=$json" >> $GITHUB_OUTPUT + - name: Build JSON Payloads + id: build-json-payloads + run: | + echo '${{ steps.add-build-config-firtool.outputs.out }}' '${{ steps.add-build-config-circt-full-shared.outputs.out }}' | jq -sc . > build_configs.json + echo '${{ steps.add-linux.outputs.out }}' '${{ steps.add-macos.outputs.out }}' '${{ steps.add-windows.outputs.out }}' | jq -sc . > runners.json + + cat runners.json build_configs.json | jq -sc '[combinations | add]' > matrix-raw.json + + # Exclude job, `BUILD_SHARED_LIBS` option is not supported on Windows + cat matrix-raw.json | jq -c 'del(.[] | select(.os == "windows" and .shared == "ON"))' > matrix.json + echo matrix=`cat matrix.json` >> $GITHUB_OUTPUT + + echo "Generated Matrix" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`json" >> $GITHUB_STEP_SUMMARY + cat matrix.json | jq >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + + case ${{ github.event_name }} in + workflow_dispatch) + ENV='{"runTests":${{ inputs.runTests }}}' + ;; + schedule) + ENV='{"runTests":false}' + ;; + *) + ENV='{"runTests":true}' + ;; + esac + echo environment=$ENV >> $GITHUB_OUTPUT + + echo "Environment Configuration:" >> $GITHUB_STEP_SUMMARY + echo "\`\`\`json" >> $GITHUB_STEP_SUMMARY + echo $ENV | jq >> $GITHUB_STEP_SUMMARY + echo "\`\`\`" >> $GITHUB_STEP_SUMMARY + outputs: + matrix: ${{ steps.build-json-payloads.outputs.matrix }} + environment: ${{ steps.build-json-payloads.outputs.environment }} + + publish: + needs: + - choose-matrix + strategy: + matrix: + generated: ${{ fromJSON(needs.choose-matrix.outputs.matrix) }} + uses: ./.github/workflows/unifiedBuildTestAndInstall.yml + with: + runner: ${{ matrix.generated.runner }} + cmake_build_type: ${{ matrix.generated.mode }} + build_shared_libs: ${{ matrix.generated.shared }} + llvm_enable_assertions: ${{ matrix.generated.assert }} + llvm_force_enable_stats: ${{ matrix.generated.stats }} + runTests: ${{ fromJSON(needs.choose-matrix.outputs.environment).runTests }} + install: ${{ matrix.generated.install_target }} + package_name_prefix: ${{ matrix.generated.package_name_prefix }} + cmake_c_compiler: ${{ matrix.generated.cmake_c_compiler }} + cmake_cxx_compiler: ${{ matrix.generated.cmake_cxx_compiler }} diff --git a/.github/workflows/uploadWheels.yml b/.github/workflows/uploadWheels.yml index 8ac83cdbbd66..8ad5c09cc81c 100644 --- a/.github/workflows/uploadWheels.yml +++ b/.github/workflows/uploadWheels.yml @@ -4,6 +4,8 @@ on: release: types: [created] workflow_dispatch: + schedule: + - cron: 0 12 * * * jobs: build_wheels: @@ -11,12 +13,25 @@ jobs: runs-on: ${{ matrix.config.os }} if: github.repository == 'llvm/circt' strategy: + fail-fast: false matrix: config: - os: ubuntu-20.04 - cibw_build: cp37-manylinux_x86_64 + cibw_build: cp38-manylinux_x86_64 + - os: ubuntu-20.04 + cibw_build: cp39-manylinux_x86_64 - os: ubuntu-20.04 cibw_build: cp310-manylinux_x86_64 + - os: ubuntu-20.04 + cibw_build: cp311-manylinux_x86_64 + - os: macos-latest + cibw_build: cp38-macosx_x86_64 + - os: macos-latest + cibw_build: cp39-macosx_x86_64 + - os: macos-latest + cibw_build: cp310-macosx_x86_64 + - os: macos-latest + cibw_build: cp311-macosx_x86_64 steps: - name: Get CIRCT @@ -27,7 +42,7 @@ jobs: - name: Unshallow CIRCT run: | - git fetch --unshallow --tags --no-recurse-submodules + git fetch --force --unshallow --tags --no-recurse-submodules - name: Setup Python uses: actions/setup-python@v3 @@ -43,16 +58,33 @@ jobs: CIBW_BUILD_FRONTEND: build SETUPTOOLS_SCM_DEBUG: True - - name: Upload wheels (Non-Tag) + - name: Upload (stage) wheels as artifacts uses: actions/upload-artifact@v3 - if: github.ref_type != 'tag' with: + name: python-wheels path: ./wheelhouse/*.whl retention-days: 7 + if-no-files-found: error + + push_wheels: + name: Push wheels (Tag or Nightly) + runs-on: ubuntu-20.04 + if: github.repository == 'llvm/circt' && (github.ref_type == 'tag' || github.event_name == 'schedule') + needs: build_wheels + + steps: + - name: Download wheels + uses: actions/download-artifact@v3 + with: + name: python-wheels + path: ./wheelhouse/ + + - name: List downloaded wheels + run: ls -laR + working-directory: ./wheelhouse/ - - name: Upload wheels (Tag) + - name: Upload wheels to pypi uses: pypa/gh-action-pypi-publish@release/v1 - if: github.ref_type == 'tag' with: password: ${{ secrets.PYPI_CIRCT_API_TOKEN }} packages-dir: wheelhouse/ diff --git a/.vscode/Unified.code-workspace.example.jsonc b/.vscode/Unified.code-workspace.example.jsonc index f9857041f499..d5962692b69e 100644 --- a/.vscode/Unified.code-workspace.example.jsonc +++ b/.vscode/Unified.code-workspace.example.jsonc @@ -32,7 +32,7 @@ "cmake.configureArgs": [ // LLVM "-DLLVM_ENABLE_PROJECTS=mlir", - "-DLLVM_TARGETS_TO_BUILD=X86;RISCV", + "-DLLVM_TARGETS_TO_BUILD=host", "-DLLVM_ENABLE_ASSERTIONS=ON", "-DLLVM_BUILD_EXAMPLES=OFF", "-DLLVM_ENABLE_OCAMLDOC=OFF", @@ -48,7 +48,8 @@ // Circt "-DLLVM_EXTERNAL_PROJECTS=circt", - "-DLLVM_EXTERNAL_CIRCT_SOURCE_DIR=${workspaceFolder:CIRCT}" + "-DLLVM_EXTERNAL_CIRCT_SOURCE_DIR=${workspaceFolder:CIRCT}", + "-DCIRCT_ENABLE_LLHD_SIM=OFF", ] }, "extensions": { diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a68a3cf88c7..29f6852d492d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,14 +49,11 @@ if( CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR ) # Options and settings #------------------------------------------------------------------------------- - option(LLVM_INCLUDE_TOOLS "Generate build targets for the LLVM tools." ON) - option(LLVM_BUILD_TOOLS "Build the LLVM tools. If OFF, just generate build targets." ON) - -if (MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHs-c- /GR-") -else () - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") -endif () + if (MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHs-c- /GR-") + else () + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti") + endif () #------------------------------------------------------------------------------- # MLIR/LLVM Configuration @@ -84,12 +81,16 @@ endif () # Handle unittests when building out-of-tree against an installed version of # LLVM/MLIR (not a build tree). Adapted from `llvm/flang/CMakeLists.txt`. set(CIRCT_GTEST_AVAILABLE 0) - set(UNITTEST_DIR ${LLVM_THIRD_PARTY_DIR}/unittest) - if (NOT EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h) - set(UNITTEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/llvm/third-party/unittest) - endif() - if (EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h) - if (NOT TARGET llvm_gtest) + if (TARGET llvm_gtest) + # Installed gtest, via LLVM_INSTALL_GTEST. Preferred. + message(STATUS "LLVM GTest found, enabling unittests") + set(CIRCT_GTEST_AVAILABLE 1) + else() + set(UNITTEST_DIR ${LLVM_THIRD_PARTY_DIR}/unittest) + if (NOT EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h) + set(UNITTEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/llvm/third-party/unittest) + endif() + if (EXISTS ${UNITTEST_DIR}/googletest/include/gtest/gtest.h) find_package(Threads) add_llvm_library(llvm_gtest ${UNITTEST_DIR}/googletest/src/gtest-all.cc @@ -112,12 +113,12 @@ endif () LINK_COMPONENTS Support # llvm::cl BUILDTREE_ONLY ) + set(CIRCT_GTEST_AVAILABLE 1) + else() + message(WARNING "Skipping unittests since LLVM install does not include \ + gtest headers and libraries") + set(CIRCT_GTEST_AVAILABLE 0) endif() - set(CIRCT_GTEST_AVAILABLE 1) - else() - message(WARNING "Skipping unittests since LLVM install does not include \ - gtest headers and libraries") - set(CIRCT_GTEST_AVAILABLE 0) endif() else() @@ -169,6 +170,12 @@ set(CIRCT_TOOLS_DIR ${CMAKE_BINARY_DIR}/bin) set(CIRCT_UTILS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/utils) set(CIRCT_PYTHON_PACKAGES_DIR ${CIRCT_BINARY_DIR}/python_packages) + +option(CIRCT_INCLUDE_TOOLS "Generate build targets for the CIRCT tools." ON) +option(CIRCT_BUILD_TOOLS "Build the CIRCT tools. If OFF, just generate build targets." ON) +set(CIRCT_TOOLS_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}" CACHE PATH + "Path for binary subdirectory (defaults to '${CMAKE_INSTALL_BINDIR}')") + list(APPEND CMAKE_MODULE_PATH "${MLIR_MAIN_SRC_DIR}/cmake/modules") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") include(AddCIRCT) @@ -390,7 +397,7 @@ else() string(REGEX MATCH "Icarus Verilog version (([0-9]+)\.([0-9]+)) \.*" MATCH ${IVERILOG_VERSION}) - + if (${CMAKE_MATCH_1} LESS 11.0) message(FATAL_ERROR "CIRCT only supports Icarus Verilog version 11.0 and up. \ Found version: ${CMAKE_MATCH_1}. You can disable \ @@ -404,32 +411,6 @@ else() endif() endif() -#------------------------------------------------------------------------------- -# capnp Configuration -#------------------------------------------------------------------------------- - -# If capnp hasn't been explicitly disabled, find it. -option(CAPNP_DISABLE "Disable Cap'nProto (needed for cosimulation).") -if (CAPNP_DISABLE) - message (STATUS "Disabling Cap'nProto.") -else() - if(DEFINED CAPNP_PATH) - set(ENV{PKG_CONFIG_PATH} - "${CAPNP_PATH}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") - find_package(CapnProto CONFIG PATHS ${CAPNP_PATH}) - else() - set(ENV{PKG_CONFIG_PATH} - "${CMAKE_CURRENT_SOURCE_DIR}/ext/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") - find_package(CapnProto CONFIG PATHS "${CMAKE_CURRENT_SOURCE_DIR}/ext") - endif() - - if(CapnProto_FOUND) - message(STATUS "Found Cap'nProto at ${CapnProto_DIR}.") - set(CMAKE_INSTALL_RPATH ${capnp_LIBDIR}) - set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) - endif() -endif() - #------------------------------------------------------------------------------- # OR-Tools Configuration #------------------------------------------------------------------------------- @@ -472,6 +453,41 @@ endif() llvm_canonicalize_cmake_booleans(CIRCT_LLHD_SIM_ENABLED) +#------------------------------------------------------------------------------- +# circt-lec Configuration +#------------------------------------------------------------------------------- + +# If circt-lec hasn't been explicitly disabled, find it. +option(CIRCT_LEC_DISABLE "Disable the Logical Equivalence Checker" OFF) +if(CIRCT_LEC_DISABLE) + message(STATUS "Disabling circt-lec") +else() + if(Z3_DIR) + # Search and load the package configuration file in the specified directory. + find_package(Z3 CONFIG REQUIRED PATHS ${Z3_DIR} NO_DEFAULT_PATH) + if(Z3_FOUND) + # Report the found library location and version + # similarly to LLVM's `FindZ3` CMake module. + get_target_property(Z3_LIB_LOCATION z3::libz3 IMPORTED_LOCATION_DEBUG) + message(STATUS "Found Z3: ${Z3_LIB_LOCATION} (found version \"${Z3_VERSION_STRING}\")") + endif() + else() + # Attempt initialising Z3 according to LLVM's `FindZ3` CMake module. + find_package(Z3) + endif() + + if(Z3_FOUND) + SET(CIRCT_LEC_Z3_VER 4.8.11) + if(Z3_VERSION_STRING VERSION_LESS ${CIRCT_LEC_Z3_VER}) + message(WARNING "Cannot build circt-lec with outdated Z3 version ${Z3_VERSION_STRING}, requires ${CIRCT_LEC_Z3_VER}.") + else() + message(STATUS "Z3 identified as a logical backend.") + # Signal to proceed building circt-lec. + set(CIRCT_LEC_ENABLED ON) + endif() + endif() +endif() + #------------------------------------------------------------------------------- # Python Configuration #------------------------------------------------------------------------------- @@ -513,7 +529,9 @@ endif() add_subdirectory(include/circt) add_subdirectory(lib) -add_subdirectory(tools) +if(CIRCT_INCLUDE_TOOLS) + add_subdirectory(tools) +endif() if (CIRCT_GTEST_AVAILABLE) add_subdirectory(unittests) endif() diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000000..adeaaa75e429 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,62 @@ +## Dialects, Conversions, Tools + +# Arc +**/Dialect/Arc @fabianschuiki @maerhart +tools/arcilator @fabianschuiki @maerhart + +# Calyx +**/Dialect/Calyx @rachitnigam @cgyurgyik @mikeurbach @andrewb1999 +**/Conversion/CalyxToFSM @mortbopet +**/Conversion/CalyxToHW @mortbopet @mikeurbach +**/Conversion/SCFToCalyx @mikeurbach @mortbopet +**/Conversion/CalyxNative @rachitnigam +**/Conversion/LoopScheduleToCalyx @andrewb1999 @mikeurbach + +# DC +**/Dialect/DC @mortbopet + +# ESI +**/Dialect/ESI @teqdruid + +# FIRRTL +**/FIRRTL* @darthscsi @dtzSiFive @seldridge +*/firtool @dtzSiFive +libs/Firtool @dtzSiFive + +# FSM +**/Dialect/FSM @mortbopet + +# HWArith +**/Dialect/HWArith @mortbopet @teqdruid + +# LLHD +**/LLHD* @fabianschuiki @maerhart +tools/llhd-sim @fabianschuiki @maerhart + +# Pipeline +**/Dialect/Pipeline @mortbopet + +# Handshake +**/Handshake* @Dinistro @mortbopet @RamirezLucas + +# HW +# **/Dialect/HW + +# SV +# **/Dialect/SV + +# Ibis +**/Dialect/Ibis @teqdruid @mortbopet @blakep-msft + +## Conversions + +lib/Conversion/ExportVerilog @dtzSiFive + + +## Infra + +# CI +.github @teqdruid @dtzSiFive + +# PrettyPrinter +**/Support/Pretty* @dtzSiFive diff --git a/cmake/modules/AddCIRCT.cmake b/cmake/modules/AddCIRCT.cmake index e9f9c59d0429..7cd758f747ac 100644 --- a/cmake/modules/AddCIRCT.cmake +++ b/cmake/modules/AddCIRCT.cmake @@ -33,15 +33,62 @@ function(add_circt_dialect_doc dialect dialect_namespace) endfunction() function(add_circt_library name) - add_mlir_library(${ARGV}) + add_mlir_library(${ARGV} DISABLE_INSTALL) add_circt_library_install(${name}) endfunction() +macro(add_circt_executable name) + add_llvm_executable(${name} ${ARGN}) + set_target_properties(${name} PROPERTIES FOLDER "circt executables") +endmacro() + +macro(add_circt_tool name) + if (NOT CIRCT_BUILD_TOOLS) + set(EXCLUDE_FROM_ALL ON) + endif() + + add_circt_executable(${name} ${ARGN}) + + if (CIRCT_BUILD_TOOLS) + get_target_export_arg(${name} CIRCT export_to_circttargets) + install(TARGETS ${name} + ${export_to_circttargets} + RUNTIME DESTINATION "${CIRCT_TOOLS_INSTALL_DIR}" + COMPONENT ${name}) + + if(NOT CMAKE_CONFIGURATION_TYPES) + add_llvm_install_targets(install-${name} + DEPENDS ${name} + COMPONENT ${name}) + endif() + set_property(GLOBAL APPEND PROPERTY CIRCT_EXPORTS ${name}) + endif() +endmacro() + # Adds a CIRCT library target for installation. This should normally only be # called from add_circt_library(). function(add_circt_library_install name) - install(TARGETS ${name} COMPONENT ${name} EXPORT CIRCTTargets) + if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY) + get_target_export_arg(${name} CIRCT export_to_circttargets UMBRELLA circt-libraries) + install(TARGETS ${name} + COMPONENT ${name} + ${export_to_circttargets} + LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX} + ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX} + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + # Note that CMake will create a directory like: + # objects-${CMAKE_BUILD_TYPE}/obj.LibName + # and put object files there. + OBJECTS DESTINATION lib${LLVM_LIBDIR_SUFFIX} + ) + + if (NOT LLVM_ENABLE_IDE) + add_llvm_install_targets(install-${name} + DEPENDS ${name} + COMPONENT ${name}) + endif() set_property(GLOBAL APPEND PROPERTY CIRCT_ALL_LIBS ${name}) + endif() set_property(GLOBAL APPEND PROPERTY CIRCT_EXPORTS ${name}) endfunction() @@ -59,3 +106,8 @@ function(add_circt_translation_library name) set_property(GLOBAL APPEND PROPERTY CIRCT_TRANSLATION_LIBS ${name}) add_circt_library(${ARGV} DEPENDS circt-headers) endfunction() + +function(add_circt_verification_library name) + set_property(GLOBAL APPEND PROPERTY CIRCT_VERIFICATION_LIBS ${name}) + add_circt_library(${ARGV} DEPENDS circt-headers) +endfunction() diff --git a/cmake/modules/CIRCTConfig.cmake.in b/cmake/modules/CIRCTConfig.cmake.in index 7b9a8418986d..337c6508611e 100644 --- a/cmake/modules/CIRCTConfig.cmake.in +++ b/cmake/modules/CIRCTConfig.cmake.in @@ -18,7 +18,9 @@ set_property(GLOBAL PROPERTY CIRCT_CONVERSION_LIBS "@CIRCT_CONVERSION_LIBS@") set_property(GLOBAL PROPERTY CIRCT_TRANSLATION_LIBS "@CIRCT_TRANSLATION_LIBS@") # Provide all our library targets to users. -@CIRCT_CONFIG_INCLUDE_EXPORTS@ +if(NOT TARGET CIRCTSupport) + @CIRCT_CONFIG_INCLUDE_EXPORTS@ +endif() # By creating these targets here, subprojects that depend on CIRCT's # tablegen-generated headers can always depend on these targets whether building diff --git a/cmake/modules/CMakeLists.txt b/cmake/modules/CMakeLists.txt index 984641616ca1..57d7564da5d0 100644 --- a/cmake/modules/CMakeLists.txt +++ b/cmake/modules/CMakeLists.txt @@ -22,6 +22,7 @@ get_property(CIRCT_DIALECT_LIBS GLOBAL PROPERTY CIRCT_DIALECT_LIBS) get_property(CIRCT_CONVERSION_LIBS GLOBAL PROPERTY CIRCT_CONVERSION_LIBS) get_property(CIRCT_TRANSLATION_LIBS GLOBAL PROPERTY CIRCT_TRANSLATION_LIBS) get_property(CIRCT_ANALYSIS_LIBS GLOBAL PROPERTY CIRCT_ANALYSIS_LIBS) +get_property(CIRCT_VERIFICATION_LIBS GLOBAL PROPERTY CIRCT_VERIFICATION_LIBS) # Generate CIRCTConfig.cmake for the build tree. set(CIRCT_CONFIG_CMAKE_DIR "${circt_cmake_builddir}") diff --git a/codeowners/esi.json b/codeowners/esi.json deleted file mode 100644 index 96512fb1f218..000000000000 --- a/codeowners/esi.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "Add code owner for triage", - "includes": [ - "**/Dialect/ESI/**" - ], - "action": "review", - "users": [ - "teqdruid" - ] -} diff --git a/codeowners/firrtl.json b/codeowners/firrtl.json deleted file mode 100644 index fa76f28ff15f..000000000000 --- a/codeowners/firrtl.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Add code owner for triage", - "includes": ["**/Dialect/FIRRTL/**"], - "action": "review", - "users": ["lattner"] -} diff --git a/codeowners/github.json b/codeowners/github.json deleted file mode 100644 index 66ba2a5c1459..000000000000 --- a/codeowners/github.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Add code owner to any github config change", - "includes": [".github/**"], - "action": "review", - "users": ["teqdruid"] -} diff --git a/codeowners/handshake.json b/codeowners/handshake.json deleted file mode 100644 index db07d331e958..000000000000 --- a/codeowners/handshake.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Add code owner for triage", - "includes": ["**/Dialect/Handshake/**"], - "action": "review", - "users": ["hanchenye", "stephenneuendorffer"] -} diff --git a/codeowners/hw.json b/codeowners/hw.json deleted file mode 100644 index 2d25afeb40e4..000000000000 --- a/codeowners/hw.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Add code owner for triage", - "includes": ["**/Dialect/HW/**"], - "action": "review", - "users": ["lattner"] -} diff --git a/codeowners/llhd.json b/codeowners/llhd.json deleted file mode 100644 index 4ef5c1f02b5e..000000000000 --- a/codeowners/llhd.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Add code owner for triage", - "includes": ["**/Dialect/LLHD/**"], - "action": "review", - "users": ["fabianschuiki"] -} diff --git a/codeowners/staticlogic.json b/codeowners/staticlogic.json deleted file mode 100644 index 5eb0c32b97f2..000000000000 --- a/codeowners/staticlogic.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Add code owner for triage", - "includes": ["**/Dialect/StaticLogic/**"], - "action": "review", - "users": ["hanchenye", "stephenneuendorffer"] -} diff --git a/codeowners/sv.json b/codeowners/sv.json deleted file mode 100644 index daebd98b1fef..000000000000 --- a/codeowners/sv.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Add code owner for triage", - "includes": ["**/Dialect/SV/**"], - "action": "review", - "users": ["lattner"] -} diff --git a/docs/Dialects/Comb/_index.md b/docs/Dialects/Comb/_index.md index 66dd9628daeb..95204d909d0a 100644 --- a/docs/Dialects/Comb/_index.md +++ b/docs/Dialects/Comb/_index.md @@ -1,3 +1,3 @@ # 'comb' Dialect -[include "Dialects/Comb.md"] \ No newline at end of file +[include "Dialects/Comb.md"] diff --git a/docs/Dialects/DC/RationaleDC.md b/docs/Dialects/DC/RationaleDC.md new file mode 100644 index 000000000000..97d8fbee85cd --- /dev/null +++ b/docs/Dialects/DC/RationaleDC.md @@ -0,0 +1,86 @@ +# DC Dialect Rationale + +[TOC] + +## Introduction + +DC (**D**ynamic **C**ontrol) IR describes independent, unsynchronized processes +communicating data through First-in First-out (FIFO) communication channels. +This can be implemented in many ways, such as using synchronous logic, or with +processors. + +The intention of DC is to model all operations required to represent such a +control flow language. DC aims to be strictly a control +flow dialect - as opposed to the Handshake dialect, which assigns control +semantics to _all_ SSA values. As such, data values are +only present in DC where they are required to model control flow. + +By having such a control language, the dialect aims to facilitate the +construction of dataflow programs where control and data +is explicitly separate. This enables optimization of the control and data side +of the program independently, as well as mapping +of the data side into functional units, pipelines, etc.. Furthermore, separating +data and control will make it easier to reason about possible critical paths of +either circuit, which may inform buffer placement. + +The DC dialect has been heavily influenced by the Handshake dialect, and can +either be seen as a successor to it, or a lower level +abstraction. As of writing, DC is _fully deterministic_. This means that +non-deterministic operators such as the ones found in Handshake - +`handshake.merge, handshake.control_merge` - do **not** have a lowering to DC. +Handshake programs must therefore be converted or by construction not contain +any of these non-deterministic operators. Apart from that, **all** handshake +operations can be lowered to a combination of DC +and e.g. `arith` operations (to represent the data side semantics of any given +operation). + +In DC IR, all values have implicit fork and sink semantics. That is, a DC-typed +value may be referenced multiple times, as well as it being legal that said +value is not referenced at all. This has been chosen to facilitate +canonicalization, thus removing the need for all canonicalization patterns to +view forks as opaque/a special case. If a given DC lowering requires explcit +fork/sink semantics, forks and sinks can be _materialized_ throuh use of the +`--dc-materialize-forks-sinks` pass. Conversely, if one wishes to optimize DC +IR which already contains fork and sink operations, one may use the +`--dc-dematerialize-forks-sinks` pass, run canonicalization, and then re-apply +the `--dc-materialize-forks-sinks` pass. + + +## Value (channel) semantics + +1. **Latency insensitive**: + * Any DC-typed value (`dc.token/dc.value`) has latency insensitive +semantics. + * DC does **not** specify the implementation of this latency +insensitivity, given that it strictly pertains to the **control** of latency +insensitive values. This should reinforce the mental model that DC isn't +strictly a hardware construct - that is, DC values could be implemented in +hardware by e.g ready/valid semantics or by FIFO interfaces (read/write, full, empty, ...) +or in software by e.g. message queues, RPC, or other streaming protocols. + * In the current state of the world (CIRCT), DC uses ESI to implement its +latency insensitive hardware protocol. By doing so, we let DC do what DC does +best (control language) and likewise with ESI (silicon interconnect). +2. **Values are channels**: + * Given the above latency insensitivity, it is useful to think of DC values +as channels, wherein a channel can be arbitrarily buffered without changing the +semantics of the program. +2. **FIFO semantics**: + * DC-typed values have FIFO semantics, meaning that the order of values in +the 'channel' is preserved (i.e. the order of values written to the channel is +the same as the order of values read from the channel). + +## Canonicalization +By explicitly separating data and control parts of a program, we allow for +control-only canonicalization to take place. +Here are some examples of non-trivial canonicalization patterns: +* **Transitive join closure**: + * Taking e.g. the Handshake dialect as the source abstraction, all operations + - unless some specific Handshake operations - will be considered as + *unit rate actors* and have join semantics. When lowering Handshake to DC, + and by separating the data and control paths, we can easily identify `join` + operations which are staggered, and can be merged through a transitive closure + of the control graph. +* **Branch to select**: Canonicalizes away a select where its inputs originate +from a branch, and both have the same select signal. +* **Identical join**: Canonicalizes away joins where all inputs are the same +(i.e. a single join can be used). diff --git a/docs/Dialects/DC/_index.md b/docs/Dialects/DC/_index.md new file mode 100644 index 000000000000..6e97b6adebeb --- /dev/null +++ b/docs/Dialects/DC/_index.md @@ -0,0 +1,3 @@ +# 'dc' Dialect + +[include "Dialects/DC.md"] diff --git a/docs/Dialects/Debug.md b/docs/Dialects/Debug.md new file mode 100644 index 000000000000..4d9dc37d2e0a --- /dev/null +++ b/docs/Dialects/Debug.md @@ -0,0 +1,165 @@ +# Debug Dialect + +This dialect provides operations and types to interleave debug information (DI) +with other parts of the IR. + +[TOC] + + +## Rationale + +The main goal of the debug dialect is to provide a mechanism to track the +correspondence between values, types, and hierarchy of a source language and the +IR being compiled and transformed. This allows simulators, synthesizers, and +other debugging tools to reconstruct a source language view into the processed +hardware and allow for easier debugging by humans. + +Debug information in CIRCT follows these principles: + +- **It is best effort:** DI is meant as a tool to aid humans in their debugging + effort, not a contractual obligation to retain all source language semantics + through the compilation pipeline. We preserve information as well as possible + and reasonable, but accept the fact that certain optimizations may cause + information to be discarded. + +- **It affects the output:** Enabling the tracking of DI is expected to block + certain optimizations. We undertake an effort to minimize the impact of DI on + the output quality, size, simulation speed, or synthesis results, but accept + the fact that preserving visibility and observability of source language + constructs may prevent certain optimizations from running. + + +### Representations + +There are two mechanisms in MLIR that lend themselves to conveying debug +information: + +- **Attributes** attached to existing operations. This is similar to LLVM's + approach of tracking DI in the operation's metadata. Translated to MLIR, an + operation's location would be an obvious choice to do this tracking, since + locations are well-preserved by passes and difficult to accidentally drop. + MLIR currently does not support custom location attributes, which would + require DI attributes to be attached to a `FusedLoc` as metadata. + +- **Operations** interleaved with the rest of the IR. This makes DI a + first-class citizen, but also causes debug information to potentially intefere + with optimizations. For example, debug dialect ops introduce additional uses + of values that might have otherwise been deleted by DCE. However, there may be + alternative ways to dealing with such situations. For example, Verilog + emission may simply ignore operations that are only used by debug ops, + therefore achieving the same effect as DCE would have. + +The debug dialect uses _operations_ to represent debug info. This decision was +based on discussions with various people in the LLVM and MLIR community, where +DI was commonly quoted as one of LLVM's weak points, with its living in metadata +space making it more of a second-class citizen rather than a first-class +concern. Since we want to represent source language types and constructs as +accurately as possible, and we want to track if values are type-lowered, +constant-folded, outlined, or adjusted in some other way, using operations seems +like a natural choice. MLIR ops already have all the machinery needed to refer +to values in the IR, and many passes will already do the right thing with them. + + +## Representing Source Language Constructs + +The `dbg.variable` op is the key mechanism to establish a mapping between +high-level source language values and low-level values in the IR that are +transformed by the compiler. Consider the following source language pseudocode: + +```plain +struct Req { + data: i42, + valid: i1, + ready: &i1, +} +struct Resp { + result: i42, + done: i1, +} +module Foo { + parameter Depth: uint; + const Width: uint = 2**Depth; + input req: Req; + output resps: Resp[2]; + let x = req; +} +``` + +A frontend for this language could generate the following debug variables as +part of the body of module `Foo`, in order to track the structs, arrays, +parameters, constants, and local bindings present in the source language: + +```mlir +hw.module @Foo_Width12( + in %req_data: i42, + in %req_valid: i1, + out req_ready: i1, + out resps0_result: i42, + out resps0_done: i1, + out resps1_result: i42, + out resps1_done: i1 +) { + // %req_ready = ... + // %resps0_result = ... + // %resps0_done = ... + // %resps1_result = ... + // %resps1_done = ... + + // parameter Depth + %c12_i32 = hw.constant 12 : i32 + dbg.variable "Depth", %c12_i32 : i32 + + // const Width + %c4096_i32 = hw.constant 4096 : i32 + dbg.variable "Width", %c4096_i32 : i32 + + // input req: Req + %0 = dbg.struct {"data": %req_data, "valid": %req_valid, "ready": %req_ready} : i42, i1, i1 + dbg.variable "req", %0 : !dbg.struct + + // output resps: Resp[2] + %1 = dbg.struct {"result": %resps0_result, "done": %resps0_done} : i42, i1 + %2 = dbg.struct {"result": %resps1_result, "done": %resps1_done} : i42, i1 + %3 = dbg.array [%1, %2] : !dbg.struct, !dbg.struct + dbg.variable "resps", %3 : !dbg.array + + // let x = req + dbg.variable "x", %0 : !dbg.struct + + hw.output %req_ready, %resps0_result, %resps0_done, %resps1_result, %resps1_done : i1, i42, i1, i42, i1 +} +``` + +Despite the fact that the `Req` and `Resp` structs, and `Resp[2]` array were +unrolled and lowered into separate scalar values in the IR, and the `ready: &i1` +input of `Req` having been turned into a `ready: i1` output, the `dbg.variable` +op accurately tracks how the original source language values can be +reconstructed. Note also how monomorphization has turned the `Depth` parameter +and `Width` into constants in the IR, but the corresponding `dbg.variable` ops +still expose the constant values under the name `Depth` and `Width` in the debug +info. + + +## Types + + +### Overview + +The debug dialect does not precisely track the type of struct and array +aggregate values. Aggregates simply return the type `!dbg.struct` and +`!dbg.array`, respectively. + +Extracting and emitting the debug information of a piece of IR involves looking +through debug ops to find actually emitted values that can be used to +reconstruct the source language values. Therefore the actual structure of the +debug ops is important, but their return type is not instrumental. The +distinction between struct and array types is an arbitrary choice that can be +changed easily, either by collapsing them into one aggregate type, or by more +precisely listing field/element types and array dimensions if the need arises. + +[include "Dialects/DebugTypes.md"] + + +## Operations + +[include "Dialects/DebugOps.md"] diff --git a/docs/Dialects/ESI/_index.md b/docs/Dialects/ESI/_index.md index d550011da569..200e7a559a53 100644 --- a/docs/Dialects/ESI/_index.md +++ b/docs/Dialects/ESI/_index.md @@ -1,3 +1,51 @@ # 'esi' Dialect -[include "Dialects/ESI.md"] \ No newline at end of file +The Elastic Silicon Interconnect dialect aims to aid in accelerator system construction. + +[TOC] + +## Application channels + +The main component of ESI are point-to-point, typed channels that allow +designers to connect modules to each other and software, then communicate by +sending messages. Channels largely abstract away the details of message +communication from the designer, though the designer can declaratively specify +how to implement the channel. + +Messages have types: ints, structs, arrays, unions, and variable-length lists. +The width of a channel is not necessarily the same width as the message. ESI +“windows” can be used to break up a message into a series of “frames”. IP blocks +can emit / absorb “windowed” messages or full-sized messages, which can be +automatically broken up to save wire area at the cost of bandwidth. + +Any channel which is exposed to the host will have a platform-agnostic software +API constructed for it based on the type of the channel. The software +application merely has to connect to the accelerator then invoke a method to +send or receive messages from the accelerator system. + +[include "Dialects/ESIChannelTypes.md"] +[include "Dialects/ESITypes.md"] +[include "Dialects/ESIChannels.md"] + +## Services + +ESI "services" provide device-wide connectivity and arbitration for shared +resources, which can be requested from any IP block (service "client"). Standard +services will include DRAM, clock/reset, statistical counter reporting, and +debug. + +[include "Dialects/ESIServices.md"] +[include "Dialects/ESIStdServices.md"] + +## Structural + +ESI has a special module which doesn't expose ports. All external interactions +are expected to be done through services. + +[include "Dialects/ESIStructure.md"] + +## Interfaces + +Misc CIRCT interfaces. + +[include "Dialects/ESIInterfaces.md"] diff --git a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md index fecb0c4fb675..fa4a80efe364 100644 --- a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md +++ b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md @@ -201,6 +201,28 @@ Annotations here are written in their JSON format. A "reference target" indicates that the annotation could target any object in the hierarchy, although there may be further restrictions in the annotation. +### [AttributeAnnotation](https://javadoc.io/doc/edu.berkeley.cs/firrtl_2.13/latest/firrtl/AttributeAnnotation.html) + +| Property | Type | Description | +| ---------- | ------ | ---------------------------- | +| class | string | `firrtl.AttributeAnnotation` | +| target | string | A reference target | +| description | string | An attribute | + +This annotation attaches SV attributes to a specified target. A reference +target must be a wire, node, reg, or module. This annotation doesn't prevent +optimizations so it's necessary to add dontTouch annotation if users want to +preseve the target. + +Example: +```json +{ + "class": "firrtl.AttributeAnnotation", + "target": "~Foo|Foo>r", + "description": "debug = \"true\"" +} +``` + ### BlackBox | Property | Type | Description | @@ -299,6 +321,30 @@ Example: } ``` +### Convention + +| Property | Type | Description | +| ---------- | ------ | --------------------------------------- | +| class | string | `circt.ConventionAnnotation` | +| convention | string | `scalarized` | +| target | string | Reference target | + +Specify the port convention for a module. The port convention controls how a +module's ports are transformed, and how that module can be instantiated, in the +output format. + +The options are: +- `scalarized`: Convert aggregate ports (i.e. vector or bundles) into multiple + ground-typed ports. + +```json +{ + "class": "circt.ConventionAnnotation", + "convention": "scalarized", + "target": "~Foo|Bar/d:Baz" +} +``` + ### ElaborationArtefactsDirectory | Property | Type | Description | @@ -367,6 +413,28 @@ Example: } ``` +### [DocStringAnnotation](https://javadoc.io/doc/edu.berkeley.cs/firrtl_2.13/latest/firrtl/DocStringAnnotation.html) + +| Property | Type | Description | +| ---------- | ------ | ---------------------------- | +| class | string | `firrtl.DocStringAnnotation` | +| target | string | A reference target | +| description | string | An attribute | + +This annotation attaches a comment to a specified target. A reference +target must be a wire, node, reg, or module. This annotation doesn't prevent +optimizations so it's necessary to add dontTouch annotation if users want to +preseve the target. + +Example: +```json +{ + "class": "firrtl.DocStringAnnotation", + "target": "~Foo|Foo>r", + "description": "comment" +} +``` + ### [DontTouchAnnotation](https://javadoc.io/doc/edu.berkeley.cs/firrtl_2.13/latest/firrtl/transforms/DontTouchAnnotation.html) | Property | Type | Description | @@ -563,6 +631,27 @@ Example: } ``` +### DedupGroupAnnotation + +| Property | Type | Description | +| ---------- | ------ | ------------- | +| class | string | `firrtl.transforms.DedupGroupAnnotation` | +| target | string | Module target | +| group | string | The dedup group that the module belongs to | + +This annotation assigns the targeted module to a dedup group. Modules that +belong to a dedup group may only be deduplicated with modules that are part of +the same group. + +Example: +```json +{ + "class":"firrtl.transforms.DedupGroupAnnotation", + "target": "~Top|A", + "group": "foo" +} +``` + ### NestedPrefixModulesAnnotation | Property | Type | Description | @@ -646,7 +735,6 @@ The `value` field can be a JSON array or dictionary (corresponding to the `OMArr - `OMBigDecimal:` - `OMFrozenTarget:` - `OMDeleted` -- `OMConstant:` - `OMReferenceTarget:` - `OMMemberReferenceTarget:` - `OMMemberInstanceTarget:` @@ -668,7 +756,6 @@ Example: {"info": "", "name": "d", "value": "OMString:hello"}, {"info": "", "name": "f", "value": "OMBigDecimal:10.5"}, {"info": "", "name": "g", "value": "OMDeleted:"}, - {"info": "", "name": "h", "value": "OMConstant:UInt<2>(\"h1\")"}, {"info": "", "name": "i", "value": 42}, {"info": "", "name": "j", "value": true}, {"info": "", "name": "k", "value": 3.14} @@ -733,8 +820,8 @@ Example: | target | string | Reference target | This annotation attaches metadata to the firrtl.mem operation. The `data` is -emitted onto the `seq_mems.json` and `tb_seq_mems.json` file. It is required -for verification only and used by memory generator tools for simulation. +emitted onto the `seq_mems.json` file. It is required for verification only and +used by memory generator tools for simulation. Example: ```json @@ -1463,11 +1550,6 @@ modules. This attribute has type `OutputFileAttr`. Used by SVExtractTestCode. Specifies the output directory for extracted modules. This attribute has type `OutputFileAttr`. -### firrtl.extract.testbench - -Used by SVExtractTestCode. Specifies the output directory for extracted -testbench only modules. This attribute has type `OutputFileAttr`. - ### firrtl.extract.assert.bindfile Used by SVExtractTestCode. Specifies the output file for extracted diff --git a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md index d86ed095389b..6788c080faa4 100644 --- a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md +++ b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md @@ -19,6 +19,10 @@ Annotations here are written in their JSON format. A "reference target" indicates that the annotation could target any object in the hierarchy, although there may be further restrictions in the annotation. +We also accept the `intmodule` version of any of these annotation strings used +as the intrinsic name. To work with the requirement of the intrinsic name being +an identifier, replace any period is with an underscore in the intrinsic name. + ### circt.sizeof Returns the size of a type. The input port is not read from and may be any @@ -63,7 +67,6 @@ type of the result. | found | output | UInt<1> | found in args | | result | output | AnyType | found in args | - ### circt.plusargs.test Tests simulator command line options with SystemVerilog `$test$plusargs`. This @@ -76,3 +79,18 @@ is described in SystemVerilog 2012 section 21.6. | Port | Direction | Type | Description | | ---------- | --------- | -------- | ----------------------------------- | | found | output | UInt<1> | found in args | + +### circt.clock_gate + +Enables and disables a clock safely, without glitches, based on a boolean enable value. If the enable input is 1, the output clock produced by the clock gate is identical to the input clock. If the enable input is 0, the output clock is a constant zero. + +The enable input is sampled at the rising edge of the input clock; any changes on the enable before or after that edge are ignored and do not affect the output clock. + +| Parameter | Type | Description | +| --------- | ---- | ----------- | + +| Port | Direction | Type | Description | +| ---- | --------- | -------- | --------------------------- | +| in | input | Clock | input clock | +| en | input | UInt<1> | enable for the output clock | +| out | output | Clock | gated output clock | diff --git a/docs/Dialects/FIRRTL/RationaleFIRRTL.md b/docs/Dialects/FIRRTL/RationaleFIRRTL.md index 88605b102897..d8247510c5f0 100644 --- a/docs/Dialects/FIRRTL/RationaleFIRRTL.md +++ b/docs/Dialects/FIRRTL/RationaleFIRRTL.md @@ -331,8 +331,8 @@ enabled. ## Symbols and Inner Symbols -Symbols and Inner Symbols are documented in the [symbol -rationale](RationaleSymbols.md). This section documents how symbols are used, +Symbols and Inner Symbols are documented in [Symbol +Rationale](https://circt.llvm.org/docs/RationaleSymbols/). This documents how symbols are used, their interaction with "Don't Touch", and the semantics imposed by them. Public Symbols indicate there are uses of an entity outside the analysis scope @@ -548,9 +548,9 @@ instance inputs which may be also read from). A value with `source` flow may be read from, but not written to. A value with `duplex` flow may be read from or written to. -For FIRRTL connects or partial connect statements, it follows that the -left-hand-side must be `sink` or `duplex` and the right-hand-side must be -`source`, `duplex`, or a port/instance `sink`. +For FIRRTL connect statements, it follows that the left-hand-side must be `sink` +or `duplex` and the right-hand-side must be `source`, `duplex`, or a +port/instance `sink`. Flow is _not_ represented as a first-class type in CIRCT. We instead provide utilities for computing flow when needed, e.g., for connect statement @@ -569,6 +569,10 @@ The expected lowering for strict connects is for the connect to be eliminated an The reason we provide this foreign type support is to allow for partial lowering of FIRRTL to HW and other dialects. Passes might lower a subset of types and operations to the target dialect and we need a mechanism to have the lowered values be passed around the FIRRTL module hierarchy untouched alongside the FIRRTL ops that are yet to be lowered. +### Const Types + +FIRRTL hardware types can be specified as `const`, meaning they can only be assigned compile-time constant values or values of other `const` types. + ## Operations ### Multiple result `firrtl.instance` operation @@ -642,9 +646,7 @@ conditions for macro replacement are as follows: 1. `–replSeqMem` option is passed and 2. `readLatency == 1` and 3. `writeLatency == 1` and -4. `numWritePorts + numReadWritePorts == 1` and -5. `numReadPorts <= 1` and -6. `width(data) > 0` +4. `width(data) > 0` Any `MemOp` not satisfying the above conditions is lowered to Register vector. diff --git a/docs/Dialects/FSM/_index.md b/docs/Dialects/FSM/_index.md index 7b7dc0644212..13e23122e8d9 100644 --- a/docs/Dialects/FSM/_index.md +++ b/docs/Dialects/FSM/_index.md @@ -1,3 +1,3 @@ # 'fsm' Dialect -[include "Dialects/FSM.md"] \ No newline at end of file +[include "Dialects/FSM.md"] diff --git a/docs/Dialects/HW/RationaleHW.md b/docs/Dialects/HW/RationaleHW.md index a5dbeab9c18d..886e72ca23d8 100644 --- a/docs/Dialects/HW/RationaleHW.md +++ b/docs/Dialects/HW/RationaleHW.md @@ -81,6 +81,17 @@ constructs that work with the inout type. These aren't necessary for combinational logic, but are nonetheless pretty useful when generating Verilog. +### `enum` Type + +Enum types have the property that the bit width of the type is the minimum +necessary to hold the tag values. Tag values are either explicit or +sequentially numbered in tag order from 0. Enum tags are unsigned values. + +### `union` Type + +Union types contain a single data element (which may be an aggregate). They +optionally have an offset per varient which allows non-SV layouts. + ## `hw.module` and `hw.instance` The basic structure of a hardware design is made up an "instance tree" of diff --git a/docs/Dialects/HW/_index.md b/docs/Dialects/HW/_index.md index f7ad0f334c6f..d338c47ca1b0 100644 --- a/docs/Dialects/HW/_index.md +++ b/docs/Dialects/HW/_index.md @@ -28,7 +28,7 @@ representation of HW outside of a particular use-case. ## Type Definitions -[include "Dialects/HWTypeDecls.md"] +[include "Dialects/HWTypeDeclsOps.md"] [include "Dialects/HWTypes.md"] [include "Dialects/HWTypesImpl.md"] diff --git a/docs/Dialects/HWArith/_index.md b/docs/Dialects/HWArith/_index.md index 5c0650e0fa69..22e3c860c2be 100644 --- a/docs/Dialects/HWArith/_index.md +++ b/docs/Dialects/HWArith/_index.md @@ -1,3 +1,3 @@ # 'hwarith' Dialect -[include "Dialects/HWArith.md"] \ No newline at end of file +[include "Dialects/HWArith.md"] diff --git a/docs/Dialects/Handshake/RationaleHandshake.md b/docs/Dialects/Handshake/RationaleHandshake.md index 99b57e8035ea..ae045ab64cb1 100644 --- a/docs/Dialects/Handshake/RationaleHandshake.md +++ b/docs/Dialects/Handshake/RationaleHandshake.md @@ -55,10 +55,13 @@ output stream (modeled as an MLIR result). The Handshake dialect adopts the following conventions for IR: - The prefix for all Handshake types and operations are `handshake.`. -## Resources +## Talks, Resources and Related Publications -MLIR Handshake Dialect-[slides](https://drive.google.com/file/d/1UYQAfHrzcsdXUZ93bHPTPNwrscwx89M-/view?usp=sharing) by Stephen Neuendorffer (Xilinx) + Lana Josipović (EPFL) +- (10/2022) [Multi-Level Rewriting for Stream Processing to RTL compilation (M.Sc. Thesis) - Christian Ulmann](https://www.research-collection.ethz.ch/bitstream/handle/20.500.11850/578713/1/Ulmann_Christian.pdf) +- (03/2022) [HLS from PyTorch to System Verilog with MLIR and CIRCT (Workshop paper) - Mike Urbach, Morten Borup Petersen](https://capra.cs.cornell.edu/latte22/paper/2.pdf) +- (01/2022) [A Dynamically Scheduled HLS Flow in MLIR (M.Sc. Thesis) - Morten Borup Petersen](https://infoscience.epfl.ch/record/292189) +- (06/2020) MLIR Handshake Dialect-[slides](https://drive.google.com/file/d/1UYQAfHrzcsdXUZ93bHPTPNwrscwx89M-/view?usp=sharing) by Stephen Neuendorffer (Xilinx) + Lana Josipović (EPFL) ## Operation definitions -[include "Dialects/HandshakeOps.md"] +[include "Dialects/Handshake.md"] diff --git a/docs/Dialects/Handshake/_index.md b/docs/Dialects/Handshake/_index.md index 84b2dc75f635..363e6c412382 100644 --- a/docs/Dialects/Handshake/_index.md +++ b/docs/Dialects/Handshake/_index.md @@ -1,3 +1,3 @@ # 'handshake' Dialect -[include "Dialects/Handshake.md"] \ No newline at end of file +[include "Dialects/Handshake.md"] diff --git a/docs/Dialects/Ibis/RationaleIbis.md b/docs/Dialects/Ibis/RationaleIbis.md new file mode 100644 index 000000000000..c1e7a6606583 --- /dev/null +++ b/docs/Dialects/Ibis/RationaleIbis.md @@ -0,0 +1,35 @@ +# `ibis` Dialect Rationale + +## Lowering flow + +1. Containerization: + At this level, one may have relative references to ports `get_port` accesses + to `!ibis.scoperef`s +2. Tunneling: + Relative `!ibis.scoperef` references are defined via `ibis.path` operations. + In this pass, we lower `ibis.path` operations by tunneling `portref`s through + the instance hierarchy, based on the `get_port` operations that were present + in the various containers. After this, various `portref>` + ports are present in the design, which represents the actual ports that are + being passed around. +3. `portref` lowering + Next, we need to convert `portref>`-typed ports into + `T` typed in- or output ports. We do this by analyzing how a portref is used + inside a container, and then creating an in- or output port based on that. + That is: + - write to `portref>` becomes `out T` + - read from `portref>` becomes `in T` + - write to `portref>` becomes `out T` (a port reference + inside the module will be driven by a value from the outside) + - read from `portref>` becomes `in T` (a port reference + inside the module will be driven by a value from the outside) +4. Removal of self-driving inputs: + In cases where children drive parent ports, the prior case may create + situations where a container drives its own input ports (and by extension, no + other instantiating container is expected to drive that port of the instance, + if the IR is correct). We thus run `ibis-clean-selfdrivers` to replace these + self-driven ports by the actual values that are being driven into them. +5. HW lowering: + At this point, there no longer exist any relative references in the Ibis IR, + and all instantiations should (if the IR is correct) have all of their inputs + driven. This means that we can now lower the IR to `hw.module`s. diff --git a/docs/Dialects/Ibis/_index.md b/docs/Dialects/Ibis/_index.md new file mode 100644 index 000000000000..7fc34e70d055 --- /dev/null +++ b/docs/Dialects/Ibis/_index.md @@ -0,0 +1,3 @@ +# 'ibis' Dialect + +[include "Dialects/Ibis.md"] diff --git a/docs/Dialects/LTL.md b/docs/Dialects/LTL.md new file mode 100644 index 000000000000..aacdf8c90a83 --- /dev/null +++ b/docs/Dialects/LTL.md @@ -0,0 +1,209 @@ +# 'ltl' Dialect + +This dialect provides operations and types to model [Linear Temporal Logic](https://en.wikipedia.org/wiki/Linear_temporal_logic), sequences, and properties, which are useful for hardware verification. + +[TOC] + + +## Rationale + +The main goal of the `ltl` dialect is to capture the core formalism underpinning SystemVerilog Assertions (SVAs), the de facto standard for describing temporal logic sequences and properties in hardware verification. (See IEEE 1800-2017 section 16 "Assertions".) We expressly try *not* to model this dialect like an AST for SVAs, but instead try to strip away all the syntactic sugar and Verilog quirks, and distill out the core foundation as an IR. Within the CIRCT project, this dialect intends to enable emission of rich temporal assertions as part of the Verilog output, but also provide a foundation for formal tools built ontop of CIRCT. + +As a primary reference, the `ltl` dialect attempts to model SVAs after the [Linear Temporal Logic](https://en.wikipedia.org/wiki/Linear_temporal_logic) formalism as a way to distill SystemVerilog's syntactic sugar and quirks down to a core representation. However, most definitions of LTL tend to be rather academic in nature and may be lacking certain building blocks to make them useful in practice. (See section on [concatenation](#concatenation) below.) To inform some practical design decisions, the `ltl` dialect tries to think of temporal sequences as "regular expressions over time", borrowing from their wide applicability and usefulness. + + +### Sequences and Properties + +The core building blocks for modeling temporal logic in the `ltl` dialect are *sequences* and *properties*. In a nutshell, sequences behave like regular expressions over time, whereas properties provide the quantifiers to express that sequences must be true under certain conditions. + +**Sequences** describe boolean expressions at different points in time. They can be easily verified by a finite state automaton, similar to how regular expressions and languages have an equivalent automaton that recognizes the language. For example: + +- The boolean `a` is a sequence. It holds if `a` is true in cycle 0 (the current cycle). +- The boolean expression `a & b` is also a sequence. It holds if `a & b` is true in cycle 0. +- `##1 a` checks that `a` is true in cycle 1 (the next cycle). +- `##[1:4] a` checks that `a` is true anywhere in cycle 1, 2, 3, or 4. +- `a ##1 b` checks that `a` holds in cycle 0 and `b` holds in cycle 1. +- `##1 (a ##1 b)` checks that `a` holds in cycle 1 and `b` holds in cycle 2. +- `(a ##1 b) ##5 (c ##1 d)` checks that the sequence `(a ##1 b)` holds and is followed by the sequence `(c ##1 d)` 5 or 6 cycles later. Concretely, this checks that `a` holds in cycle 0, `b` holds in cycle 1, `c` holds in cycle 6 (5 cycles after the first sequence ended in cycle 1), and `d` holds in cycle 7. + +**Properties** describe concrete, testable propositions or claims built from sequences. While sequences can observe and match a certain behavior in a circuit at a specific point in time, properties allow you to express that these sequences hold in every cycle, or hold at some future point in time, or that one sequence is always followed by another. For example: + +- `always s` checks that the sequence `s` holds in every cycle. This is often referred to as the **G** (or "globally") operator in LTL. +- `eventually s` checks that the sequence `s` will hold at some cycle now or in the future. This is often referred to as the **F** (or "finally") operator in LTL. +- `s implies t` checks that whenever the sequence `s` is observed, it is immediately followed by sequence `t`. + +Traditional definitions of the LTL formalism do not make a distinction between sequences and properties. Most of their operators fall into the property category, for example, quantifiers like *globally*, *finally*, *release*, and *until*. The set of sequence operators is usually very small, since it is not necessary for academic treatment, consisting only of the *next* operator. The `ltl` dialect provides a richer set of operations to model sequences. + + +## Representing SVAs + + +### Sequence Concatenation and Cycle Delay + +The primary building block for sequences in SVAs is the *concatenation* expression. Concatenation is always associated with a cycle delay, which indicates how many cycles pass between the end of the LHS sequence and the start of the RHS sequence. One, two, or more sequences can be concatenated at once, and the overall concatenation can have an initial cycle delay. For example: + +``` +a ##1 b ##1 c // 1 cycle delay between a, b, and c +##2 a ##1 b ##1 c // same, plus 2 cycles of initial delay before a +``` + +In the simplest form, a cycle delay can appear as a prefix of another sequence, e.g., `##1 a`. This is essentially a concatenation with only one sequence, `a`, and an initial cycle delay of the concatenation of `1`. The prefix delays map to the LTL dialect as follows: + +- `##N seq`. **Fixed delay.** Sequence `seq` has to match exactly `N` cycles in the future. Equivalent to `ltl.delay %seq, N, 0`. +- `##[N:M] seq`. **Bounded range delay.** Sequence `seq` has to match anywhere between `N` and `M` cycles in the future, inclusive. Equivalent to `ltl.delay %seq, N, (M-N)` +- `##[N:$] seq`. **Unbounded range delay.** Sequence `seq` has to match anywhere at or beyond `N` cycles in the future, after a finite amount of cycles. Equivalent to `ltl.delay %seq, N`. +- `##[*] seq`. Shorthand for `##[0:$]`. Equivalent to `ltl.delay %seq, 0`. +- `##[+] seq`. Shorthand for `##[1:$]`. Equivalent to `ltl.delay %seq, 1`. + +Concatenation of two sequences always involves a cycle delay specification in between them, e.g., `a ##1 b` where sequence `b` starts in the cycle after `a` ends. Zero-cycle delays can be specified, e.g., `a ##0 b` where `b` starts in the same cycle as `a` ends. If `a` and `b` are booleans, `a ##0 b` is equivalent to `a && b`. + +The dialect separates concatenation and cycle delay into two orthogonal operations, `ltl.concat` and `ltl.delay`, respectively. The former models concatenation as `a ##0 b`, and the latter models delay as a prefix `##1 c`. The SVA concatenations with their infix delays map to the LTL dialect as follows: + +- `seqA ##N seqB`. **Binary concatenation.** Sequence `seqB` follows `N` cycles after `seqA`. This can be represented as `seqA ##0 (##N seqB)`, which is equivalent to + ``` + %0 = ltl.delay %seqB, N, 0 + ltl.concat %seqA, %0 + ``` + +- `seqA ##N seqB ##M seqC`. **Variadic concatenation.** Sequence `seqC` follows `M` cycles after `seqB`, which itself follows `N` cycles after `seqA`. This can be represented as `seqA ##0 (##N seqB) ##0 (##M seqC)`, which is equivalent to + ``` + %0 = ltl.delay %seqB, N, 0 + %1 = ltl.delay %seqC, M, 0 + ltl.concat %seqA, %0, %1 + ``` + Since concatenation is associative, this is also equivalent to `seqA ##N (seqB ##M seqC)`: + ``` + %0 = ltl.delay %seqC, M, 0 + %1 = ltl.concat %seqB, %0 + %2 = ltl.delay %1, N, 0 + ltl.concat %seqA, %2 + ``` + And also `(seqA ##N seqB) ##M seqC`: + ``` + %0 = ltl.delay %seqB, N, 0 + %1 = ltl.concat %seqA, %0 + %2 = ltl.delay %seqC, M, 0 + ltl.concat %1, %2 + ``` + +- `##N seqA ##M seqB`. **Initial delay.** Sequence `seqB` follows `M` cycles afer `seqA`, which itself starts `N` cycles in the future. This is equivalent to a delay on `seqA` within the concatenation: + ``` + %0 = ltl.delay %seqA, N, 0 + %1 = ltl.delay %seqB, M, 0 + ltl.concat %0, %1 + ``` + Alternatively, the delay can also be placed on the entire concatenation: + ``` + %0 = ltl.delay %seqB, M, 0 + %1 = ltl.concat %seqA, %0 + ltl.delay %1, N, 0 + ``` + +- Only the fixed delay `##N` is shown here for simplicity, but the examples extend to the other delay flavors `##[N:M]`, `##[N:$]`, `##[*]`, and `##[+]`. + + +### Implication + +``` +seq |-> prop +seq |=> prop +``` + +The overlapping `|->` and non-overlapping `|=>` implication operators of SVA, which only check a property after a precondition sequence matches, map to the `ltl.implication` operation. When the sequence matches in the overlapping case `|->`, the property check starts at the same time the matched sequence ended. In the non-overlapping case `|=>`, the property check starts *at the clock tick after the* end of the matched sequence, unless the matched sequence was empty, in which special rules apply. (See IEEE 1800-2017 section 16.12.7 "Implication".) The non-overlapping operator can be expressed in terms of the overlapping operator: + +``` +seq |=> prop +``` +is equivalent to +``` +(seq ##1 true) |-> prop +``` + +The `ltl.implication` op implements the overlapping case `|->`, such that the two SVA operator flavors map to the `ltl` dialect as follows: + +- `seq |-> prop`. **Overlapping implication.** Equivalent to `ltl.implication %seq, %prop`. +- `seq |=> prop`. **Non-overlapping implication.** Equivalent to + ``` + %true = hw.constant true + %0 = ltl.delay %true, 1, 0 + %1 = ltl.concat %seq, %0 + ltl.implication %1, %prop + ``` + +An important benefit of only modeling the overlapping `|->` implication operator is that it does not interact with a clock. The end point of the left-hand sequence is the starting point of the right-hand sequence. There is no notion of delay between the end of the left and the start of the right sequence. Compare this to the `|=>` operator in SVA, which implies that the right-hand sequence happens at "strictly the next clock tick", which requires the operator to have a notion of time and clocking. As described above, it is still possible to model this using an explicit `ltl.delay` op, which already has an established interaction with a clock. + + +### Clocking + +Sequence and property expressions in SVAs can specify a clock with respect to which all cycle delays are expressed. (See IEEE 1800-2017 section 16.16 "Clock resolution".) These map to the `ltl.clock` operation. + +- `@(posedge clk) seqOrProp`. **Trigger on low-to-high clock edge.** Equivalent to `ltl.clock %seqOrProp, posedge %clk`. +- `@(negedge clk) seqOrProp`. **Trigger on high-to-low clock edge.** Equivalent to `ltl.clock %seqOrProp, negedge %clk`. +- `@(edge clk) seqOrProp`. **Trigger on any clock edge.** Equivalent to `ltl.clock %seqOrProp, edge %clk`. + + +### Disable Iff + +Properties in SVA can have a disable condition attached, which allows for preemptive resets to be expressed. If the disable condition is true at any time during the evaluation of a property, the property is considered disabled. (See IEEE 1800-2017 end of section 16.12 "Declaring properties".) This maps to the `ltl.disable` operation. + +- `disable iff (expr) prop`. **Disable condition.** Equivalent to `ltl.disable %prop if %expr`. + +Note that SVAs only allow for entire properties to be disabled, at the point at which they are passed to an assert, assume, or cover statement. It is explicitly forbidden to define a property with a `disable iff` clause and then using it within another property. For example, the following is forbidden: +``` +property p0; disable iff (cond) a |-> b; endproperty +property p1; eventually p0; endproperty +``` +In this example, `p1` refers to property `p0`, which is illegal in SVA since `p0` itself defines a disable condition. + +In contrast, the LTL dialect explicitly allows for properties to be disabled at arbitrary points, and disabled properties to be used in other properties. Since a disabled nested property also disables the parent property, the IR can always be rewritten into a form where there is only one `disable iff` condition at the root of a property expression. + + +## Representing the LTL Formalism + + +### Next / Delay + +The `ltl.delay` sequence operation represents various shorthands for the *next*/**X** operator in LTL: + +| Operation | LTL Formula | +|----------------------|-----------------------------| +| `ltl.delay %a, 0, 0` | a | +| `ltl.delay %a, 1, 0` | **X**a | +| `ltl.delay %a, 3, 0` | **XXX**a | +| `ltl.delay %a, 0, 2` | a ∨ **X**a ∨ **XX**a | +| `ltl.delay %a, 1, 2` | **X**(a ∨ **X**a ∨ **XX**a) | +| `ltl.delay %a, 0` | **F**a | +| `ltl.delay %a, 2` | **XXF**a | + + +### Concatenation + +The `ltl.concat` sequence operation does not have a direct equivalent in LTL. It builds a longer sequence by composing multiple shorter sequences one after another. LTL has no concept of concatenation, or a *"v happens after u"*, where the point in time at which v starts is dependent on how long the sequence u was. + +For a sequence u with a fixed length of 2, concatenation can be represented as *"(u happens) and (v happens 2 cycles in the future)"*, u ∧ **XX**v. If u has a dynamic length though, for example a delay between 1 and 2, `ltl.delay %u, 1, 1` or **X**u ∨ **XX**u in LTL, there is no fixed number of cycles by which the sequence v can be delayed to make it start after u. Instead, all different-length variants of sequence u have to be enumerated and combined with a copy of sequence v delayed by the appropriate amount: (**X**u ∧ **XX**v) ∨ (**XX**u ∧ **XXX**v). This is basically saying "u delayed by 1 to 2 cycles followed by v" is the same as either *"u delayed by 1 cycle and v delayed by 2 cycles"*, or *"u delayed by 2 cycles and v delayed by 3 cycles"*. + +The *"v happens after u"* relationship is crucial to express sequences efficiently, which is why the LTL dialect has the `ltl.concat` op. If sequences are thought of as regular expressions over time, for example, `a(b|cd)` or *"a followed by either (b) or (c followed by d)"*, the importance of having a concatenation operation as temporal connective becomes apparent. Why LTL formalisms tend to not include such an operator is unclear. + + +## Types + + +### Overview + +The `ltl` dialect operations defines two main types returned by its operations: sequences and properties. These types form a hierarchy together with the boolean type `i1`: + +- a boolean `i1` is also a valid sequence +- a sequence `!ltl.sequence` is also a valid property + +``` +i1 <: ltl.sequence <: ltl.property +``` + +The two type constraints `AnySequenceType` and `AnyPropertyType` are provided to implement this hierarchy. Operations use these constraints for their operands, such that they can properly accept `i1` as a sequence, `i1` or a sequence as a property. The return type is an explicit `!ltl.sequence` or `!ltl.property`. + +[include "Dialects/LTLTypes.md"] + + +## Operations + +[include "Dialects/LTLOps.md"] diff --git a/docs/Dialects/LoopSchedule/LoopSchedule.md b/docs/Dialects/LoopSchedule/LoopSchedule.md new file mode 100644 index 000000000000..21fcfc58b54f --- /dev/null +++ b/docs/Dialects/LoopSchedule/LoopSchedule.md @@ -0,0 +1,107 @@ +# LoopSchedule Dialect Rationale + +This document describes various design points of the `loopschedule` dialect, why it is +the way it is, and current status. This follows in the spirit of other [MLIR +Rationale docs](https://mlir.llvm.org/docs/Rationale/). + +## Introduction + +The `loopschedule` dialect provides a collection of ops to represent software-like loops +after scheduling. There are currently two main kinds of loops that can be represented: +pipelined and sequential. Pipelined loops allow multiple iterations of the loop to be +in-flight at a time and have an associated initiation interval (`II`) to specify the number +of cycles between the start of successive loop iterations. In contrast, sequential loops +are guaranteed to only have one iteration in-flight at any given time. + +A primary goal of the `loopschedule` dialect, as opposed to many other High-Level Synthesis +(HLS) representations, is to maintain the structure of loops after scheduling. As such, the +`loopschedule` ops are inspired by the `scf` and `affine` dialect ops. + +## Pipelined Loops + +Pipelined loops are represented with the `loopschedule.pipeline` op. A `pipeline` +loop resembles a `while` loop in the `scf` dialect, but the body must contain only +`loopschedule.pipeline.stage` and `loopschedule.terminator` ops. To have a better +understanding of how `loopschedule.pipeline` works, we will look at the following +example: + +``` +func.func @test1(%arg0: memref<10xi32>) -> i32 { + %c0 = arith.constant 0 : index + %c1 = arith.constant 1 : index + %c10 = arith.constant 10 : index + %c0_i32 = arith.constant 0 : i32 + %0 = loopschedule.pipeline II = 1 iter_args(%arg1 = %c0, %arg2 = %c0_i32) : (index, i32) -> i32 { + %1 = arith.cmpi ult, %arg1, %c10 : index + loopschedule.register %1 : i1 + } do { + %1:2 = loopschedule.pipeline.stage start = 0 { + %3 = arith.addi %arg1, %c1 : index + %4 = memref.load %arg0[%arg1] : memref<10xi32> + loopschedule.register %3, %4 : index, i32 + } : index, i32 + %2 = loopschedule.pipeline.stage start = 1 { + %3 = arith.addi %1#1, %arg2 : i32 + pipeline.register %3 : i32 + } : i32 + loopschedule.terminator iter_args(%1#0, %2), results(%2) : (index, i32) -> i32 + } + return %0 : i32 +} +``` + +A `pipeline` op first defines the initial values for the `iter_args`. `iter_args` are values that will +be passed back to the first stage after the last stage of the pipeline. The pipeline also defines a +specific, static `II`. Each pipeline stage in the `do` block represents a series of ops run in parallel. + +Values are registered at the end of a stage and passed out as results for future pipeline stages to +use. Each pipeline stage must have a defined start time, which is the number of cycles between the +start of the pipeline and when the first valid data will be available as input to that stage. + +Finally, the terminator is called with the `iter_args` for the next iteration and the result values +that will be returned when the pipeline completes. Even though the terminator is located at the +end of the loop body, its values are passed back to a previous stage whenever needed. We do not +need to wait for an entire iteration to finish before `iter_args` become valid for the next iteration. + +Multi-cycle and pipelined ops can also be supported in `pipeline` loops. In the following example, +assume the multiply op is bound to a 3-stage pipelined multiplier: + +``` +func.func @test1(%arg0: memref<10xi32>, %arg1: memref<10xi32>) { + %c0 = arith.constant 0 : index + %c1 = arith.constant 1 : index + %c10 = arith.constant 10 : index + %c1_i32 = arith.constant 1 : i32 + loopschedule.pipeline II = 1 iter_args(%arg2 = %c0) : (index, i32) -> () { + %1 = arith.cmpi ult, %arg1, %c10 : index + loopschedule.register %1 : i1 + } do { + %1:2 = loopschedule.pipeline.stage start = 0 { + %3 = arith.addi %arg1, %c1 : index + %4 = memref.load %arg0[%arg2] : memref<10xi32> + loopschedule.register %3, %4 : index, i32 + } : index, i32 + %2:2 = loopschedule.pipeline.stage start = 1 { + %3 = arith.muli %1#1, %c1_i32 : i32 + pipeline.register %3, %1#0 : i32 + } : i32 + loopschedule.pipeline.stage start = 4 { + memref.store %2#0, %arg0[%2#1] : i32 + pipeline.register + } : i32 + loopschedule.terminator iter_args(%1#0), results() : (index, i32) -> () + } + return +} +``` + +Here, the `II` is still 1 because new values can be introduced to the multiplier every cycle. The last +stage is delayed by 3 cycles because of the 3 cycle latency of the multiplier. The `pipeline` op is +currently tightly coupled to the lowering implementation used, as the latency of operators is not +represented in the IR, but rather an implicit assumption made when lowering later. The scheduling +problem is constructed with these implicit operator latencies in mind. This coupling can be addressed +in the future with a proper operator library to maintain explicit operator latencies in the IR. + +## Status + +Added pipeline loop representation, more documentation and rationale to come as ops are added. diff --git a/docs/Dialects/LoopSchedule/_index.md b/docs/Dialects/LoopSchedule/_index.md new file mode 100644 index 000000000000..f012c2842684 --- /dev/null +++ b/docs/Dialects/LoopSchedule/_index.md @@ -0,0 +1,3 @@ +# 'loopschedule' Dialect + +[include "Dialects/LoopSchedule.md"] diff --git a/docs/Dialects/Pipeline/RationalePipeline.md b/docs/Dialects/Pipeline/RationalePipeline.md new file mode 100644 index 000000000000..25057c5447c8 --- /dev/null +++ b/docs/Dialects/Pipeline/RationalePipeline.md @@ -0,0 +1,232 @@ +# Pipeline Dialect Rationale + +This document describes various design points of the `pipeline` dialect, why it is +the way it is, and current status. This follows in the spirit of other [MLIR +Rationale docs](https://mlir.llvm.org/docs/Rationale/). + +## Introduction + +## Pipeline Phases + +A `pipeline.pipeline` operation can be used in a sequence of phases, each +of which incrementally transforms the pipeline from being unscheduled towards +being an RTL representation of a pipeline. Each phase is mutually exlusive, +meaning that the "phase-defining" operations +(`pipeline.ss, pipeline.ss.reg, pipeline.stage`) are not allowed to co-exist. + +### Phase 1: Unscheduled + +The highest-level phase that a pipeline may be in is the unscheduled phase. +In this case, the body of the pipeline simply consists of a feed-forward set of +operations representing a dataflow graph. + +```mlir +%out = pipeline.unscheduled(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> (i32) { + ^bb0(%a0 : i32, %a1: i32, %g : i1): + %add0 = comb.add %a0, %a1 : i32 + %add1 = comb.add %add0, %a0 : i32 + %add2 = comb.add %add1, %add0 : i32 + pipeline.return %add2 valid %s1_valid : i32 +} +``` + +### Phase 2: Scheduled + +Uisng e.g. the `pipeline-schedule-linear` pass, a pipeline may be scheduled wrt. +an operator library denoting the latency of each operation. The result of a scheduling +problem is the movement of operations to specific blocks. +Each block represents a pipeline stage, with `pipeline.stage` operations being +stage-terminating operations that determine the order of stages. + +At this level, the semantics of the pipeline are that **any SSA def-use edge that +crosses a stage is a pipeline register**. +Note that we also intend to add support for attaching multi-cycle latencies to +SSA values in the future, which will allow for more fine-grained control over +the registers in the pipeline. +Given these relaxed semantics, this level of abstraction is suitable for pipeline +retiming. Operations may be moved from one stage to another, or new blocks may be +inserted between existing blocks, without changing the semantics of the pipeline. +The only requirement is that def-use edges wrt. the order of stages are preserved. + +```mlir +%out = pipeline.scheduled(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> (i32) { +^bb0(%a0 : i32, %a1: i32, %go : i1): + %add0 = comb.add %a0, %a1 : i32 + pipeline.stage ^bb1 enable %go + +^bb1: + %add1 = comb.add %add0, %a0 : i32 // %a0 is a block argument fed through a stage. + pipeline.stage ^bb2 enable %go + +^bb2: + %add2 = comb.add %add1, %add0 : i32 // %add0 crosses multiple stages. + pipeline.return %add2 enable %go : i32 // %go crosses multiple stages +} +``` + +### Phase 3: Register materialized + +Once the prior phase has been completed, pipeline registers must be materialized. +This amounts to a dataflow analysis to check the phase 2 property of def-use edges +across pipeline stages, performed by the `pipeline-explicit-regs` pass. + +The result of this pass is the addition of block arguments to each block representing +a pipeline stage, block arguments which represent stage inputs. It is the +`pipeline.stage` operation which determines which values are registered and which +are passed through directly. The block arguments to each stage should thus "just" +be seen as wires feeding into the stage. +In case a value was marked as multicycle, a value is passed through the `pass` list +in the stage terminating `pipeline.stage` operation. This indicates that the value +is to be passed through to the target stage without being registered. +At this level, an invariant of the pipeline is that **any SSA value used within +a stage must be defined within the stage**, wherein that definition may be either +a block argument or as a result of another operation in the stage. +The order of block arguments to a stage is, that register inputs from the +predecessor stage come first, followed by pass-through values. A verifier will +check that the signature of a stage block matches the predecessor `pipeline.stage` +operation. + +```mlir +%0 = pipeline.scheduled(%arg0, %arg1, %go) clock %clk reset %rst : (i32, i32, i1) -> i32 { +^bb0(%a0: i32, %a1: i32, %go: i1): + %1 = comb.add %a0, %a1 : i32 + pipeline.stage ^bb1 regs (%1, %a0, %go) pass () enable %go + +^bb1(%1_s0 : i32, %a0_s0 : i32, %go_s0 : i1): + %2 = comb.add %1_s0, %a0_s0 : i32 + pipeline.stage ^bb2 regs (%2, %1_s0, %go_s0) pass () enable %go_s0 + +^bb2(%2_s1 : i32, %1_s1 : i32, %go_s1 : i1): + %3 = comb.add %2_s1, %1_s1 : i32 // %1 from the entry stage is chained through both stage 1 and 2. + pipeline.return %3 valid %go_s1 : i32 // and likewise with %go +} +``` + +## A note on constants +Constants (defined as all operations which the `OpTrait::ConstantLike` trait) are +special cases in the pipeline dialect. These are allowed to be used anywhere +within the pipeline, and are not subject to the SSA def-use edge restrictions +described above. By doing so, we allow for constant canonicalizers to run, +which may apply regardless of where a constant is used within the pipeline. +The result of this is that constant-like operations will be moved to the +entry stage of the pipeline. +In the `pipeline-to-hw` pass, in case the user selects to perform `outline`d +lowering, constants will be **copied** into the stages which reference them. + +## Multicycle operations +Oftentimes, we may have operations which take multiple cycles to complete within +a pipeline. Support for this is provided by the `pipeline.latency` operation. +The main purpose of this operation is to provide a way to inform the +register materialization pass to pass values through stages _without_ registering them. + +Currently, all return values have an identical latency. This is an +arbitrary restriction, and may be lifted in the future if needed. + +As an example pipeline: +```mlir +^bb1: +... +%out = pipeline.latency 2 -> (i32) { + %dl1 = seq.compreg %in : i32 + %dl2 = seq.compreg %dl1 : i32 + pipeline.latency.return %dl2 : i32 +} +pipeline.stage ^bb2 + +^bb2: +// It is illegal to reference %out here +pipeline.stage ^bb3 + + +^bb3: +// It is legal to reference %out here +pipeline.stage ^bb4 + +^bb4: +// It is legal to reference %out here. This will also imply a register +// between stage bb3 and bb4. +foo.bar %out : i32 +``` + +which will register materialize to: +```mlir +^bb1: +... +%out = pipeline.latency 2 -> (i32) { + %dl1 = seq.compreg %in : i32 + %dl2 = seq.compreg %dl1 : i32 + pipeline.latency.return %dl2 : i32 +} +pipeline.stage ^bb2 pass(%out : i32) + +^bb2(%out_s2 : i32): +pipeline.stage ^bb3 pass(%out_s2 : i32) + + +^bb3(%out_s3 : i32): +pipeline.stage ^bb4 regs(%out_s3 : i32) + +^bb4(%out_s4 : i32): +foo.bar %out_s4 : i32 +``` + +## Non-stallable Pipeline Stages + +**Note:** the following is only valid for pipelines with a stall signal. + +An option of the Pipeline abstraction presented in this dialect is the ability +to have _non-stallable stages_ (NS). NS stages are used whereever a pipeline +access resources that are not able to stop on a dime, and thus require a fixed +amount of cycles to complete. + +Non-stallable stages are marked as an attribute of the pipeline operations, +wherein a bitvector is provided (by the user) to indicate which stage(s) are +non-stallable. + +To see how non-stallable stages are implemented, consider the following. For +every stage, we define two signals - `S_{N,en}` is the signal that indicates +that the stage currently has valid contents (i.e. not a bubble). `S_{N,valid}` +is the signal that is used as a clock-enable for the output registers of a +stage. + +Stages can be grouped into three distinct types based on how their valid signal +is defined: stallable stages, non-stallable stages and runoff stages. + + + +1. Stallable stages are any stages which appear **before** the first + non-stallable stage in the pipeline. +2. Non-stallable stages are the stages explicitly marked as non-stallable by the + user. +3. Runoff stages and stages that appear **after** (and by extension, **between** + non-stallable stages). Runoff stages consider their own enablement wrt. the + stall signal, as well as the enablement of the **last non-stallable + register** (LNS) wrt. the runoff stage's position in the pipeline. + +The purpose of the runoff stages is to ensure that they are able to pass through +as many pipeline cycles as there are upstream non-stallable stages, such that +the contents of the non-stallable stages is not discarded. +An important implication of this is that pipelines with non-stallable stages +**must** be connected to some buffer mechanism that is able to hold as many +pipeline output value cycles as there are non-stallable stages in the pipeline. + +As an example, the following 6 stage pipeline will have the following valid +signals: + + +### Example 1: +In this example, we have two NS stages followed by three runoff stages: + + + +In this example we see that, as expected, two cycles are output from the +pipeline after the stall signal goes high, corresponding to the two NS stages. + +### Example 2: +In this example, we have two NS stages, then one runoff stage, then one NS +stage, and finally one runoff stage: + + + +In this example we see that, as expected, three cycles are output from the +pipeline after the stall signal goes high, corresponding to the three NS stages. diff --git a/docs/Dialects/Pipeline/_index.md b/docs/Dialects/Pipeline/_index.md new file mode 100644 index 000000000000..88444b1b97cc --- /dev/null +++ b/docs/Dialects/Pipeline/_index.md @@ -0,0 +1,4 @@ +# 'pipeline' Dialect + +[include "Dialects/Pipeline.md"] + diff --git a/docs/Dialects/SV/RationaleSV.md b/docs/Dialects/SV/RationaleSV.md index e84d1876a6ad..40e56ab64fe1 100644 --- a/docs/Dialects/SV/RationaleSV.md +++ b/docs/Dialects/SV/RationaleSV.md @@ -117,7 +117,7 @@ The verbatim operation produces a typed value expressed by a string of SystemVerilog. This can be used to access macros and other values that are only sensible as Verilog text. There are three kinds of verbatim operations: - 1. VerbatimOp(`sv.verbatim`, the statement form + 1. VerbatimOp(`sv.verbatim`), the statement form 2. VerbatimExprOp(`sv.verbatim.expr`), the expression form. 3. VerbatimExprSEOp(`sv.verbatim.expr.se`), the effectful expression form. @@ -141,6 +141,21 @@ sv.verbatim "MACRO({{0}}, {{1}} reg={{4}}, {{3}})" {symRefs = [@reg1, @Module1, @instance1]} ``` +Substitions also allow format specifier after a ':'. The meaning of said options +depends on the operand type or the operation pointed to by the symbol. So far, +the following format specifiers are supported: + +- Symbol refering to a `hw.hierpath`: the separation string for joining names +in the path. Defaults to ".". + +Example: + +``` +hw.hierpath @instref_1 [@TopModule::@foo, @FooModule::@bar, @BarModule::@leaf] +sv.verbatim "hierpath {{0:|}}" {symbols = [@instref_1]} +// Produces: "hierpath foo|bar|leaf" +``` + ## Cost Model The SV dialect is primarily designed for human consumption, not machines. As @@ -172,4 +187,4 @@ Example, ==> (* foo, bar = baz *) wire GEN; -``` \ No newline at end of file +``` diff --git a/docs/Dialects/SV/_index.md b/docs/Dialects/SV/_index.md index 233aebae558b..29f3e6dd6d87 100644 --- a/docs/Dialects/SV/_index.md +++ b/docs/Dialects/SV/_index.md @@ -1,3 +1,3 @@ # 'sv' Dialect -[include "Dialects/SV.md"] \ No newline at end of file +[include "Dialects/SV.md"] diff --git a/docs/Dialects/Seq/RationaleSeq.md b/docs/Dialects/Seq/RationaleSeq.md index 7ff7bab8012a..defea0ee29be 100644 --- a/docs/Dialects/Seq/RationaleSeq.md +++ b/docs/Dialects/Seq/RationaleSeq.md @@ -56,6 +56,7 @@ addressing, meaning that this operation sets / reads the entire value. - **reset**: Signal to set the state to 'resetValue'. Optional. - **resetValue**: A value which the state is set to upon reset. Required iff 'reset' is present. +- **powerOn**: A value which will be assigned to the register upon system power-on. - **name**: A name for the register, defaults to `""`. Inferred from the textual SSA value name, or passed explicitly in builder APIs. The name will be passed to the `sv.reg` during lowering. @@ -63,7 +64,7 @@ the `sv.reg` during lowering. passed to the `sv.reg` during lowering if present. ```mlir -%q = seq.compreg %input, %clk [, %reset, %resetValue ] : $type(input) +%q = seq.compreg %input, %clk [ reset %reset, %resetValue ] [powerOn %powerOn] : $type(input) ``` Upon initialization, the state is defined to be uninitialized. @@ -119,10 +120,13 @@ from the design. reset is asynchronous. - **isAsync**: Optional boolean flag indicating whether the reset is asynchronous. +- **preset**: Optional attribute specifying a preset value. If no preset +attribute is present, the register is random-initialized. ```mlir %reg = seq.firreg %input clock %clk [ sym @sym ] - [ reset (sync|async) %reset, %value ] : $type(input) + [ reset (sync|async) %reset, %value ] + [ preset value ] : $type(input) ``` Examples of registers: @@ -134,7 +138,9 @@ Examples of registers: reset sync %reset, %value : i64 %reg_async_reset = seq.firreg %input clock %clk sym @sym - reset async %reset, %value : i1 + reset async %reset, %value : i1f + +%reg_preset = seq.firreg %next clock %clock preset 123 : i32 ``` A register without a reset lowers directly to an always block: @@ -158,7 +164,7 @@ end ``` Additionally, `sv` operations are also included to provide the register with -a randomized preset value. +a randomized preset value or an explicit preset constant. Since items assigned in an `always_ff` block cannot be initialised in an `initial` block, this register lowers to `always`. @@ -298,3 +304,28 @@ specialization is needed) attached to the memory symbol. ```mlir %mem = seq.debug @myMemory : !seq.hlmem<4xi32> ``` + +## The FIFO operation +The `seq.fifo` operation intends to capture the semantics of a FIFO which +eventually map to some form of on-chip resources. By having a FIFO abstraction, +we provide an abstraction that can be targeted for target-specialized implementations, +as well as default behavioral lowerings (based on `seq.hlmem`). + +The FIFO interface consists of: +- **Inputs**: + - clock, reset + - input data + - read/write enable +- **Outputs**: + - output data + - full, empty flags + - optional almost full, almost empty flags + +The fifo operation is configurable with the following parameters: +1. Depth (cycles) +2. Differing in- and output widths +3. Almost empty/full thresholds (optional) + +Like `seq.hlmem` there are no guarantees that all possible fifo configuration +are able to be lowered. Available lowering passes will pattern match on the +requested fifo configuration and attempt to provide a legal lowering. diff --git a/docs/Dialects/Seq/_index.md b/docs/Dialects/Seq/_index.md index 4ec5daf83800..98a29e609456 100644 --- a/docs/Dialects/Seq/_index.md +++ b/docs/Dialects/Seq/_index.md @@ -1,3 +1,3 @@ # 'seq' Dialect -[include "Dialects/Seq.md"] \ No newline at end of file +[include "Dialects/Seq.md"] diff --git a/docs/Dialects/SystemC/_index.md b/docs/Dialects/SystemC/_index.md index bb12cf844daa..65b77b0d38ec 100644 --- a/docs/Dialects/SystemC/_index.md +++ b/docs/Dialects/SystemC/_index.md @@ -1,3 +1,3 @@ # 'systemc' Dialect -[include "Dialects/SystemC.md"] \ No newline at end of file +[include "Dialects/SystemC.md"] diff --git a/docs/Dialects/Verif.md b/docs/Dialects/Verif.md new file mode 100644 index 000000000000..680a2db006d2 --- /dev/null +++ b/docs/Dialects/Verif.md @@ -0,0 +1,9 @@ +# 'verif' Dialect + +This dialect provides a collection of operations to express various verification concerns, such as assertions and interacting with a piece of hardware for the sake of verifying its proper functioning. + +[TOC] + +## Operations + +[include "Dialects/VerifOps.md"] diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md index b6c3165c81e1..85bd4056bc7b 100644 --- a/docs/GettingStarted.md +++ b/docs/GettingStarted.md @@ -4,22 +4,22 @@ Welcome to the CIRCT project! -"CIRCT" stands for "Circuit IR Compilers and Tools". The CIRCT project is an -(experimental!) effort looking to apply MLIR and the LLVM development +"CIRCT" stands for "Circuit IR Compilers and Tools". The CIRCT project is an +(experimental!) effort looking to apply MLIR and the LLVM development methodology to the domain of hardware design tools. -Take a look at the following diagram, which gives a brief overview of the -current [dialects and how they interact](includes/img/dialects.svg): +Take a look at the following diagram, which gives a brief overview of the +current [dialects and how they interact](/includes/img/dialects.svg): -

-

+

+

## Setting this up These commands can be used to setup CIRCT project: 1) **Install Dependencies** of LLVM/MLIR according to [the - instructions](https://mlir.llvm.org/getting_started/), including cmake and + instructions](https://mlir.llvm.org/getting_started/), including cmake and ninja. *Note:* CIRCT is known to build with at least GCC 9.4 and Clang 13.0.1, but @@ -46,8 +46,8 @@ $ git submodule init $ git submodule update ``` -*Note:* The repository is set up so that `git submodule update` performs a -shallow clone, meaning it downloads just enough of the LLVM repository to check +*Note:* The repository is set up so that `git submodule update` performs a +shallow clone, meaning it downloads just enough of the LLVM repository to check out the currently specified commit. If you wish to work with the full history of the LLVM repository, you can manually "unshallow" the submodule: @@ -101,10 +101,10 @@ If you plan to use the Python bindings, you should also specify 5) **Optionally configure your environment**: -It is useful to add the `.../circt/build/bin` and `.../circt/llvm/build/bin` -directories to the end of your PATH, allowing you to use the tools like `circt-opt` -in a natural way on the command line. Similarly, you need to be in the build -directory to invoke ninja, which is super annoying. You might find a bash/zsh +It is useful to add the `.../circt/build/bin` and `.../circt/llvm/build/bin` +directories to the end of your PATH, allowing you to use the tools like `circt-opt` +in a natural way on the command line. Similarly, you need to be in the build +directory to invoke ninja, which is super annoying. You might find a bash/zsh alias like this to be useful: ```bash @@ -157,6 +157,24 @@ satisfiability problems. Here, it is optionally used in the static scheduling infrastructure. Binary distributions often do not include the required CMake build info. The `utils/get-or-tools.sh` script downloads, compiles, and installs a known good version to a directory within the CIRCT source code, + +## Setting up VS Code Workspace + +We've provided an example VS Code file in `.vscode/Unified.code-workspace.jsonc` that can be used with the VS Code editor. To use the file, first copy to into a workspace file: +```sh +cp .vscode/Unified.code-workspace.jsonc .vscode/circt.code-workspace +``` + +Next, open the workspace file in VS code using the command palette (Ctrl + Shift + P) and selecting "Open workspace from file" and selecting the `.vscode/circt.code-workspace` file. + +Alternatively, open the file using: +``` +code .vscode/circt.code-workspace +``` +and select "open workspace" on the bottom right. + +Once the workspace is loaded, install the recommended tools and select "CMake: Build" from the command palette to start the unified build process. This will build the LLVM dependencies and CIRCT together. + where it is then picked up automatically by the build. ## Windows: notes on setting up with Ninja @@ -215,8 +233,8 @@ GitHub issues. Here are some high-level guidelines: * Beyond mechanical formatting issues, please follow the [LLVM Coding Standards](https://llvm.org/docs/CodingStandards.html). - * Please practice "[incremental development](https://llvm.org/docs/DeveloperPolicy.html#incremental-development)", - preferring to send a small series of incremental patches rather than large + * Please practice "[incremental development](https://llvm.org/docs/DeveloperPolicy.html#incremental-development)", + preferring to send a small series of incremental patches rather than large patches. There are other policies in the LLVM Developer Policy document that are worth skimming. @@ -229,14 +247,14 @@ GitHub issues. Here are some high-level guidelines: ## Submitting changes to LLVM / MLIR -This project depends on MLIR and LLVM, and it is occasionally useful to improve +This project depends on MLIR and LLVM, and it is occasionally useful to improve them. To get set up for this: -1) Follow the "[How to Contribute](https://mlir.llvm.org/getting_started/Contributing/)" +1) Follow the "[How to Contribute](https://mlir.llvm.org/getting_started/Contributing/)" instructions, and install the right tools, e.g. 'arcanist' and `clang-format`. 2) Get an LLVM Phabricator account -3) [Ask for LLVM commit access](https://llvm.org/docs/DeveloperPolicy.html#obtaining-commit-access), +3) [Ask for LLVM commit access](https://llvm.org/docs/DeveloperPolicy.html#obtaining-commit-access), the barrier is low. ### Submitting a patch diff --git a/docs/HLS.md b/docs/HLS.md new file mode 100644 index 000000000000..73f10879f5f3 --- /dev/null +++ b/docs/HLS.md @@ -0,0 +1,58 @@ +# HLS in CIRCT + +// write a compelling introduction here + +[TOC] + +## `hlstool` + +[`hlstool`](https://github.com/llvm/circt/blob/main/tools/hlstool/hlstool.cpp) is a tool for driving various CIRCT-based HLS flows. The tool works by defining [pass pipelines](https://mlir.llvm.org/docs/PassManagement/) that string together various MLIR and CIRCT passes to realize an HLS flow. +For new users to MLIR, it is important to recognize the different between such compiler driver tools, and the MLIR `opt` tools (optimization drivers), such as `mlir-opt` and `circt-opt`. +For instance, in `hlstool` we may specify a pass pipeline such as: +```c++ + // Memref legalization. + pm.addPass(circt::createFlattenMemRefPass()); + pm.nest().addPass( + circt::handshake::createHandshakeLegalizeMemrefsPass()); + pm.addPass(mlir::createConvertSCFToCFPass()); + pm.nest().addPass(createSimpleCanonicalizerPass()); +``` +For the compiler developer, however, we often *don't* want to run the entire HLS pass pipeline on our IR, but instead work on a specific level of IR and run a specific pass. Thus, every pass that is nested within `hlstool` can also be driven by an `opt` tool, e.g. `circt::createFlattenMemRefPass` is registered in `circt-opt` as `circt-opt --flatten-memref`. + +## Flows + +CIRCT homes multiple actively developed HLS flows, each with their own approach to HLS. This section aims to provide a brief overview of the different flows, and how to use them. + +**Note:** we do mean 'active development'**(!)** - most of these flows are the combined efforts of various student, phd and research projects, and should **not** be considered production-ready. You **will** encounter bugs, but you will also have a group of people are happy to help you fix them, and improve the flows. Feel free to reach out to the [codeowner](https://github.com/llvm/circt/blob/main/CODEOWNERS) of the dialect/pass that you're working on, or ask a question in the [LLVM discord](https://discord.gg/Jsktb5PR) which has a dedicated `circt` channel. + +How flow documentation is structured: +* **Description**: A brief description/rationale of the flow, what it is and why it is how it is. +* **Relevant dialects**: A listing of the dialects used in the flow (preferably in order), as well as links to the dialect rationales. We encourage the user to read the dialect rationales to understand the design decisions behind the dialects. +* **Example usage/relevant tests**: A vast majority of CIRCTs flow-specific knowledge is (unfortunately) institutional. However, users are often referred to the CIRCT tests to understand what different passes do, and how to drive the various CIRCT tools to achieve their goals. This section thus lists some relevant tests that may serve as important references of how to use the flow. + +## Dynamically scheduled HLS (DHLS) + + + +#### Relevant dialects: +- [cf](https://mlir.llvm.org/docs/Dialects/ControlFlowDialect/) +- [Handshake](https://circt.llvm.org/docs/Dialects/Handshake/RationaleHandshake/) + +#### Example usage/Relevant tests +- [handshake integration tests](https://github.com/llvm/circt/tree/main/integration_test/Dialect/Handshake) is a collection of cocotb-based tests that use `hlstool` to convert `cf`-level code to verilog. +- [handshake-runner](https://github.com/llvm/circt/tree/main/integration_test/handshake-runner) tests show how to use the `handshake-runner` to interpret `handshake`-level IR + +#### Resources + +The following is a list of work that has used or contributed to the DHLS flow in CIRCT: +- (10/2022) [Multi-Level Rewriting for Stream Processing to RTL compilation (M.Sc. Thesis) - Christian Ulmann](https://www.research-collection.ethz.ch/handle/20.500.11850/578713) +- (01/2022) [A Dynamically Scheduled HLS Flow in MLIR (M.Sc. Thesis) - Morten Borup Petersen](https://infoscience.epfl.ch/record/292189) + + +## Calyx + +#### Relevant dialects: + +#### Example usage/Relevant tests + +#### Resources diff --git a/docs/Passes.md b/docs/Passes.md index f8af79aad1ed..aa4b7b470ba8 100644 --- a/docs/Passes.md +++ b/docs/Passes.md @@ -8,10 +8,22 @@ This document describes the available CIRCT passes and their contracts. [include "CIRCTConversionPasses.md"] +## Arc Dialect Passes + +[include "ArcPasses.md"] + ## Calyx Dialect Passes [include "CalyxPasses.md"] +## Comb Dialect Passes + +[include "CombPasses.md"] + +## DC Dialect Passes + +[include "DCPasses.md"] + ## ESI Dialect Passes [include "ESIPasses.md"] @@ -32,6 +44,10 @@ This document describes the available CIRCT passes and their contracts. [include "HWPasses.md"] +## Ibis Dialect Passes + +[include "IbisPasses.md"] + ## LLHD Dialect Passes [include "LLHDPasses.md"] @@ -40,6 +56,10 @@ This document describes the available CIRCT passes and their contracts. [include "MSFTPasses.md"] +## OM Dialect Passes + +[include "OMPasses.md"] + ## Pipeline Dialect Passes [include "PipelinePasses.md"] @@ -55,3 +75,7 @@ This document describes the available CIRCT passes and their contracts. ## SV Dialect Passes [include "SVPasses.md"] + +## SystemC Dialect Passes + +[include "SystemCPasses.md"] diff --git a/docs/PyCDE/_index.md b/docs/PyCDE/_index.md index 8f2df4c1d61c..023f017a2455 100644 --- a/docs/PyCDE/_index.md +++ b/docs/PyCDE/_index.md @@ -26,7 +26,7 @@ from pycde import Input, Output, Module, System from pycde import generator from pycde.types import Bits -class AddInts(Module): +class OrInts(Module): a = Input(Bits(32)) b = Input(Bits(32)) c = Output(Bits(32)) @@ -36,6 +36,6 @@ class AddInts(Module): self.c = self.a | self.b -system = System([AddInts], name="ExampleSystem", output_directory="exsys") +system = System([OrInts], name="ExampleSystem", output_directory="exsys") system.compile() ``` diff --git a/docs/PyCDE/basics.md b/docs/PyCDE/basics.md index 4115166aa338..29bfdf4de4de 100644 --- a/docs/PyCDE/basics.md +++ b/docs/PyCDE/basics.md @@ -10,7 +10,7 @@ from pycde import Input, Output, Module, System from pycde import generator from pycde.types import Bits -class AddInts(Module): +class OrInts(Module): a = Input(Bits(32)) b = Input(Bits(32)) c = Output(Bits(32)) @@ -20,7 +20,7 @@ class AddInts(Module): self.c = self.a | self.b -system = System([AddInts], name="ExampleSystem", output_directory="exsys") +system = System([OrInts], name="ExampleSystem", output_directory="exsys") system.compile() ``` @@ -51,8 +51,8 @@ class Top(Module): @generator def construct(self): - add_ints = AddInts(a=self.a, b=self.b) - self.c = add_ints.c + or_ints = OrInts(a=self.a, b=self.b) + self.c = or_ints.c system = System([Top], name="ExampleSystem") @@ -213,20 +213,21 @@ from pycde import modparams def AddInts(width: int): class AddInts(Module): - a = Input(Bits(width)) - b = Input(Bits(width)) - c = Output(Bits(width)) + a = Input(UInt(width)) + b = Input(UInt(width)) + c = Output(UInt(width + 1)) @generator - def construct(self): - self.c = self.a | self.b + def build(self): + self.c = self.a + self.b return AddInts + class Top(Module): - a = Input(Bits(32)) - b = Input(Bits(32)) - c = Output(Bits(32)) + a = Input(UInt(32)) + b = Input(UInt(32)) + c = Output(UInt(33)) @generator def construct(self): @@ -246,6 +247,50 @@ PyCDE does not produce parameterized SystemVerilog modules! The specialization happens with Python code, which is far more powerful than SystemVerilog parameterization constructs. +## External parameterized modules + +Just like internally defined parameterized modules, leave off the generator and +PyCDE will output SystemVerilog instantations with the module parameters. The +parameter types are best effort based on the first instantiation encountered. + +```python +from pycde import modparams, Module + +@modparams +def AddInts(width: int): + + class AddInts(Module): + a = Input(UInt(width)) + b = Input(UInt(width)) + c = Output(UInt(width + 1)) + + return AddInts + + +class Top(Module): + a = Input(UInt(32)) + b = Input(UInt(32)) + c = Output(UInt(33)) + + @generator + def construct(self): + add_ints_m = AddInts(32) + add_ints = add_ints_m(a=self.a, b=self.b) + self.c = add_ints.c +``` + +For the instantiation produces: + +```verilog + AddInts #( + .width(64'd32) + ) AddInts ( + .a (a), + .b (b), + .c (c) + ); +``` + ## Using CIRCT dialects directly (instead of with PyCDE syntactic sugar) Generally speaking, don't. diff --git a/docs/PyCDE/compiling.md b/docs/PyCDE/compiling.md index 8a4c13af7d46..4315a9af0e7b 100644 --- a/docs/PyCDE/compiling.md +++ b/docs/PyCDE/compiling.md @@ -30,6 +30,13 @@ git submodule update --init ### Installing and Building with Wheels +If you are using a fork, you'll need the git tags since the package versioning step requires them: + +```bash +git remote add upstream git@github.com:llvm/circt.git +git fetch upstream --tags +``` + The simplest way to compile PyCDE for local use is to install it with the `pip install` command: diff --git a/docs/PyCDE/examples/basic.py b/docs/PyCDE/examples/basic.py index 9a8bbfe06141..218b088d8092 100644 --- a/docs/PyCDE/examples/basic.py +++ b/docs/PyCDE/examples/basic.py @@ -3,7 +3,7 @@ from pycde.types import Bits -class AddInts(Module): +class OrInts(Module): a = Input(Bits(32)) b = Input(Bits(32)) c = Output(Bits(32)) @@ -20,7 +20,7 @@ class Top(Module): @generator def construct(self): - add_ints = AddInts(a=self.a, b=self.b) + add_ints = OrInts(a=self.a, b=self.b) self.c = add_ints.c diff --git a/docs/PyCDE/examples/parameters.py b/docs/PyCDE/examples/parameters.py index d13f90dae4c3..edbab0abe574 100644 --- a/docs/PyCDE/examples/parameters.py +++ b/docs/PyCDE/examples/parameters.py @@ -1,23 +1,27 @@ from pycde import Input, Output, Module, System from pycde import generator, modparams -from pycde.types import Bits +from pycde.types import UInt @modparams def AddInts(width: int): class AddInts(Module): - a = Input(Bits(width)) - b = Input(Bits(width)) - c = Output(Bits(width)) + a = Input(UInt(width)) + b = Input(UInt(width)) + c = Output(UInt(width + 1)) + + @generator + def build(self): + self.c = self.a + self.b return AddInts class Top(Module): - a = Input(Bits(32)) - b = Input(Bits(32)) - c = Output(Bits(32)) + a = Input(UInt(32)) + b = Input(UInt(32)) + c = Output(UInt(33)) @generator def construct(self): diff --git a/docs/PythonBindings.md b/docs/PythonBindings.md index 4a41c6b0e743..10f2fb764c5f 100644 --- a/docs/PythonBindings.md +++ b/docs/PythonBindings.md @@ -2,7 +2,7 @@ If you are mainly interested in using CIRCT from Python scripts, you need to compile both LLVM/MLIR and CIRCT with Python bindings enabled. Furthermore, you must use a unified build, where LLVM/MLIR and CIRCT are compiled together in one step. -CIRCT also includes an experimental, opinionated frontend for CIRCT's Python bindings, called [PyCDE](/PyCDE). +CIRCT also includes an experimental, opinionated frontend for CIRCT's Python bindings, called [PyCDE](PyCDE). ## Installing and Building with Wheels diff --git a/docs/VerilogGeneration.md b/docs/VerilogGeneration.md index 15bf1090b0bd..d3ba0221ee29 100644 --- a/docs/VerilogGeneration.md +++ b/docs/VerilogGeneration.md @@ -40,15 +40,15 @@ and use of the correct idioms is important. ## Baseline Assumptions -Circt assumes, as a baseline, that tools support the subset of verilog specified -in IEEE 1364.1-2002 "IEEE Standard for Verilog Register Transfer Level -Synthesis". Although this standard is deprecated and no replacement has been -created for System Verilog, it provides a reasonable, established, and defined -subset of the verilog specification which all tools should support. Circt may -assume more features than specified in that document (e.g. system verilog -constructs), but should be willing to consider lowering to simpler -implementations when justified to support a major tool and when such lowering is -possible. Supporting tools which do not meet this baseline will require +Circt assumes, as a baseline, that tools support the subset of verilog specified +in IEEE 1364.1-2002 "IEEE Standard for Verilog Register Transfer Level +Synthesis". Although this standard is deprecated and no replacement has been +created for System Verilog, it provides a reasonable, established, and defined +subset of the verilog specification which all tools should support. Circt may +assume more features than specified in that document (e.g. system verilog +constructs), but should be willing to consider lowering to simpler +implementations when justified to support a major tool and when such lowering is +possible. Supporting tools which do not meet this baseline will require substantial justification. ## Controlling output style with `LoweringOptions` @@ -82,6 +82,8 @@ The current set of "tool capability" Lowering Options is: forced to be simple wires. Some EDA tools rely on these being simple wires. * `disallowPackedArrays` (default=`false`). If true, eliminate packed arrays for tools that don't support them (e.g. Yosys). + * `disallowPackedStructAssignments` (default=`false`). If true, eliminate packed + struct assignments in favor of a wire + assignments to the individual fields. * `disallowLocalVariables` (default=`false`). If true, do not emit SystemVerilog locally scoped "automatic" or logic declarations - emit top level wire and reg's instead. @@ -89,7 +91,7 @@ The current set of "tool capability" Lowering Options is: like `assert`, `assume`, and `cover` will always be emitted with a label. If the statement has no label in the IR, a generic one will be created. Some EDA tools require verification statements to be labeled. - + The current set of "style" Lowering Options is: * `emittedLineLength` (default=`90`). This is the target width of lines in an @@ -130,6 +132,10 @@ The current set of "lint warnings fix" Lowering Options is: * `disallowExpressionInliningInPorts` (default=`false`). If true, every expression passed to an instance port is driven by a wire. Some lint tools dislike expressions being inlined into input ports so this option avoids such warnings. + * `caseInsensitiveKeywords` (default=`false`). If true, then check for + collisions with Verilog keywords insensitively. E.g., this will treat a + variable called `WIRE` as a collision with the keyword and rename it to + `WIRE_0` (or similar). When set to `false`, then `WIRE` will not be renamed. ## Recommended `LoweringOptions` by Target @@ -176,7 +182,7 @@ required. Additionally, Yosys doesn't accept packed arrays, so we suggest using ### Specifying `LoweringOptions` in a front-end HDL tool -The [`circt::LoweringOptions` struct itself](https://github.com/llvm/circt/blob/main/include/circt/Support/LoweringOptions.h) +The [`circt::LoweringOptions` struct itself](https://github.com/llvm/circt/blob/main/include/circt/Support/LoweringOptions.h) is very simple: it projects each of the lowering options as a boolean, integer or other property. This allows C++ code to set up and query these properties with a natural and easy to use API. diff --git a/docs/dialects.dot b/docs/dialects.dot index 796e73e7fc73..a1f46bcf9cec 100644 --- a/docs/dialects.dot +++ b/docs/dialects.dot @@ -44,6 +44,7 @@ digraph G { Calyx [URL="https://circt.llvm.org/docs/Dialects/Calyx/"] FIRRTL [URL="https://circt.llvm.org/docs/Dialects/FIRRTL/"] Pipeline [URL="https://circt.llvm.org/docs/Dialects/Pipeline/"] + LoopSchedule [URL="https://circt.llvm.org/docs/Dialects/LoopSchedule/"] SSP [URL="https://circt.llvm.org/docs/Dialects/SSP/"] MSFT [URL="https://circt.llvm.org/docs/Dialects/MSFT/"] ESI [URL="https://circt.llvm.org/docs/Dialects/ESI/"] @@ -51,6 +52,12 @@ digraph G { HWArith [URL="https://circt.llvm.org/docs/Dialects/HWArith/"] MooreMIR [URL="https://circt.llvm.org/docs/Dialects/Moore/", label="Moore MIR"] + // Intermediate node to target when lowering to both SV and Core dialects + lower_to_sv_and_core [shape=point label="" fillcolor=black] + + // Intermediate node to target when lowering to Core dialects + lower_to_core [shape=point label="" fillcolor=black] + // Invisible node to make space for RTL cluster's incoming edges. space_above_RTL [style = invis, label=""] @@ -63,6 +70,7 @@ digraph G { Interop [URL="https://circt.llvm.org/docs/Dialects/Interop/"] } LLHD [URL="https://circt.llvm.org/docs/Dialects/LLHD/"] + Arc [URL="https://circt.llvm.org/docs/Dialects/Arc/"] // Backend dialects SV [URL="https://circt.llvm.org/docs/Dialects/SV/"] @@ -70,6 +78,7 @@ digraph G { subgraph backend_internal_tools{ node [fillcolor="#fdc086"] + Arcilator llhd_sim [label="llhd-sim"] ExportSystemC ExportVerilog [URL="https://circt.llvm.org/docs/VerilogGeneration/"] @@ -99,16 +108,17 @@ digraph G { VCDTrace [label="Trace (vcd)"] SystemCFile [label="SystemC (c++)"] SVFile [label="SystemVerilog"] - ServiceDesc [label="ESI system description\n(JSON/Capnp)"] + ServiceDesc [label="ESI system description\n(JSON)"] SoftwareAPI [label="Software API\n(e.g. py/c++/c#)" style=dashed] TCL [label="Placements (tcl)"] + SimBinary [label="Simulation Binary (obj)"] } // ===== Connections ===== // Dialect conversions SCF -> Calyx - Affine -> Pipeline - Pipeline -> Calyx + Affine -> LoopSchedule + LoopSchedule -> Calyx Arith -> {Handshake Calyx} [ltail=cluster_std_arith_dialect] Handshake -> FIRRTL @@ -116,13 +126,21 @@ digraph G { // Things that lower into a subset of the RTL-like dialects. Cluster these // together to avoid a massive clutter. - {FIRRTL FSM ESI MSFT HWArith MooreMIR} -> HW [lhead=cluster_RTL] + {Pipeline MSFT HWArith MooreMIR} -> lower_to_core [arrowhead=none] + {ESI FIRRTL FSM} -> lower_to_sv_and_core [arrowhead=none] + lower_to_sv_and_core -> SV + lower_to_sv_and_core -> Comb [lhead=cluster_RTL] + lower_to_core -> Comb [lhead=cluster_RTL] Seq -> SV {HW MooreMIR} -> LLHD + Interop -> Arc [ltail=cluster_RTL] Comb -> SystemC [ltail=cluster_RTL] ExportVerilog -> SVFile // Tool flows + Arc -> Arcilator + Arcilator -> SimBinary + Scheduling -> LoopSchedule [dir=both] Scheduling -> Pipeline [dir=both] Chisel -> FIRFile FIRFile -> FIRRTLParser @@ -135,17 +153,13 @@ digraph G { SVVHDL -> Moore [weight=999] Moore -> MooreMIR Calyx -> Calyx_native - Calyx_native -> HW [lhead=cluster_RTL] + Calyx_native -> lower_to_sv_and_core [arrowhead=none] LLHD -> llhd_sim llhd_sim -> VCDTrace - //ESI -> CapNProto ESI -> ServiceDesc MSFT -> TCL PyFile -> PyCDE - PyCDE -> ESI - PyCDE -> MSFT - PyCDE -> HWArith - PyCDE -> FSM + PyCDE -> {ESI MSFT HWArith FSM} Scheduling -> SSP [dir=both] Scheduling -> MSFT [dir=both, style=dashed] ServiceDesc -> SoftwareAPI [style=dashed] @@ -157,10 +171,10 @@ digraph G { // Leave one rank free above the RTL cluster to improve routing of incoming // edges. {FIRRTL FSM ESI MSFT HWArith MooreMIR} -> space_above_RTL [weight=999, style=invis] - space_above_RTL -> {Seq HW Comb} [lhead=cluster_RTL, weight=999, style=invis] + {space_above_RTL} -> {Seq HW Comb} [lhead=cluster_RTL, weight=999, style=invis] // Fix the following sink nodes below the CIRCT cluster - SystemCFile -> {TCL ServiceDesc} [weight=999 style=invis] + SystemCFile -> {TCL ServiceDesc SVFile VCDTrace} [weight=999 style=invis] // Better organize the top. {PyTorch Polygeist Chisel} -> CF [weight=999 style=invis] diff --git a/docs/includes/img/Pipeline/ns_ex2.png b/docs/includes/img/Pipeline/ns_ex2.png new file mode 100644 index 000000000000..6204c2d48119 Binary files /dev/null and b/docs/includes/img/Pipeline/ns_ex2.png differ diff --git a/docs/includes/img/Pipeline/ns_ex3.png b/docs/includes/img/Pipeline/ns_ex3.png new file mode 100644 index 000000000000..82836b7989bc Binary files /dev/null and b/docs/includes/img/Pipeline/ns_ex3.png differ diff --git a/docs/includes/img/Pipeline/stage_control.png b/docs/includes/img/Pipeline/stage_control.png new file mode 100644 index 000000000000..4a1a593df1b6 Binary files /dev/null and b/docs/includes/img/Pipeline/stage_control.png differ diff --git a/docs/includes/img/dialects.png b/docs/includes/img/dialects.png index d6b0bed397b0..f907d7c08071 100644 Binary files a/docs/includes/img/dialects.png and b/docs/includes/img/dialects.png differ diff --git a/docs/includes/img/dialects.svg b/docs/includes/img/dialects.svg index 0b28cf24188d..8d0d3c745753 100644 --- a/docs/includes/img/dialects.svg +++ b/docs/includes/img/dialects.svg @@ -1,653 +1,738 @@ - - + Codestin Search App - + Codestin Search App - -Upstream frontends (selection) + +Upstream frontends (selection) Codestin Search App - -Upstream MLIR + +Upstream MLIR Codestin Search App - + Codestin Search App - -CIRCT + +CIRCT Codestin Search App - -Core dialects + +Core dialects Codestin Search App - -Input languages + +Input languages Codestin Search App - -PyTorch + +PyTorch Codestin Search App - -CF + +CF Codestin Search App - - + + Codestin Search App - -Polygeist + +Polygeist Codestin Search App - -Affine + +Affine Codestin Search App - - + + Codestin Search App - -SCF + +SCF Codestin Search App - -Calyx + +Calyx Codestin Search App - - + + - - -Codestin Search App - - -Pipeline + + +Codestin Search App + + +LoopSchedule - + -Codestin Search App - - +Codestin Search App + + Codestin Search App - -Arith + +Arith Codestin Search App - -Handshake + +Handshake Codestin Search App - - + + Codestin Search App - - + + Codestin Search App - -FIRRTLParser + +FIRRTLParser Codestin Search App - -FIRRTL + +FIRRTL - + Codestin Search App - - + + Codestin Search App - -PyCDE + +PyCDE - + Codestin Search App - - -MSFT + + +MSFT - + Codestin Search App - - + + - + Codestin Search App - - -ESI + + +ESI - + Codestin Search App - - + + - + Codestin Search App - - -FSM + + +FSM - + Codestin Search App - - + + - + Codestin Search App - - -HWArith + + +HWArith - + Codestin Search App - - + + Codestin Search App - -Scheduling + +Scheduling + + + + + +Codestin Search App + + +Pipeline - + Codestin Search App - - - + + + + + + +Codestin Search App + + + - + Codestin Search App - - -SSP + + +SSP - + Codestin Search App - - - + + + - + Codestin Search App - - - + + + Codestin Search App - - + + Codestin Search App - - + + - + Codestin Search App - -Calyx native + +Calyx native - + Codestin Search App - - + + + + + +Codestin Search App + + + + +Codestin Search App + - + -Codestin Search App - - -HW - - +Codestin Search App + - + -Codestin Search App - - +Codestin Search App + - + -Codestin Search App - - +Codestin Search App + + - - + -Codestin Search App - - +Codestin Search App + + - + Codestin Search App - - - -Placements (tcl) + + + +Placements (tcl) - + Codestin Search App - - + + - - - -Codestin Search App - - + + +Codestin Search App + + - + Codestin Search App - - - -ESI system description -(JSON/Capnp) + + + +ESI system description +(JSON) - + Codestin Search App - - + + + + + +Codestin Search App + - - -Codestin Search App - - + + +Codestin Search App + - - -Codestin Search App - - - - + Codestin Search App - - -Moore MIR + + +Moore MIR - - - -Codestin Search App - - + + +Codestin Search App + + - + Codestin Search App - - -LLHD + + +LLHD - + Codestin Search App - - - - - -Codestin Search App - - -Seq - - + + - - - + Codestin Search App - - -Comb + + +Comb - + + +Codestin Search App + + + - + Codestin Search App - - -SV + + +SV + + + + + +Codestin Search App + + + + + +Codestin Search App + + + + + +Codestin Search App + + +Seq + + + + + + +Codestin Search App + + +HW + + - + Codestin Search App - - + + - + Codestin Search App - - -ExportVerilog + + +ExportVerilog - + Codestin Search App - - + + - + Codestin Search App - - + + - + Codestin Search App - -llhd-sim + +llhd-sim - + Codestin Search App - - + + - + Codestin Search App - - -SystemC + + +SystemC - + Codestin Search App - - + + - + Codestin Search App - - -Interop + + +Interop + + +Codestin Search App + + +Arc + + + + + +Codestin Search App + + + - + Codestin Search App - - + + + + + +Codestin Search App + +Arcilator + + + +Codestin Search App + + - + Codestin Search App - - + + - + Codestin Search App - -ExportSystemC + +ExportSystemC - + Codestin Search App - - + + + + + +Codestin Search App + + + +Simulation Binary (obj) + + + +Codestin Search App + + - + Codestin Search App - - - -Trace (vcd) + + + +Trace (vcd) - + Codestin Search App - - + + - + Codestin Search App - - - -SystemC (c++) + + + +SystemC (c++) - + Codestin Search App - - + + - + Codestin Search App - - - -SystemVerilog + + + +SystemVerilog - + Codestin Search App - - + + - + Codestin Search App - -Moore + +Moore - + Codestin Search App - - + + - - -Codestin Search App - - + + +Codestin Search App + - + Codestin Search App - - - -.fir + + + +.fir - + Codestin Search App - - + + - + Codestin Search App - -Chisel + +Chisel - + Codestin Search App - - + + - + Codestin Search App - -SV/VHDL + +SV/VHDL - + Codestin Search App - - + + - + Codestin Search App - -Python + +Python - + Codestin Search App - - + + + + - + Codestin Search App - - - -Software API -(e.g. py/c++/c#) + + + +Software API +(e.g. py/c++/c#) - + Codestin Search App - - + + diff --git a/frontends/PyCDE/integration_test/CMakeLists.txt b/frontends/PyCDE/integration_test/CMakeLists.txt index f67114d3a5aa..7e8ded0b7a19 100644 --- a/frontends/PyCDE/integration_test/CMakeLists.txt +++ b/frontends/PyCDE/integration_test/CMakeLists.txt @@ -10,15 +10,24 @@ set(PYCDE_INTEGRATION_TEST_DEPENDS FileCheck count not split-file circt-rtl-sim - esi-cosim-runner PyCDE + esi-collateral mlir-opt hlstool CIRCTPythonModules) +if (TARGET esi-collateral) + list(APPEND PYCDE_INTEGRATION_TEST_DEPENDS + esi-collateral + ) +endif() + # If ESI Cosim is available to build then enable its tests. if (TARGET EsiCosimDpiServer) - list(APPEND PYCDE_INTEGRATION_TEST_DEPENDS EsiCosimDpiServer) + list(APPEND PYCDE_INTEGRATION_TEST_DEPENDS + EsiCosimDpiServer + esi-cosim-runner + ) get_property(ESI_COSIM_LIB_DIR TARGET EsiCosimDpiServer PROPERTY LIBRARY_OUTPUT_DIRECTORY) set(ESI_COSIM_PATH ${ESI_COSIM_LIB_DIR}/libEsiCosimDpiServer.so) endif() diff --git a/frontends/PyCDE/integration_test/esi_ram.py b/frontends/PyCDE/integration_test/esi_ram.py index 33a3624b5755..a17baee3135d 100644 --- a/frontends/PyCDE/integration_test/esi_ram.py +++ b/frontends/PyCDE/integration_test/esi_ram.py @@ -1,7 +1,10 @@ # REQUIRES: esi-cosim # RUN: rm -rf %t # RUN: %PYTHON% %s %t 2>&1 -# RUN: esi-cosim-runner.py --tmpdir %t --schema %t/hw/schema.capnp %s %t/hw/*.sv +# ... can't glob *.sv because PyCDE always includes driver.sv, but that's not the +# top that we want to use. Just delete it. +# RUN: rm -f %t/hw/driver.sv +# RUN: esi-cosim-runner.py --no-aux-files --tmpdir %t --schema %t/hw/schema.capnp %s %t/hw/*.sv # PY: from esi_ram import run_cosim # PY: run_cosim(tmpdir, rpcschemapath, simhostport) @@ -9,6 +12,7 @@ from pycde import (Clock, Input, Module, generator, types) from pycde.constructs import Wire from pycde import esi +from pycde.bsp import cosim import sys @@ -39,7 +43,7 @@ def construct(ports): RamI64x8.write(write_data) -class top(Module): +class Top(Module): clk = Clock(types.i1) rst = Input(types.i1) @@ -57,7 +61,6 @@ def construct(ports): RamI64x8.instantiate_builtin("sv_mem", result_types=[], inputs=[ports.clk, ports.rst]) - esi.Cosim(MemComms, ports.clk, ports.rst) def run_cosim(tmpdir=".", schema_path="schema.capnp", rpchostport=None): @@ -71,25 +74,28 @@ def run_cosim(tmpdir=".", schema_path="schema.capnp", rpchostport=None): cosim = Cosim(schema_path, rpchostport) print(cosim.list()) - top = esi_sys.top(cosim) + top = esi_sys.top(cosim).bsp + print(dir(top)) write_cmd = {"address": 2, "data": 42} - loopback_result = top.mem_comms.loopback[0](write_cmd) + loopback_result = top.loopback[0](write_cmd) assert loopback_result == write_cmd - read_result = top.mem_comms.read[0](2) + read_result = top.read[0](2) assert read_result == 0 - read_result = top.mem_comms.read[0](3) + read_result = top.read[0](3) assert read_result == 0 - top.mem_comms.write[0].write(write_cmd) - read_result = top.mem_comms.read[0](2) + top.write[0].write(write_cmd) + read_result = top.read[0](2) assert read_result == 42 - read_result = top.mem_comms.read[0](3) + read_result = top.read[0](3) assert read_result == 42 if __name__ == "__main__": - s = pycde.System([top], name="ESIMem", output_directory=sys.argv[1]) + s = pycde.System([cosim.CosimBSP(Top)], + name="ESIMem", + output_directory=sys.argv[1]) s.compile() s.package() diff --git a/frontends/PyCDE/integration_test/esi_ram_cpp/CMakeLists.txt b/frontends/PyCDE/integration_test/esi_ram_cpp/CMakeLists.txt new file mode 100644 index 000000000000..1ff10000bc78 --- /dev/null +++ b/frontends/PyCDE/integration_test/esi_ram_cpp/CMakeLists.txt @@ -0,0 +1,102 @@ +cmake_minimum_required(VERSION 3.13.4) +project(esi_ram_test) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# TODO: most of this stuff should be moved to a .cmake file that is included. + +# fetch https://github.com/veselink1/refl-cpp +include(FetchContent) +FetchContent_Declare( + refl-cpp + GIT_REPOSITORY https://github.com/veselink1/refl-cpp + GIT_TAG v0.12.4 +) +FetchContent_MakeAvailable(refl-cpp) + +# Assert that CIRCT_DIR is defined. +if(NOT DEFINED CIRCT_DIR) + message(FATAL_ERROR "CIRCT_DIR must be defined.") +endif() + +if(NOT DEFINED PYCDE_OUT_DIR) + message(FATAL_ERROR "PYCDE_OUT_DIR must be defined.") +endif() + +message(STATUS "CIRCT_DIR= ${CIRCT_DIR}") +message(STATUS "PYCDE_OUT_DIR= ${PYCDE_OUT_DIR}") + +set(CAPNP_SCHEMA "${PYCDE_OUT_DIR}/hw/schema.capnp") +set(ESI_CPP_API "${PYCDE_OUT_DIR}/hw/ESISystem.h") +set(ESI_HW_INCLUDE_DIR "${PYCDE_OUT_DIR}/hw") + +# Ensure that the above files are present +if(NOT EXISTS ${CAPNP_SCHEMA}) + message(FATAL_ERROR "CAPNP_SCHEMA not found: ${CAPNP_SCHEMA}") +endif() + +if(NOT EXISTS ${ESI_CPP_API}) + message(FATAL_ERROR "ESI_CPP_API not found: ${ESI_CPP_API}") +endif() + + +if(DEFINED CAPNP_PATH) + set(ENV{PKG_CONFIG_PATH} + "${CAPNP_PATH}/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") + find_package(CapnProto CONFIG PATHS ${CAPNP_PATH}) + else() + set(ENV{PKG_CONFIG_PATH} + "${CIRCT_DIR}/ext/lib/pkgconfig:$ENV{PKG_CONFIG_PATH}") + find_package(CapnProto CONFIG PATHS "${CIRCT_DIR}/ext") +endif() + +if (NOT CapnProto_FOUND) + message(FATAL_ERROR "Cap'n Proto not found.") +endif() + +# Move schema to the build directory - required by capnp_generate_cpp. +set(CAPNPC_SRC_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/capnp_generated) +set(CAPNPC_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/capnp_generated) +file(COPY ${CAPNP_SCHEMA} DESTINATION ${CAPNPC_OUTPUT_DIR}) +get_filename_component(CAPNP_SCHEMA_BASENAME ${CAPNP_SCHEMA} NAME) +set(COPIED_CAPNP_SCHEMA ${CAPNP_OUTDIR}/${CAPNP_SCHEMA_BASENAME}) +capnp_generate_cpp( + ESI_RAM_SRCS + ESI_RAM_HDRS + ${CAPNPC_SRC_PREFIX}/${CAPNP_SCHEMA_BASENAME} +) + +message(STATUS "CAPNP_OUTDIR= ${CAPNP_OUTDIR}") +add_executable(esi_ram_test + esi_ram.cpp + ${ESI_RAM_SRCS} + ${ESI_RAM_HDRS} +) +target_link_libraries(esi_ram_test + ${CAPNP_LIBRARIES} + refl-cpp +) +target_include_directories(esi_ram_test PUBLIC + # Include the copied ESI C++ runtime headers. + ${PYCDE_OUT_DIR}/runtime/cpp/include) + +message("ESI_RAM_SRCS: ${ESI_RAM_SRCS}") +message("ESI_RAM_HDRS: ${ESI_RAM_HDRS}") + +target_include_directories( + esi_ram_test + PUBLIC + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CAPNP_INCLUDE_DIRS} + ${CAPNP_OUTDIR} + ${CIRCT_DIR}/include + ${ESI_HW_INCLUDE_DIR} +) + +target_compile_definitions( + esi_ram_test + PUBLIC + -DESI_COSIM_CAPNP_H=\"${CAPNPC_OUTPUT_DIR}/schema.capnp.h\" +) diff --git a/frontends/PyCDE/integration_test/esi_ram_cpp/esi_ram.cpp b/frontends/PyCDE/integration_test/esi_ram_cpp/esi_ram.cpp new file mode 100644 index 000000000000..a7573152defe --- /dev/null +++ b/frontends/PyCDE/integration_test/esi_ram_cpp/esi_ram.cpp @@ -0,0 +1,138 @@ +// REQUIRES: esi-cosim +// XFAIL: * + +// clang-format off + +// Create ESI system +// RUN: rm -rf %t +// RUN: %PYTHON% %S/../esi_ram.py %t 2>&1 + +// Build the project using the CMakeLists.txt from this directory. Just move +// everything to the output folder in the build directory; this is very convenient +// if we want to run the build manually afterwards. +// RUN: cp %s %t +// RUN: cp %S/CMakeLists.txt %t +// RUN: cmake -S %t \ +// RUN: -B %t/build \ +// RUN: -DCIRCT_DIR=%CIRCT_SOURCE% \ +// RUN: -DPYCDE_OUT_DIR=%t +// RUN: cmake --build %t/build + +// Run test +// ... can't glob *.sv because PyCDE always includes driver.sv, but that's not the +// top that we want to use. Just delete it. +// RUN: rm %t/hw/driver.sv +// RUN: esi-cosim-runner.py --tmpdir=%t \ +// RUN: --no-aux-files \ +// RUN: --schema %t/hw/schema.capnp \ +// RUN: --exec %t/build/esi_ram_test \ +// RUN: %t/hw/*.sv + +// To run this test manually: +// 1. run `ninja check-pycde-integration` (this will create the output folder, run PyCDE, ...) +// 2. navigate to %t +// 3. In a separate terminal, run esi-cosim-runner.py in server only mode: +// - cd %t +// - esi-cosim-runner.py --tmpdir=$(pwd) --schema=$(pwd)/hw/schema.capnp --server-only $(pwd)/hw/top.sv $(pwd) +// 4. In another terminal, run the test executable. When running esi-cosim-runner, it'll print the $port which +// the test executable should connect to. +// - cd %t/build +// - ./esi_ram_test localhost:$port ../hw/schema.capn + +// clang-format on +#include +#include +#include +#include + +#include "esi/backends/capnp.h" + +#include ESI_COSIM_CAPNP_H + +#include "ESISystem.h" + +using namespace esi; +using namespace runtime; + +template +int logTestFailure(T expected, T actual, int testID) { + std::cerr << "Test " << testID << " failed: expected " << expected << ", got " + << actual << std::endl; + return testID; +} + +template +int runTest(TBackend &backend) { + // Connect the ESI system to the provided backend. + esi::runtime::top top(backend); + + auto write_cmd = + ESITypes::Struct16871797234873963366{.address = 2, .data = 42}; + + auto loopback_result = (*top.bsp->loopback)(write_cmd); + if (loopback_result != write_cmd) + return logTestFailure(write_cmd, loopback_result, 1); + + auto read_result = (*top.bsp->read)(2); + if (read_result != ESITypes::I64(0)) + return logTestFailure(ESITypes::I64(0), read_result, 2); + + read_result = (*top.bsp->read)(3); + if (read_result != ESITypes::I64(0)) + return logTestFailure(ESITypes::I64(0), read_result, 3); + + (*top.bsp->write)(write_cmd); + read_result = (*top.bsp->read)(2); + if (read_result != ESITypes::I64(42)) + return logTestFailure(ESITypes::I64(42), read_result, 4); + + read_result = (*top.bsp->read)(3); + if (read_result != ESITypes::I64(42)) + return logTestFailure(ESITypes::I64(42), read_result, 5); + + // Re-write a 0 to the memory (mostly for debugging purposes to allow us to + // keep the server alive and rerun the test). + write_cmd = ESITypes::Struct16871797234873963366{.address = 2, .data = 0}; + (*top.bsp->write)(write_cmd); + read_result = (*top.bsp->read)(2); + if (read_result != ESITypes::I64(0)) + return logTestFailure(ESITypes::I64(0), read_result, 6); + + return 0; +} + +int run_cosim_test(const std::string &host, unsigned port) { + // Run test with cosimulation backend. + esi::runtime::cosim::CapnpBackend cosim(host, port); + return runTest(cosim); +} + +int main(int argc, char **argv) { + std::string rpchostport; + if (argc != 3) { + // Schema not currently used but required by the ESI cosim tester + std::cerr + << "usage: esi_ram_test {rpc hostname}:{rpc port} {path to schema}" + << std::endl; + return 1; + } + + rpchostport = argv[1]; + + // Parse the RPC host and port from the command line. + auto colon = rpchostport.find(':'); + if (colon == std::string::npos) { + std::cerr << "Invalid RPC host:port string: " << rpchostport << std::endl; + return 1; + } + auto host = rpchostport.substr(0, colon); + auto port = stoi(rpchostport.substr(colon + 1)); + + auto res = run_cosim_test(host, port); + if (res != 0) { + std::cerr << "Test failed with error code " << res << std::endl; + return 1; + } + std::cout << "Test passed" << std::endl; + return 0; +} diff --git a/frontends/PyCDE/integration_test/esi_ram_cpp/lit.local.cfg b/frontends/PyCDE/integration_test/esi_ram_cpp/lit.local.cfg new file mode 100644 index 000000000000..81261555b424 --- /dev/null +++ b/frontends/PyCDE/integration_test/esi_ram_cpp/lit.local.cfg @@ -0,0 +1 @@ +config.suffixes.add('.cpp') diff --git a/frontends/PyCDE/integration_test/esi_test.py b/frontends/PyCDE/integration_test/esi_test.py index b9fe89b5e67a..3479d5d2898a 100644 --- a/frontends/PyCDE/integration_test/esi_test.py +++ b/frontends/PyCDE/integration_test/esi_test.py @@ -9,6 +9,7 @@ from pycde import (Clock, Input, InputChannel, OutputChannel, Module, generator, types) from pycde import esi +from pycde.bsp import cosim from pycde.constructs import Wire import sys @@ -23,7 +24,7 @@ class HostComms: class Producer(Module): - clk = Input(types.i1) + clk = Clock() int_out = OutputChannel(types.i32) @generator @@ -33,7 +34,7 @@ def construct(ports): class Consumer(Module): - clk = Input(types.i1) + clk = Clock() int_in = InputChannel(types.i32) @generator @@ -56,7 +57,7 @@ def construct(ports): class Mid(Module): - clk = Clock(types.i1) + clk = Clock() rst = Input(types.i1) @generator @@ -68,7 +69,7 @@ def construct(ports): class Top(Module): - clk = Clock(types.i1) + clk = Clock() rst = Input(types.i1) @generator @@ -77,7 +78,7 @@ def construct(ports): if __name__ == "__main__": - s = pycde.System(esi.CosimBSP(Top), + s = pycde.System(cosim.CosimBSP(Top), name="ESILoopback", output_directory=sys.argv[1], sw_api_langs=["python"]) diff --git a/frontends/PyCDE/integration_test/lit.cfg.py b/frontends/PyCDE/integration_test/lit.cfg.py index 539b6b38558b..1dc0a6b2792f 100644 --- a/frontends/PyCDE/integration_test/lit.cfg.py +++ b/frontends/PyCDE/integration_test/lit.cfg.py @@ -154,5 +154,6 @@ # Enable ESI's Capnp tests if they're supported. if config.esi_capnp != "": config.available_features.add('capnp') + config.substitutions.append(('%CAPNP_CMAKE_DIR%', config.capnp_path)) llvm_config.add_tool_substitutions(tools, tool_dirs) diff --git a/frontends/PyCDE/integration_test/lit.site.cfg.py.in b/frontends/PyCDE/integration_test/lit.site.cfg.py.in index 915250a2e79b..73d4d1bdabef 100644 --- a/frontends/PyCDE/integration_test/lit.site.cfg.py.in +++ b/frontends/PyCDE/integration_test/lit.site.cfg.py.in @@ -45,6 +45,7 @@ config.quartus_path = "@QUARTUS_PATH@" config.vivado_path = "@VIVADO_PATH@" config.questa_path = "@QUESTA_PATH@" config.esi_capnp = "@ESI_CAPNP@" +config.capnp_path = "@CapnProto_DIR@" config.iverilog_path = "@IVERILOG_PATH@" config.bindings_python_enabled = @CIRCT_BINDINGS_PYTHON_ENABLED@ diff --git a/frontends/PyCDE/integration_test/pytorch/dot_prod_system.py b/frontends/PyCDE/integration_test/pytorch/dot_prod_system.py index eb2b1c659955..0a175d8729b3 100644 --- a/frontends/PyCDE/integration_test/pytorch/dot_prod_system.py +++ b/frontends/PyCDE/integration_test/pytorch/dot_prod_system.py @@ -1,7 +1,8 @@ # REQUIRES: esi-cosim +# XFAIL: * # RUN: rm -rf %t # RUN: mlir-opt %S/dot.linalg.mlir --empty-tensor-to-alloc-tensor --one-shot-bufferize="allow-return-allocs bufferize-function-boundaries" --buffer-results-to-out-params --convert-linalg-to-affine-loops --lower-affine --convert-scf-to-cf --canonicalize > dot.cf.mlir -# RUN: hlstool dot.cf.mlir --with-esi --dynamic-hw -ir -ir-output-level 2 > dot.hw.mlir +# RUN: hlstool dot.cf.mlir --with-esi --dynamic-hw -ir -output-level rtl > dot.hw.mlir # RUN: %PYTHON% %s %t 2>&1 # RUN: esi-cosim-runner.py --no-aux-files --tmpdir %t --schema %t/runtime/schema.capnp %s `ls %t/hw/*.sv | grep -v driver.sv` # PY: from dot_prod_system import run_cosim diff --git a/frontends/PyCDE/pyproject.toml b/frontends/PyCDE/pyproject.toml index c15193cc2bef..94874cacf1e7 100644 --- a/frontends/PyCDE/pyproject.toml +++ b/frontends/PyCDE/pyproject.toml @@ -1,26 +1,25 @@ [build-system] requires = [ - "setuptools>=42", - "setuptools_scm>=6.2", - "wheel", - "cmake>=3.12", - - # MLIR build depends. - "numpy", - "pybind11>=2.7.1", - "PyYAML", + "setuptools>=68", + "setuptools_scm>=8.0", + "wheel", + "cmake>=3.12", - # PyCDE depends - "cocotb>=1.6.2", - "cocotb-test>=0.2.2", - "jinja2" + # MLIR build depends. + "numpy", + "pybind11>=2.9", + "PyYAML", + + # PyCDE depends + "cocotb>=1.6.2", + "cocotb-test>=0.2.2", + "jinja2", ] build-backend = "setuptools.build_meta" # Enable version inference from Git. [tool.setuptools_scm] root = "../.." -relative_to = "setup.py" tag_regex = "^pycde-(\\d+\\.\\d+\\.\\d+)?$" local_scheme = "no-local-version" git_describe_command = "git describe --dirty --tags --long --match pycde*" @@ -32,3 +31,14 @@ manylinux-x86_64-image = "ghcr.io/circt/images/pycde-build" [tool.cibuildwheel.linux] # Use our internal auditwheel script so as to not mess up the collateral. repair-wheel-command = "frontends/PyCDE/auditwheel.sh {dest_dir} {wheel}" + +[project] +name = "pycde" +dynamic = ["version"] +description = "Python CIRCT Design Entry" +authors = [{ name = "John Demme", email = "John.Demme@microsoft.com" }] +dependencies = ['numpy', 'jinja2'] +requires-python = ">=3.8" + +[project.urls] +"Homepage" = "https://circt.llvm.org/docs/PyCDE/" diff --git a/frontends/PyCDE/python/requirements.txt b/frontends/PyCDE/python/requirements.txt index f5679e9d14a0..9a9c66e5d7f0 100644 --- a/frontends/PyCDE/python/requirements.txt +++ b/frontends/PyCDE/python/requirements.txt @@ -1,6 +1,6 @@ numpy -pybind11>=2.7.1 +pybind11>=2.9 PyYAML cocotb>=1.6.2 cocotb-test>=0.2.2 -jinja2 \ No newline at end of file +jinja2 diff --git a/frontends/PyCDE/setup.py b/frontends/PyCDE/setup.py index 9b42b988cc73..f6dffd7056d9 100644 --- a/frontends/PyCDE/setup.py +++ b/frontends/PyCDE/setup.py @@ -99,17 +99,11 @@ def build_extension(self, ext): setup(name="pycde", - version="0.0.1", - author="John Demme", - author_email="John.Demme@microsoft.com", - description="Python CIRCT Design Entry", - long_description="", include_package_data=True, ext_modules=[ CMakeExtension("pycde.circt._mlir_libs._mlir"), CMakeExtension("pycde.circt._mlir_libs._circt"), ], - install_requires=["numpy", "jinja2"], cmdclass={ "build": CustomBuild, "built_ext": NoopBuildExtension, diff --git a/frontends/PyCDE/src/CMakeLists.txt b/frontends/PyCDE/src/CMakeLists.txt index 81c3b6b2f660..830a5d672c14 100644 --- a/frontends/PyCDE/src/CMakeLists.txt +++ b/frontends/PyCDE/src/CMakeLists.txt @@ -8,6 +8,10 @@ include(AddMLIRPython) +# Some of the PyCDE CMake code which is only used in Capnp builds uses newer +# CMake features. +cmake_minimum_required(VERSION 3.21.0) + add_compile_definitions("MLIR_PYTHON_PACKAGE_PREFIX=pycde.circt.") declare_mlir_python_sources(PyCDESources @@ -44,6 +48,7 @@ declare_mlir_python_sources(PyCDESources Makefile.cosim bsp/__init__.py + bsp/cosim.py bsp/xrt.py bsp/EsiXrtPython.cpp bsp/Makefile.xrt.j2 @@ -91,6 +96,7 @@ add_mlir_python_modules(PyCDE add_dependencies(PyCDE PyCDE_CIRCTPythonModules + esi-collateral ) add_dependencies(install-PyCDE install-PyCDE_CIRCTPythonModules @@ -109,10 +115,17 @@ install(RUNTIME_DEPENDENCY_SET PyCDE_RUNTIME_DEPS ) # Add necessary ESI collateral to the package. -install(TARGETS esi-collateral - PUBLIC_HEADER DESTINATION python_packages/pycde/collateral - COMPONENT PyCDE -) +get_target_property(CollateralPrefix esi-collateral BINARY_DIR) +get_target_property(CollateralFiles esi-collateral SOURCES) +foreach(CFile IN LISTS CollateralFiles) + get_filename_component(CDir ${CFile} DIRECTORY) + install(FILES + "${CollateralPrefix}/${CFile}" + DESTINATION "python_packages/pycde/collateral/${CDir}" + COMPONENT PyCDE + ) +endforeach() + install(TARGETS circt-std-sim-drivers PUBLIC_HEADER DESTINATION python_packages/pycde/collateral COMPONENT PyCDE @@ -135,4 +148,9 @@ if(ESI_COSIM) DESTINATION python_packages/pycde/collateral COMPONENT PyCDE ) + install(FILES + "$/CosimDpi.capnp" + DESTINATION python_packages/pycde/collateral/runtime + COMPONENT PyCDE + ) endif() diff --git a/frontends/PyCDE/src/__init__.py b/frontends/PyCDE/src/__init__.py index 1f8af4074f88..14a861c15bbe 100644 --- a/frontends/PyCDE/src/__init__.py +++ b/frontends/PyCDE/src/__init__.py @@ -18,7 +18,8 @@ def __exit_ctxt(): DefaultContext.__exit__(None, None, None) -from .common import (AppID, Clock, Input, InputChannel, Output, OutputChannel) +from .common import (AppID, Clock, Reset, Input, InputChannel, Output, + OutputChannel) from .module import (generator, modparams, Module) from .system import (System) from .types import (dim, types) diff --git a/frontends/PyCDE/src/bsp/__init__.py b/frontends/PyCDE/src/bsp/__init__.py index f5155b5c1cf1..c4c37e67ab9b 100644 --- a/frontends/PyCDE/src/bsp/__init__.py +++ b/frontends/PyCDE/src/bsp/__init__.py @@ -2,4 +2,5 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +from .cosim import CosimBSP from .xrt import XrtBSP diff --git a/frontends/PyCDE/src/bsp/cosim.py b/frontends/PyCDE/src/bsp/cosim.py new file mode 100644 index 000000000000..c2826fa6408d --- /dev/null +++ b/frontends/PyCDE/src/bsp/cosim.py @@ -0,0 +1,73 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ..common import Clock, Input +from ..module import Module, generator +from ..system import System +from ..types import types +from .. import esi + +from ..circt import ir +from ..circt.dialects import esi as raw_esi + +import shutil +from pathlib import Path + +__root_dir__ = Path(__file__).parent.parent + + +def CosimBSP(user_module): + """Wrap and return a cosimulation 'board support package' containing + 'user_module'""" + + class top(Module): + clk = Clock() + rst = Input(types.int(1)) + + @generator + def build(ports): + user_module(clk=ports.clk, rst=ports.rst) + raw_esi.ServiceInstanceOp(result=[], + service_symbol=None, + impl_type=ir.StringAttr.get("cosim"), + inputs=[ports.clk.value, ports.rst.value]) + + System.current().add_packaging_step(esi.package) + System.current().add_packaging_step(top.package) + + @staticmethod + def package(sys: System): + """Run the packaging to create a cosim package.""" + + # When pycde is installed through a proper install, all of the collateral + # files are under a dir called "collateral". + hw_src = sys.hw_output_dir + collateral_dir = __root_dir__ / "collateral" + if collateral_dir.exists(): + bin_dir = collateral_dir + lib_dir = collateral_dir + esi_lib_dir = collateral_dir + else: + # Build we also want to allow pycde to work in-tree for developers. The + # necessary files are screwn around the build tree. + build_dir = __root_dir__.parents[4] + bin_dir = build_dir / "bin" + lib_dir = build_dir / "lib" + circt_lib_dir = build_dir / "tools" / "circt" / "lib" + esi_lib_dir = circt_lib_dir / "Dialect" / "ESI" + + shutil.copy(bin_dir / "driver.cpp", hw_src) + shutil.copy(bin_dir / "driver.sv", hw_src) + shutil.copy(esi_lib_dir / "cosim" / "Cosim_DpiPkg.sv", hw_src) + shutil.copy(esi_lib_dir / "cosim" / "Cosim_Endpoint.sv", hw_src) + + for f in lib_dir.glob("*.so"): + shutil.copy(f, hw_src) + for f in lib_dir.glob("*.dll"): + shutil.copy(f, hw_src) + + shutil.copy(__root_dir__ / "Makefile.cosim", sys.output_directory) + shutil.copy(sys.hw_output_dir / "schema.capnp", sys.runtime_output_dir) + + return top diff --git a/frontends/PyCDE/src/bsp/xrt.py b/frontends/PyCDE/src/bsp/xrt.py index fde5b4fd2fad..52d284194f89 100644 --- a/frontends/PyCDE/src/bsp/xrt.py +++ b/frontends/PyCDE/src/bsp/xrt.py @@ -7,6 +7,7 @@ from ..module import Module, generator from ..system import System from ..types import bit, types, Bits +from .. import esi import glob from io import FileIO @@ -221,6 +222,7 @@ def construct(ports): # Copy additional sources sys: System = System.current() + sys.add_packaging_step(esi.package) sys.add_packaging_step(top.package) @staticmethod diff --git a/frontends/PyCDE/src/common.py b/frontends/PyCDE/src/common.py index 26893698fe8f..e4b52842d7e8 100644 --- a/frontends/PyCDE/src/common.py +++ b/frontends/PyCDE/src/common.py @@ -4,10 +4,10 @@ from __future__ import annotations -from .circt.dialects import msft +from .circt.dialects import esi from .circt import ir -from .types import Type, Channel, ChannelSignaling, ClockType +from .types import Type, Bundle, Channel, ChannelSignaling, ClockType, Bits from functools import singledispatchmethod @@ -41,6 +41,13 @@ def __init__(self, super().__init__(type, name) +class SendBundle(Output): + """Create an ESI bundle output port (aka sending port).""" + + def __init__(self, bundle: Bundle, name: str = None): + super().__init__(bundle, name) + + class Input(ModuleDecl): """Create an RTL-level input port.""" @@ -52,6 +59,13 @@ def __init__(self, name: str = None): super().__init__(ClockType(), name) +class Reset(Input): + """Create a reset input.""" + + def __init__(self, name: str = None): + super().__init__(Bits(1), name) + + class InputChannel(Input): """Create an ESI input channel port.""" @@ -63,16 +77,23 @@ def __init__(self, super().__init__(type, name) +class RecvBundle(Input): + """Create an ESI bundle input port (aka receiving port).""" + + def __init__(self, bundle: Bundle, name: str = None): + super().__init__(bundle, name) + + class AppID: - AttributeName = "msft.appid" + AttributeName = "esi.appid" @singledispatchmethod def __init__(self, name: str, idx: int): - self._appid = msft.AppIDAttr.get(name, idx) + self._appid = esi.AppIDAttr.get(name, idx) @__init__.register(ir.Attribute) def __init__mlir_attr(self, attr: ir.Attribute): - self._appid = msft.AppIDAttr(attr) + self._appid = esi.AppIDAttr(attr) @property def name(self) -> str: diff --git a/frontends/PyCDE/src/constructs.py b/frontends/PyCDE/src/constructs.py index 3b797119c4ac..8fd4d778ad99 100644 --- a/frontends/PyCDE/src/constructs.py +++ b/frontends/PyCDE/src/constructs.py @@ -40,7 +40,7 @@ def __init__(self): # inner_symbols purely for the purpose of disallowing the SV # canonicalizers to eliminate wires! uniq_name = _BlockContext.current().uniquify_symbol(name) - self.wire_op = sv.WireOp(InOut(type), name, inner_sym=uniq_name) + self.wire_op = sv.WireOp(InOut(type), name, sym_name=uniq_name) read_val = sv.ReadInOutOp(self.wire_op) super().__init__(read_val, type) self.name = name @@ -188,17 +188,17 @@ def Mux(sel: BitVectorSignal, *data_inputs: typing.List[Signal]): if sel.type.width != (num_inputs - 1).bit_length(): raise TypeError("'Sel' bit width must be clog2 of number of inputs") + input_names = [ + i.name if i.name is not None else f"in{idx}" + for idx, i in enumerate(data_inputs) + ] if num_inputs == 2: m = comb.MuxOp(sel, data_inputs[1], data_inputs[0]) else: - a = ArraySignal(data_inputs) - a.name = "arr_" + "_".join([i.name for i in data_inputs]) + a = ArraySignal.create(data_inputs) + a.name = "arr_" + "_".join(input_names) m = a[sel] - input_names = [ - i.name if i.name is not None else f"in{idx}" - for idx, i in enumerate(data_inputs) - ] m.name = f"mux_{sel.name}_" + "_".join(input_names) return m diff --git a/frontends/PyCDE/src/esi.py b/frontends/PyCDE/src/esi.py index 357a743dc063..fecef0f9ff98 100644 --- a/frontends/PyCDE/src/esi.py +++ b/frontends/PyCDE/src/esi.py @@ -2,8 +2,8 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -from .common import (Input, Output, InputChannel, OutputChannel, Clock, - _PyProxy, PortError) +from .common import (Input, Output, InputChannel, OutputChannel, _PyProxy, + PortError) from .module import Generator, Module, ModuleLikeBuilderBase, PortProxyBase from .signals import ChannelSignal, Signal, _FromCirctValue from .system import System @@ -13,7 +13,6 @@ from .circt.dialects import esi as raw_esi, hw, msft from pathlib import Path -import shutil from typing import Dict, List, Optional __dir__ = Path(__file__).parent @@ -184,62 +183,6 @@ def Cosim(decl: ServiceDecl, clk, rst): decl.instantiate_builtin("cosim", [], [clk, rst]) -def CosimBSP(user_module): - """Wrap and return a cosimulation 'board support package' containing - 'user_module'""" - from .module import Module, generator - from .common import Clock, Input - - class top(Module): - clk = Clock() - rst = Input(types.int(1)) - - @generator - def build(ports): - user_module(clk=ports.clk, rst=ports.rst) - raw_esi.ServiceInstanceOp(result=[], - service_symbol=None, - impl_type=ir.StringAttr.get("cosim"), - inputs=[ports.clk.value, ports.rst.value]) - - System.current().add_packaging_step(top.package) - - @staticmethod - def package(sys: System): - """Run the packaging to create a cosim package.""" - - # When pycde is installed through a proper install, all of the collateral - # files are under a dir called "collateral". - collateral_dir = __dir__ / "collateral" - if collateral_dir.exists(): - bin_dir = collateral_dir - lib_dir = collateral_dir - esi_inc_dir = collateral_dir - else: - # Build we also want to allow pycde to work in-tree for developers. The - # necessary files are screwn around the build tree. - build_dir = __dir__.parents[4] - bin_dir = build_dir / "bin" - lib_dir = build_dir / "lib" - circt_inc_dir = build_dir / "tools" / "circt" / "include" / "circt" - esi_inc_dir = circt_inc_dir / "Dialect" / "ESI" - - hw_src = sys.hw_output_dir - for f in lib_dir.glob("*.so"): - shutil.copy(f, hw_src) - for f in lib_dir.glob("*.dll"): - shutil.copy(f, hw_src) - shutil.copy(bin_dir / "driver.cpp", hw_src) - shutil.copy(bin_dir / "driver.sv", hw_src) - shutil.copy(esi_inc_dir / "ESIPrimitives.sv", hw_src) - shutil.copy(esi_inc_dir / "Cosim_DpiPkg.sv", hw_src) - shutil.copy(esi_inc_dir / "Cosim_Endpoint.sv", hw_src) - shutil.copy(__dir__ / "Makefile.cosim", sys.output_directory) - shutil.copy(sys.hw_output_dir / "schema.capnp", sys.runtime_output_dir) - - return top - - class NamedChannelValue(ChannelSignal): """A ChannelValue with the name of the client request.""" @@ -558,3 +501,37 @@ def param(name: str, type: Type = None): else: type_attr = ir.TypeAttr.get(type._type) esi.ESIPureModuleParamOp(name, type_attr) + + +def package(sys: System): + """Package all ESI collateral.""" + + import os + import shutil + __root_dir__ = Path(__file__).parent + + # When pycde is installed through a proper install, all of the collateral + # files are under a dir called "collateral". + collateral_dir = __root_dir__ / "collateral" + if collateral_dir.exists(): + esi_lib_dir = collateral_dir + else: + # Build we also want to allow pycde to work in-tree for developers. The + # necessary files are screwn around the build tree. + build_dir = __root_dir__.parents[4] + circt_lib_dir = build_dir / "tools" / "circt" / "lib" + esi_lib_dir = circt_lib_dir / "Dialect" / "ESI" + shutil.copy(esi_lib_dir / "ESIPrimitives.sv", sys.hw_output_dir) + + # Copy everything from the 'runtime' directory + for root, dir, files in os.walk(esi_lib_dir / "runtime"): + if ".dir" in dir: + continue + for file in files: + if ".cmake" in file or file.endswith(".o"): + continue + to_dir = sys.runtime_output_dir + if len(dir) > 0: + to_dir = to_dir / os.path.join(*dir) + to_dir.mkdir(parents=True, exist_ok=True) + shutil.copyfile(os.path.join(root, file), to_dir / file) diff --git a/frontends/PyCDE/src/fsm.py b/frontends/PyCDE/src/fsm.py index 66d0b6d8ab95..fbde0bc4ef84 100644 --- a/frontends/PyCDE/src/fsm.py +++ b/frontends/PyCDE/src/fsm.py @@ -1,11 +1,11 @@ -from .common import Input, Output from .dialects import fsm -from .module import generator, Generator, Module -from .support import _obj_to_attribute, attributes_of_type +from .module import Module, ModuleLikeBuilderBase +from .support import _obj_to_attribute from .types import types -from .circt.ir import InsertionPoint -from .circt.support import connect +from .circt.ir import FlatSymbolRefAttr, InsertionPoint, StringAttr +from .circt.support import attribute_to_var +from .circt.dialects import fsm as raw_fsm from typing import Callable @@ -26,13 +26,13 @@ def __init__(self, to_state, condition: Callable = None): self.to_state = to_state self.condition = condition - def _emit(self, state_op, ports): + def _emit(self, ports): op = fsm.TransitionOp(self.to_state.name) # If a condition function was specified, execute it on the ports and # assign the result as the guard of this transition. if self.condition: - op.set_guard(lambda: self.condition(ports)) + op.set_guard(lambda: self.condition(ports).value) def __init__(self, initial=False): self.initial = initial @@ -56,14 +56,14 @@ def _emit(self, spec_mod, ports): # Assign the current state as being active in the FSM output vector. with InsertionPoint(state_op.output): outputs = [] - for (outport_it_name, _) in spec_mod.output_ports: - outputs.append(types.i1(outport_it_name == self.output.name)) + for idx in range(len(spec_mod.outputs)): + outputs.append(types.i1(idx == self.output)) fsm.OutputOp(*outputs) # Emit outgoing transitions from this state. with InsertionPoint(state_op.transitions): for transition in self.transitions: - transition._emit(state_op, ports) + transition._emit(ports) def States(n): @@ -71,66 +71,34 @@ def States(n): return [State() for _ in range(n)] -def create_fsm_machine_op(sys, mod: Module, symbol): - """Creation callback for creating a FSM MachineOp.""" +class MachineModuleBuilder(ModuleLikeBuilderBase): + """Define how to build an FSM.""" - # Add attributes for in- and output names. - attributes = {} - attributes["in_names"] = _obj_to_attribute( - [port_name for port_name, _ in mod.input_ports]) - attributes["out_names"] = _obj_to_attribute( - [port_name for port_name, _ in mod.output_ports]) + @property + def circt_mod(self): + """Get the raw CIRCT operation for the module definition. DO NOT store the + returned value!!! It needs to get reaped after the current action (e.g. + instantiation, generation). Memory safety when interacting with native code + can be painful.""" - # Add attributes for clock and reset names. - attributes["clock_name"] = _obj_to_attribute(mod.modcls.clock_name) - attributes["reset_name"] = _obj_to_attribute(mod.modcls.reset_name) + from .system import System + sys: System = System.current() + ret = sys._op_cache.get_circt_mod(self) + if ret is None: + return sys._create_circt_mod(self) + return ret - return fsm.MachineOp(symbol, - mod.modcls._initial_state, - mod.input_ports, - mod.output_ports, - attributes=attributes, - loc=mod.loc, - ip=sys._get_ip()) + def scan_cls(self): + """Scan the class as usual, but also scan for State. Add implicit signals. + Run some other validity checks.""" + super().scan_cls() - -def generate_fsm_machine_op(generate_obj: Generator, spec_mod): - """ Generator callback for generating an FSM op. """ - entry_block = spec_mod.circt_mod.body.blocks[0] - ports = _GeneratorPortAccess(spec_mod, entry_block.arguments) - - with InsertionPoint(entry_block), generate_obj.loc: - for state in spec_mod.modcls.states: - state._emit(spec_mod, ports) - - -def machine(clock: str = 'clk', reset: str = 'rst'): - """ - Top-level FSM decorator which gives the user the option specify port - names for the clock and (optional) reset signal. - These port names will be used in the FSM HW module wrapper, as well as - carried through as attributes on the fsm.machine operation to inform - FSM HW lowering about desired port names. - """ - - def machine_clocked(to_be_wrapped): - """ - Wrap a class as an FSM machine. - - An FSM PyCDE module is expected to implement: - - - A set of input ports: - These can be of any type, and are used to drive the FSM. - - Transitions can be specified either as a tuple of (next_state, condition) - or as a single `next_state` string (unconditionally taken). - a `condition` function is a function which takes a single input representing - the `ports` of a component, similar to the `@generator` decorator used - elsewhere in PyCDE. - """ states = {} initial_state = None - for name, v in attributes_of_type(to_be_wrapped, State).items(): + for name, v in self.cls_dct.items(): + if not isinstance(v, State): + continue + if name in states: raise ValueError("Duplicate state name: {}".format(name)) v.name = name @@ -141,182 +109,110 @@ def machine_clocked(to_be_wrapped): f"Multiple initial states specified ({name}, {initial_state}).") initial_state = name - if initial_state is None: - raise ValueError( - "No initial state specified, please create a state with `initial=True`." - ) - - for name, v in attributes_of_type(to_be_wrapped, Input).items(): - if v.type.width != 1: + from .types import ClockType + for name, v in self.inputs: + if not (isinstance(v, ClockType) or + (hasattr(v, "width") and v.width == 1)): raise ValueError( - f"Input port {name} has width {v.type.width}. For now, FSMs only support i1 inputs." - ) + f"Input port {name} has width {v.width}. For now, FSMs only " + "support i1 inputs.") # At this point, the 'states' attribute should be considered an immutable, # ordered list of states. - to_be_wrapped.states = states.values() - to_be_wrapped._initial_state = initial_state + self.states = states.values() + self.initial_state = initial_state - if len(states) == 0: - raise ValueError("No States defined") + if len(states) > 0 and self.initial_state is None: + raise ValueError("No initial state specified, please create a state with " + "`initial=True`.") # Add an output port for each state. for state_name, state in states.items(): - output_for_state = Output(types.i1) - setattr(to_be_wrapped, 'is_' + state_name, output_for_state) - state.output = output_for_state - - # Store requested clock and reset names. - to_be_wrapped.clock_name = clock - to_be_wrapped.reset_name = reset - - # Set module creation and generation callbacks. - setattr(to_be_wrapped, 'create_cb', create_fsm_machine_op) - setattr(to_be_wrapped, 'generator_cb', generate_fsm_machine_op) - - # Create a dummy Generator function to trigger module generation. - # This function doesn't do anything, since all generation logic is embedded - # within generate_fsm_machine_op. In the future, we may allow an actual - # @generator function specified by the user if they want to do something - # specific. - setattr(to_be_wrapped, 'dummy_generator_f', generator(lambda x: None)) - - # Treat the remainder of the class as a module. - # Rename the fsm_mod before creating the module to ensure that the wrapped - # module will be named as the user specified (that of fsm_mod), and the - # FSM itself will have a suffixed name. - fsm_name = to_be_wrapped.__name__ - to_be_wrapped.__name__ = to_be_wrapped.__name__ + '_impl' - to_be_wrapped.__qualname__ = to_be_wrapped.__qualname__ + '_impl' - fsm_mod = module(to_be_wrapped) - - # Next we build the outer wrapper class that contains clock and reset ports. - fsm_hw_mod = fsm_wrapper_class(fsm_mod=fsm_mod, - fsm_name=fsm_name, - clock=clock, - reset=reset) - - return module(fsm_hw_mod) - - return machine_clocked - - -def fsm_wrapper_class(fsm_mod, fsm_name, clock, reset=None): - """ - Generate a wrapper class for the FSM class which contains the clock and reset - signals, as well as a `fsm.hw_instance` instaitiation of the FSM. - """ - - class fsm_hw_mod: - - @generator - def construct(ports): - in_ports = { - port_name: getattr(ports, port_name) - for (port_name, _) in fsm_mod._pycde_mod.input_ports - } - fsm_instance = fsm_mod(**in_ports) - # Attach clock and optional reset on the backedges created during - # the MachineOp:instatiate call. - clock_be = getattr(fsm_instance._instantiation, '_clock_backedge') - connect(clock_be, getattr(ports, clock)) - if hasattr(fsm_instance._instantiation, '_reset_backedge'): - reset_be = getattr(fsm_instance._instantiation, '_reset_backedge') - connect(reset_be, getattr(ports, reset)) - - # Connect outputs - for (port_name, _) in fsm_mod._pycde_mod.output_ports: - setattr(ports, port_name, getattr(fsm_instance, port_name)) - - # Inherit in and output ports. We do this outside of the wrapped class - # since we cannot do setattr inside the class scope (def-use). - for (name, type) in fsm_mod._pycde_mod.input_ports: - setattr(fsm_hw_mod, name, Input(type)) - for (name, type) in fsm_mod._pycde_mod.output_ports: - setattr(fsm_hw_mod, name, Output(type)) - - # Add clock and additional reset port. - setattr(fsm_hw_mod, clock, Input(types.i1)) - - if reset is not None: - setattr(fsm_hw_mod, reset, Input(types.i1)) - - # The wrapper class now overloads the name of the user-defined FSM. - # From this point on, instantiating the user FSM class will actually - # instantiate the wrapper HW module class. - fsm_hw_mod.__qualname__ = fsm_name - fsm_hw_mod.__name__ = fsm_name - fsm_hw_mod.__module__ = fsm_name - return fsm_hw_mod - - -def gen_fsm(transitions: dict, name: str = "MyFSM"): - """ - Generate a FSM from a dictionary of states and their transitions. - - E.g.: - { - "a": [ - ("c", "go"), - "b", - ], - "b": [], - "c": [] - } - - creates an FSM with 3 states (a, b, c). 'b' and 'c' have no outgoing transitions, - and 'a' has two outgoing transitions. The first transition ("c", "go") transitions - to 'c' whenever an input port 'go' is asserted. The second transition is a default - transition to 'b'. - - Any state and guard referenced within the dictionary will automatically be created - as a state operation and top-level input, respectively. - """ - - class FSM: - pass - - # Gather states and input variables - states = set() - inputs = set() - initial = True - - def ensure_state(state, initial=False): - if state not in states: - setattr(FSM, state, State(initial=initial)) - states.add(state) - return getattr(FSM, state) - - def ensure_input(input): - if input not in inputs: - setattr(FSM, input, Input(types.i1)) - inputs.add(input) - - for (state, state_transitions) in transitions.items(): - currentStateAttr = ensure_state(state, initial) - if not type(state_transitions) is list: - raise TypeError(f"Transitions for state '{state}' must be a list") - - for transition in state_transitions: - guard_port = None - if isinstance(transition, tuple): - (nextState, guard_port) = transition - else: - if not type(transition) is str: - raise TypeError( - f"Transition for state '{state}' must be of the form 'nextstate' or ('nextstate', 'guard')" - ) - nextState = transition - - nextStateAttr = ensure_state(nextState) - if guard_port: - ensure_input(guard_port) - currentStateAttr.add_transitions( - (nextStateAttr, - lambda ports, guard_port=guard_port: getattr(ports, guard_port))) - else: - currentStateAttr.add_transitions((nextStateAttr,)) - - setattr(FSM, "__name__", name) - setattr(FSM, "__qualname__", name) - return machine()(FSM) + state.output = len(self.outputs) + self.outputs.append(('is_' + state_name, types.i1)) + + inputs_to_remove = [] + if len(self.clocks) > 1: + raise ValueError("FSMs must have at most one clock") + else: + self.clock_name = "clk" + if len(self.clocks) == 1: + idx = self.clocks.pop() + self.clock_name = self.inputs[idx][0] + inputs_to_remove.append(idx) + + if len(self.resets) > 1: + raise ValueError("FSMs must have at most one reset") + else: + self.reset_name = "rst" + if len(self.resets) == 1: + idx = self.resets.pop() + self.reset_name = self.inputs[idx][0] + inputs_to_remove.append(idx) + + # Remove the clock and reset inputs, if necessary. + inputs_to_remove.sort(reverse=True) + for idx in inputs_to_remove: + self.inputs.pop(idx) + + def create_op(self, sys, symbol): + """Creation callback for creating a FSM MachineOp.""" + + if len(self.states) == 0: + raise ValueError("No States defined") + + # Add attributes for in- and output names. + attributes = {} + attributes["in_names"] = _obj_to_attribute( + [port_name for port_name, _ in self.inputs]) + attributes["out_names"] = _obj_to_attribute( + [port_name for port_name, _ in self.outputs]) + + # Add attributes for clock and reset names. + attributes["clock_name"] = _obj_to_attribute(self.clock_name) + attributes["reset_name"] = _obj_to_attribute(self.reset_name) + + machine_op = fsm.MachineOp(symbol, + self.initial_state, + [(n, t._type) for (n, t) in self.inputs], + [(n, t._type) for (n, t) in self.outputs], + attributes=attributes, + loc=self.loc, + ip=sys._get_ip()) + + entry_block = machine_op.body.blocks[0] + ports = self.generator_port_proxy(entry_block.arguments, self) + + with self.GeneratorCtxt(self, ports, entry_block, self.loc): + for state in self.states: + state._emit(self, ports) + + return machine_op + + def instantiate(self, impl, instance_name: str, **kwargs): + circt_mod = self.circt_mod + + in_names = attribute_to_var(circt_mod.attributes['in_names']) + inputs = [kwargs[port].value for port in in_names] + + # Clock and resets are not part of the input ports of the FSM, but + # it is at the point of `fsm.hw_instance` instantiation that they + # must be connected. + clock = kwargs[StringAttr(circt_mod.attributes['clock_name']).value] + reset = kwargs[StringAttr(circt_mod.attributes['reset_name']).value] + + op = raw_fsm.HWInstanceOp(outputs=circt_mod.type.results, + inputs=inputs, + sym_name=StringAttr.get(instance_name), + machine=FlatSymbolRefAttr.get( + StringAttr( + circt_mod.attributes["sym_name"]).value), + clock=clock.value, + reset=reset.value) + return op + + +class Machine(Module): + """Base class to be extended for defining an FSM.""" + + BuilderType = MachineModuleBuilder diff --git a/frontends/PyCDE/src/instance.py b/frontends/PyCDE/src/instance.py index f16a749ac654..76731a4a88c9 100644 --- a/frontends/PyCDE/src/instance.py +++ b/frontends/PyCDE/src/instance.py @@ -7,7 +7,7 @@ from .devicedb import LocationVector from .module import AppID -from .circt.dialects import msft, seq +from .circt.dialects import esi, hw, msft, seq from .circt import ir from typing import Dict, Iterator, List, Optional, Tuple, Union @@ -34,7 +34,10 @@ def __init__(self, parent: Instance, inside_of: Module, self.inside_of = inside_of self.parent = parent self.root = parent.root - self.symbol = symbol + if symbol is not None: + self.symbol = hw.InnerSymAttr(symbol) + else: + self.symbol = None self._op_cache = parent.root.system._op_cache def _get_ip(self) -> ir.InsertionPoint: @@ -90,13 +93,13 @@ def path(self) -> list[Instance]: def name(self) -> str: assert self.symbol is not None, \ "If symbol is None, name() needs to be overridden" - return ir.StringAttr(self.symbol).value + return self.symbol.symName.value @property def appid(self) -> Optional[AppID]: """Get the appid assigned to this instance or None if one was not assigned during generation.""" - static_op = self._op_cache.get_sym_ops_in_module( + static_op = self._op_cache.get_inner_sym_ops_in_module( self.inside_of)[self.symbol] if AppID.AttributeName in static_op.attributes: return AppID(static_op.attributes[AppID.AttributeName]) @@ -119,40 +122,46 @@ def __init__(self, parent: Instance, instance_sym: Optional[ir.Attribute], def _add_appids(self): """Implicitly add members for each AppID which is contained by this instance.""" + with self.root.system: circt_mod = self.tgt_mod.circt_mod - if not isinstance(circt_mod, - msft.MSFTModuleOp) or circt_mod.childAppIDBases is None: - return - for name in [n.value for n in circt_mod.childAppIDBases]: + child_appids = ir.ArrayAttr( + self.root.system._appid_index.get_child_appids_of(circt_mod)) + child_appid_names = {} + for id in [esi.AppIDAttr(id) for id in child_appids]: + if id.name not in child_appid_names: + child_appid_names[id.name] = {} + child_appid_names[id.name][id.index] = id + + for (name, ids) in child_appid_names.items(): if hasattr(self, name): continue self.__slots__.append(name) - setattr(self, name, _AppIDInstance(self, name)) + setattr(self, name, _AppIDInstance(self, circt_mod, name, ids)) def _create_instance(self, static_op: ir.Operation) -> Instance: """Create a new `Instance` which is a child of `parent` in the instance hierarchy and corresponds to the given static operation. The static operation need not be a module instantiation.""" - sym_name = static_op.attributes["sym_name"] - if isinstance(static_op, msft.InstanceOp): + inner_sym = static_op.attributes["inner_sym"] + if isinstance(static_op, hw.InstanceOp): tgt_mod = self._op_cache.get_symbol_pyproxy(static_op.moduleName) return ModuleInstance(self, - instance_sym=sym_name, + instance_sym=inner_sym, inside_of=self.tgt_mod, tgt_mod=tgt_mod) if isinstance(static_op, seq.CompRegOp): - return RegInstance(self, self.tgt_mod, sym_name, static_op) + return RegInstance(self, self.tgt_mod, inner_sym, static_op) - return Instance(self, self.tgt_mod, sym_name) + return Instance(self, self.tgt_mod, inner_sym) - def _children(self) -> Dict[ir.StringAttr, Instance]: + def _children(self) -> Dict[hw.InnerSymAttr, Instance]: """Return a dict of MLIR StringAttr this instances' children. Cache said list.""" if self._child_cache is not None: return self._child_cache - symbols_in_mod = self._op_cache.get_sym_ops_in_module(self.tgt_mod) + symbols_in_mod = self._op_cache.get_inner_sym_ops_in_module(self.tgt_mod) children = { sym: self._create_instance(op) for (sym, op) in symbols_in_mod.items() } @@ -167,7 +176,7 @@ def children(self) -> Dict[str, Instance]: def __getitem__(self, child_name: str) -> Instance: """Get a child instance.""" - return self._children()[ir.StringAttr.get(child_name)] + return self._children()[hw.InnerSymAttr.get(ir.StringAttr.get(child_name))] def walk(self, callback): """Descend the instance hierarchy, calling back on each instance.""" @@ -213,47 +222,30 @@ def conv(op): class _AppIDInstance: """Helper class to provide accessors to AppID'd instances.""" - __slots__ = ["owner_instance", "appid_name"] + __slots__ = ["owner_instance", "appid_name", "appids", "circt_mod"] - def __init__(self, owner_instance: ModuleInstance, appid_name: str): + def __init__(self, owner_instance: ModuleInstance, circt_mod, appid_name: str, + appids: Dict[int, esi.AppIDAttr]): self.owner_instance = owner_instance + self.circt_mod = circt_mod self.appid_name = appid_name - - def _search(self) -> Iterator[AppID, Instance]: - inner_sym_ops = self.owner_instance._op_cache.get_sym_ops_in_module( - self.owner_instance.tgt_mod) - for child in self.owner_instance._children().values(): - # "Look through" instance hierarchy levels if the child has an AppID - # accessor for our name. - if hasattr(child, self.appid_name): - child_appid_inst = getattr(child, self.appid_name) - if not isinstance(child_appid_inst, _AppIDInstance): - continue - for c in child_appid_inst._search(): - yield c - - # Check if the AppID is local, then return the instance if it is. - instance_op = inner_sym_ops[child.symbol] - if AppID.AttributeName in instance_op.attributes: - try: - # This is the only way to test that a certain attribute is a certain - # attribute type. *Sigh*. - appid = msft.AppIDAttr(instance_op.attributes[AppID.AttributeName]) - if appid.name == self.appid_name: - yield (appid, child) - except ValueError: - pass + self.appids = appids def __getitem__(self, index: int) -> Instance: - for (appid, child) in self._search(): - if appid.index == index: - return child - - raise IndexError(f"{self.appid_name}[{index}] not found") + if index not in self.appids: + raise IndexError(f"{self.appid_name}[{index}] not found") + appid = self.appids[index] + path = ir.ArrayAttr( + self.owner_instance.root.system._appid_index.get_appid_path( + self.circt_mod, appid)) + ret = self.owner_instance + for inst_ref in path: + ret = ret[ir.StringAttr(hw.InnerRefAttr(inst_ref).name).value] + return ret def __iter__(self) -> Iterator[Instance]: - for (_, child) in self._search(): - yield child + for appid in self.appids.values(): + yield self[appid.index] class RegInstance(Instance): diff --git a/frontends/PyCDE/src/module.py b/frontends/PyCDE/src/module.py index ba0ed6353d91..c169064fff8f 100644 --- a/frontends/PyCDE/src/module.py +++ b/frontends/PyCDE/src/module.py @@ -5,14 +5,14 @@ from __future__ import annotations from typing import List, Optional, Set, Tuple, Dict -from .common import (AppID, Clock, Input, Output, PortError, _PyProxy) +from .common import (AppID, Clock, Input, Output, PortError, _PyProxy, Reset) from .support import (get_user_loc, _obj_to_attribute, create_type_string, create_const_zero) from .signals import ClockSignal, Signal, _FromCirctValue from .types import ClockType from .circt import ir, support -from .circt.dialects import hw, msft +from .circt.dialects import hw from .circt.support import BackedgeBuilder, attribute_to_var import builtins @@ -198,10 +198,15 @@ def __init__(self, cls, cls_dct, loc): self.outputs: Optional[List[Tuple[str, Type]]] = None self.inputs: Optional[List[Tuple[str, Type]]] = None self.clocks: Optional[Set[int]] = None + self.resets: Optional[Set[int]] = None self.generators = None self.generator_port_proxy = None self.parameters = None - self.attributes: Dict = {} + self.attributes: Dict = { + "output_file": + hw.OutputFileAttr.get_from_filename( + ir.StringAttr.get(f"{cls.__name__}.sv"), False, True) + } def go(self): """Execute the analysis and mutation to make a `ModuleLike` class operate @@ -214,11 +219,11 @@ def go(self): def scan_cls(self): """Scan the class for input/output ports and generators. (Most `ModuleLike` will use these.) Store the results for later use.""" - from .types import Bits input_ports = [] output_ports = [] clock_ports = set() + reset_ports = set() generators = {} for attr_name, attr in self.cls_dct.items(): if attr_name.startswith("_"): @@ -239,7 +244,10 @@ def scan_cls(self): if isinstance(attr, Clock): clock_ports.add(len(input_ports)) - input_ports.append((attr_name, Bits(1))) + input_ports.append((attr_name, attr.type)) + elif isinstance(attr, Reset): + reset_ports.add(len(input_ports)) + input_ports.append((attr_name, attr.type)) elif isinstance(attr, Input): input_ports.append((attr_name, attr.type)) elif isinstance(attr, Output): @@ -250,6 +258,7 @@ def scan_cls(self): self.outputs = output_ports self.inputs = input_ports self.clocks = clock_ports + self.resets = reset_ports self.generators = generators def create_port_proxy(self): @@ -267,13 +276,10 @@ def create_port_proxy(self): output_port_lookup: Dict[str, int] = {} for idx, (name, port_type) in enumerate(self.outputs): - def fget(self, idx=idx): - self._get_output(idx) - def fset(self, val, idx=idx): self._set_output(idx, val) - proxy_attrs[name] = property(fget=fget, fset=fset) + proxy_attrs[name] = property(fget=None, fset=fset) output_port_lookup[name] = idx proxy_attrs["_output_port_lookup"] = output_port_lookup @@ -294,7 +300,7 @@ def fget(self): for idx, (name, port_type) in enumerate(self.outputs): def fget(self, idx=idx): - return _FromCirctValue(self.inst.results[idx]) + return _FromCirctValue(self.inst.operation.results[idx]) named_outputs[name] = fget setattr(self.modcls, name, property(fget=fget)) @@ -307,7 +313,7 @@ def fget(self, idx=idx): def name(self): if hasattr(self.modcls, "module_name"): return self.modcls.module_name - elif self.parameters is not None: + elif self.parameters is not None and len(self.generators) > 0: return _create_module_name(self.modcls.__name__, self.parameters) else: return self.modcls.__name__ @@ -393,12 +399,13 @@ def create_op(self, sys, symbol): """Callback for creating a module op.""" if len(self.generators) > 0: + if hasattr(self, "parameters") and self.parameters is not None: + self.attributes["pycde.parameters"] = self.parameters # If this Module has a generator, it's a real module. - return msft.MSFTModuleOp( + return hw.HWModuleOp( symbol, [(n, t._type) for (n, t) in self.inputs], [(n, t._type) for (n, t) in self.outputs], - self.parameters if hasattr(self, "parameters") else None, attributes=self.attributes, loc=self.loc, ip=sys._get_ip(), @@ -413,10 +420,15 @@ def create_op(self, sys, symbol): for i in self.parameters ] self.attributes["verilogName"] = ir.StringAttr.get(self.name) - return msft.MSFTModuleExternOp( + self.attributes: Dict = { + "output_file": + hw.OutputFileAttr.get_from_filename( + ir.StringAttr.get("external_modules.sv"), False, True) + } + return hw.HWModuleExternOp( symbol, - [(n, t._type) for (n, t) in self.inputs], - [(n, t._type) for (n, t) in self.outputs], + input_ports=[(n, t._type) for (n, t) in self.inputs], + output_ports=[(n, t._type) for (n, t) in self.outputs], parameters=paramdecl_list, attributes=self.attributes, loc=self.loc, @@ -426,54 +438,49 @@ def create_op(self, sys, symbol): def instantiate(self, module_inst, instance_name: str, **inputs): """"Instantiate this Module. Check that the input types match expectations.""" - from .circt.dialects import _hw_ops_ext as hwext - input_lookup = { - name: (idx, ptype) for idx, (name, ptype) in enumerate(self.inputs) - } - input_values: List[Optional[Signal]] = [None] * len(self.inputs) - + port_input_lookup = {name: ptype for name, ptype in self.inputs} + circt_inputs = {} for name, signal in inputs.items(): - if name not in input_lookup: + if name not in port_input_lookup: raise PortError(f"Input port {name} not found in module") - idx, ptype = input_lookup[name] - if signal is None: + ptype = port_input_lookup[name] + if isinstance(signal, Signal): + # If the input is a signal, the types must match. + if signal.type._type != ptype._type: + raise ValueError( + f"Wrong type on input signal '{name}'. Got '{signal.type}'," + f" expected '{type}'") + circt_inputs[name] = signal.value + elif signal is None: if len(self.generators) > 0: raise PortError( f"Port {name} cannot be None (disconnected ports only allowed " "on extern mods.") - signal = create_const_zero(ptype) - if isinstance(signal, Signal): - # If the input is a signal, the types must match. - if ptype._type != signal.type._type: - raise PortError( - f"Input port {name} expected type {ptype}, not {signal.type}") + circt_inputs[name] = create_const_zero(ptype) else: # If it's not a signal, assume the user wants to specify a constant and # try to convert it to a hardware constant. - signal = ptype(signal) - input_values[idx] = signal - del input_lookup[name] + circt_inputs[name] = ptype(signal).value - if len(input_lookup) > 0: - missing = ", ".join(list(input_lookup.keys())) - raise ValueError(f"Missing input signals for ports: {missing}") + missing = list( + filter(lambda name: name not in circt_inputs, port_input_lookup.keys())) + if len(missing) > 0: + raise ValueError(f"Missing input signals for ports: {', '.join(missing)}") circt_mod = self.circt_mod - parameters = None + parameters = {} # If this is a parameterized external module, the parameters must be # supplied. if len(self.generators) == 0 and self.parameters is not None: - parameters = ir.ArrayAttr.get( - hwext.create_parameters(self.parameters, circt_mod)) - inst = msft.InstanceOp(circt_mod.type.results, - instance_name, - ir.FlatSymbolRefAttr.get( - ir.StringAttr( - circt_mod.attributes["sym_name"]).value), - [sig.value for sig in input_values], - parameters=parameters, - loc=get_user_loc()) - inst.verify() + parameters = self.parameters + from .circt.dialects import _hw_ops_ext as hwext + inst = hwext.InstanceBuilder(circt_mod, + instance_name, + circt_inputs, + parameters=parameters, + sym_name=instance_name, + loc=get_user_loc()) + inst.operation.verify() return inst def generate(self): @@ -490,7 +497,7 @@ def generate(self): raise ValueError("Generators must not return a value") ports._check_unconnected_outputs() - msft.OutputOp([o.value for o in ports._output_values]) + hw.OutputOp([o.value for o in ports._output_values]) class Module(metaclass=ModuleLikeType): @@ -579,8 +586,7 @@ def __call__(self, *args, **kwargs): if not issubclass(cls, Module): raise ValueError("Parameterization function must return Module class") - if len(cls._builder.generators) > 0: - cls._builder.parameters = cache_key[1] + cls._builder.parameters = cache_key[1] _MODULE_CACHE[cache_key] = cls return cls @@ -619,7 +625,10 @@ def import_hw_module(hw_module: hw.HWModuleOp): """Import a CIRCT module into PyCDE. Returns a standard Module subclass which operates just like an external PyCDE module. - For now, the imported module name MUST NOT conflict with any other modules.""" + For now, the imported module name MUST NOT conflict with any other modules. + + THIS IS BROKEN: https://github.com/llvm/circt/issues/6130""" + # TODO: fix me # Get the module name to use in the generated class and as the external name. name = ir.StringAttr(hw_module.name).value diff --git a/frontends/PyCDE/src/signals.py b/frontends/PyCDE/src/signals.py index 7ff789e482f2..159e36f8a5fc 100644 --- a/frontends/PyCDE/src/signals.py +++ b/frontends/PyCDE/src/signals.py @@ -5,15 +5,15 @@ from __future__ import annotations from .support import get_user_loc, _obj_to_value_infer_type -from .types import ChannelSignaling, Type +from .types import ChannelDirection, ChannelSignaling, Type -from .circt.dialects import sv +from .circt.dialects import esi, sv from .circt import support from .circt import ir from contextvars import ContextVar from functools import singledispatchmethod -from typing import List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import re import numpy as np @@ -134,13 +134,11 @@ def name(self): if hasattr(owner, "attributes") and self._namehint_attrname in owner.attributes: return ir.StringAttr(owner.attributes[self._namehint_attrname]).value - from .circt.dialects import msft - if isinstance(owner, ir.Block) and isinstance(owner.owner, - msft.MSFTModuleOp): + from .circt.dialects import hw + if isinstance(owner, ir.Block) and isinstance(owner.owner, hw.HWModuleOp): block_arg = ir.BlockArgument(self.value) - mod = owner.owner - return ir.StringAttr( - ir.ArrayAttr(mod.attributes["argNames"])[block_arg.arg_number]).value + mod_type = hw.ModuleType(owner.owner.module_type.value) + return mod_type.input_names[block_arg.arg_number] if hasattr(self, "_name"): return self._name @@ -162,7 +160,7 @@ def appid(self) -> Optional[object]: # Optional AppID. @appid.setter def appid(self, appid) -> None: - if "sym_name" not in self.value.owner.attributes: + if "inner_sym" not in self.value.owner.attributes: raise ValueError("AppIDs can only be attached to ops with symbols") from .module import AppID self.value.owner.attributes[AppID.AttributeName] = appid._appid @@ -248,7 +246,10 @@ def _exec_cast(self, targetValueType, type_getter, width: int = None): if isinstance(self, targetValueType) and width == self.type.width: return self - return hwarith.CastOp(self.value, type_getter(width)) + cast = hwarith.CastOp(self.value, type_getter(width)) + if self.name is not None: + cast.name = self.name + return cast def as_bits(self, width: int = None): """ @@ -272,6 +273,18 @@ def as_uint(self, width: int = None): return self._exec_cast(UIntSignal, ir.IntegerType.get_unsigned, width) +def And(*items: List[BitVectorSignal]): + """Compute a bitwise 'and' of the arguments.""" + from .dialects import comb + return comb.AndOp(*items) + + +def Or(*items: List[BitVectorSignal]): + """Compute a bitwise 'or' of the arguments.""" + from .dialects import comb + return comb.OrOp(*items) + + class BitsSignal(BitVectorSignal): """Operations on signless ints (bits). These will all return signless values - a user is expected to reapply signedness semantics if needed.""" @@ -332,6 +345,18 @@ def pad_or_truncate(self, num_bits: int): v.name = f"{self.name}_padto_{num_bits}" return v + def and_reduce(self): + from .types import types + bits = [self[i] for i in range(len(self))] + assert bits[0].type == types.i1 + return And(*bits) + + def or_reduce(self): + from .types import types + bits = [self[i] for i in range(len(self))] + assert bits[0].type == types.i1 + return Or(*bits) + # === Infix operators === def __exec_signless_binop_nocast__(self, other, op, op_symbol: str, @@ -462,16 +487,21 @@ def __ne__(self, other): from .circt.dialects import hwarith return self.__exec_icmp__(other, hwarith.ICmpOp.PRED_NE, "neq") - # TODO: This class will contain comparison operators (<, >, <=, >=) - def __lt__(self, other): - assert False, "Unimplemented" + from .circt.dialects import hwarith + return self.__exec_icmp__(other, hwarith.ICmpOp.PRED_LT, "lt") + + def __gt__(self, other): + from .circt.dialects import hwarith + return self.__exec_icmp__(other, hwarith.ICmpOp.PRED_GT, "gt") def __le__(self, other): - assert False, "Unimplemented" + from .circt.dialects import hwarith + return self.__exec_icmp__(other, hwarith.ICmpOp.PRED_LE, "le") def __ge__(self, other): - assert False, "Unimplemented" + from .circt.dialects import hwarith + return self.__exec_icmp__(other, hwarith.ICmpOp.PRED_GE, "ge") class UIntSignal(IntSignal): @@ -485,18 +515,6 @@ def __neg__(self): return self * types.int(self.type.width)(-1).as_sint() -def Or(*items: List[BitVectorSignal]): - """Compute a bitwise 'or' of the arguments.""" - from .dialects import comb - return comb.OrOp(*items) - - -def And(*items: List[BitVectorSignal]): - """Compute a bitwise 'and' of the arguments.""" - from .dialects import comb - return comb.AndOp(*items) - - class ArraySignal(Signal): @singledispatchmethod @@ -545,6 +563,12 @@ def slice(self, low_idx: Union[int, BitVectorSignal], v.name = self.name + f"__{low_idx}upto{low_idx+num_elems}" return v + def and_reduce(self): + from .types import types + bits = [self[i] for i in range(len(self))] + assert bits[0].type == types.i1 + return And(*bits) + def or_reduce(self): from .types import types bits = [self[i] for i in range(len(self))] @@ -682,6 +706,52 @@ def unwrap(self, readyOrRden): raise TypeError("Unknown signaling standard") +class BundleSignal(Signal): + """Signal for types.Bundle.""" + + def reg(self, clk, rst=None, name=None): + raise TypeError("Cannot register a bundle") + + def unpack(self, **kwargs: Dict[str, + ChannelSignal]) -> Dict[str, ChannelSignal]: + """Given FROM channels, unpack a bundle into the TO channels.""" + from_channels = { + bc.name: (idx, bc) for idx, bc in enumerate( + filter(lambda c: c.direction == ChannelDirection.FROM, + self.type.channels)) + } + to_channels = [ + c for c in self.type.channels if c.direction == ChannelDirection.TO + ] + + operands = [None] * len(to_channels) + for name, value in kwargs.items(): + if name not in from_channels: + raise ValueError(f"Unknown channel name '{name}'") + idx, bc = from_channels[name] + if value.type != bc.channel: + raise TypeError(f"Expected channel type {bc.channel}, got {value.type} " + f"on channel '{name}'") + operands[idx] = value.value + del from_channels[name] + if len(from_channels) > 0: + raise ValueError( + f"Missing channel values for {', '.join(from_channels.keys())}") + + unpack_op = esi.UnpackBundleOp([bc.channel._type for bc in to_channels], + self.value, operands) + + to_channels_results = unpack_op.toChannels + return { + bc.name: _FromCirctValue(to_channels_results[idx]) + for idx, bc in enumerate(to_channels) + } + + +class ListSignal(Signal): + pass + + def wrap_opviews_with_values(dialect, module_name, excluded=[]): """Wraps all of a dialect's OpView classes to have their create method return a Signal instead of an OpView. The wrapped classes are inserted into diff --git a/frontends/PyCDE/src/system.py b/frontends/PyCDE/src/system.py index 73f963cc971d..db6012041980 100644 --- a/frontends/PyCDE/src/system.py +++ b/frontends/PyCDE/src/system.py @@ -40,7 +40,8 @@ class System: __slots__ = [ "mod", "top_modules", "name", "passed", "_old_system_token", "_op_cache", "_generate_queue", "output_directory", "files", "mod_files", - "packaging_funcs", "sw_api_langs", "_instance_roots", "_placedb" + "packaging_funcs", "sw_api_langs", "_instance_roots", "_placedb", + "_appid_index" ] def __init__(self, @@ -66,6 +67,7 @@ def __init__(self, self._instance_roots: dict[(Module, str), InstanceHierarchyRoot] = {} self._placedb: PlacementDB = None + self._appid_index: esi.AppIDIndex = None # The set of all files generated by PyCDE. self.files: Set[os.PathLike] = set() @@ -107,7 +109,7 @@ def _get_ip(self): def set_debug(): ir._GlobalDebug.flag = True - # TODO: Ideally, we'd be able to run the std-to-handshake lowering passes in + # TODO: Ideally, we'd be able to run the cf-to-handshake lowering passes in # pycde. As of now, however, the cf/memref/arith dialects are not registered # so the assembly can't be loaded. The right way to do this is to have pycde # load those dialects, though there isn't a python hook to selectively load @@ -118,7 +120,7 @@ def set_debug(): # "flatten-memref", # "flatten-memref-calls", # "func.func(handshake-legalize-memrefs)", - # "lower-std-to-handshake", + # "lower-cf-to-handshake", # "canonicalize", # "handshake-lower-extmem-to-hw{wrap-esi}", # "canonicalize", @@ -181,7 +183,7 @@ def _create_circt_mod(self, builder: ModuleLikeBuilderBase): if len(builder.generators) > 0: self._generate_queue.append(builder) file_name = builder.modcls.__name__ + ".sv" - outfn = self.output_directory / file_name + outfn = self.hw_output_dir / file_name self.files.add(outfn) self.mod_files.add(outfn) op.fileName = ir.StringAttr.get(str(file_name)) @@ -224,14 +226,7 @@ def generate(self, generator_names=[], iters=None): m.generate() i += 1 - # Run passes which must get run between generation and instance hierarch - # browsing. - gen_left = len(self._generate_queue) - if gen_left == 0: - self._op_cache.release_ops() - pm = passmanager.PassManager.parse("builtin.module(msft-discover-appids)") - pm.run(self.mod.operation) - return + self._appid_index = esi.AppIDIndex(self.mod.operation) def get_instance(self, mod_cls: object, @@ -251,12 +246,13 @@ def get_instance(self, # After all of the pycde code has been executed, we have all the types # defined so we can go through and output the typedefs delcarations. lambda sys: TypeAlias.declare_aliases(sys.mod), - "builtin.module(msft-lower-constructs, msft-lower-instances)", + "builtin.module(lower-hwarith-to-hw, msft-lower-constructs, msft-lower-instances)", + # "builtin.module(esi-emit-cpp-api{{output-file=ESISystem.h}})", "builtin.module(esi-emit-collateral{{tops={tops} schema-file=schema.capnp}})", - "builtin.module(lower-msft-to-hw{{verilog-file={verilog_file}}})", - "builtin.module(lower-hwarith-to-hw)", + "builtin.module(esi-clean-metadata)", "builtin.module(hw.module(lower-seq-hlmem))", - "builtin.module(lower-esi-to-physical, lower-esi-ports, lower-esi-to-hw)", + "builtin.module(lower-esi-to-physical)", + "builtin.module(lower-esi-bundles, lower-esi-ports, lower-esi-to-hw)", "builtin.module(convert-fsm-to-sv)", "builtin.module(lower-seq-to-sv)", "builtin.module(cse, canonicalize, cse)", @@ -499,13 +495,13 @@ def create_or_get_dyn_inst(self, inst: Instance) -> msft.DynamicInstanceOp: the instance doesn't have a static op in the IR.""" # Check static op existence. - inside_of_syms = self.get_sym_ops_in_module(inst.inside_of) + inside_of_syms = self.get_inner_sym_ops_in_module(inst.inside_of) if inst.symbol not in inside_of_syms: return None if inst not in self._instance_cache: ref = hw.InnerRefAttr.get(ir.StringAttr.get(inst._inside_of_symbol), - inst.symbol) + inst.symbol.symName) # Check if the dynamic instance op exists. parent_op = inst.parent._dyn_inst insts_in_parent = self.get_dyn_insts_in_inst(parent_op) @@ -532,26 +528,26 @@ def get_or_create_inst_from_op(self, op: ir.OpView) -> pi.Instance: if isinstance(op, msft.DynamicInstanceOp): parent_inst = self.get_or_create_inst_from_op(op.operation.parent.opview) instance_ref = hw.InnerRefAttr(op.instanceRef) - return parent_inst._children()[instance_ref.name] + return parent_inst._children()[hw.InnerSymAttr.get(instance_ref.name)] raise TypeError( "Can only resolve from InstanceHierarchyOp or DynamicInstanceOp") - def get_sym_ops_in_module(self, - module: Module) -> Dict[ir.Attribute, ir.Operation]: - """Look into the IR inside 'module' for any ops which have a `sym_name` + def get_inner_sym_ops_in_module( + self, module: Module) -> Dict[ir.Attribute, ir.Operation]: + """Look into the IR inside 'module' for any ops which have an `inner_sym` attribute. Cached.""" if module is None: return {} circt_mod = self.get_circt_mod(module) - if isinstance(circt_mod, msft.MSFTModuleExternOp): + if isinstance(circt_mod, hw.HWModuleExternOp): return {} if circt_mod not in self._module_inside_sym_cache: self._module_inside_sym_cache[circt_mod] = \ - {op.attributes["sym_name"]: op + {op.attributes["inner_sym"]: op for op in circt_mod.entry_block - if "sym_name" in op.attributes} + if "inner_sym" in op.attributes} return self._module_inside_sym_cache[circt_mod] diff --git a/frontends/PyCDE/src/testing.py b/frontends/PyCDE/src/testing.py index b724f0fa2a33..2dd27935fcfd 100644 --- a/frontends/PyCDE/src/testing.py +++ b/frontends/PyCDE/src/testing.py @@ -121,7 +121,7 @@ def extra_compile_args(self, pycde_system: System): # timescale of '1'. This prevents cocotb from creating small timescale clocks. # Since a timescale is not emitted by default from export-verilog, make our # lives easier and create a minimum timescale through the command-line. - cmd_file = os.path.join(pycde_system._output_directory, "cmds.f") + cmd_file = os.path.join(pycde_system.output_directory, "cmds.f") with open(cmd_file, "w+") as f: f.write("+timescale+1ns/1ps") @@ -155,6 +155,7 @@ def cocotestbench(pycde_mod, simulator='icarus', **kwargs): def testbenchmodule_inner(tb_class): sys = System([pycde_mod]) sys.generate() + sys.run_passes() sys.emit_outputs() testmodule = "test_" + pycde_mod.__name__ @@ -175,7 +176,7 @@ def testbenchmodule_inner(tb_class): ] # Generate the cocotb test file. - testfile_path = Path(sys._output_directory, f"{testmodule}.py") + testfile_path = Path(sys.output_directory, f"{testmodule}.py") with open(testfile_path, "w") as f: f.write(_gen_cocotb_testfile(testbench_funcs)) @@ -191,7 +192,7 @@ def testbenchmodule_inner(tb_class): toplevel=pycde_mod.__name__, toplevel_lang="verilog", verilog_sources=list(test_files), - work_dir=sys._output_directory, + work_dir=sys.output_directory, **kwargs) return pycde_mod diff --git a/frontends/PyCDE/src/types.py b/frontends/PyCDE/src/types.py index 875ed3003d21..cc6b1971654a 100644 --- a/frontends/PyCDE/src/types.py +++ b/frontends/PyCDE/src/types.py @@ -7,10 +7,11 @@ from .support import get_user_loc from .circt import ir, support -from .circt.dialects import esi, hw, sv -from .circt.dialects.esi import ChannelSignaling +from .circt.dialects import esi, hw, seq, sv +from .circt.dialects.esi import ChannelSignaling, ChannelDirection import typing +from dataclasses import dataclass class _Types: @@ -147,10 +148,16 @@ def _FromCirctType(type: typing.Union[ir.Type, Type]) -> Type: return Type.__new__(UInt, type) else: return Type.__new__(Bits, type) + if isinstance(type, seq.ClockType): + return Type.__new__(ClockType, type) if isinstance(type, esi.AnyType): return Type.__new__(Any, type) if isinstance(type, esi.ChannelType): return Type.__new__(Channel, type) + if isinstance(type, esi.BundleType): + return Type.__new__(Bundle, type) + if isinstance(type, esi.ListType): + return Type.__new__(List, type) return Type(type) @@ -474,16 +481,12 @@ def _from_obj(self, x: int, alias: typing.Optional[TypeAlias] = None): return hwarith.ConstantOp(circt_type, x) -class ClockType(Bits): +class ClockType(Type): """A special single bit to represent a clock. Can't do any special operations on it, except enter it as a implicit clock block.""" - # TODO: the 'clock' type isn't represented in CIRCT IR. It may be useful to - # have it there if for no other reason than being able to round trip this - # type. - def __new__(cls): - return super(ClockType, cls).__new__(cls, 1) + return super(ClockType, cls).__new__(cls, seq.ClockType.get()) def _get_value_class(self): from .signals import ClockSignal @@ -561,6 +564,102 @@ def wrap(self, value, raise TypeError("Unknown signaling standard") +@dataclass +class BundledChannel: + """A named, directed channel for inclusion in a bundle.""" + name: str + direction: ChannelDirection + channel: Channel + + def __repr__(self) -> str: + return f"('{self.name}', {str(self.direction)}, {self.channel})" + + +class Bundle(Type): + """A group of named, directed channels. Typically used in a service.""" + + def __new__(cls, channels: typing.List[BundledChannel]): + type = esi.BundleType.get( + [(bc.name, bc.direction, bc.channel._type) for bc in channels], False) + return super(Bundle, cls).__new__(cls, type) + + def _get_value_class(self): + from .signals import BundleSignal + return BundleSignal + + @property + def channels(self): + return [ + BundledChannel(name, dir, _FromCirctType(type)) + for (name, dir, type) in self._type.channels + ] + + def __repr__(self): + return f"Bundle<{self.channels}>" + + def pack( + self, **kwargs: typing.Dict[str, "ChannelSignal"] + ) -> ("BundleSignal", typing.Dict[str, "ChannelSignal"]): + """Pack a dict of TO channels into a bundle. Returns the bundle AND a dict + of all the FROM channels.""" + + from .signals import BundleSignal, _FromCirctValue + to_channels = { + bc.name: (idx, bc) for idx, bc in enumerate( + filter(lambda c: c.direction == ChannelDirection.TO, self.channels)) + } + from_channels = [ + c for c in self.channels if c.direction == ChannelDirection.FROM + ] + + operands = [None] * len(to_channels) + for name, value in kwargs.items(): + if name not in to_channels: + raise ValueError(f"Unknown channel name '{name}'") + idx, bc = to_channels[name] + if value.type != bc.channel: + raise TypeError(f"Expected channel type {bc.channel}, got {value.type} " + f"on channel '{name}'") + operands[idx] = value.value + del to_channels[name] + if len(to_channels) > 0: + raise ValueError(f"Missing channels: {', '.join(to_channels.keys())}") + + pack_op = esi.PackBundleOp(self._type, + [bc.channel._type for bc in from_channels], + operands) + + from_channels_results = pack_op.fromChannels + from_channels_ret = { + bc.name: _FromCirctValue(from_channels_results[idx]) + for idx, bc in enumerate(from_channels) + } + return BundleSignal(pack_op.bundle, self), from_channels_ret + + +class List(Type): + """An ESI list type represents variable length data. Just like a Python list.""" + + def __new__(cls, element_type: Type): + return super(List, cls).__new__(cls, esi.ListType.get(element_type._type)) + + @property + def element_type(self): + return _FromCirctType(self._type.element_type) + + @property + def _get_value_class(self): + from .signals import ListSignal + return ListSignal + + def __repr__(self): + return f"List<{self.element_type}>" + + @property + def inner(self): + return self.inner_type + + def dim(inner_type_or_bitwidth: typing.Union[Type, int], *size: typing.List[int], name: str = None) -> Array: diff --git a/frontends/PyCDE/test/test_behavioral.py b/frontends/PyCDE/test/test_behavioral.py index ab01ae551b73..e917a0124634 100644 --- a/frontends/PyCDE/test/test_behavioral.py +++ b/frontends/PyCDE/test/test_behavioral.py @@ -4,7 +4,7 @@ from pycde.behavioral import If, Else, EndIf from pycde.testing import unittestmodule -# CHECK-LABEL: msft.module @IfNestedTest {} (%a: ui8, %b: ui8, %cond: i1, %cond2: i1) -> (out: ui17, out2: ui24) +# CHECK-LABEL: hw.module @IfNestedTest(in %a : ui8, in %b : ui8, in %cond : i1, in %cond2 : i1, out out : ui17, out out2 : ui24) # CHECK: %0 = hwarith.mul %a, %b {sv.namehint = "a_mul_b"} : (ui8, ui8) -> ui16 # CHECK: %1 = comb.mux bin %cond2, %a, %b {sv.namehint = "x"} : ui8 # CHECK: %2 = hwarith.mul %a, %1 {sv.namehint = "v_thenvalue"} : (ui8, ui8) -> ui16 @@ -15,7 +15,7 @@ # CHECK: %7 = comb.mux bin %cond, %2, %5 {sv.namehint = "v"} : ui16 # CHECK: %8 = comb.mux bin %cond, %3, %6 {sv.namehint = "u"} : ui24 # CHECK: %9 = hwarith.add %7, %0 {sv.namehint = "v_plus_a_mul_b"} : (ui16, ui16) -> ui17 -# CHECK: msft.output %9, %8 : ui17, ui24 +# CHECK: hw.output %9, %8 : ui17, ui24 @unittestmodule() @@ -53,10 +53,10 @@ def build(ports): ports.out = v + w -# CHECK-LABEL: msft.module @IfDefaultTest {} (%a: ui8, %b: ui8, %cond: i1, %cond2: i1) -> (out: ui8) +# CHECK-LABEL: hw.module @IfDefaultTest(in %a : ui8, in %b : ui8, in %cond : i1, in %cond2 : i1, out out : ui8) # CHECK: [[r1:%.+]] = comb.mux bin %cond2, %b, %a {sv.namehint = "v_thenvalue"} : ui8 # CHECK: [[r0:%.+]] = comb.mux bin %cond, [[r1]], %a {sv.namehint = "v"} : ui8 -# CHECK: msft.output [[r0]] : ui8 +# CHECK: hw.output [[r0]] : ui8 @unittestmodule() diff --git a/frontends/PyCDE/test/test_cocotb_testbench.py b/frontends/PyCDE/test/test_cocotb_testbench.py index fe9c91cf763d..91cb330c1a5a 100644 --- a/frontends/PyCDE/test/test_cocotb_testbench.py +++ b/frontends/PyCDE/test/test_cocotb_testbench.py @@ -1,6 +1,6 @@ # REQUIRES: iverilog,cocotb # RUN: %PYTHON% %s 2>&1 | FileCheck %s -from pycde import Input, Output, generator, module, Clock, externmodule +from pycde import Input, Output, generator, modparams, Module, Clock from pycde.types import types from pycde.testing import cocotestbench, cocotest, cocoextra from pycde.dialects import comb @@ -15,7 +15,7 @@ # CHECK-NEXT: ******************************** -@module +@modparams def make_adder(width): class Adder(Module): @@ -30,7 +30,6 @@ def build(ports): return Adder -@module class RegAdd(Module): rst = Input(types.i1) clk = Clock() @@ -103,14 +102,14 @@ async def inc_test(ports): # CHECK-NEXT: ******************************* -@externmodule("adder") class ExternAdder(Module): in1 = Input(types.i16) in2 = Input(types.i16) out = Output(types.i16) + module_name = "adder" + -@module class RegAdd(Module): rst = Input(types.i1) clk = Clock() diff --git a/frontends/PyCDE/test/test_constructs.py b/frontends/PyCDE/test/test_constructs.py index 2e315749732e..419aeacd183c 100644 --- a/frontends/PyCDE/test/test_constructs.py +++ b/frontends/PyCDE/test/test_constructs.py @@ -6,7 +6,7 @@ from pycde.dialects import comb from pycde.testing import unittestmodule -# CHECK-LABEL: msft.module @WireAndRegTest {} (%In: i8, %InCE: i1, %clk: i1, %rst: i1) -> (Out: i8, OutReg: i8, OutRegRst: i8, OutRegCE: i8) +# CHECK-LABEL: hw.module @WireAndRegTest(in %In : i8, in %InCE : i1, in %clk : !seq.clock, in %rst : i1, out Out : i8, out OutReg : i8, out OutRegRst : i8, out OutRegCE : i8) # CHECK: [[r0:%.+]] = comb.extract %In from 0 {sv.namehint = "In_0upto7"} : (i8) -> i7 # CHECK: [[r1:%.+]] = comb.extract %In from 7 {sv.namehint = "In_7upto8"} : (i8) -> i1 # CHECK: [[r2:%.+]] = comb.concat [[r1]], [[r0]] {sv.namehint = "w1"} : i1, i7 @@ -15,9 +15,9 @@ # CHECK: sv.assign %in, %In : i8 # CHECK: [[r1:%.+]] = seq.compreg %In, %clk : i8 # CHECK: %c0_i8{{.*}} = hw.constant 0 : i8 -# CHECK: [[r5:%.+]] = seq.compreg %In, %clk, %rst, %c0_i8{{.*}} : i8 +# CHECK: [[r5:%.+]] = seq.compreg %In, %clk reset %rst, %c0_i8{{.*}} : i8 # CHECK: [[r6:%.+]] = seq.compreg.ce %In, %clk, %InCE : i8 -# CHECK: msft.output [[r2]], [[r1]], [[r5]], [[r6]] : i8, i8, i8, i8 +# CHECK: hw.output [[r2]], [[r1]], [[r5]], [[r6]] : i8, i8, i8, i8 @unittestmodule() @@ -59,12 +59,12 @@ def create(ports): # CHECK: msft.pe.output [[SUMR]] : i8 -# CHECK-LABEL: hw.module @SystolicArrayTest<__INST_HIER: none = "INSTANTIATE_WITH_INSTANCE_PATH">(%clk: i1, %col_data: !hw.array<2xi8>, %row_data: !hw.array<3xi8>) -> (out: !hw.array<3xarray<2xi8>>) +# CHECK-LABEL: hw.module @SystolicArrayTest(in %clk : i1, in %col_data : !hw.array<2xi8>, in %row_data : !hw.array<3xi8>, out out : !hw.array<3xarray<2xi8>>) # CHECK: %sum__reg1_0_0 = sv.reg sym @sum__reg1 : !hw.inout # CHECK: sv.read_inout %sum__reg1_0_0 : !hw.inout @unittestmodule(print=True, run_passes=True, print_after_passes=True) class SystolicArrayTest(Module): - clk = Input(types.i1) + clk = Clock() col_data = Input(dim(8, 2)) row_data = Input(dim(8, 3)) out = Output(dim(8, 2, 3)) @@ -86,16 +86,16 @@ def pe(r, c): ports.out = pe_outputs -# CHECK-LABEL: msft.module @ControlReg_num_asserts2_num_resets1 +# CHECK-LABEL: hw.module @ControlReg_num_asserts2_num_resets1 # CHECK: [[r0:%.+]] = hw.array_get %asserts[%false] # CHECK: [[r1:%.+]] = hw.array_get %asserts[%true] # CHECK: [[r2:%.+]] = comb.or bin [[r0]], [[r1]] # CHECK: [[r3:%.+]] = hw.array_get %resets[%c0_i0] # CHECK: [[r4:%.+]] = comb.or bin [[r3]] -# CHECK: %state = seq.compreg [[r6]], %clk, %rst, %false{{.*}} +# CHECK: %state = seq.compreg [[r6]], %clk reset %rst, %false{{.*}} # CHECK: [[r5:%.+]] = comb.mux bin [[r4]], %false{{.*}}, %state # CHECK: [[r6:%.+]] = comb.mux bin [[r2]], %true{{.*}}, [[r5]] -# CHECK: msft.output %state +# CHECK: hw.output %state @unittestmodule() class ControlRegTest(Module): clk = Clock() diff --git a/frontends/PyCDE/test/test_esi.py b/frontends/PyCDE/test/test_esi.py index aaf97f541aa2..0994ab891120 100644 --- a/frontends/PyCDE/test/test_esi.py +++ b/frontends/PyCDE/test/test_esi.py @@ -4,11 +4,12 @@ from pycde import (Clock, Input, InputChannel, OutputChannel, Module, generator, types) from pycde import esi -from pycde.common import Output +from pycde.common import Output, RecvBundle, SendBundle from pycde.constructs import Wire -from pycde.types import Bits, Channel, ChannelSignaling, UInt +from pycde.types import (Bits, Bundle, BundledChannel, Channel, + ChannelDirection, ChannelSignaling, UInt, ClockType) from pycde.testing import unittestmodule -from pycde.signals import BitVectorSignal, ChannelSignal, Struct +from pycde.signals import BitVectorSignal, ChannelSignal @esi.ServiceDecl @@ -20,7 +21,7 @@ class HostComms: class Producer(Module): - clk = Input(types.i1) + clk = Clock() int_out = OutputChannel(types.i32) @generator @@ -30,7 +31,7 @@ def construct(ports): class Consumer(Module): - clk = Input(types.i1) + clk = Clock() int_in = InputChannel(types.i32) @generator @@ -38,17 +39,17 @@ def construct(ports): HostComms.to_host(ports.int_in, "loopback_out") -# CHECK-LABEL: msft.module @LoopbackTop {} (%clk: i1, %rst: i1) -# CHECK: %Producer.int_out = msft.instance @Producer @Producer(%clk) : (i1) -> !esi.channel -# CHECK: msft.instance @Consumer @Consumer(%clk, %Producer.int_out) : (i1, !esi.channel) -> () -# CHECK: esi.service.instance svc @HostComms impl as "cosim"(%clk, %rst) : (i1, i1) -> () -# CHECK: msft.output -# CHECK-LABEL: msft.module @Producer {} (%clk: i1) -> (int_out: !esi.channel) +# CHECK-LABEL: hw.module @LoopbackTop(in %clk : !seq.clock, in %rst : i1) +# CHECK: %Producer.int_out = hw.instance "Producer" sym @Producer @Producer(clk: %clk: !seq.clock) -> (int_out: !esi.channel) +# CHECK: hw.instance "Consumer" sym @Consumer @Consumer(clk: %clk: !seq.clock, int_in: %Producer.int_out: !esi.channel) -> ( +# CHECK: esi.service.instance svc @HostComms impl as "cosim"(%clk, %rst) : (!seq.clock, i1) -> () +# CHECK: hw.output +# CHECK-LABEL: hw.module @Producer(in %clk : !seq.clock, out int_out : !esi.channel) # CHECK: [[R0:%.+]] = esi.service.req.to_client <@HostComms::@from_host>(["loopback_in"]) : !esi.channel -# CHECK: msft.output [[R0]] : !esi.channel -# CHECK-LABEL: msft.module @Consumer {} (%clk: i1, %int_in: !esi.channel) +# CHECK: hw.output [[R0]] : !esi.channel +# CHECK-LABEL: hw.module @Consumer(in %clk : !seq.clock, in %int_in : !esi.channel) # CHECK: esi.service.req.to_server %int_in -> <@HostComms::@to_host>(["loopback_out"]) : !esi.channel -# CHECK: msft.output +# CHECK: hw.output # CHECK-LABEL: esi.service.decl @HostComms { # CHECK: esi.service.to_server @to_host : !esi.channel # CHECK: esi.service.to_client @from_host : !esi.channel @@ -56,7 +57,7 @@ def construct(ports): @unittestmodule(print=True) class LoopbackTop(Module): - clk = Clock(types.i1) + clk = Clock() rst = Input(types.i1) @generator @@ -67,16 +68,16 @@ def construct(ports): esi.Cosim(HostComms, ports.clk, ports.rst) -# CHECK-LABEL: msft.module @LoopbackInOutTop {} (%clk: i1, %rst: i1) -# CHECK: esi.service.instance svc @HostComms impl as "cosim"(%clk, %rst) : (i1, i1) -> () +# CHECK-LABEL: hw.module @LoopbackInOutTop(in %clk : !seq.clock, in %rst : i1) +# CHECK: esi.service.instance svc @HostComms impl as "cosim"(%clk, %rst) : (!seq.clock, i1) -> () # CHECK: %0 = esi.service.req.inout %chanOutput -> <@HostComms::@req_resp>(["loopback_inout"]) : !esi.channel -> !esi.channel # CHECK: %rawOutput, %valid = esi.unwrap.vr %0, %ready : i32 # CHECK: %1 = comb.extract %rawOutput from 0 : (i32) -> i16 # CHECK: %chanOutput, %ready = esi.wrap.vr %1, %valid : i16 -# CHECK: msft.output +# CHECK: hw.output @unittestmodule(print=True) class LoopbackInOutTop(Module): - clk = Clock(types.i1) + clk = Clock() rst = Input(types.i1) @generator @@ -94,7 +95,7 @@ def construct(self): loopback.assign(data_chan) -# CHECK-LABEL: esi.pure_module @LoopbackInOutPure { +# CHECK-LABEL: esi.pure_module @LoopbackInOutPure # CHECK: [[r0:%.+]] = esi.service.req.to_client <@HostComms::@from_host>(["loopback_in"]) : !esi.channel # CHECK: esi.service.req.to_server [[r0]] -> <@HostComms::@to_host>(["loopback"]) : !esi.channel @unittestmodule(print=True) @@ -152,22 +153,22 @@ def unwrap_and_pad(ports, input_channel: ChannelSignal): ports.trunk_out_valid = valid -# CHECK-LABEL: hw.module @MultiplexerTop{{.*}}(%clk: i1, %rst: i1, %trunk_in: i256, %trunk_in_valid: i1, %trunk_out_ready: i1) -> (trunk_in_ready: i1, trunk_out: i256, trunk_out_valid: i1) +# CHECK-LABEL: hw.module @MultiplexerTop{{.*}}(in %clk : !seq.clock, in %rst : i1, in %trunk_in : i256, in %trunk_in_valid : i1, in %trunk_out_ready : i1, out trunk_in_ready : i1, out trunk_out : i256, out trunk_out_valid : i1) # CHECK: %c0_i224 = hw.constant 0 : i224 # CHECK: [[r0:%.+]] = comb.concat %c0_i224, %Consumer.loopback_out : i224, i32 # CHECK: [[r1:%.+]] = comb.extract %trunk_in from 0 {sv.namehint = "trunk_in_0upto32"} : (i256) -> i32 # CHECK: %Producer.loopback_in_ready, %Producer.int_out, %Producer.int_out_valid = hw.instance "Producer" sym @Producer @Producer{{.*}}(clk: %clk: i1, loopback_in: [[r1]]: i32, loopback_in_valid: %trunk_in_valid: i1, int_out_ready: %Consumer.int_in_ready: i1) -> (loopback_in_ready: i1, int_out: i32, int_out_valid: i1) # CHECK: %Consumer.int_in_ready, %Consumer.loopback_out, %Consumer.loopback_out_valid = hw.instance "Consumer" sym @Consumer @Consumer{{.*}}(clk: %clk: i1, int_in: %Producer.int_out: i32, int_in_valid: %Producer.int_out_valid: i1, loopback_out_ready: %trunk_out_ready: i1) -> (int_in_ready: i1, loopback_out: i32, loopback_out_valid: i1) # CHECK: hw.output %Producer.loopback_in_ready, [[r0]], %Consumer.loopback_out_valid : i1, i256, i1 -# CHECK-LABEL: hw.module @Producer{{.*}}(%clk: i1, %loopback_in: i32, %loopback_in_valid: i1, %int_out_ready: i1) -> (loopback_in_ready: i1, int_out: i32, int_out_valid: i1) +# CHECK-LABEL: hw.module @Producer{{.*}}(in %clk : i1, in %loopback_in : i32, in %loopback_in_valid : i1, in %int_out_ready : i1, out loopback_in_ready : i1, out int_out : i32, out int_out_valid : i1) # CHECK: hw.output %int_out_ready, %loopback_in, %loopback_in_valid : i1, i32, i1 -# CHECK-LABEL: hw.module @Consumer{{.*}}(%clk: i1, %int_in: i32, %int_in_valid: i1, %loopback_out_ready: i1) -> (int_in_ready: i1, loopback_out: i32, loopback_out_valid: i1) +# CHECK-LABEL: hw.module @Consumer{{.*}}(in %clk : i1, in %int_in : i32, in %int_in_valid : i1, in %loopback_out_ready : i1, out int_in_ready : i1, out loopback_out : i32, out loopback_out_valid : i1) # CHECK: hw.output %loopback_out_ready, %int_in, %int_in_valid : i1, i32, i1 @unittestmodule(run_passes=True, print_after_passes=True, emit_outputs=True) class MultiplexerTop(Module): - clk = Clock(types.i1) + clk = Clock() rst = Input(types.i1) trunk_in = Input(types.i256) @@ -206,7 +207,7 @@ def generate(self, channels): req.assign(esi.PureModule.input_port(name, req.type)) -# CHECK-LABEL: hw.module @PureTest(%in_Producer_loopback_in: i32, %in_Producer_loopback_in_valid: i1, %in_prod2_loopback_in: i32, %in_prod2_loopback_in_valid: i1, %clk: i1, %out_Consumer_loopback_out_ready: i1, %p2_int_ready: i1) -> (in_Producer_loopback_in_ready: i1, in_prod2_loopback_in_ready: i1, out_Consumer_loopback_out: i32, out_Consumer_loopback_out_valid: i1, p2_int: i32, p2_int_valid: i1) { +# CHECK-LABEL: hw.module @PureTest(in %in_Producer_loopback_in : i32, in %in_Producer_loopback_in_valid : i1, in %in_prod2_loopback_in : i32, in %in_prod2_loopback_in_valid : i1, in %clk : i1, in %out_Consumer_loopback_out_ready : i1, in %p2_int_ready : i1, out in_Producer_loopback_in_ready : i1, out in_prod2_loopback_in_ready : i1, out out_Consumer_loopback_out : i32, out out_Consumer_loopback_out_valid : i1, out p2_int : i32, out p2_int_valid : i1) # CHECK-NEXT: %Producer.loopback_in_ready, %Producer.int_out, %Producer.int_out_valid = hw.instance "Producer" sym @Producer @Producer{{.*}}(clk: %clk: i1, loopback_in: %in_Producer_loopback_in: i32, loopback_in_valid: %in_Producer_loopback_in_valid: i1, int_out_ready: %Consumer.int_in_ready: i1) -> (loopback_in_ready: i1, int_out: i32, int_out_valid: i1) # CHECK-NEXT: %Consumer.int_in_ready, %Consumer.loopback_out, %Consumer.loopback_out_valid = hw.instance "Consumer" sym @Consumer @Consumer{{.*}}(clk: %clk: i1, int_in: %Producer.int_out: i32, int_in_valid: %Producer.int_out_valid: i1, loopback_out_ready: %out_Consumer_loopback_out_ready: i1) -> (int_in_ready: i1, loopback_out: i32, loopback_out_valid: i1) # CHECK-NEXT: %prod2.loopback_in_ready, %prod2.int_out, %prod2.int_out_valid = hw.instance "prod2" sym @prod2 @Producer{{.*}}(clk: %clk: i1, loopback_in: %in_prod2_loopback_in: i32, loopback_in_valid: %in_prod2_loopback_in_valid: i1, int_out_ready: %p2_int_ready: i1) -> (loopback_in_ready: i1, int_out: i32, int_out_valid: i1) @@ -218,7 +219,7 @@ class PureTest(esi.PureModule): def construct(ports): PassUpService(None) - clk = esi.PureModule.input_port("clk", types.i1) + clk = esi.PureModule.input_port("clk", ClockType()) p = Producer(clk=clk) Consumer(clk=clk, int_in=p.int_out) p2 = Producer(clk=clk, instance_name="prod2") @@ -227,10 +228,10 @@ def construct(ports): esi.PureModule.param("STR") -# CHECK-LABEL: msft.module @FIFOSignalingMod {} (%a: !esi.channel) -> (x: !esi.channel) +# CHECK-LABEL: hw.module @FIFOSignalingMod(in %a : !esi.channel, out x : !esi.channel) # CHECK-NEXT: %data, %empty = esi.unwrap.fifo %a, %rden : !esi.channel # CHECK-NEXT: %chanOutput, %rden = esi.wrap.fifo %data, %empty : !esi.channel -# CHECK-NEXT: msft.output %chanOutput : !esi.channel +# CHECK-NEXT: hw.output %chanOutput : !esi.channel @unittestmodule(print=True) class FIFOSignalingMod(Module): a = InputChannel(Bits(32), ChannelSignaling.FIFO0) @@ -251,7 +252,8 @@ def build(self): }) -# CHECK-LABEL: hw.module @FlattenTest{{.*}}(%a_a: i4, %a_b: ui32, %a_valid: i1) -> (a_ready: i1) +# TODO: figure out a replacement for `esi.FlattenStructPorts`. +# XFAIL-LABEL: hw.module @FlattenTest{{.*}}(in %a_a : i4, in %a_b : ui32, in %a_valid : i1, out a_ready : i1) @unittestmodule(print=False, run_passes=True, print_after_passes=True) class FlattenTest(Module): a = InputChannel(ExStruct) @@ -263,7 +265,7 @@ def build(self): pass -# CHECK-LABEL: hw.module.extern @FlattenExternTest{{.*}}(%a_a: i4, %a_b: ui32, %a_valid: i1) -> (a_ready: i1) +# XFAIL-LABEL: hw.module.extern @FlattenExternTest{{.*}}(%a_a: i4, %a_b: ui32, %a_valid: i1) -> (a_ready: i1) @unittestmodule(print=False, run_passes=True, print_after_passes=True) class FlattenExternTest(Module): a = InputChannel(ExStruct) @@ -271,7 +273,7 @@ class FlattenExternTest(Module): Attributes = {esi.FlattenStructPorts} -# CHECK-LABEL: hw.module @FlattenPureTest(%a_a: i4, %a_b: ui32, %a_valid: i1) -> (a_ready: i1) attributes {esi.portFlattenStructs} +# XFAIL-LABEL: hw.module @FlattenPureTest(%a_a: i4, %a_b: ui32, %a_valid: i1) -> (a_ready: i1) attributes {esi.portFlattenStructs} @unittestmodule(print=False, run_passes=True, print_after_passes=True) class FlattenPureTest(esi.PureModule): @@ -280,3 +282,41 @@ class FlattenPureTest(esi.PureModule): @generator def build(self): esi.PureModule.input_port("a", types.channel(ExStruct)) + + +Bundle1 = Bundle([ + BundledChannel("req", ChannelDirection.TO, types.channel(types.i32)), + BundledChannel("resp", ChannelDirection.FROM, types.channel(types.i1)), +]) +# CHECK: Bundle<[('req', ChannelDirection.TO, Channel, ValidReady>), ('resp', ChannelDirection.FROM, Channel, ValidReady>)]> +print(Bundle1) + + +# CHECK-LABEL: hw.module @SendBundleTest(in %s1_in : !esi.channel, out b_send : !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]>, out i1_out : !esi.channel) attributes {output_file = #hw.output_file<"SendBundleTest.sv", includeReplicatedOps>} { +# CHECK-NEXT: %bundle, %resp = esi.bundle.pack %s1_in : !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]> +# CHECK-NEXT: hw.output %bundle, %resp : !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]>, !esi.channel +@unittestmodule() +class SendBundleTest(Module): + b_send = SendBundle(Bundle1) + s1_in = InputChannel(types.i32) + i1_out = OutputChannel(types.i1) + + @generator + def build(self): + (self.b_send, from_chans) = Bundle1.pack(req=self.s1_in) + self.i1_out = from_chans['resp'] + + +# CHECK-LABEL: hw.module @RecvBundleTest(in %b_recv : !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]>, in %i1_in : !esi.channel, out s1_out : !esi.channel) attributes {output_file = #hw.output_file<"RecvBundleTest.sv", includeReplicatedOps>} { +# CHECK-NEXT: %req = esi.bundle.unpack %i1_in from %b_recv : !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]> +# CHECK-NEXT: hw.output %req : !esi.channel +@unittestmodule() +class RecvBundleTest(Module): + b_recv = RecvBundle(Bundle1) + s1_out = OutputChannel(types.i32) + i1_in = InputChannel(types.i1) + + @generator + def build(self): + to_channels = self.b_recv.unpack(resp=self.i1_in) + self.s1_out = to_channels['req'] diff --git a/frontends/PyCDE/test/test_esi_errors.py b/frontends/PyCDE/test/test_esi_errors.py index 18dab35c1037..15eaa7fe1a95 100644 --- a/frontends/PyCDE/test/test_esi_errors.py +++ b/frontends/PyCDE/test/test_esi_errors.py @@ -3,6 +3,8 @@ from pycde import (Clock, Input, InputChannel, OutputChannel, Module, generator, types) +from pycde.common import SendBundle, RecvBundle +from pycde.types import Bundle, BundledChannel, ChannelDirection from pycde import esi from pycde.testing import unittestmodule @@ -14,7 +16,7 @@ class HostComms: class Producer(Module): - clk = Input(types.i1) + clk = Clock() int_out = OutputChannel(types.i32) @generator @@ -24,7 +26,7 @@ def construct(ports): class Consumer(Module): - clk = Input(types.i1) + clk = Clock() int_in = InputChannel(types.i32) @generator @@ -34,7 +36,7 @@ def construct(ports): @unittestmodule(print=True) class LoopbackTop(Module): - clk = Clock(types.i1) + clk = Clock() rst = Input(types.i1) @generator @@ -75,7 +77,7 @@ def generate(ports, channels): @unittestmodule(run_passes=True, print_after_passes=True) class MultiplexerTop(Module): - clk = Clock(types.i1) + clk = Clock() rst = Input(types.i1) @generator @@ -105,9 +107,102 @@ def generate(ports, channels): @unittestmodule(run_passes=True, print_after_passes=True) class BrokenTop(Module): - clk = Clock(types.i1) + clk = Clock() rst = Input(types.i1) @generator def construct(ports): BrokenService(clk=ports.clk, rst=ports.rst) + + +# ----- + +Bundle1 = Bundle([ + BundledChannel("req", ChannelDirection.TO, types.channel(types.i32)), + BundledChannel("resp", ChannelDirection.FROM, types.channel(types.i1)), +]) + + +@unittestmodule(print=True, run_passes=True, print_after_passes=True) +class SendBundleTest(Module): + b_send = SendBundle(Bundle1) + s1_in = InputChannel(types.i32) + i1_out = OutputChannel(types.i1) + + @generator + def build(self): + (self.b_send, from_chans) = Bundle1.pack() + # CHECK: ValueError: Missing channels: req + + +# ----- + + +@unittestmodule(print=True, run_passes=True, print_after_passes=True) +class SendBundleTest(Module): + b_send = SendBundle(Bundle1) + s1_in = InputChannel(types.i32) + i1_out = OutputChannel(types.i1) + + @generator + def build(self): + (self.b_send, from_chans) = Bundle1.pack(asdf=self.s1_in) + # CHECK: ValueError: Unknown channel name 'asdf' + + +# ----- + + +@unittestmodule() +class SendBundleTest(Module): + b_send = SendBundle(Bundle1) + s1_in = InputChannel(types.i2) + + @generator + def build(self): + (self.b_send, from_chans) = Bundle1.pack(req=self.s1_in) + # CHECK: TypeError: Expected channel type Channel, ValidReady>, got Channel, ValidReady> on channel 'req' + + +# ----- + + +@unittestmodule(print=True, run_passes=True, print_after_passes=True) +class RecvBundleTest(Module): + b_recv = RecvBundle(Bundle1) + s1_out = OutputChannel(types.i32) + i1_in = InputChannel(types.i1) + + @generator + def build(self): + to_channels = self.b_recv.unpack() + # CHECK: ValueError: Missing channel values for resp + + +# ----- + + +@unittestmodule(print=True, run_passes=True, print_after_passes=True) +class RecvBundleTest(Module): + b_recv = RecvBundle(Bundle1) + s1_out = OutputChannel(types.i32) + i1_in = InputChannel(types.i1) + + @generator + def build(self): + to_channels = self.b_recv.unpack(asdf=self.i1_in) + # CHECK: ValueError: Unknown channel name 'asdf' + + +# ----- + + +@unittestmodule() +class RecvBundleTest(Module): + b_recv = RecvBundle(Bundle1) + i1_in = InputChannel(types.i4) + + @generator + def build(self): + to_channels = self.b_recv.unpack(resp=self.i1_in) + # CHECK: TypeError: Expected channel type Channel, ValidReady>, got Channel, ValidReady> on channel 'resp' diff --git a/frontends/PyCDE/test/test_fsm.py b/frontends/PyCDE/test/test_fsm.py index eb0eec3722b5..c0ff2eca7396 100644 --- a/frontends/PyCDE/test/test_fsm.py +++ b/frontends/PyCDE/test/test_fsm.py @@ -1,81 +1,44 @@ # RUN: %PYTHON% py-split-input-file.py %s | FileCheck %s -# XFAIL: * from pycde import System, Input, Output, generator, Module +from pycde.common import Clock, Reset from pycde.dialects import comb from pycde import fsm from pycde.types import types from pycde.testing import unittestmodule -# FSM instantiation example - -# CHECK-LABEL: msft.module @FSMUser {} (%a: i1, %b: i1, %c: i1, %clk: i1, %rst: i1) -> (is_a: i1, is_b: i1, is_c: i1) attributes {fileName = "FSMUser.sv"} { -# CHECK: %FSM.is_A, %FSM.is_B, %FSM.is_C = msft.instance @FSM @FSM(%a, %b, %c, %clk, %rst) : (i1, i1, i1, i1, i1) -> (i1, i1, i1) -# CHECK: msft.output %FSM.is_A, %FSM.is_B, %FSM.is_C : i1, i1, i1 -# CHECK: } -# CHECK-LABEL: msft.module @FSM {} (%a: i1, %b: i1, %c: i1, %clk: i1, %rst: i1) -> (is_A: i1, is_B: i1, is_C: i1) attributes {fileName = "FSM.sv"} { -# CHECK: %0:3 = fsm.hw_instance "FSM_impl" @FSM_impl(%a, %b, %c), clock %clk, reset %rst : (i1, i1, i1) -> (i1, i1, i1) -# CHECK: msft.output %0#0, %0#1, %0#2 : i1, i1, i1 -# CHECK: } - - -@fsm.machine() -class FSM: - a = Input(types.i1) - b = Input(types.i1) - c = Input(types.i1) - - # States - A = fsm.State(initial=True) - (B, C) = fsm.States(2) - - # Transitions - A.set_transitions((B, lambda ports: ports.a)) - B.set_transitions((A, lambda ports: ports.b), (C,)) - C.set_transitions((B, lambda ports: ports.a)) - - -@unittestmodule() -class FSMUser(Module): - a = Input(types.i1) - b = Input(types.i1) - c = Input(types.i1) - clk = Input(types.i1) - rst = Input(types.i1) - is_a = Output(types.i1) - is_b = Output(types.i1) - is_c = Output(types.i1) - - @generator - def construct(ports): - fsm = FSM(a=ports.a, b=ports.b, c=ports.c, clk=ports.clk, rst=ports.rst) - ports.is_a = fsm.is_A - ports.is_b = fsm.is_B - ports.is_c = fsm.is_C - - -# ----- - # FSM state transitions example - -# CHECK: fsm.machine @F0_impl(%arg0: i1, %arg1: i1, %arg2: i1) -> (i1, i1, i1, i1) attributes {clock_name = "clock", in_names = ["a", "b", "c"], initialState = "idle", out_names = ["is_A", "is_B", "is_C", "is_idle"], reset_name = "rst"} { -# CHECK-NEXT: fsm.state @A output { +# CHECK-LABEL: hw.module @FSMUser(in %a : i1, in %b : i1, in %c : i1, in %clk : !seq.clock, in %rst : i1, out is_a : i1, out is_b : i1, out is_c : i1) +# CHECK-NEXT: %0:4 = fsm.hw_instance "F0" @F0(%a, %b, %c), clock %clk, reset %rst : (i1, i1, i1) -> (i1, i1, i1, i1) +# CHECK-NEXT: hw.output %0#1, %0#2, %0#3 : i1, i1, i1 +# CHECK-NEXT: } +# CHECK-LABEL: fsm.machine @F0(%arg0: i1, %arg1: i1, %arg2: i1) -> (i1, i1, i1, i1) attributes {clock_name = "clk", in_names = ["a", "b", "c"], initialState = "idle", out_names = ["is_idle", "is_A", "is_B", "is_C"], reset_name = "rst"} { +# CHECK-NEXT: fsm.state @idle output { # CHECK-NEXT: %true = hw.constant true # CHECK-NEXT: %false = hw.constant false # CHECK-NEXT: %false_0 = hw.constant false # CHECK-NEXT: %false_1 = hw.constant false # CHECK-NEXT: fsm.output %true, %false, %false_0, %false_1 : i1, i1, i1, i1 # CHECK-NEXT: } transitions { +# CHECK-NEXT: fsm.transition @A +# CHECK-NEXT: } +# CHECK-NEXT: fsm.state @A output { +# CHECK-NEXT: %false = hw.constant false +# CHECK-NEXT: %true = hw.constant true +# CHECK-NEXT: %false_0 = hw.constant false +# CHECK-NEXT: %false_1 = hw.constant false +# CHECK-NEXT: fsm.output %false, %true, %false_0, %false_1 : i1, i1, i1, i1 +# CHECK-NEXT: } transitions { # CHECK-NEXT: fsm.transition @B guard { # CHECK-NEXT: fsm.return %arg0 # CHECK-NEXT: } # CHECK-NEXT: } # CHECK-NEXT: fsm.state @B output { # CHECK-NEXT: %false = hw.constant false -# CHECK-NEXT: %true = hw.constant true # CHECK-NEXT: %false_0 = hw.constant false +# CHECK-NEXT: %true = hw.constant true # CHECK-NEXT: %false_1 = hw.constant false -# CHECK-NEXT: fsm.output %false, %true, %false_0, %false_1 : i1, i1, i1, i1 +# CHECK-NEXT: fsm.output %false, %false_0, %true, %false_1 : i1, i1, i1, i1 # CHECK-NEXT: } transitions { # CHECK-NEXT: fsm.transition @C guard { # CHECK-NEXT: %0 = comb.and bin %arg0, %arg1 : i1 @@ -96,9 +59,9 @@ def construct(ports): # CHECK-NEXT: fsm.state @C output { # CHECK-NEXT: %false = hw.constant false # CHECK-NEXT: %false_0 = hw.constant false -# CHECK-NEXT: %true = hw.constant true # CHECK-NEXT: %false_1 = hw.constant false -# CHECK-NEXT: fsm.output %false, %false_0, %true, %false_1 : i1, i1, i1, i1 +# CHECK-NEXT: %true = hw.constant true +# CHECK-NEXT: fsm.output %false, %false_0, %false_1, %true : i1, i1, i1, i1 # CHECK-NEXT: } transitions { # CHECK-NEXT: fsm.transition @idle guard { # CHECK-NEXT: fsm.return %arg2 @@ -109,20 +72,10 @@ def construct(ports): # CHECK-NEXT: fsm.return %0 # CHECK-NEXT: } # CHECK-NEXT: } -# CHECK-NEXT: fsm.state @idle output { -# CHECK-NEXT: %false = hw.constant false -# CHECK-NEXT: %false_0 = hw.constant false -# CHECK-NEXT: %false_1 = hw.constant false -# CHECK-NEXT: %true = hw.constant true -# CHECK-NEXT: fsm.output %false, %false_0, %false_1, %true : i1, i1, i1, i1 -# CHECK-NEXT: } transitions { -# CHECK-NEXT: fsm.transition @A -# CHECK-NEXT: } # CHECK-NEXT: } -@fsm.machine(clock="clock") -class F0: +class F0(fsm.Machine): a = Input(types.i1) b = Input(types.i1) c = Input(types.i1) @@ -147,47 +100,12 @@ def nand(*args): (A, lambda ports: comb.XorOp(ports.b, types.i1(1)))) -system = System([F0]) -system.generate() -system.print() - -# ----- - -# Shorthand FSM generator. - -# CHECK: fsm.machine @Generated_FSM_impl(%arg0: i1) -> (i1, i1, i1) attributes {clock_name = "clk", in_names = ["go"], initialState = "a", out_names = ["is_a", "is_b", "is_c"], reset_name = "rst"} { -# CHECK-NEXT: fsm.state @a output { -# CHECK-NEXT: %true = hw.constant true -# CHECK-NEXT: %false = hw.constant false -# CHECK-NEXT: %false_0 = hw.constant false -# CHECK-NEXT: fsm.output %true, %false, %false_0 : i1, i1, i1 -# CHECK-NEXT: } transitions { -# CHECK-NEXT: fsm.transition @b -# CHECK-NEXT: fsm.transition @c guard { -# CHECK-NEXT: fsm.return %arg0 -# CHECK-NEXT: } -# CHECK-NEXT: } -# CHECK-NEXT: fsm.state @b output { -# CHECK-NEXT: %false = hw.constant false -# CHECK-NEXT: %true = hw.constant true -# CHECK-NEXT: %false_0 = hw.constant false -# CHECK-NEXT: fsm.output %false, %true, %false_0 : i1, i1, i1 -# CHECK-NEXT: } transitions { -# CHECK-NEXT: } -# CHECK-NEXT: fsm.state @c output { -# CHECK-NEXT: %false = hw.constant false -# CHECK-NEXT: %false_0 = hw.constant false -# CHECK-NEXT: %true = hw.constant true -# CHECK-NEXT: fsm.output %false, %false_0, %true : i1, i1, i1 -# CHECK-NEXT: } transitions { -# CHECK-NEXT: } -# CHECK-NEXT: } - - @unittestmodule() class FSMUser(Module): - go = Input(types.i1) - clk = Input(types.i1) + a = Input(types.i1) + b = Input(types.i1) + c = Input(types.i1) + clk = Clock() rst = Input(types.i1) is_a = Output(types.i1) is_b = Output(types.i1) @@ -195,16 +113,30 @@ class FSMUser(Module): @generator def construct(ports): - MyFSM = fsm.gen_fsm({ - "a": [ - "b", - ("c", "go"), - ], - "b": [], - "c": [] - }, "Generated_FSM") - - inst = MyFSM(go=ports.go, clk=ports.clk, rst=ports.rst) - ports.is_a = inst.is_a - ports.is_b = inst.is_b - ports.is_c = inst.is_c + fsm = F0(a=ports.a, b=ports.b, c=ports.c, clk=ports.clk, rst=ports.rst) + ports.is_a = fsm.is_A + ports.is_b = fsm.is_B + ports.is_c = fsm.is_C + + +system = System([FSMUser]) +system.generate() +system.print() + +# ------ + +# Test alternative clock / reset names. + + +# CHECK-LABEL: fsm.machine @FsmClockTest(%arg0: i1) -> (i1, i1) attributes {clock_name = "clock", in_names = ["a"], initialState = "A", out_names = ["is_A", "is_B"], reset_name = "reset"} +@unittestmodule() +class FsmClockTest(fsm.Machine): + clock = Clock() + reset = Reset() + + a = Input(types.i1) + A = fsm.State(initial=True) + B = fsm.State() + + A.set_transitions((B, lambda ports: ports.a)) + B.set_transitions((A, lambda ports: ports.a)) diff --git a/frontends/PyCDE/test/test_fsm_errors.py b/frontends/PyCDE/test/test_fsm_errors.py index d4aef38464ab..dfbe3123606f 100644 --- a/frontends/PyCDE/test/test_fsm_errors.py +++ b/frontends/PyCDE/test/test_fsm_errors.py @@ -5,8 +5,7 @@ from pycde.types import types -@fsm.machine() -class FSM: +class FSM(fsm.Machine): # CHECK: ValueError: Input port a has width 2. For now, FSMs only support i1 inputs. a = Input(types.i2) A = fsm.State(initial=True) @@ -16,8 +15,7 @@ class FSM: # CHECK: ValueError: No initial state specified, please create a state with `initial=True`. -@fsm.machine() -class FSM: +class FSM(fsm.Machine): a = Input(types.i1) A = fsm.State() @@ -26,8 +24,7 @@ class FSM: # CHECK: ValueError: Multiple initial states specified (B, A). -@fsm.machine() -class FSM: +class FSM(fsm.Machine): a = Input(types.i1) A = fsm.State(initial=True) B = fsm.State(initial=True) diff --git a/frontends/PyCDE/test/test_hwarith.py b/frontends/PyCDE/test/test_hwarith.py index 4fd2f059b0c7..5f16dd078676 100644 --- a/frontends/PyCDE/test/test_hwarith.py +++ b/frontends/PyCDE/test/test_hwarith.py @@ -5,7 +5,7 @@ from pycde.types import types, UInt -# CHECK: msft.module @InfixArith {} (%in0: si16, %in1: ui16) +# CHECK: hw.module @InfixArith(in %in0 : si16, in %in1 : ui16) # CHECK-NEXT: %0 = hwarith.add %in0, %in1 {{({sv.namehint = ".*"} )?}}: (si16, ui16) -> si18 # CHECK-NEXT: %1 = hwarith.sub %in0, %in1 {{({sv.namehint = ".*"} )?}}: (si16, ui16) -> si18 # CHECK-NEXT: %2 = hwarith.mul %in0, %in1 {{({sv.namehint = ".*"} )?}}: (si16, ui16) -> si32 @@ -13,7 +13,7 @@ # CHECK-NEXT: %c-1_i16 = hw.constant -1 {{({sv.namehint = ".*"} )?}}: i16 # CHECK-NEXT: %4 = hwarith.cast %c-1_i16 {{({sv.namehint = ".*"} )?}}: (i16) -> si16 # CHECK-NEXT: %5 = hwarith.mul %in0, %4 {{({sv.namehint = ".*"} )?}}: (si16, si16) -> si32 -# CHECK-NEXT: msft.output +# CHECK-NEXT: hw.output @unittestmodule(run_passes=True) class InfixArith(Module): in0 = Input(types.si16) @@ -31,13 +31,13 @@ def construct(ports): # ----- -# CHECK: msft.module @InfixLogic {} (%in0: i16, %in1: i16) +# CHECK: hw.module @InfixLogic(in %in0 : i16, in %in1 : i16) # CHECK-NEXT: comb.and bin %in0, %in1 {{({sv.namehint = ".*"} )?}}: i16 # CHECK-NEXT: comb.or bin %in0, %in1 {{({sv.namehint = ".*"} )?}}: i16 # CHECK-NEXT: comb.xor bin %in0, %in1 {{({sv.namehint = ".*"} )?}}: i16 # CHECK-NEXT: %c-1_i16 = hw.constant -1 {{({sv.namehint = ".*"} )?}}: i16 # CHECK-NEXT: comb.xor bin %in0, %c-1_i16 {{({sv.namehint = ".*"} )?}}: i16 -# CHECK-NEXT: msft.output +# CHECK-NEXT: hw.output @unittestmodule(run_passes=True) class InfixLogic(Module): in0 = Input(types.i16) @@ -54,10 +54,10 @@ def construct(ports): # ----- -# CHECK: msft.module @SignlessInfixComparison {} (%in0: i16, %in1: i16) +# CHECK: hw.module @SignlessInfixComparison(in %in0 : i16, in %in1 : i16) # CHECK-NEXT: %0 = comb.icmp bin eq %in0, %in1 {{({sv.namehint = ".*"} )?}}: i16 # CHECK-NEXT: %1 = comb.icmp bin ne %in0, %in1 {{({sv.namehint = ".*"} )?}}: i16 -# CHECK-NEXT: msft.output +# CHECK-NEXT: hw.output @unittestmodule(run_passes=True) class SignlessInfixComparison(Module): in0 = Input(types.i16) @@ -72,10 +72,14 @@ def construct(ports): # ----- -# CHECK: msft.module @InfixComparison {} (%in0: ui16, %in1: ui16) -# CHECK-NEXT: %0 = hwarith.icmp eq %in0, %in1 {sv.namehint = "in0_eq_in1"} : ui16, ui16 -# CHECK-NEXT: %1 = hwarith.icmp ne %in0, %in1 {sv.namehint = "in0_neq_in1"} : ui16, ui16 -# CHECK-NEXT: msft.output +# CHECK: hw.module @InfixComparison(in %in0 : ui16, in %in1 : ui16) +# CHECK: %0 = hwarith.icmp eq %in0, %in1 {sv.namehint = "in0_eq_in1"} : ui16, ui16 +# CHECK: %1 = hwarith.icmp ne %in0, %in1 {sv.namehint = "in0_neq_in1"} : ui16, ui16 +# CHECK: %2 = hwarith.icmp lt %in0, %in1 {sv.namehint = "in0_lt_in1"} : ui16, ui16 +# CHECK: %3 = hwarith.icmp gt %in0, %in1 {sv.namehint = "in0_gt_in1"} : ui16, ui16 +# CHECK: %4 = hwarith.icmp le %in0, %in1 {sv.namehint = "in0_le_in1"} : ui16, ui16 +# CHECK: %5 = hwarith.icmp ge %in0, %in1 {sv.namehint = "in0_ge_in1"} : ui16, ui16 +# CHECK-NEXT: hw.output @unittestmodule(run_passes=False) class InfixComparison(Module): in0 = Input(types.ui16) @@ -85,17 +89,21 @@ class InfixComparison(Module): def construct(ports): eq = ports.in0 == ports.in1 neq = ports.in0 != ports.in1 + lt = ports.in0 < ports.in1 + gt = ports.in0 > ports.in1 + le = ports.in0 <= ports.in1 + ge = ports.in0 >= ports.in1 # ----- -# CHECK: msft.module @Multiple {} (%in0: si16, %in1: si16) -> (out0: i16) +# CHECK: hw.module @Multiple(in %in0 : si16, in %in1 : si16, out out0 : i16) # CHECK-NEXT: %0 = hwarith.add %in0, %in1 {{({sv.namehint = ".*"} )?}}: (si16, si16) -> si17 # CHECK-NEXT: %1 = hwarith.add %0, %in0 {{({sv.namehint = ".*"} )?}}: (si17, si16) -> si18 # CHECK-NEXT: %2 = hwarith.add %1, %in1 {{({sv.namehint = ".*"} )?}}: (si18, si16) -> si19 # CHECK-NEXT: %3 = hwarith.cast %2 {{({sv.namehint = ".*"} )?}}: (si19) -> i16 -# CHECK-NEXT: msft.output %3 {{({sv.namehint = ".*"} )?}}: i16 +# CHECK-NEXT: hw.output %3 {{({sv.namehint = ".*"} )?}}: i16 @unittestmodule(run_passes=True) class Multiple(Module): in0 = Input(types.si16) @@ -110,7 +118,7 @@ def construct(ports): # ----- -# CHECK: msft.module @Casting {} (%in0: i16) +# CHECK: hw.module @Casting(in %in0 : i16) # CHECK-NEXT: %0 = hwarith.cast %in0 {{({sv.namehint = ".*"} )?}}: (i16) -> si16 # CHECK-NEXT: %1 = hwarith.cast %in0 {{({sv.namehint = ".*"} )?}}: (i16) -> ui16 # CHECK-NEXT: %2 = hwarith.cast %0 {{({sv.namehint = ".*"} )?}}: (si16) -> i16 @@ -118,7 +126,7 @@ def construct(ports): # CHECK-NEXT: %4 = hwarith.cast %in0 {{({sv.namehint = ".*"} )?}}: (i16) -> ui8 # CHECK-NEXT: %5 = hwarith.cast %0 {{({sv.namehint = ".*"} )?}}: (si16) -> i8 # CHECK-NEXT: %6 = hwarith.cast %0 {{({sv.namehint = ".*"} )?}}: (si16) -> si24 -# CHECK-NEXT: msft.output +# CHECK-NEXT: hw.output @unittestmodule(run_passes=True) class Casting(Module): in0 = Input(types.i16) @@ -137,10 +145,13 @@ def construct(ports): # ----- -# CHECK: hw.module @Lowering<__INST_HIER: none = "INSTANTIATE_WITH_INSTANCE_PATH">(%in0: i16, %in1: i16) -> (out0: i16) -# CHECK-NEXT: %0 = comb.add %in0, %in1 {{({sv.namehint = ".*"} )?}}: i16 -# CHECK-NEXT: hw.output %0 {{({sv.namehint = ".*"} )?}}: i16 -@unittestmodule(generate=True, run_passes=True, print_after_passes=True) +# CHECK-LABEL: hw.module @Lowering(in %in0 : i16, in %in1 : i16, out out0 : i16) +# CHECK-NEXT: %0 = hwarith.cast %in0 {sv.namehint = "in0"} : (i16) -> si16 +# CHECK-NEXT: %1 = hwarith.cast %in1 {sv.namehint = "in1"} : (i16) -> si16 +# CHECK-NEXT: %2 = hwarith.add %0, %1 {sv.namehint = "in0_plus_in1"} : (si16, si16) -> si17 +# CHECK-NEXT: %3 = hwarith.cast %2 {sv.namehint = "in0_plus_in1"} : (si17) -> i16 +# CHECK-NEXT: hw.output %3 : i16 +@unittestmodule(generate=True, run_passes=True, debug=True) class Lowering(Module): in0 = Input(types.i16) in1 = Input(types.i16) @@ -154,7 +165,7 @@ def construct(ports): # ----- -# CHECK-LABEL: msft.module @Constants {} (%uin: ui16, %sin: si16) attributes {fileName = "Constants.sv"} { +# CHECK-LABEL: hw.module @Constants(in %uin : ui16, in %sin : si16) # CHECK-NEXT: [[R0:%.+]] = hwarith.constant 1 : ui1 # CHECK-NEXT: [[R1:%.+]] = hwarith.add %uin, [[R0]] : (ui16, ui1) -> ui17 # CHECK-NEXT: [[R2:%.+]] = hwarith.constant -1 : si2 @@ -186,10 +197,7 @@ def construct(ports): # ----- -@unittestmodule(generate=True, - run_passes=True, - print_after_passes=True, - debug=True) +@unittestmodule(generate=True, run_passes=True, print_after_passes=True) class AddInts(Module): a = Input(UInt(32)) b = Input(UInt(32)) diff --git a/frontends/PyCDE/test/test_import_hw_modules.py b/frontends/PyCDE/test/test_import_hw_modules.py index 3155c5beab0e..60bc3c82b2b1 100644 --- a/frontends/PyCDE/test/test_import_hw_modules.py +++ b/frontends/PyCDE/test/test_import_hw_modules.py @@ -1,3 +1,4 @@ +# XFAIL: * # RUN: %PYTHON% %s %t | FileCheck %s from pycde.circt.ir import Module as IrModule @@ -46,10 +47,10 @@ def generate(ports): system = System([Top], output_directory=sys.argv[1]) system.generate() -# CHECK: msft.module @Top {} (%a: i1, %b: i1) -> (out0: i1, out1: i1) +# CHECK: hw.module @Top(%a: i1, %b: i1) -> (out0: i1, out1: i1) # CHECK: %add.out = hw.instance "add" @add(a: %a: i1, b: %b: i1) -> (out: i1) # CHECK: %and.out = hw.instance "and" @and(a: %a: i1, b: %b: i1) -> (out: i1) -# CHECK: msft.output %add.out, %and.out : i1, i1 +# CHECK: hw.output %add.out, %and.out : i1, i1 # CHECK: hw.module @add(%a: i1, %b: i1) -> (out: i1) # CHECK: %0 = comb.add %a, %b : i1 diff --git a/frontends/PyCDE/test/test_instances.py b/frontends/PyCDE/test/test_instances.py index aabf1e0e2b15..e450d0b706e3 100644 --- a/frontends/PyCDE/test/test_instances.py +++ b/frontends/PyCDE/test/test_instances.py @@ -59,9 +59,9 @@ def build(ports): t = pycde.System([Test], name="Test", output_directory=sys.argv[1]) t.generate(["construct"]) t.print() -# CHECK: )] outputs: []> +# CHECK: Test.print() -# CHECK: ), ('x', Bits<1>)] outputs: [('y', Bits<1>)]> +# CHECK: )] outputs: [('y', Bits<1>)]> UnParameterized.print() print(PhysLocation(PrimitiveType.DSP, 39, 25)) @@ -184,9 +184,11 @@ def place_inst(inst: Instance): foo_inst = t.get_instance(Test, "foo_inst") foo_inst["UnParameterized"]["Nothing"].place(PrimitiveType.DSP, 39, 90, 0) +print() print("=== Pre-pass mlir dump") t.print() +print() print("=== Running passes") t.run_passes() diff --git a/frontends/PyCDE/test/test_muxing.py b/frontends/PyCDE/test/test_muxing.py index 38fca2b69391..27228d74bdcd 100644 --- a/frontends/PyCDE/test/test_muxing.py +++ b/frontends/PyCDE/test/test_muxing.py @@ -4,8 +4,9 @@ from pycde.signals import Signal from pycde.constructs import Mux from pycde.testing import unittestmodule +from pycde.types import Bits -# CHECK-LABEL: msft.module @ComplexMux {} (%Clk: i1, %In: !hw.array<5xarray<4xi3>>, %Sel: i1) -> (Out: !hw.array<4xi3>, OutArr: !hw.array<2xarray<4xi3>>, OutInt: i1, OutSlice: !hw.array<3xarray<4xi3>>) +# CHECK-LABEL: hw.module @ComplexMux(in %Clk : !seq.clock, in %In : !hw.array<5xarray<4xi3>>, in %Sel : i1, out Out : !hw.array<4xi3>, out OutArr : !hw.array<2xarray<4xi3>>, out OutInt : i1, out OutSlice : !hw.array<3xarray<4xi3>>) # CHECK: %c3_i3 = hw.constant 3 : i3 # CHECK: %0 = hw.array_get %In[%c3_i3] {sv.namehint = "In__3"} : !hw.array<5xarray<4xi3>> # CHECK: %In__3__reg1 = seq.compreg sym @In__3__reg1 %0, %Clk : !hw.array<4xi3> @@ -27,7 +28,7 @@ # CHECK: [[R10:%.+]] = comb.concat %c0_i2_3, %Sel {sv.namehint = "Sel_padto_3"} : i2, i1 # CHECK: [[R11:%.+]] = comb.shru bin [[R9]], [[R10]] : i3 # CHECK: [[R12:%.+]] = comb.extract [[R11]] from 0 : (i3) -> i1 -# CHECK: msft.output [[R3]], [[R6]], [[R12]], [[R7]] : !hw.array<4xi3>, !hw.array<2xarray<4xi3>>, i1, !hw.array<3xarray<4xi3>> +# CHECK: hw.output [[R3]], [[R6]], [[R12]], [[R7]] : !hw.array<4xi3>, !hw.array<2xarray<4xi3>>, i1, !hw.array<3xarray<4xi3>> @unittestmodule() @@ -53,7 +54,7 @@ def create(ports): # ----- -# CHECK-LABEL: msft.module @Slicing {} (%In: !hw.array<5xarray<4xi8>>, %Sel8: i8, %Sel2: i2) -> (OutIntSlice: i2, OutArrSlice8: !hw.array<2xarray<4xi8>>, OutArrSlice2: !hw.array<2xarray<4xi8>>) +# CHECK-LABEL: hw.module @Slicing(in %In : !hw.array<5xarray<4xi8>>, in %Sel8 : i8, in %Sel2 : i2, out OutIntSlice : i2, out OutArrSlice8 : !hw.array<2xarray<4xi8>>, out OutArrSlice2 : !hw.array<2xarray<4xi8>>) # CHECK: [[R0:%.+]] = hw.array_get %In[%c0_i3] {sv.namehint = "In__0"} : !hw.array<5xarray<4xi8>> # CHECK: [[R1:%.+]] = hw.array_get %0[%c0_i2] {sv.namehint = "In__0__0"} : !hw.array<4xi8> # CHECK: [[R2:%.+]] = comb.concat %c0_i6, %Sel2 {sv.namehint = "Sel2_padto_8"} : i6, i2 @@ -63,7 +64,7 @@ def create(ports): # CHECK: [[R6:%.+]] = hw.array_slice %In[[[R5]]] : (!hw.array<5xarray<4xi8>>) -> !hw.array<2xarray<4xi8>> # CHECK: [[R7:%.+]] = comb.extract %Sel8 from 0 : (i8) -> i3 # CHECK: [[R8:%.+]] = hw.array_slice %In[[[R7]]] : (!hw.array<5xarray<4xi8>>) -> !hw.array<2xarray<4xi8>> -# CHECK: msft.output %4, %8, %6 : i2, !hw.array<2xarray<4xi8>>, !hw.array<2xarray<4xi8>> +# CHECK: hw.output %4, %8, %6 : i2, !hw.array<2xarray<4xi8>>, !hw.array<2xarray<4xi8>> @unittestmodule() @@ -82,3 +83,36 @@ def create(ports): ports.OutIntSlice = i.slice(ports.Sel2, 2) ports.OutArrSlice2 = ports.In.slice(ports.Sel2, 2) ports.OutArrSlice8 = ports.In.slice(ports.Sel8, 2) + + +# CHECK-LABEL: hw.module @SimpleMux2(in %op : i1, in %a : i32, in %b : i32, out out : i32) +# CHECK-NEXT: [[r0:%.+]] = comb.mux bin %op, %b, %a +# CHECK-NEXT: hw.output %0 : i32 +@unittestmodule() +class SimpleMux2(Module): + op = Input(Bits(1)) + a = Input(Bits(32)) + b = Input(Bits(32)) + out = Output(Bits(32)) + + @generator + def construct(self): + self.out = Mux(self.op, self.a, self.b) + + +# CHECK-LABEL: hw.module @SimpleMux4(in %op : i2, in %a : i32, in %b : i32, in %c : i32, in %d : i32, out out : i32) +# CHECK-NEXT: [[r0:%.+]] = hw.array_create %d, %c, %b, %a +# CHECK-NEXT: [[r1:%.+]] = hw.array_get [[r0]][%op] +# CHECK-NEXT: hw.output [[r1]] : i32 +@unittestmodule() +class SimpleMux4(Module): + op = Input(Bits(2)) + a = Input(Bits(32)) + b = Input(Bits(32)) + c = Input(Bits(32)) + d = Input(Bits(32)) + out = Output(Bits(32)) + + @generator + def construct(self): + self.out = Mux(self.op, self.a, self.b, self.c, self.d) diff --git a/frontends/PyCDE/test/test_ndarray.py b/frontends/PyCDE/test/test_ndarray.py index d68cb6e29dcf..97f08cc84f9f 100644 --- a/frontends/PyCDE/test/test_ndarray.py +++ b/frontends/PyCDE/test/test_ndarray.py @@ -163,7 +163,7 @@ def build(ports): # ----- -# CHECK-LABEL: msft.module @M1 {} () -> (out: !hw.array<3xarray<3xi32>>) attributes {fileName = "M1.sv"} { +# CHECK-LABEL: hw.module @M1(out out : !hw.array<3xarray<3xi32>>) # CHECK: %[[VAL_0:.*]] = hw.constant 0 : i32 # CHECK: %[[VAL_1:.*]] = hw.constant 1 : i32 # CHECK: %[[VAL_2:.*]] = hw.constant 2 : i32 @@ -180,7 +180,7 @@ def build(ports): # CHECK: %[[VAL_13:.*]] = sv.wire : !hw.inout>> # CHECK: sv.assign %[[VAL_13]], %[[VAL_12]] : !hw.array<3xarray<3xi32>> # CHECK: %[[VAL_14:.*]] = sv.read_inout %[[VAL_13]] : !hw.inout>> -# CHECK: msft.output %[[VAL_14]] : !hw.array<3xarray<3xi32>> +# CHECK: hw.output %[[VAL_14]] : !hw.array<3xarray<3xi32>> # CHECK: } diff --git a/frontends/PyCDE/test/test_polynomial.py b/frontends/PyCDE/test/test_polynomial.py index e57957d717c1..7c38c92a5133 100755 --- a/frontends/PyCDE/test/test_polynomial.py +++ b/frontends/PyCDE/test/test_polynomial.py @@ -66,14 +66,14 @@ def __init__(self, coefficients, **inputs): @modparams -def ExternWithParams(a, b): +def ExternWithParams(A: str, B: int): typedef1 = types.struct({"a": types.i1}, "exTypedef") class M(Module): module_name = "parameterized_extern" ignored_input = Input(types.i1) - used_input = Input(types.i4) + used_input = Input(types.int(B)) @property def instance_name(self): @@ -105,7 +105,7 @@ def construct(self): CoolPolynomialCompute([4, 42], x=23) w1 = Wire(types.i4) - m = ExternWithParams({"foo": "bar"}, 3)(ignored_input=None, used_input=w1) + m = ExternWithParams("foo", 4)(ignored_input=None, used_input=w1) m.name = "pexternInst" w1.assign(0) @@ -115,43 +115,25 @@ def construct(self): poly = pycde.System([PolynomialSystem], name="PolynomialSystem", output_directory=sys.argv[1]) -poly.print() - -print("Generating 1...") -poly.generate(iters=1) -print("Printing...") -poly.print() -# CHECK-LABEL: msft.module @PolynomialSystem {} () -> (y: i32) attributes {fileName = "PolynomialSystem.sv"} { -# CHECK: %example.y = msft.instance @example @PolyComputeForCoeff__62__42__6_(%c23_i32) {msft.appid = #msft.appid<"poly"[0]>} : (i32) -> i32 -# CHECK: %example2.y = msft.instance @example2 @PolyComputeForCoeff__62__42__6_(%example.y) : (i32) -> i32 -# CHECK: %example2_1.y = msft.instance @example2_1 @PolyComputeForCoeff__1__2__3__4__5_(%example.y) : (i32) -> i32 -# CHECK: %CoolPolynomialCompute.y = msft.instance @CoolPolynomialCompute @supercooldevice(%{{.+}}) : (i32) -> i32 -# CHECK: [[R0:%.+]] = hw.bitcast %false : (i1) -> i1 -# CHECK: msft.instance @singleton @parameterized_extern([[R0]], %c0_i4) : (i1, i4) -> () -# CHECK: %c0_i4 = hw.constant 0 : i4 -# CHECK: msft.output %example.y : i32 -# CHECK: } -# CHECK: msft.module @PolyComputeForCoeff__62__42__6_ {coefficients = {coeff = [62, 42, 6]}} (%x: i32) -> (y: i32) -# CHECK: msft.module @PolyComputeForCoeff__1__2__3__4__5_ {coefficients = {coeff = [1, 2, 3, 4, 5]}} (%x: i32) -> (y: i32) -# CHECK: msft.module.extern @supercooldevice(%x: i32) -> (y: i32) attributes {verilogName = "supercooldevice"} -# CHECK: msft.module.extern @parameterized_extern(%ignored_input: i1, %used_input: i4) attributes {verilogName = "parameterized_extern"} +# TODO: before generating all the modules, the IR doesn't verify since the +# hw.instances don't resolve. Fix this. +# poly.print() print("Generating rest...") poly.generate() -poly.print() - -print("=== Post-generate IR...") poly.run_passes() + +print("=== Final IR...") poly.print() -# CHECK-LABEL: === Post-generate IR... +# CHECK-LABEL: === Final IR... # CHECK: hw.module @PolynomialSystem -# CHECK: %[[EXAMPLE_Y:.+]] = hw.instance "example" sym @example @PolyComputeForCoeff__62__42__6_<__INST_HIER: none = #hw.param.expr.str.concat<#hw.param.decl.ref<"__INST_HIER">, ".example">>(x: %c23_i32: i32) -> (y: i32) -# CHECK: %example2.y = hw.instance "example2" sym @example2 @PolyComputeForCoeff__62__42__6_<__INST_HIER: none = #hw.param.expr.str.concat<#hw.param.decl.ref<"__INST_HIER">, ".example2">>(x: %[[EXAMPLE_Y]]: i32) -> (y: i32) -# CHECK: hw.instance "example2_1" sym @example2_1 @PolyComputeForCoeff__1__2__3__4__5_<__INST_HIER: none = #hw.param.expr.str.concat<#hw.param.decl.ref<"__INST_HIER">, ".example2_1">>(x: %[[EXAMPLE_Y]]: i32) +# CHECK: %[[EXAMPLE_Y:.+]] = hw.instance "example" sym @example @PolyComputeForCoeff__62__42__6_(x: %c23_i32: i32) -> (y: i32) +# CHECK: %example2.y = hw.instance "example2" sym @example2 @PolyComputeForCoeff__62__42__6_(x: %[[EXAMPLE_Y]]: i32) -> (y: i32) +# CHECK: hw.instance "example2_1" sym @example2_1 @PolyComputeForCoeff__1__2__3__4__5_(x: %[[EXAMPLE_Y]]: i32) # CHECK: %CoolPolynomialCompute.y = hw.instance "CoolPolynomialCompute" sym @CoolPolynomialCompute @supercooldevice(x: %c23_i32{{.*}}: i32) -> (y: i32) -# CHECK-LABEL: hw.module @PolyComputeForCoeff__62__42__6_<__INST_HIER: none = "INSTANTIATE_WITH_INSTANCE_PATH">(%x: i32) -> (y: i32) -# CHECK-LABEL: hw.module @PolyComputeForCoeff__1__2__3__4__5_<__INST_HIER: none = "INSTANTIATE_WITH_INSTANCE_PATH">(%x: i32) +# CHECK-LABEL: hw.module @PolyComputeForCoeff__62__42__6_(in %x : i32, out y : i32) +# CHECK-LABEL: hw.module @PolyComputeForCoeff__1__2__3__4__5_(in %x : i32, out y : i32) # CHECK-NOT: hw.module @pycde.PolynomialCompute poly.emit_outputs() diff --git a/frontends/PyCDE/test/test_pycde_types.py b/frontends/PyCDE/test/test_pycde_types.py index 03af4b762960..122040cec9dc 100644 --- a/frontends/PyCDE/test/test_pycde_types.py +++ b/frontends/PyCDE/test/test_pycde_types.py @@ -1,7 +1,7 @@ # RUN: %PYTHON% %s | FileCheck %s from pycde import dim, types, Input, Output, generator, System, Module -from pycde.types import bit, Bits, StructType, TypeAlias, UInt +from pycde.types import bit, Bits, List, StructType, TypeAlias, UInt from pycde.testing import unittestmodule from pycde.signals import Struct, UIntSignal @@ -30,6 +30,10 @@ dim_alias = dim(1, 8, name="myname5") +# CHECK: List> +i5list = List(Bits(5)) +print(i5list) + class Dummy(Module): pass @@ -59,7 +63,7 @@ def get_b_plus1(self) -> UIntSignal: print(ExStruct) -# CHECK-LABEL: msft.module @TestStruct {} (%inp1: !hw.typealias<@pycde::@ExStruct, !hw.struct>) -> (out1: ui33, out2: !hw.typealias<@pycde::@ExStruct, !hw.struct>) +# CHECK-LABEL: hw.module @TestStruct(in %inp1 : !hw.typealias<@pycde::@ExStruct, !hw.struct>, out out1 : ui33, out out2 : !hw.typealias<@pycde::@ExStruct, !hw.struct>) # CHECK-NEXT: %b = hw.struct_extract %inp1["b"] {sv.namehint = "inp1__b"} : !hw.typealias<@pycde::@ExStruct, !hw.struct> # CHECK-NEXT: [[r0:%.+]] = hwarith.constant 1 : ui1 # CHECK-NEXT: [[r1:%.+]] = hwarith.add %b, [[r0]] : (ui32, ui1) -> ui33 @@ -69,7 +73,7 @@ def get_b_plus1(self) -> UIntSignal: # CHECK-NEXT: [[r3:%.+]] = hwarith.add %b_0, [[r2]] : (ui32, ui1) -> ui33 # CHECK-NEXT: [[r4:%.+]] = hwarith.cast [[r3]] : (ui33) -> ui32 # CHECK-NEXT: [[r5:%.+]] = hw.struct_create (%a, [[r4]]) : !hw.typealias<@pycde::@ExStruct, !hw.struct> -# CHECK-NEXT: msft.output [[r1]], [[r5]] : ui33, !hw.typealias<@pycde::@ExStruct, !hw.struct> +# CHECK-NEXT: hw.output [[r1]], [[r5]] : ui33, !hw.typealias<@pycde::@ExStruct, !hw.struct> @unittestmodule() class TestStruct(Module): inp1 = Input(ExStruct) diff --git a/frontends/PyCDE/test/test_pycde_values.py b/frontends/PyCDE/test/test_pycde_values.py index 102730f65295..4ed76d5356b1 100644 --- a/frontends/PyCDE/test/test_pycde_values.py +++ b/frontends/PyCDE/test/test_pycde_values.py @@ -6,6 +6,41 @@ from pycde.testing import unittestmodule +# CHECK-LABEL: hw.module @BitsMod(in %inp : i5) +@unittestmodule() +class BitsMod(Module): + inp = Input(types.i5) + + @generator + def construct(ports): + # CHECK: %0 = comb.extract %inp from 0 {sv.namehint = "inp_0upto1"} : (i5) -> i1 + # CHECK: %1 = comb.extract %inp from 1 {sv.namehint = "inp_1upto2"} : (i5) -> i1 + # CHECK: %2 = comb.extract %inp from 2 {sv.namehint = "inp_2upto3"} : (i5) -> i1 + # CHECK: %3 = comb.extract %inp from 3 {sv.namehint = "inp_3upto4"} : (i5) -> i1 + # CHECK: %4 = comb.extract %inp from 4 {sv.namehint = "inp_4upto5"} : (i5) -> i1 + # CHECK: %5 = comb.and bin %0, %1, %2, %3, %4 : i1 + ports.inp.and_reduce() + + # CHECK: %6 = comb.extract %inp from 0 {sv.namehint = "inp_0upto1"} : (i5) -> i1 + # CHECK: %7 = comb.extract %inp from 1 {sv.namehint = "inp_1upto2"} : (i5) -> i1 + # CHECK: %8 = comb.extract %inp from 2 {sv.namehint = "inp_2upto3"} : (i5) -> i1 + # CHECK: %9 = comb.extract %inp from 3 {sv.namehint = "inp_3upto4"} : (i5) -> i1 + # CHECK: %10 = comb.extract %inp from 4 {sv.namehint = "inp_4upto5"} : (i5) -> i1 + # CHECK: %11 = comb.or bin %6, %7, %8, %9, %10 : i1 + ports.inp.or_reduce() + + # CHECK: %12 = comb.extract %inp from 0 {sv.namehint = "inp_0upto1"} : (i5) -> i1 + # CHECK: %13 = comb.extract %inp from 1 {sv.namehint = "inp_1upto2"} : (i5) -> i1 + # CHECK: %14 = comb.extract %inp from 2 {sv.namehint = "inp_2upto3"} : (i5) -> i1 + a, b, c = ports.inp[0], ports.inp[1], ports.inp[2] + + # CHECK: %15 = comb.or bin %12, %13, %14 : i1 + Or(a, b, c) + + # CHECK: %16 = comb.and bin %12, %13, %14 : i1 + And(a, b, c) + + @unittestmodule(SIZE=4) def MyModule(SIZE: int): @@ -29,9 +64,9 @@ def construct(mod): return Mod -# CHECK-LABEL: msft.module @Mod {} (%inp: !hw.array<5xi1>) +# CHECK-LABEL: hw.module @ArrayMod(in %inp : !hw.array<5xi1>) @unittestmodule() -class Mod(Module): +class ArrayMod(Module): inp = Input(dim(types.i1, 5)) @generator @@ -46,8 +81,8 @@ def construct(ports): # CHECK: %3 = hw.array_get %inp[%c3_i3] {sv.namehint = "inp__3"} : !hw.array<5xi1>, i3 # CHECK: %c-4_i3 = hw.constant -4 : i3 # CHECK: %4 = hw.array_get %inp[%c-4_i3] {sv.namehint = "inp__4"} : !hw.array<5xi1>, i3 - # CHECK: %5 = comb.or bin %0, %1, %2, %3, %4 : i1 - ports.inp.or_reduce() + # CHECK: %5 = comb.and bin %0, %1, %2, %3, %4 : i1 + ports.inp.and_reduce() # CHECK: %c0_i3_0 = hw.constant 0 : i3 # CHECK: %6 = hw.array_get %inp[%c0_i3_0] {sv.namehint = "inp__0"} : !hw.array<5xi1>, i3 @@ -55,10 +90,23 @@ def construct(ports): # CHECK: %7 = hw.array_get %inp[%c1_i3_1] {sv.namehint = "inp__1"} : !hw.array<5xi1>, i3 # CHECK: %c2_i3_2 = hw.constant 2 : i3 # CHECK: %8 = hw.array_get %inp[%c2_i3_2] {sv.namehint = "inp__2"} : !hw.array<5xi1>, i3 + # CHECK: %c3_i3_3 = hw.constant 3 : i3 + # CHECK: %9 = hw.array_get %inp[%c3_i3_3] {sv.namehint = "inp__3"} : !hw.array<5xi1>, i3 + # CHECK: %c-4_i3_4 = hw.constant -4 : i3 + # CHECK: %10 = hw.array_get %inp[%c-4_i3_4] {sv.namehint = "inp__4"} : !hw.array<5xi1>, i3 + # CHECK: %11 = comb.or bin %6, %7, %8, %9, %10 : i1 + ports.inp.or_reduce() + + # CHECK: %c0_i3_5 = hw.constant 0 : i3 + # CHECK: %12 = hw.array_get %inp[%c0_i3_5] {sv.namehint = "inp__0"} : !hw.array<5xi1>, i3 + # CHECK: %c1_i3_6 = hw.constant 1 : i3 + # CHECK: %13 = hw.array_get %inp[%c1_i3_6] {sv.namehint = "inp__1"} : !hw.array<5xi1>, i3 + # CHECK: %c2_i3_7 = hw.constant 2 : i3 + # CHECK: %14 = hw.array_get %inp[%c2_i3_7] {sv.namehint = "inp__2"} : !hw.array<5xi1>, i3 a, b, c = ports.inp[0], ports.inp[1], ports.inp[2] - # CHECK: %9 = comb.or bin %6, %7, %8 : i1 + # CHECK: %15 = comb.or bin %12, %13, %14 : i1 Or(a, b, c) - # CHECK: %10 = comb.and bin %6, %7, %8 : i1 + # CHECK: %16 = comb.and bin %12, %13, %14 : i1 And(a, b, c) diff --git a/frontends/PyCDE/test/test_slicing.py b/frontends/PyCDE/test/test_slicing.py index 779a08b2efcf..b85c7edfdd19 100644 --- a/frontends/PyCDE/test/test_slicing.py +++ b/frontends/PyCDE/test/test_slicing.py @@ -3,10 +3,10 @@ from pycde import System, Input, Output, generator, Module from pycde.types import dim -# CHECK-LABEL: msft.module @MyMod {} (%in_port: i8) -> (out0: i5, out1: i5) attributes {fileName = "MyMod.sv"} { +# CHECK-LABEL: hw.module @MyMod(in %in_port : i8, out out0 : i5, out out1 : i5) # CHECK: %0 = comb.extract %in_port from 3 {sv.namehint = "in_port_3upto8"} : (i8) -> i5 # CHECK: %1 = comb.extract %in_port from 0 {sv.namehint = "in_port_0upto5"} : (i8) -> i5 -# CHECK: msft.output %0, %1 : i5, i5 +# CHECK: hw.output %0, %1 : i5, i5 # CHECK: } diff --git a/frontends/PyCDE/test/test_syntactic_sugar.py b/frontends/PyCDE/test/test_syntactic_sugar.py index 86264dae3c65..9d5674360d13 100644 --- a/frontends/PyCDE/test/test_syntactic_sugar.py +++ b/frontends/PyCDE/test/test_syntactic_sugar.py @@ -1,9 +1,9 @@ # RUN: %PYTHON% %s | FileCheck %s -from pycde import (Output, Input, generator, types, dim, Module) +from pycde import (Clock, Output, Input, generator, types, dim, Module) from pycde.testing import unittestmodule -# CHECK-LABEL: msft.module @Top {} () attributes {fileName = "Top.sv"} { +# CHECK-LABEL: hw.module @Top() # CHECK: %c7_i12 = hw.constant 7 : i12 # CHECK: hw.struct_create (%c7_i12) : !hw.struct # CHECK: %c42_i8 = hw.constant 42 : i8 @@ -12,14 +12,14 @@ # CHECK: %c5_i8 = hw.constant 5 : i8 # CHECK: %c7_i12_0 = hw.constant 7 : i12 # CHECK: hw.struct_create (%c7_i12_0) : !hw.typealias<@pycde::@bar, !hw.struct> -# CHECK: %Taps.taps = msft.instance @Taps @Taps() : () -> !hw.array<3xi8> -# CHECK: msft.output -# CHECK-LABEL: msft.module @Taps {} () -> (taps: !hw.array<3xi8>) attributes {fileName = "Taps.sv"} { +# CHECK: %Taps.taps = hw.instance "Taps" sym @Taps @Taps() -> (taps: !hw.array<3xi8>) +# CHECK: hw.output +# CHECK-LABEL: hw.module @Taps(out taps : !hw.array<3xi8>) # CHECK: %c-53_i8 = hw.constant -53 : i8 # CHECK: %c100_i8 = hw.constant 100 : i8 # CHECK: %c23_i8 = hw.constant 23 : i8 # CHECK: [[R0:%.+]] = hw.array_create %c23_i8, %c100_i8, %c-53_i8 : i8 -# CHECK: msft.output [[R0]] : !hw.array<3xi8> +# CHECK: hw.output [[R0]] : !hw.array<3xi8> class Taps(Module): @@ -49,7 +49,7 @@ def build(_): # ----- -# CHECK: msft.module @ComplexPorts {} (%clk: i1, %data_in: !hw.array<3xi32>, %sel: i2, %struct_data_in: !hw.struct) -> (a: i32, b: i32, c: i32) +# CHECK: hw.module @ComplexPorts(in %clk : !seq.clock, in %data_in : !hw.array<3xi32>, in %sel : i2, in %struct_data_in : !hw.struct, out a : i32, out b : i32, out c : i32) # CHECK: %c0_i2 = hw.constant 0 : i2 # CHECK: [[REG0:%.+]] = hw.array_get %data_in[%c0_i2] {sv.namehint = "data_in__0"} : !hw.array<3xi32> # CHECK: [[REGR1:%data_in__0__reg1]] = seq.compreg sym @data_in__0__reg1 [[REG0]], %clk : i32 @@ -57,12 +57,12 @@ def build(_): # CHECK: [[REG1:%.+]] = hw.array_get %data_in[%sel] : !hw.array<3xi32> # CHECK: [[REG2:%.+]] = hw.struct_extract %struct_data_in["foo"] {sv.namehint = "struct_data_in__foo"} : !hw.struct # CHECK: [[REG3:%.+]] = comb.extract [[REG2]] from 0 {sv.namehint = "struct_data_in__foo_0upto32"} : (i36) -> i32 -# CHECK: msft.output [[REGR2]], [[REG1]], [[REG3]] : i32, i32, i32 +# CHECK: hw.output [[REGR2]], [[REG1]], [[REG3]] : i32, i32, i32 @unittestmodule() class ComplexPorts(Module): - clk = Input(types.i1) + clk = Clock() data_in = Input(dim(32, 3)) sel = Input(types.i2) struct_data_in = Input(types.struct({"foo": types.i36})) diff --git a/frontends/PyCDE/test/test_verilog_readablility.py b/frontends/PyCDE/test/test_verilog_readablility.py index 46800ddedc56..cd48a312df0b 100644 --- a/frontends/PyCDE/test/test_verilog_readablility.py +++ b/frontends/PyCDE/test/test_verilog_readablility.py @@ -1,12 +1,12 @@ # RUN: %PYTHON% %s %t | FileCheck %s -from pycde import (Output, Input, Module, generator, types, dim, System) +from pycde import (Clock, Output, Input, Module, generator, types, dim, System) import sys class WireNames(Module): - clk = Input(types.i1) + clk = Clock() data_in = Input(dim(32, 3)) sel = Input(types.i2) @@ -26,7 +26,7 @@ def build(self): sys.generate() sys.run_passes() sys.print() -# CHECK-LABEL: hw.module @WireNames<__INST_HIER: none = "INSTANTIATE_WITH_INSTANCE_PATH">(%clk: i1, %data_in: !hw.array<3xi32>, %sel: i2) -> (a: i32, b: i32) {{.*}} { +# CHECK-LABEL: hw.module @WireNames(in %clk : i1, in %data_in : !hw.array<3xi32>, in %sel : i2, out a : i32, out b : i32) # CHECK: %foo__reg1 = sv.reg sym @foo__reg1 : !hw.inout # CHECK: %foo__reg2 = sv.reg sym @foo__reg2 : !hw.inout # CHECK: %{{.+}} = sv.read_inout %foo__reg2 : !hw.inout diff --git a/frontends/PyCDE/test/test_xrt.py b/frontends/PyCDE/test/test_xrt.py index 6798d4488b7f..773554d0b160 100644 --- a/frontends/PyCDE/test/test_xrt.py +++ b/frontends/PyCDE/test/test_xrt.py @@ -42,8 +42,7 @@ def construct(ports): s.compile() s.package() -# TOP-LABEL: module top -# TOP: #(parameter __INST_HIER = "INSTANTIATE_WITH_INSTANCE_PATH") ( +# TOP-LABEL: module top( # TOP: input ap_clk, # TOP: ap_resetn, # TOP: s_axi_control_AWVALID, @@ -64,16 +63,14 @@ def construct(ports): # TOP: output s_axi_control_BVALID, # TOP: output [1:0] s_axi_control_BRESP -# TOP: XrtService #( -# TOP: ) XrtService ( +# TOP: XrtService XrtService ( # TOP: .clk (ap_clk), # TOP: .rst (~ap_resetn), # TOP: .axil_in (_GEN), # TOP: .axil_out (_XrtService_axil_out) # TOP: ); -# TOP: Main #( -# TOP: ) Main ( +# TOP: Main Main ( # TOP: .clk (ap_clk), # TOP: .rst (~ap_resetn) # TOP: ); diff --git a/include/circt-c/Conversion.h b/include/circt-c/Conversion.h new file mode 100644 index 000000000000..5482050a3332 --- /dev/null +++ b/include/circt-c/Conversion.h @@ -0,0 +1,12 @@ +//===-- circt-c/Dialect.h - C API for dialect conversions ---------*- C -*-===// +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_C_CONVERSION_H +#define CIRCT_C_CONVERSION_H + +#include "mlir-c/Support.h" + +#include "mlir/Conversion/Passes.capi.h.inc" + +#endif // CIRCT_C_CONVERSION_H diff --git a/include/circt-c/Dialect/CHIRRTL.h b/include/circt-c/Dialect/CHIRRTL.h new file mode 100644 index 000000000000..e0c76ae83a43 --- /dev/null +++ b/include/circt-c/Dialect/CHIRRTL.h @@ -0,0 +1,40 @@ +//===-- circt-c/Dialect/CHIRRTL.h - C API for CHIRRTL dialect -----*- C -*-===// +// +// This header declares the C interface for registering and accessing the +// CHIRRTL dialect. A dialect should be registered with a context to make it +// available to users of the context. These users must load the dialect +// before using any of its attributes, operations or types. Parser and pass +// manager can load registered dialects automatically. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_C_DIALECT_CHIRRTL_H +#define CIRCT_C_DIALECT_CHIRRTL_H + +#include "mlir-c/IR.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//===----------------------------------------------------------------------===// +// Dialect API. +//===----------------------------------------------------------------------===// + +MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(CHIRRTL, chirrtl); + +//===----------------------------------------------------------------------===// +// Type API. +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED MlirType chirrtlTypeGetCMemory(MlirContext ctx, + MlirType elementType, + uint64_t numElements); + +MLIR_CAPI_EXPORTED MlirType chirrtlTypeGetCMemoryPort(MlirContext ctx); + +#ifdef __cplusplus +} +#endif + +#endif // CIRCT_C_DIALECT_CHIRRTL_H diff --git a/include/circt-c/Dialect/ESI.h b/include/circt-c/Dialect/ESI.h index e0dcaa7f7dbc..ec257b4ef312 100644 --- a/include/circt-c/Dialect/ESI.h +++ b/include/circt-c/Dialect/ESI.h @@ -18,11 +18,7 @@ extern "C" { #endif MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(ESI, esi); -MLIR_CAPI_EXPORTED void registerESIPasses(); -MLIR_CAPI_EXPORTED void registerESITranslations(); - -MLIR_CAPI_EXPORTED MlirLogicalResult -circtESIExportCosimSchema(MlirModule, MlirStringCallback, void *userData); +MLIR_CAPI_EXPORTED void registerESIPasses(void); MLIR_CAPI_EXPORTED bool circtESITypeIsAChannelType(MlirType type); MLIR_CAPI_EXPORTED MlirType circtESIChannelTypeGet(MlirType inner, @@ -33,20 +29,89 @@ MLIR_CAPI_EXPORTED uint32_t circtESIChannelGetSignaling(MlirType channelType); MLIR_CAPI_EXPORTED bool circtESITypeIsAnAnyType(MlirType type); MLIR_CAPI_EXPORTED MlirType circtESIAnyTypeGet(MlirContext); -MLIR_CAPI_EXPORTED MlirOperation circtESIWrapModule(MlirOperation cModOp, - long numPorts, - const MlirStringRef *ports); +MLIR_CAPI_EXPORTED bool circtESITypeIsAListType(MlirType type); +MLIR_CAPI_EXPORTED MlirType circtESIListTypeGet(MlirType inner); +MLIR_CAPI_EXPORTED MlirType +circtESIListTypeGetElementType(MlirType channelType); MLIR_CAPI_EXPORTED void circtESIAppendMlirFile(MlirModule, MlirStringRef fileName); MLIR_CAPI_EXPORTED MlirOperation circtESILookup(MlirModule, MlirStringRef symbol); +//===----------------------------------------------------------------------===// +// Channel bundles +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE(modernize-use-using) +typedef struct { + MlirIdentifier name; + unsigned direction; + MlirType channelType; // MUST be ChannelType. +} CirctESIBundleTypeBundleChannel; + +MLIR_CAPI_EXPORTED bool circtESITypeIsABundleType(MlirType type); +MLIR_CAPI_EXPORTED MlirType circtESIBundleTypeGet( + MlirContext, size_t numChannels, + const CirctESIBundleTypeBundleChannel *channels, bool resettable); +MLIR_CAPI_EXPORTED bool circtESIBundleTypeGetResettable(MlirType bundle); +MLIR_CAPI_EXPORTED size_t circtESIBundleTypeGetNumChannels(MlirType bundle); +MLIR_CAPI_EXPORTED CirctESIBundleTypeBundleChannel +circtESIBundleTypeGetChannel(MlirType bundle, size_t idx); + +//===----------------------------------------------------------------------===// +// Services +//===----------------------------------------------------------------------===// + typedef MlirLogicalResult (*CirctESIServiceGeneratorFunc)( MlirOperation serviceImplementReqOp, MlirOperation declOp, void *userData); MLIR_CAPI_EXPORTED void circtESIRegisterGlobalServiceGenerator( MlirStringRef impl_type, CirctESIServiceGeneratorFunc, void *userData); +//===----------------------------------------------------------------------===// +// AppID +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED bool circtESIAttributeIsAnAppIDAttr(MlirAttribute); +MLIR_CAPI_EXPORTED +MlirAttribute circtESIAppIDAttrGet(MlirContext, MlirStringRef name, + uint64_t index); +MLIR_CAPI_EXPORTED MlirStringRef circtESIAppIDAttrGetName(MlirAttribute attr); +MLIR_CAPI_EXPORTED bool circtESIAppIDAttrGetIndex(MlirAttribute attr, + uint64_t *index); + +MLIR_CAPI_EXPORTED bool circtESIAttributeIsAnAppIDPathAttr(MlirAttribute); +MLIR_CAPI_EXPORTED +MlirAttribute circtESIAppIDAttrPathGet(MlirContext, MlirAttribute root, + intptr_t numElements, + MlirAttribute const *elements); +MLIR_CAPI_EXPORTED MlirAttribute +circtESIAppIDAttrPathGetRoot(MlirAttribute attr); +MLIR_CAPI_EXPORTED uint64_t +circtESIAppIDAttrPathGetNumComponents(MlirAttribute attr); +MLIR_CAPI_EXPORTED MlirAttribute +circtESIAppIDAttrPathGetComponent(MlirAttribute attr, uint64_t index); + +// NOLINTNEXTLINE(modernize-use-using) +typedef struct { + void *ptr; +} CirctESIAppIDIndex; + +/// Create an index of appids through which to do appid lookups efficiently. +MLIR_CAPI_EXPORTED CirctESIAppIDIndex circtESIAppIDIndexGet(MlirOperation root); + +/// Free an AppIDIndex. +MLIR_CAPI_EXPORTED void circtESIAppIDIndexFree(CirctESIAppIDIndex); + +MLIR_CAPI_EXPORTED MlirAttribute + circtESIAppIDIndexGetChildAppIDsOf(CirctESIAppIDIndex, MlirOperation); + +MLIR_CAPI_EXPORTED +MlirAttribute circtESIAppIDIndexGetAppIDPath(CirctESIAppIDIndex, + MlirOperation fromMod, + MlirAttribute appid, + MlirLocation loc); + #ifdef __cplusplus } #endif diff --git a/include/circt-c/Dialect/FIRRTL.h b/include/circt-c/Dialect/FIRRTL.h index 473f28b46801..72c36f1fee36 100644 --- a/include/circt-c/Dialect/FIRRTL.h +++ b/include/circt-c/Dialect/FIRRTL.h @@ -17,8 +17,106 @@ extern "C" { #endif +// NOLINTNEXTLINE(modernize-use-using) +typedef struct FIRRTLBundleField { + MlirIdentifier name; + bool isFlip; + MlirType type; +} FIRRTLBundleField; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FIRRTLConvention { + FIRRTL_CONVENTION_INTERNAL, + FIRRTL_CONVENTION_SCALARIZED, +} FIRRTLConvention; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FIRRTLPortDir { + FIRRTL_PORT_DIR_INPUT, + FIRRTL_PORT_DIR_OUTPUT, +} FIRRTLPortDir; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FIRRTLNameKind { + FIRRTL_NAME_KIND_DROPPABLE_NAME, + FIRRTL_NAME_KIND_INTERESTING_NAME, +} FIRRTLNameKind; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FIRRTLRUW { + FIRRTL_RUW_UNDEFINED, + FIRRTL_RUW_OLD, + FIRRTL_RUW_NEW, +} FIRRTLRUW; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FIRRTLMemDir { + FIRRTL_MEM_DIR_INFER, + FIRRTL_MEM_DIR_READ, + FIRRTL_MEM_DIR_WRITE, + FIRRTL_MEM_DIR_READ_WRITE, +} FIRRTLMemDir; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FIRRTLEventControl { + FIRRTL_EVENT_CONTROL_AT_POS_EDGE, + FIRRTL_EVENT_CONTROL_AT_NEG_EDGE, + FIRRTL_EVENT_CONTROL_AT_EDGE, +} FIRRTLEventControl; + +//===----------------------------------------------------------------------===// +// Dialect API. +//===----------------------------------------------------------------------===// + MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(FIRRTL, firrtl); +//===----------------------------------------------------------------------===// +// Type API. +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED MlirType firrtlTypeGetUInt(MlirContext ctx, int32_t width); +MLIR_CAPI_EXPORTED MlirType firrtlTypeGetSInt(MlirContext ctx, int32_t width); +MLIR_CAPI_EXPORTED MlirType firrtlTypeGetClock(MlirContext ctx); +MLIR_CAPI_EXPORTED MlirType firrtlTypeGetReset(MlirContext ctx); +MLIR_CAPI_EXPORTED MlirType firrtlTypeGetAsyncReset(MlirContext ctx); +MLIR_CAPI_EXPORTED MlirType firrtlTypeGetAnalog(MlirContext ctx, int32_t width); +MLIR_CAPI_EXPORTED MlirType firrtlTypeGetVector(MlirContext ctx, + MlirType element, size_t count); +MLIR_CAPI_EXPORTED MlirType firrtlTypeGetBundle( + MlirContext ctx, size_t count, const FIRRTLBundleField *fields); + +//===----------------------------------------------------------------------===// +// Attribute API. +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED MlirAttribute +firrtlAttrGetConvention(MlirContext ctx, FIRRTLConvention convention); + +MLIR_CAPI_EXPORTED MlirAttribute +firrtlAttrGetPortDirs(MlirContext ctx, size_t count, const FIRRTLPortDir *dirs); + +MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetParamDecl(MlirContext ctx, + MlirIdentifier name, + MlirType type, + MlirAttribute value); + +MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetNameKind(MlirContext ctx, + FIRRTLNameKind nameKind); + +MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetRUW(MlirContext ctx, + FIRRTLRUW ruw); + +MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetMemInit(MlirContext ctx, + MlirIdentifier filename, + bool isBinary, + bool isInline); + +MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetMemDir(MlirContext ctx, + FIRRTLMemDir dir); + +MLIR_CAPI_EXPORTED MlirAttribute +firrtlAttrGetEventControl(MlirContext ctx, FIRRTLEventControl eventControl); + #ifdef __cplusplus } #endif diff --git a/include/circt-c/Dialect/FSM.h b/include/circt-c/Dialect/FSM.h index 772c6508bde2..c8979fd98a28 100644 --- a/include/circt-c/Dialect/FSM.h +++ b/include/circt-c/Dialect/FSM.h @@ -18,7 +18,7 @@ extern "C" { #endif MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(FSM, fsm); -MLIR_CAPI_EXPORTED void registerFSMPasses(); +MLIR_CAPI_EXPORTED void registerFSMPasses(void); #ifdef __cplusplus } diff --git a/include/circt-c/Dialect/HW.h b/include/circt-c/Dialect/HW.h index b1b72655f848..033eeee3d279 100644 --- a/include/circt-c/Dialect/HW.h +++ b/include/circt-c/Dialect/HW.h @@ -29,6 +29,16 @@ struct HWStructFieldInfo { }; typedef struct HWStructFieldInfo HWStructFieldInfo; +enum HWModulePortDirection { Input, Output, InOut }; +typedef enum HWModulePortDirection HWModulePortDirection; + +struct HWModulePort { + MlirAttribute name; + MlirType type; + HWModulePortDirection dir; +}; +typedef struct HWModulePort HWModulePort; + //===----------------------------------------------------------------------===// // Dialect API. //===----------------------------------------------------------------------===// @@ -58,6 +68,9 @@ MLIR_CAPI_EXPORTED bool hwTypeIsAArrayType(MlirType); /// If the type is an HW inout. MLIR_CAPI_EXPORTED bool hwTypeIsAInOut(MlirType type); +/// If the type is an HW module type. +MLIR_CAPI_EXPORTED bool hwTypeIsAModuleType(MlirType type); + /// If the type is an HW struct. MLIR_CAPI_EXPORTED bool hwTypeIsAStructType(MlirType); @@ -82,6 +95,31 @@ MLIR_CAPI_EXPORTED MlirType hwInOutTypeGet(MlirType element); /// Returns the element type of an inout type. MLIR_CAPI_EXPORTED MlirType hwInOutTypeGetElementType(MlirType); +/// Creates an HW module type. +MLIR_CAPI_EXPORTED MlirType hwModuleTypeGet(MlirContext ctx, intptr_t numPorts, + HWModulePort const *ports); + +/// Get an HW module type's number of inputs. +MLIR_CAPI_EXPORTED intptr_t hwModuleTypeGetNumInputs(MlirType type); + +/// Get an HW module type's input type at a specific index. +MLIR_CAPI_EXPORTED MlirType hwModuleTypeGetInputType(MlirType type, + intptr_t index); + +/// Get an HW module type's input name at a specific index. +MLIR_CAPI_EXPORTED MlirStringRef hwModuleTypeGetInputName(MlirType type, + intptr_t index); + +/// Get an HW module type's number of outputs. +MLIR_CAPI_EXPORTED intptr_t hwModuleTypeGetNumOutputs(MlirType type); + +/// Get an HW module type's output type at a specific index. +MLIR_CAPI_EXPORTED MlirType hwModuleTypeGetOutputType(MlirType type, + intptr_t index); + +/// Get an HW module type's output name at a specific index. +MLIR_CAPI_EXPORTED MlirStringRef hwModuleTypeGetOutputName(MlirType type, + intptr_t index); /// Creates an HW struct type in the context associated with the elements. MLIR_CAPI_EXPORTED MlirType hwStructTypeGet(MlirContext ctx, intptr_t numElements, @@ -94,8 +132,12 @@ MLIR_CAPI_EXPORTED MlirType hwParamIntTypeGet(MlirAttribute parameter); MLIR_CAPI_EXPORTED MlirAttribute hwParamIntTypeGetWidthAttr(MlirType); +MLIR_CAPI_EXPORTED MlirAttribute +hwStructTypeGetFieldIndex(MlirType structType, MlirStringRef fieldName); + MLIR_CAPI_EXPORTED HWStructFieldInfo hwStructTypeGetFieldNum(MlirType structType, unsigned idx); + MLIR_CAPI_EXPORTED intptr_t hwStructTypeGetNumFields(MlirType structType); MLIR_CAPI_EXPORTED MlirType hwTypeAliasTypeGet(MlirStringRef scope, @@ -114,15 +156,16 @@ MLIR_CAPI_EXPORTED MlirStringRef hwTypeAliasTypeGetScope(MlirType typeAlias); // Attribute API. //===----------------------------------------------------------------------===// +MLIR_CAPI_EXPORTED bool hwAttrIsAInnerSymAttr(MlirAttribute); +MLIR_CAPI_EXPORTED MlirAttribute hwInnerSymAttrGet(MlirAttribute symName); +MLIR_CAPI_EXPORTED MlirAttribute hwInnerSymAttrGetSymName(MlirAttribute); + MLIR_CAPI_EXPORTED bool hwAttrIsAInnerRefAttr(MlirAttribute); MLIR_CAPI_EXPORTED MlirAttribute hwInnerRefAttrGet(MlirAttribute moduleName, MlirAttribute innerSym); MLIR_CAPI_EXPORTED MlirAttribute hwInnerRefAttrGetName(MlirAttribute); MLIR_CAPI_EXPORTED MlirAttribute hwInnerRefAttrGetModule(MlirAttribute); -MLIR_CAPI_EXPORTED bool hwAttrIsAGlobalRefAttr(MlirAttribute); -MLIR_CAPI_EXPORTED MlirAttribute hwGlobalRefAttrGet(MlirAttribute symName); - MLIR_CAPI_EXPORTED bool hwAttrIsAParamDeclAttr(MlirAttribute); MLIR_CAPI_EXPORTED MlirAttribute hwParamDeclAttrGet(MlirStringRef name, MlirType type, @@ -140,6 +183,10 @@ MLIR_CAPI_EXPORTED MlirType hwParamDeclRefAttrGetType(MlirAttribute decl); MLIR_CAPI_EXPORTED bool hwAttrIsAParamVerbatimAttr(MlirAttribute); MLIR_CAPI_EXPORTED MlirAttribute hwParamVerbatimAttrGet(MlirAttribute text); +MLIR_CAPI_EXPORTED bool hwAttrIsAOutputFileAttr(MlirAttribute); +MLIR_CAPI_EXPORTED MlirAttribute hwOutputFileGetFromFileName( + MlirAttribute text, bool excludeFromFileList, bool includeReplicatedOp); + #ifdef __cplusplus } #endif diff --git a/include/circt-c/Dialect/HWArith.h b/include/circt-c/Dialect/HWArith.h index 37bd0f546371..8166582bfc4e 100644 --- a/include/circt-c/Dialect/HWArith.h +++ b/include/circt-c/Dialect/HWArith.h @@ -18,7 +18,7 @@ extern "C" { #endif MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(HWArith, hwarith); -MLIR_CAPI_EXPORTED void registerHWArithPasses(); +MLIR_CAPI_EXPORTED void registerHWArithPasses(void); #ifdef __cplusplus } diff --git a/include/circt-c/Dialect/Handshake.h b/include/circt-c/Dialect/Handshake.h index 7771bc70c709..82710bce5a16 100644 --- a/include/circt-c/Dialect/Handshake.h +++ b/include/circt-c/Dialect/Handshake.h @@ -12,7 +12,7 @@ extern "C" { #endif MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(Handshake, handshake); -MLIR_CAPI_EXPORTED void registerHandshakePasses(); +MLIR_CAPI_EXPORTED void registerHandshakePasses(void); #ifdef __cplusplus } diff --git a/include/circt-c/Dialect/LTL.h b/include/circt-c/Dialect/LTL.h new file mode 100644 index 000000000000..a923fb8cf1b1 --- /dev/null +++ b/include/circt-c/Dialect/LTL.h @@ -0,0 +1,20 @@ +//===-- circt-c/Ltl.h - C API for Ltl dialect -----------------*- C -*-===// +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_C_DIALECT_LTL_H +#define CIRCT_C_DIALECT_LTL_H + +#include "mlir-c/IR.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(LTL, ltl); + +#ifdef __cplusplus +} +#endif + +#endif // CIRCT_C_DIALECT_LTL_H diff --git a/include/circt-c/Dialect/MSFT.h b/include/circt-c/Dialect/MSFT.h index f69b2ebafa74..3763696ae89f 100644 --- a/include/circt-c/Dialect/MSFT.h +++ b/include/circt-c/Dialect/MSFT.h @@ -11,7 +11,6 @@ #ifndef CIRCT_C_DIALECT_MSFT_H #define CIRCT_C_DIALECT_MSFT_H -#include "circt/Dialect/MSFT/MSFTDialect.h" #include "mlir-c/IR.h" #include "mlir-c/Pass.h" @@ -21,7 +20,7 @@ extern "C" { MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(MSFT, msft); -MLIR_CAPI_EXPORTED void mlirMSFTRegisterPasses(); +MLIR_CAPI_EXPORTED void mlirMSFTRegisterPasses(void); // Values represented in `MSFT.td`. typedef int32_t CirctMSFTPrimitiveType; @@ -63,13 +62,6 @@ intptr_t circtMSFTLocationVectorAttrGetNumElements(MlirAttribute); MLIR_CAPI_EXPORTED MlirAttribute circtMSFTLocationVectorAttrGetElement(MlirAttribute attr, intptr_t pos); -MLIR_CAPI_EXPORTED bool circtMSFTAttributeIsAnAppIDAttr(MlirAttribute); -MLIR_CAPI_EXPORTED -MlirAttribute circtMSFTAppIDAttrGet(MlirContext, MlirStringRef name, - uint64_t index); -MLIR_CAPI_EXPORTED MlirStringRef circtMSFTAppIDAttrGetName(MlirAttribute attr); -MLIR_CAPI_EXPORTED uint64_t circtMSFTAppIDAttrGetIndex(MlirAttribute attr); - //===----------------------------------------------------------------------===// // PrimitiveDB. //===----------------------------------------------------------------------===// @@ -96,14 +88,13 @@ typedef struct { enum CirctMSFTDirection { NONE = 0, ASC = 1, DESC = 2 }; typedef struct { - CirctMSFTDirection columns; - CirctMSFTDirection rows; + enum CirctMSFTDirection columns; + enum CirctMSFTDirection rows; } CirctMSFTWalkOrder; MLIR_CAPI_EXPORTED CirctMSFTPlacementDB circtMSFTCreatePlacementDB(MlirModule top, CirctMSFTPrimitiveDB seed); MLIR_CAPI_EXPORTED void circtMSFTDeletePlacementDB(CirctMSFTPlacementDB self); -MLIR_CAPI_EXPORTED MLIR_CAPI_EXPORTED MlirOperation circtMSFTPlacementDBPlace( CirctMSFTPlacementDB, MlirOperation inst, MlirAttribute loc, MlirStringRef subpath, MlirLocation srcLoc); diff --git a/include/circt-c/Dialect/OM.h b/include/circt-c/Dialect/OM.h new file mode 100644 index 000000000000..4879e29ea5de --- /dev/null +++ b/include/circt-c/Dialect/OM.h @@ -0,0 +1,267 @@ +//===-- circt-c/Dialect/OM.h - C API for OM dialect -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This header declares the C interface for registering and accessing the +// OM dialect. A dialect should be registered with a context to make it +// available to users of the context. These users must load the dialect +// before using any of its attributes, operations or types. Parser and pass +// manager can load registered dialects automatically. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_C_DIALECT_OM_H +#define CIRCT_C_DIALECT_OM_H + +#include "mlir-c/IR.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//===----------------------------------------------------------------------===// +// Dialect API. +//===----------------------------------------------------------------------===// + +MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(OM, om); + +//===----------------------------------------------------------------------===// +// Type API. +//===----------------------------------------------------------------------===// + +/// Is the Type a ClassType. +MLIR_CAPI_EXPORTED bool omTypeIsAClassType(MlirType type); + +/// Get the TypeID for a ClassType. +MLIR_CAPI_EXPORTED MlirTypeID omClassTypeGetTypeID(void); + +/// Get the name for a ClassType. +MLIR_CAPI_EXPORTED MlirIdentifier omClassTypeGetName(MlirType type); + +/// Is the Type a FrozenBasePathType. +MLIR_CAPI_EXPORTED bool omTypeIsAFrozenBasePathType(MlirType type); + +/// Get the TypeID for a FrozenBasePathType. +MLIR_CAPI_EXPORTED MlirTypeID omFrozenBasePathTypeGetTypeID(void); + +/// Is the Type a FrozenPathType. +MLIR_CAPI_EXPORTED bool omTypeIsAFrozenPathType(MlirType type); + +/// Get the TypeID for a FrozenPathType. +MLIR_CAPI_EXPORTED MlirTypeID omFrozenPathTypeGetTypeID(void); + +/// Is the Type a MapType. +MLIR_CAPI_EXPORTED bool omTypeIsAMapType(MlirType type); + +// Return a key type of a MapType. +MLIR_CAPI_EXPORTED MlirType omMapTypeGetKeyType(MlirType type); + +/// Is the Type a StringType. +MLIR_CAPI_EXPORTED bool omTypeIsAStringType(MlirType type); + +//===----------------------------------------------------------------------===// +// Evaluator data structures. +//===----------------------------------------------------------------------===// + +/// A value type for use in C APIs that just wraps a pointer to an Evaluator. +/// This is in line with the usual MLIR DEFINE_C_API_STRUCT. +struct OMEvaluator { + void *ptr; +}; + +// clang-tidy doesn't respect extern "C". +// see https://github.com/llvm/llvm-project/issues/35272. +// NOLINTNEXTLINE(modernize-use-using) +typedef struct OMEvaluator OMEvaluator; + +/// A value type for use in C APIs that just wraps a pointer to an Object. +/// This is in line with the usual MLIR DEFINE_C_API_STRUCT. +struct OMEvaluatorValue { + void *ptr; +}; + +// clang-tidy doesn't respect extern "C". +// see https://github.com/llvm/llvm-project/issues/35272. +// NOLINTNEXTLINE(modernize-use-using) +typedef struct OMEvaluatorValue OMEvaluatorValue; + +//===----------------------------------------------------------------------===// +// Evaluator API. +//===----------------------------------------------------------------------===// + +/// Construct an Evaluator with an IR module. +MLIR_CAPI_EXPORTED OMEvaluator omEvaluatorNew(MlirModule mod); + +/// Use the Evaluator to Instantiate an Object from its class name and actual +/// parameters. +MLIR_CAPI_EXPORTED OMEvaluatorValue +omEvaluatorInstantiate(OMEvaluator evaluator, MlirAttribute className, + intptr_t nActualParams, OMEvaluatorValue *actualParams); + +/// Get the Module the Evaluator is built from. +MLIR_CAPI_EXPORTED MlirModule omEvaluatorGetModule(OMEvaluator evaluator); + +//===----------------------------------------------------------------------===// +// Object API. +//===----------------------------------------------------------------------===// + +/// Query if the Object is null. +MLIR_CAPI_EXPORTED bool omEvaluatorObjectIsNull(OMEvaluatorValue object); + +/// Get the Type from an Object, which will be a ClassType. +MLIR_CAPI_EXPORTED MlirType omEvaluatorObjectGetType(OMEvaluatorValue object); + +/// Get a field from an Object, which must contain a field of that name. +MLIR_CAPI_EXPORTED OMEvaluatorValue +omEvaluatorObjectGetField(OMEvaluatorValue object, MlirAttribute name); + +/// Get the object hash. +MLIR_CAPI_EXPORTED unsigned omEvaluatorObjectGetHash(OMEvaluatorValue object); + +/// Check equality of two objects. +MLIR_CAPI_EXPORTED bool omEvaluatorObjectIsEq(OMEvaluatorValue object, + OMEvaluatorValue other); + +/// Get all the field names from an Object, can be empty if object has no +/// fields. +MLIR_CAPI_EXPORTED MlirAttribute +omEvaluatorObjectGetFieldNames(OMEvaluatorValue object); + +//===----------------------------------------------------------------------===// +// EvaluatorValue API. +//===----------------------------------------------------------------------===// + +// Get a context from an EvaluatorValue. +MLIR_CAPI_EXPORTED MlirContext +omEvaluatorValueGetContext(OMEvaluatorValue evaluatorValue); + +// Get Location from an EvaluatorValue. +MLIR_CAPI_EXPORTED MlirLocation +omEvaluatorValueGetLoc(OMEvaluatorValue evaluatorValue); + +// Query if the EvaluatorValue is null. +MLIR_CAPI_EXPORTED bool omEvaluatorValueIsNull(OMEvaluatorValue evaluatorValue); + +/// Query if the EvaluatorValue is an Object. +MLIR_CAPI_EXPORTED bool +omEvaluatorValueIsAObject(OMEvaluatorValue evaluatorValue); + +/// Query if the EvaluatorValue is a Primitive. +MLIR_CAPI_EXPORTED bool +omEvaluatorValueIsAPrimitive(OMEvaluatorValue evaluatorValue); + +/// Get the Primitive from an EvaluatorValue, which must contain a Primitive. +MLIR_CAPI_EXPORTED MlirAttribute +omEvaluatorValueGetPrimitive(OMEvaluatorValue evaluatorValue); + +/// Get the EvaluatorValue from a Primitive value. +MLIR_CAPI_EXPORTED OMEvaluatorValue +omEvaluatorValueFromPrimitive(MlirAttribute primitive); + +/// Query if the EvaluatorValue is an Object. +MLIR_CAPI_EXPORTED bool +omEvaluatorValueIsAList(OMEvaluatorValue evaluatorValue); + +/// Get the length of the list. +MLIR_CAPI_EXPORTED intptr_t +omEvaluatorListGetNumElements(OMEvaluatorValue evaluatorValue); + +/// Get an element of the list. +MLIR_CAPI_EXPORTED OMEvaluatorValue +omEvaluatorListGetElement(OMEvaluatorValue evaluatorValue, intptr_t pos); + +/// Query if the EvaluatorValue is a Tuple. +MLIR_CAPI_EXPORTED bool +omEvaluatorValueIsATuple(OMEvaluatorValue evaluatorValue); + +/// Get the size of the tuple. +MLIR_CAPI_EXPORTED intptr_t +omEvaluatorTupleGetNumElements(OMEvaluatorValue evaluatorValue); + +/// Get an element of the tuple. +MLIR_CAPI_EXPORTED OMEvaluatorValue +omEvaluatorTupleGetElement(OMEvaluatorValue evaluatorValue, intptr_t pos); + +/// Get an element of the map. +MLIR_CAPI_EXPORTED OMEvaluatorValue +omEvaluatorMapGetElement(OMEvaluatorValue evaluatorValue, MlirAttribute attr); + +MLIR_CAPI_EXPORTED MlirAttribute omEvaluatorMapGetKeys(OMEvaluatorValue object); + +/// Query if the EvaluatorValue is a Map. +MLIR_CAPI_EXPORTED bool omEvaluatorValueIsAMap(OMEvaluatorValue evaluatorValue); + +/// Get the Type from a Map, which will be a MapType. +MLIR_CAPI_EXPORTED MlirType +omEvaluatorMapGetType(OMEvaluatorValue evaluatorValue); + +/// Query if the EvaluatorValue is a BasePath. +MLIR_CAPI_EXPORTED bool +omEvaluatorValueIsABasePath(OMEvaluatorValue evaluatorValue); + +/// Create an empty BasePath. +MLIR_CAPI_EXPORTED OMEvaluatorValue +omEvaluatorBasePathGetEmpty(MlirContext context); + +/// Query if the EvaluatorValue is a Path. +MLIR_CAPI_EXPORTED bool +omEvaluatorValueIsAPath(OMEvaluatorValue evaluatorValue); + +/// Get a string representation of a Path. +MLIR_CAPI_EXPORTED MlirAttribute +omEvaluatorPathGetAsString(OMEvaluatorValue evaluatorValue); + +//===----------------------------------------------------------------------===// +// ReferenceAttr API +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED bool omAttrIsAReferenceAttr(MlirAttribute attr); + +MLIR_CAPI_EXPORTED MlirAttribute omReferenceAttrGetInnerRef(MlirAttribute attr); + +//===----------------------------------------------------------------------===// +// IntegerAttr API +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED bool omAttrIsAIntegerAttr(MlirAttribute attr); + +/// Given an om::IntegerAttr, return the mlir::IntegerAttr. +MLIR_CAPI_EXPORTED MlirAttribute omIntegerAttrGetInt(MlirAttribute attr); + +/// Get an om::IntegerAttr from mlir::IntegerAttr. +MLIR_CAPI_EXPORTED MlirAttribute omIntegerAttrGet(MlirAttribute attr); + +//===----------------------------------------------------------------------===// +// ListAttr API +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED bool omAttrIsAListAttr(MlirAttribute attr); + +MLIR_CAPI_EXPORTED intptr_t omListAttrGetNumElements(MlirAttribute attr); + +MLIR_CAPI_EXPORTED MlirAttribute omListAttrGetElement(MlirAttribute attr, + intptr_t pos); + +//===----------------------------------------------------------------------===// +// MapAttr API +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED bool omAttrIsAMapAttr(MlirAttribute attr); + +MLIR_CAPI_EXPORTED intptr_t omMapAttrGetNumElements(MlirAttribute attr); + +MLIR_CAPI_EXPORTED MlirIdentifier omMapAttrGetElementKey(MlirAttribute attr, + intptr_t pos); + +MLIR_CAPI_EXPORTED MlirAttribute omMapAttrGetElementValue(MlirAttribute attr, + intptr_t pos); + +#ifdef __cplusplus +} +#endif + +#endif // CIRCT_C_DIALECT_OM_H diff --git a/include/circt-c/Dialect/SV.h b/include/circt-c/Dialect/SV.h index 7ebd940da270..8a9faa6e15c5 100644 --- a/include/circt-c/Dialect/SV.h +++ b/include/circt-c/Dialect/SV.h @@ -18,7 +18,7 @@ extern "C" { #endif MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(SystemVerilog, sv); -MLIR_CAPI_EXPORTED void registerSVPasses(); +MLIR_CAPI_EXPORTED void registerSVPasses(void); //===----------------------------------------------------------------------===// // Attribute API. diff --git a/include/circt-c/Dialect/Seq.h b/include/circt-c/Dialect/Seq.h index 268fd6fa51f2..289161045899 100644 --- a/include/circt-c/Dialect/Seq.h +++ b/include/circt-c/Dialect/Seq.h @@ -18,7 +18,13 @@ extern "C" { #endif MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(Sequential, seq); -MLIR_CAPI_EXPORTED void registerSeqPasses(); +MLIR_CAPI_EXPORTED void registerSeqPasses(void); + +/// If the type is an clock type +MLIR_CAPI_EXPORTED bool seqTypeIsAClock(MlirType type); + +/// Creates an seq clock type +MLIR_CAPI_EXPORTED MlirType seqClockTypeGet(MlirContext ctx); #ifdef __cplusplus } diff --git a/include/circt-c/Dialect/Verif.h b/include/circt-c/Dialect/Verif.h new file mode 100644 index 000000000000..7d0e251928bb --- /dev/null +++ b/include/circt-c/Dialect/Verif.h @@ -0,0 +1,20 @@ +//===-- circt-c/Verif.h - C API for Verif dialect -----------------*- C -*-===// +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_C_DIALECT_VERIF_H +#define CIRCT_C_DIALECT_VERIF_H + +#include "mlir-c/IR.h" + +#ifdef __cplusplus +extern "C" { +#endif + +MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(Verif, verif); + +#ifdef __cplusplus +} +#endif + +#endif // CIRCT_C_DIALECT_VERIF_H diff --git a/include/circt-c/ExportFIRRTL.h b/include/circt-c/ExportFIRRTL.h new file mode 100644 index 000000000000..416c21117f37 --- /dev/null +++ b/include/circt-c/ExportFIRRTL.h @@ -0,0 +1,27 @@ +//===- circt-c/ExportFIRRTL.h - C API for emitting FIRRTL ---------*- C -*-===// +// +// This header declares the C interface for emitting FIRRTL from a CIRCT MLIR +// module. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_C_EXPORTFIRRTL_H +#define CIRCT_C_EXPORTFIRRTL_H + +#include "mlir-c/IR.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// Emits FIRRTL for the specified module using the provided callback and user +/// data +MLIR_CAPI_EXPORTED MlirLogicalResult mlirExportFIRRTL(MlirModule, + MlirStringCallback, + void *userData); + +#ifdef __cplusplus +} +#endif + +#endif // CIRCT_C_EXPORTFIRRTL_H diff --git a/include/circt-c/Firtool/Firtool.h b/include/circt-c/Firtool/Firtool.h new file mode 100644 index 000000000000..5e343c05f7e6 --- /dev/null +++ b/include/circt-c/Firtool/Firtool.h @@ -0,0 +1,146 @@ +//===-- circt-c/Firtool/Firtool.h - C API for Firtool-lib ---------*- C -*-===// +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_C_FIRTOOL_FIRTOOL_H +#define CIRCT_C_FIRTOOL_FIRTOOL_H + +#include "mlir-c/Pass.h" +#include "mlir-c/Support.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//===----------------------------------------------------------------------===// +// Option API. +//===----------------------------------------------------------------------===// + +#define DEFINE_C_API_STRUCT(name, storage) \ + struct name { \ + storage *ptr; \ + }; \ + typedef struct name name + +DEFINE_C_API_STRUCT(FirtoolOptions, void); + +#undef DEFINE_C_API_STRUCT + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FirtoolPreserveAggregateMode { + FIRTOOL_PRESERVE_AGGREGATE_MODE_NONE, + FIRTOOL_PRESERVE_AGGREGATE_MODE_ONE_DIM_VEC, + FIRTOOL_PRESERVE_AGGREGATE_MODE_VEC, + FIRTOOL_PRESERVE_AGGREGATE_MODE_ALL, +} FirtoolPreserveAggregateMode; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FirtoolPreserveValuesMode { + FIRTOOL_PRESERVE_VALUES_MODE_NONE, + FIRTOOL_PRESERVE_VALUES_MODE_NAMED, + FIRTOOL_PRESERVE_VALUES_MODE_ALL, +} FirtoolPreserveValuesMode; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FirtoolCompanionMode { + FIRTOOL_COMPANION_MODE_BIND, + FIRTOOL_COMPANION_MODE_INSTANTIATE, + FIRTOOL_COMPANION_MODE_DROP, +} FirtoolCompanionMode; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FirtoolBuildMode { + FIRTOOL_BUILD_MODE_DEBUG, + FIRTOOL_BUILD_MODE_RELEASE, +} FirtoolBuildMode; + +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FirtoolRandomKind { + FIRTOOL_RANDOM_KIND_NONE, + FIRTOOL_RANDOM_KIND_MEM, + FIRTOOL_RANDOM_KIND_REG, + FIRTOOL_RANDOM_KIND_ALL, +} FirtoolRandomKind; + +MLIR_CAPI_EXPORTED FirtoolOptions firtoolOptionsCreateDefault(); +MLIR_CAPI_EXPORTED void firtoolOptionsDestroy(FirtoolOptions options); + +#define DECLARE_FIRTOOL_OPTION(name, type) \ + MLIR_CAPI_EXPORTED void firtoolOptionsSet##name(FirtoolOptions options, \ + type value); \ + MLIR_CAPI_EXPORTED type firtoolOptionsGet##name(FirtoolOptions options) + +DECLARE_FIRTOOL_OPTION(OutputFilename, MlirStringRef); +DECLARE_FIRTOOL_OPTION(DisableAnnotationsUnknown, bool); +DECLARE_FIRTOOL_OPTION(DisableAnnotationsClassless, bool); +DECLARE_FIRTOOL_OPTION(LowerAnnotationsNoRefTypePorts, bool); +DECLARE_FIRTOOL_OPTION(PreserveAggregate, FirtoolPreserveAggregateMode); +DECLARE_FIRTOOL_OPTION(PreserveValues, FirtoolPreserveValuesMode); +DECLARE_FIRTOOL_OPTION(BuildMode, FirtoolBuildMode); +DECLARE_FIRTOOL_OPTION(DisableOptimization, bool); +DECLARE_FIRTOOL_OPTION(ExportChiselInterface, bool); +DECLARE_FIRTOOL_OPTION(ChiselInterfaceOutDirectory, MlirStringRef); +DECLARE_FIRTOOL_OPTION(VbToBv, bool); +DECLARE_FIRTOOL_OPTION(Dedup, bool); +DECLARE_FIRTOOL_OPTION(CompanionMode, FirtoolCompanionMode); +DECLARE_FIRTOOL_OPTION(DisableAggressiveMergeConnections, bool); +DECLARE_FIRTOOL_OPTION(EmitOMIR, bool); +DECLARE_FIRTOOL_OPTION(OMIROutFile, MlirStringRef); +DECLARE_FIRTOOL_OPTION(LowerMemories, bool); +DECLARE_FIRTOOL_OPTION(BlackBoxRootPath, MlirStringRef); +DECLARE_FIRTOOL_OPTION(ReplSeqMem, bool); +DECLARE_FIRTOOL_OPTION(ReplSeqMemFile, MlirStringRef); +DECLARE_FIRTOOL_OPTION(ExtractTestCode, bool); +DECLARE_FIRTOOL_OPTION(IgnoreReadEnableMem, bool); +DECLARE_FIRTOOL_OPTION(DisableRandom, FirtoolRandomKind); +DECLARE_FIRTOOL_OPTION(OutputAnnotationFilename, MlirStringRef); +DECLARE_FIRTOOL_OPTION(EnableAnnotationWarning, bool); +DECLARE_FIRTOOL_OPTION(AddMuxPragmas, bool); +DECLARE_FIRTOOL_OPTION(EmitChiselAssertsAsSVA, bool); +DECLARE_FIRTOOL_OPTION(EmitSeparateAlwaysBlocks, bool); +DECLARE_FIRTOOL_OPTION(EtcDisableInstanceExtraction, bool); +DECLARE_FIRTOOL_OPTION(EtcDisableRegisterExtraction, bool); +DECLARE_FIRTOOL_OPTION(EtcDisableModuleInlining, bool); +DECLARE_FIRTOOL_OPTION(AddVivadoRAMAddressConflictSynthesisBugWorkaround, bool); +DECLARE_FIRTOOL_OPTION(CkgModuleName, MlirStringRef); +DECLARE_FIRTOOL_OPTION(CkgInputName, MlirStringRef); +DECLARE_FIRTOOL_OPTION(CkgOutputName, MlirStringRef); +DECLARE_FIRTOOL_OPTION(CkgEnableName, MlirStringRef); +DECLARE_FIRTOOL_OPTION(CkgTestEnableName, MlirStringRef); +DECLARE_FIRTOOL_OPTION(ExportModuleHierarchy, bool); +DECLARE_FIRTOOL_OPTION(StripFirDebugInfo, bool); +DECLARE_FIRTOOL_OPTION(StripDebugInfo, bool); + +#undef DECLARE_FIRTOOL_OPTION + +//===----------------------------------------------------------------------===// +// Populate API. +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED MlirLogicalResult +firtoolPopulatePreprocessTransforms(MlirPassManager pm, FirtoolOptions options); + +MLIR_CAPI_EXPORTED MlirLogicalResult firtoolPopulateCHIRRTLToLowFIRRTL( + MlirPassManager pm, FirtoolOptions options, MlirStringRef inputFilename); + +MLIR_CAPI_EXPORTED MlirLogicalResult +firtoolPopulateLowFIRRTLToHW(MlirPassManager pm, FirtoolOptions options); + +MLIR_CAPI_EXPORTED MlirLogicalResult +firtoolPopulateHWToSV(MlirPassManager pm, FirtoolOptions options); + +MLIR_CAPI_EXPORTED MlirLogicalResult +firtoolPopulateExportVerilog(MlirPassManager pm, FirtoolOptions options, + MlirStringCallback callback, void *userData); + +MLIR_CAPI_EXPORTED MlirLogicalResult firtoolPopulateExportSplitVerilog( + MlirPassManager pm, FirtoolOptions options, MlirStringRef directory); + +MLIR_CAPI_EXPORTED MlirLogicalResult +firtoolPopulateFinalizeIR(MlirPassManager pm, FirtoolOptions options); + +#ifdef __cplusplus +} +#endif + +#endif // CIRCT_C_FIRTOOL_FIRTOOL_H diff --git a/include/circt/Analysis/ControlFlowLoopAnalysis.h b/include/circt/Analysis/ControlFlowLoopAnalysis.h deleted file mode 100644 index f5cde40ee874..000000000000 --- a/include/circt/Analysis/ControlFlowLoopAnalysis.h +++ /dev/null @@ -1,57 +0,0 @@ -//===- ControlFlowLoopAnalysis.h - CF Loop Analysis -------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This header file defines prototypes for methods that perform loop analysis on -// structures expressed as a CFG -// -//===----------------------------------------------------------------------===// - -#ifndef CIRCT_ANALYSIS_CONTROL_FLOW_LOOP_ANALYSIS_H -#define CIRCT_ANALYSIS_CONTROL_FLOW_LOOP_ANALYSIS_H - -#include "circt/Support/LLVM.h" -#include "mlir/IR/Dominance.h" -#include "llvm/ADT/SmallSet.h" - -/// TODO can we reuse parts of Polygeist's implementation? -namespace circt { -namespace analysis { - -/// Container that holds information about a cfg loop. -struct LoopInfo { - SmallPtrSet loopLatches; - SmallPtrSet inLoop; - SmallPtrSet exitBlocks; - Block *loopHeader; -}; - -struct ControlFlowLoopAnalysis { - // Construct the analysis from a FuncOp. - ControlFlowLoopAnalysis(Region ®ion); - LogicalResult analyzeRegion(); - - bool isLoopHeader(Block *b); - bool isLoopElement(Block *b); - LoopInfo *getLoopInfoForHeader(Block *b); - LoopInfo *getLoopInfo(Block *b); - - SmallVector topLevelLoops; - -private: - bool hasBackedge(Block *); - LogicalResult collectLoopInfo(Block *entry, LoopInfo &loopInfo); - -private: - Region ®ion; - mlir::DominanceInfo domInfo; -}; - -} // namespace analysis -} // namespace circt - -#endif // CIRCT_ANALYSIS_CONTROL_FLOW_LOOP_ANALYSIS_H diff --git a/include/circt/Analysis/DebugAnalysis.h b/include/circt/Analysis/DebugAnalysis.h new file mode 100644 index 000000000000..9d6fc044b4f5 --- /dev/null +++ b/include/circt/Analysis/DebugAnalysis.h @@ -0,0 +1,34 @@ +//===- DebugAnalysis.h ----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_ANALYSIS_DEBUGANALYSIS_H +#define CIRCT_ANALYSIS_DEBUGANALYSIS_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/Value.h" +#include "llvm/ADT/DenseSet.h" + +namespace mlir { +class Operation; +class OpOperand; +} // namespace mlir + +namespace circt { + +/// Identify operations and values that are only used for debug info. +struct DebugAnalysis { + DebugAnalysis(Operation *op); + + DenseSet debugOps; + DenseSet debugValues; + DenseSet debugOperands; +}; + +} // namespace circt + +#endif // CIRCT_ANALYSIS_DEBUGANALYSIS_H diff --git a/include/circt/Analysis/DebugInfo.h b/include/circt/Analysis/DebugInfo.h new file mode 100644 index 000000000000..141df0f4a5cf --- /dev/null +++ b/include/circt/Analysis/DebugInfo.h @@ -0,0 +1,79 @@ +//===- DebugInfo.h - Debug info analysis ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_ANALYSIS_DEBUGINFO_H +#define CIRCT_ANALYSIS_DEBUGINFO_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Operation.h" +#include "llvm/ADT/MapVector.h" + +namespace circt { + +struct DIInstance; +struct DIVariable; + +namespace detail { +struct DebugInfoBuilder; +} // namespace detail + +struct DIModule { + /// The operation that generated this level of hierarchy. + Operation *op = nullptr; + /// The name of this level of hierarchy. + StringAttr name; + /// Levels of hierarchy nested under this module. + SmallVector instances; + /// Variables declared within this module. + SmallVector variables; + /// If this is an extern declaration. + bool isExtern = false; +}; + +struct DIInstance { + /// The operation that generated this instance. + Operation *op = nullptr; + /// The name of this instance. + StringAttr name; + /// The instantiated module. + DIModule *module; +}; + +struct DIVariable { + /// The name of this variable. + StringAttr name; + /// The location of the variable's declaration. + LocationAttr loc; + /// The SSA value representing the value of this variable. + Value value = nullptr; +}; + +/// Debug information attached to an operation and the operations nested within. +/// +/// This is an analysis that gathers debug information for a piece of IR, either +/// from attributes attached to operations or the general structure of the IR. +struct DebugInfo { + /// Collect the debug information nested under the given operation. + DebugInfo(Operation *op); + + /// The operation that was passed to the constructor. + Operation *operation; + /// A mapping from module name to module debug info. + llvm::MapVector moduleNodes; + +protected: + friend struct detail::DebugInfoBuilder; + llvm::SpecificBumpPtrAllocator moduleAllocator; + llvm::SpecificBumpPtrAllocator instanceAllocator; + llvm::SpecificBumpPtrAllocator variableAllocator; +}; + +} // namespace circt + +#endif // CIRCT_ANALYSIS_DEBUGINFO_H diff --git a/include/circt/Analysis/DependenceAnalysis.h b/include/circt/Analysis/DependenceAnalysis.h index 2547baf95e15..6f7d0cef0942 100644 --- a/include/circt/Analysis/DependenceAnalysis.h +++ b/include/circt/Analysis/DependenceAnalysis.h @@ -19,7 +19,9 @@ #include namespace mlir { +namespace affine { struct DependenceComponent; +} // namespace affine namespace func { class FuncOp; } // namespace func @@ -32,9 +34,10 @@ namespace analysis { /// It represents the destination of the dependence edge, the type of the /// dependence, and the components associated with each enclosing loop. struct MemoryDependence { - MemoryDependence(Operation *source, - mlir::DependenceResult::ResultEnum dependenceType, - ArrayRef dependenceComponents) + MemoryDependence( + Operation *source, + mlir::affine::DependenceResult::ResultEnum dependenceType, + ArrayRef dependenceComponents) : source(source), dependenceType(dependenceType), dependenceComponents(dependenceComponents.begin(), dependenceComponents.end()) {} @@ -43,10 +46,10 @@ struct MemoryDependence { Operation *source; // The dependence type denotes whether or not there is a dependence. - mlir::DependenceResult::ResultEnum dependenceType; + mlir::affine::DependenceResult::ResultEnum dependenceType; // The dependence components include lower and upper bounds for each loop. - SmallVector dependenceComponents; + SmallVector dependenceComponents; }; /// MemoryDependenceResult captures a set of memory dependences. The map key is diff --git a/include/circt/Analysis/SchedulingAnalysis.h b/include/circt/Analysis/SchedulingAnalysis.h index 2493951c4c80..26d20580bf6c 100644 --- a/include/circt/Analysis/SchedulingAnalysis.h +++ b/include/circt/Analysis/SchedulingAnalysis.h @@ -39,10 +39,11 @@ namespace analysis { struct CyclicSchedulingAnalysis { CyclicSchedulingAnalysis(Operation *funcOp, AnalysisManager &am); - CyclicProblem &getProblem(AffineForOp forOp); + CyclicProblem &getProblem(affine::AffineForOp forOp); private: - void analyzeForOp(AffineForOp forOp, MemoryDependenceAnalysis memoryAnalysis); + void analyzeForOp(affine::AffineForOp forOp, + MemoryDependenceAnalysis memoryAnalysis); DenseMap problems; }; diff --git a/include/circt/CMakeLists.txt b/include/circt/CMakeLists.txt index 495310c6626c..3303fcf9ceb4 100644 --- a/include/circt/CMakeLists.txt +++ b/include/circt/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(Conversion) add_subdirectory(Dialect) add_subdirectory(Transforms) +add_subdirectory(Support) diff --git a/include/circt/Conversion/AffineToPipeline.h b/include/circt/Conversion/AffineToLoopSchedule.h similarity index 57% rename from include/circt/Conversion/AffineToPipeline.h rename to include/circt/Conversion/AffineToLoopSchedule.h index faa878b547c0..5a15a2eb39a9 100644 --- a/include/circt/Conversion/AffineToPipeline.h +++ b/include/circt/Conversion/AffineToLoopSchedule.h @@ -1,4 +1,5 @@ -//===- AffineToPipeline.h -------------------------------------------------===// +//===- AffineToLoopSchedule.h +//-------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,8 +7,8 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_CONVERSION_AFFINETOPIPELINE_H_ -#define CIRCT_CONVERSION_AFFINETOPIPELINE_H_ +#ifndef CIRCT_CONVERSION_AFFINETOLOOPSCHEDULE_H_ +#define CIRCT_CONVERSION_AFFINETOLOOPSCHEDULE_H_ #include @@ -16,7 +17,7 @@ class Pass; } // namespace mlir namespace circt { -std::unique_ptr createAffineToPipeline(); +std::unique_ptr createAffineToLoopSchedule(); } // namespace circt -#endif // CIRCT_CONVERSION_AFFINETOPIPELINE_H_ +#endif // CIRCT_CONVERSION_AFFINETOLOOPSCHEDULE_H_ diff --git a/include/circt/Conversion/ArcToLLVM.h b/include/circt/Conversion/ArcToLLVM.h new file mode 100644 index 000000000000..48d92eb90587 --- /dev/null +++ b/include/circt/Conversion/ArcToLLVM.h @@ -0,0 +1,19 @@ +//===- ArcToLLVM.h ----------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_ARCTOLLVM_H +#define CIRCT_CONVERSION_ARCTOLLVM_H + +#include "circt/Support/LLVM.h" +#include + +namespace circt { +std::unique_ptr> createLowerArcToLLVMPass(); +} // namespace circt + +#endif // CIRCT_CONVERSION_ARCTOLLVM_H diff --git a/include/circt/Conversion/StandardToHandshake.h b/include/circt/Conversion/CFToHandshake.h similarity index 66% rename from include/circt/Conversion/StandardToHandshake.h rename to include/circt/Conversion/CFToHandshake.h index 0cae80f0f8e6..85fcb198b3c8 100644 --- a/include/circt/Conversion/StandardToHandshake.h +++ b/include/circt/Conversion/CFToHandshake.h @@ -1,4 +1,4 @@ -//===- StandardToHandshake.h ------------------------------------*- C++ -*-===// +//===- CFToHandshake.h ------------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -11,12 +11,13 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_CONVERSION_STANDARDTOHANDSHAKE_H_ -#define CIRCT_CONVERSION_STANDARDTOHANDSHAKE_H_ +#ifndef CIRCT_CONVERSION_CFTOHANDSHAKE_H_ +#define CIRCT_CONVERSION_CFTOHANDSHAKE_H_ #include "circt/Dialect/Handshake/HandshakeOps.h" #include "circt/Dialect/Handshake/HandshakePasses.h" #include "circt/Support/BackedgeBuilder.h" +#include "circt/Transforms/Passes.h" #include "mlir/Transforms/DialectConversion.h" #include @@ -28,63 +29,6 @@ class OperationPass; } // namespace mlir namespace circt { - -/// Strategy class to control the behavior of SSA maximization. The class -/// exposes overridable filter functions to dynamically select which blocks, -/// block arguments, operations, and operation results should be put into -/// maximal SSA form. All filter functions should return true whenever the -/// entity they operate on should be considered for SSA maximization. By -/// default, all filter functions always return true. -class SSAMaximizationStrategy { -public: - /// Determines whether a block should have the values it defines (i.e., block - /// arguments and operation results within the block) SSA maximized. - virtual bool maximizeBlock(Block *block); - /// Determines whether a block argument should be SSA maximized. - virtual bool maximizeArgument(BlockArgument arg); - /// Determines whether an operation should have its results SSA maximized. - virtual bool maximizeOp(Operation *op); - /// Determines whether an operation's result should be SSA maximized. - virtual bool maximizeResult(OpResult res); - - virtual ~SSAMaximizationStrategy() = default; -}; - -/// Converts a single value within a function into maximal SSA form. This -/// removes any implicit dataflow of this specific value within the enclosing -/// function. The function adds new block arguments wherever necessary to carry -/// the value explicitly between blocks. -/// Succeeds when it was possible to convert the value into maximal SSA form. -LogicalResult maximizeSSA(Value value, PatternRewriter &rewriter); - -/// Considers all of an operation's results for SSA maximization, following a -/// provided strategy. This removes any implicit dataflow of the selected -/// operation's results within the enclosing function. The function adds new -/// block arguments wherever necessary to carry the results explicitly between -/// blocks. Succeeds when it was possible to convert the selected operation's -/// results into maximal SSA form. -LogicalResult maximizeSSA(Operation *op, SSAMaximizationStrategy &strategy, - PatternRewriter &rewriter); - -/// Considers all values defined by a block (i.e., block arguments and operation -/// results within the block) for SSA maximization, following a provided -/// strategy. This removes any implicit dataflow of the selected values within -/// the enclosing function. The function adds new block arguments wherever -/// necessary to carry the values explicitly between blocks. Succeeds when it -/// was possible to convert the selected values defined by the block into -/// maximal SSA form. -LogicalResult maximizeSSA(Block *block, SSAMaximizationStrategy &strategy, - PatternRewriter &rewriter); - -/// Considers all blocks within a region for SSA maximization, following a -/// provided strategy. This removes any implicit dataflow of the values defined -/// by selected blocks within the region. The function adds new block arguments -/// wherever necessary to carry the region's values explicitly between blocks. -/// Succeeds when it was possible to convert all of the values defined by -/// selected blocks into maximal SSA form. -LogicalResult maximizeSSA(Region ®ion, SSAMaximizationStrategy &strategy, - PatternRewriter &rewriter); - namespace handshake { // ============================================================================ @@ -125,23 +69,22 @@ class HandshakeLowering { LogicalResult addBranchOps(ConversionPatternRewriter &rewriter); LogicalResult replaceCallOps(ConversionPatternRewriter &rewriter); - template - LogicalResult setControlOnlyPath(ConversionPatternRewriter &rewriter) { + template + LogicalResult setControlOnlyPath(ConversionPatternRewriter &rewriter, + Value entryCtrl) { + assert(entryCtrl.getType().isa() && + "Expected NoneType for entry control value"); // Creates start and end points of the control-only path - - // Add start point of the control-only path to the entry block's arguments Block *entryBlock = &r.front(); - startCtrl = entryBlock->addArgument(rewriter.getNoneType(), - rewriter.getUnknownLoc()); - setBlockEntryControl(entryBlock, startCtrl); + setBlockEntryControl(entryBlock, entryCtrl); // Replace original return ops with new returns with additional control // input - for (auto retOp : llvm::make_early_inc_range(r.getOps())) { + for (auto retOp : llvm::make_early_inc_range(r.getOps())) { rewriter.setInsertionPoint(retOp); SmallVector operands(retOp->getOperands()); - operands.push_back(startCtrl); - rewriter.replaceOpWithNewOp(retOp, operands); + operands.push_back(entryCtrl); + rewriter.replaceOpWithNewOp(retOp, operands); } // Store the number of block arguments in each block @@ -152,7 +95,7 @@ class HandshakeLowering { // Apply SSA maximization on the newly added entry block argument to // propagate it explicitly between the start-point of the control-only // network and the function's terminators - if (failed(maximizeSSA(startCtrl, rewriter))) + if (failed(maximizeSSA(entryCtrl, rewriter))) return failure(); // Identify all block arguments belonging to the control-only network @@ -226,13 +169,19 @@ LogicalResult runPartialLowering( instance.getContext(), instance.getRegion()); } +/// Remove basic blocks inside the given region. This allows the result to be +/// a valid graph region, since multi-basic block regions are not allowed to +/// be graph regions currently. +void removeBasicBlocks(Region &r); + // Helper to check the validity of the dataflow conversion // Driver that applies the partial lowerings expressed in HandshakeLowering to // the region encapsulated in it. The region is assumed to have a terminator of -// type TTerm. See HandshakeLowering for the different lowering steps. -template +// type TSrcTerm, and will replace it with TDstTerm. See HandshakeLowering for +// the different lowering steps. +template LogicalResult lowerRegion(HandshakeLowering &hl, bool sourceConstants, - bool disableTaskPipelining) { + bool disableTaskPipelining, Value entryCtrl) { // Perform initial dataflow conversion. This process allows for the use of // non-deterministic merge-like operations. HandshakeLowering::MemRefToMemoryAccessOp memOps; @@ -240,8 +189,9 @@ LogicalResult lowerRegion(HandshakeLowering &hl, bool sourceConstants, if (failed( runPartialLowering(hl, &HandshakeLowering::replaceMemoryOps, memOps))) return failure(); - if (failed(runPartialLowering(hl, - &HandshakeLowering::setControlOnlyPath))) + if (failed(runPartialLowering( + hl, &HandshakeLowering::setControlOnlyPath, + entryCtrl))) return failure(); if (failed(runPartialLowering(hl, &HandshakeLowering::addMergeOps))) return failure(); @@ -271,14 +221,13 @@ LogicalResult lowerRegion(HandshakeLowering &hl, bool sourceConstants, lsq))) return failure(); + // Legalize the resulting regions, removing basic blocks and performing + // any simple conversions. + removeBasicBlocks(hl.getRegion()); + return success(); } -/// Remove basic blocks inside the given region. This allows the result to be -/// a valid graph region, since multi-basic block regions are not allowed to -/// be graph regions currently. -void removeBasicBlocks(Region &r); - /// Lowers the mlir operations into handshake that are not part of the dataflow /// conversion. LogicalResult postDataflowConvert(Operation *op); @@ -289,8 +238,8 @@ std::unique_ptr> createHandshakeAnalysisPass(); std::unique_ptr> -createStandardToHandshakePass(bool sourceConstants = false, - bool disableTaskPipelining = false); +createCFToHandshakePass(bool sourceConstants = false, + bool disableTaskPipelining = false); std::unique_ptr> createHandshakeCanonicalizePass(); @@ -309,12 +258,6 @@ insertMergeBlocks(mlir::Region &r, mlir::ConversionPatternRewriter &rewriter); std::unique_ptr createInsertMergeBlocksPass(); -// Returns true if the region is into maximal SSA form i.e., if all the values -// within the region are in maximal SSA form. -bool isRegionSSAMaximized(Region ®ion); - -std::unique_ptr createMaximizeSSAPass(); - } // namespace circt -#endif // CIRCT_CONVERSION_STANDARDTOHANDSHAKE_H_ +#endif // CIRCT_CONVERSION_CFTOHANDSHAKE_H_ diff --git a/include/circt/Conversion/CMakeLists.txt b/include/circt/Conversion/CMakeLists.txt index d6e1b9c06f1f..bc227db34cb6 100644 --- a/include/circt/Conversion/CMakeLists.txt +++ b/include/circt/Conversion/CMakeLists.txt @@ -1,5 +1,7 @@ set(LLVM_TARGET_DEFINITIONS Passes.td) mlir_tablegen(Passes.h.inc -gen-pass-decls -name Conversion) +mlir_tablegen(Conversion.capi.h.inc -gen-pass-capi-header --prefix Conversion) +mlir_tablegen(Conversion.capi.cpp.inc -gen-pass-capi-impl --prefix Conversion) add_public_tablegen_target(CIRCTConversionPassIncGen) add_circt_doc(Passes CIRCTConversionPasses -gen-pass-doc) diff --git a/include/circt/Conversion/CalyxNative.h b/include/circt/Conversion/CalyxNative.h new file mode 100644 index 000000000000..d46e2d314e7d --- /dev/null +++ b/include/circt/Conversion/CalyxNative.h @@ -0,0 +1,31 @@ +//===- CalyxNative.h - Calyx Native pass ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares passes which together will lower the Calyx dialect to the +// HW dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_CALYXNATIVE_H +#define CIRCT_CONVERSION_CALYXNATIVE_H + +#include "circt/Dialect/Calyx/CalyxOps.h" +#include "circt/Support/LLVM.h" +#include + +namespace mlir { +class Pass; +} // namespace mlir + +namespace circt { + +std::unique_ptr createCalyxNativePass(); + +} // namespace circt + +#endif // CIRCT_CONVERSION_CALYXNATIVE_H diff --git a/include/circt/Conversion/CombToArith.h b/include/circt/Conversion/CombToArith.h index 762b8cb2de90..4647ce7798d8 100644 --- a/include/circt/Conversion/CombToArith.h +++ b/include/circt/Conversion/CombToArith.h @@ -13,6 +13,9 @@ #include namespace circt { +void populateCombToArithConversionPatterns(TypeConverter &converter, + RewritePatternSet &patterns); + std::unique_ptr> createConvertCombToArithPass(); } // namespace circt diff --git a/include/circt/Conversion/CombToLLVM.h b/include/circt/Conversion/CombToLLVM.h index 1ced77fccb93..1b4695018ebf 100644 --- a/include/circt/Conversion/CombToLLVM.h +++ b/include/circt/Conversion/CombToLLVM.h @@ -27,9 +27,6 @@ namespace circt { void populateCombToLLVMConversionPatterns(mlir::LLVMTypeConverter &converter, RewritePatternSet &patterns); -/// Create an Comb to LLVM conversion pass. -std::unique_ptr> createConvertCombToLLVMPass(); - } // namespace circt #endif // CIRCT_CONVERSION_COMBTOLLVM_COMBTOLLVM_H diff --git a/include/circt/Conversion/HandshakeToFIRRTL.h b/include/circt/Conversion/DCToHW.h similarity index 56% rename from include/circt/Conversion/HandshakeToFIRRTL.h rename to include/circt/Conversion/DCToHW.h index d1a80942a565..f9fc9fbdcd91 100644 --- a/include/circt/Conversion/HandshakeToFIRRTL.h +++ b/include/circt/Conversion/DCToHW.h @@ -1,4 +1,4 @@ -//===- HandshakeToFIRRTL.h --------------------------------------*- C++ -*-===// +//===- DCToHW.h - DC to HW conversion pass ----------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,13 +6,13 @@ // //===----------------------------------------------------------------------===// // -// This file declares passes which together will lower the Handshake dialect to -// FIRRTL dialect. +// This file declares passes which together will lower the DC dialect to the +// HW dialect. // //===----------------------------------------------------------------------===// -#ifndef CIRCT_CONVERSION_HANDSHAKETOFIRRTL_H_ -#define CIRCT_CONVERSION_HANDSHAKETOFIRRTL_H_ +#ifndef CIRCT_CONVERSION_DCTOHW_H +#define CIRCT_CONVERSION_DCTOHW_H #include @@ -21,7 +21,8 @@ class Pass; } // namespace mlir namespace circt { -std::unique_ptr createHandshakeToFIRRTLPass(); +std::unique_ptr createDCToHWPass(); + } // namespace circt -#endif // MLIR_CONVERSION_HANDSHAKETOFIRRTL_H_ +#endif // CIRCT_CONVERSION_DCTOHW_H diff --git a/include/circt/Conversion/ExportVerilog.h b/include/circt/Conversion/ExportVerilog.h index 4d8e3a63a5ac..836148a5e170 100644 --- a/include/circt/Conversion/ExportVerilog.h +++ b/include/circt/Conversion/ExportVerilog.h @@ -23,7 +23,10 @@ createTestApplyLoweringOptionPass(llvm::StringRef options); std::unique_ptr createTestApplyLoweringOptionPass(); std::unique_ptr createPrepareForEmissionPass(); +std::unique_ptr createLegalizeAnonEnumsPass(); +std::unique_ptr +createExportVerilogPass(std::unique_ptr os); std::unique_ptr createExportVerilogPass(llvm::raw_ostream &os); std::unique_ptr createExportVerilogPass(); diff --git a/include/circt/Conversion/FIRRTLToHW.h b/include/circt/Conversion/FIRRTLToHW.h index 7d70693c7769..2f7aedb5048c 100644 --- a/include/circt/Conversion/FIRRTLToHW.h +++ b/include/circt/Conversion/FIRRTLToHW.h @@ -25,8 +25,7 @@ namespace circt { std::unique_ptr createLowerFIRRTLToHWPass( bool enableAnnotationWarning = false, bool emitChiselAssertsAsSVA = false, - bool addMuxPragmas = false, bool disableMemRandomization = false, - bool disableRegRandomization = false); + bool disableMemRandomization = false, bool disableRegRandomization = false); } // namespace circt diff --git a/include/circt/Conversion/HWToSV.h b/include/circt/Conversion/HWToSV.h new file mode 100644 index 000000000000..bf3cb995c5d7 --- /dev/null +++ b/include/circt/Conversion/HWToSV.h @@ -0,0 +1,34 @@ +//===- HWToSV.h - HW to SystemC pass entry point --------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This header file defines prototypes that expose the HWToSV pass +// constructors. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_HWTOSV_H +#define CIRCT_CONVERSION_HWTOSV_H + +#include + +namespace mlir { +template +class OperationPass; +} // namespace mlir + +namespace circt { +namespace hw { +class HWModuleOp; +} // namespace hw +} // namespace circt + +namespace circt { +std::unique_ptr> createLowerHWToSVPass(); +} // namespace circt + +#endif // CIRCT_CONVERSION_HWTOSV_H diff --git a/include/circt/Conversion/HandshakeToDC.h b/include/circt/Conversion/HandshakeToDC.h new file mode 100644 index 000000000000..802a572c2291 --- /dev/null +++ b/include/circt/Conversion/HandshakeToDC.h @@ -0,0 +1,54 @@ +//===- HandshakeToDC.h ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares passes which together will lower the Handshake dialect to +// CIRCT RTL dialects. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_HANDSHAKETODC_H +#define CIRCT_CONVERSION_HANDSHAKETODC_H + +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/IR/Builders.h" +#include + +namespace mlir { +class Pass; +} // namespace mlir + +namespace circt { + +std::unique_ptr createHandshakeToDCPass(); + +namespace handshaketodc { +using ConvertedOps = DenseSet; + +// Runs Handshake to DC conversion on the provided op. `patternBuilder` can be +// used to describe additional patterns to run - typically this will be a +// pattern that converts the container operation (e.g. `op`). +// `configureTarget` can be provided to specialize legalization. +LogicalResult runHandshakeToDC( + mlir::Operation *op, + llvm::function_ref + patternBuilder, + llvm::function_ref configureTarget = {}); +} // namespace handshaketodc + +namespace handshake { + +// Converts 't' into a valid HW type. This is strictly used for converting +// 'index' types into a fixed-width type. +Type toValidType(Type t); + +} // namespace handshake +} // namespace circt + +#endif // CIRCT_CONVERSION_HANDSHAKETODC_H diff --git a/include/circt/Conversion/LoopScheduleToCalyx.h b/include/circt/Conversion/LoopScheduleToCalyx.h new file mode 100644 index 000000000000..a584a9586fa4 --- /dev/null +++ b/include/circt/Conversion/LoopScheduleToCalyx.h @@ -0,0 +1,28 @@ +//===- LoopScheduleToCalyx.h - LoopSchedule to Calyx pass entry point -----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This header file defines prototypes that expose the LoopScheduleToCalyx pass +// constructor. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_LOOPSCHEDULETOCALYX_H +#define CIRCT_CONVERSION_LOOPSCHEDULETOCALYX_H + +#include "circt/Dialect/LoopSchedule/LoopScheduleDialect.h" +#include "circt/Support/LLVM.h" +#include + +namespace circt { + +/// Create a LoopSchedule to Calyx conversion pass. +std::unique_ptr> createLoopScheduleToCalyxPass(); + +} // namespace circt + +#endif // CIRCT_CONVERSION_LOOPSCHEDULETOCALYX_H diff --git a/include/circt/Conversion/Passes.h b/include/circt/Conversion/Passes.h index 4c814b0a3976..c3ecc1686c16 100644 --- a/include/circt/Conversion/Passes.h +++ b/include/circt/Conversion/Passes.h @@ -13,12 +13,15 @@ #ifndef CIRCT_CONVERSION_PASSES_H #define CIRCT_CONVERSION_PASSES_H -#include "circt/Conversion/AffineToPipeline.h" +#include "circt/Conversion/AffineToLoopSchedule.h" +#include "circt/Conversion/ArcToLLVM.h" +#include "circt/Conversion/CFToHandshake.h" +#include "circt/Conversion/CalyxNative.h" #include "circt/Conversion/CalyxToFSM.h" #include "circt/Conversion/CalyxToHW.h" #include "circt/Conversion/CombToArith.h" -#include "circt/Conversion/CombToLLVM.h" #include "circt/Conversion/ConvertToArcs.h" +#include "circt/Conversion/DCToHW.h" #include "circt/Conversion/ExportChiselInterface.h" #include "circt/Conversion/ExportVerilog.h" #include "circt/Conversion/FIRRTLToHW.h" @@ -26,15 +29,17 @@ #include "circt/Conversion/HWArithToHW.h" #include "circt/Conversion/HWToLLHD.h" #include "circt/Conversion/HWToLLVM.h" +#include "circt/Conversion/HWToSV.h" #include "circt/Conversion/HWToSystemC.h" -#include "circt/Conversion/HandshakeToFIRRTL.h" +#include "circt/Conversion/HandshakeToDC.h" #include "circt/Conversion/HandshakeToHW.h" #include "circt/Conversion/LLHDToLLVM.h" +#include "circt/Conversion/LoopScheduleToCalyx.h" #include "circt/Conversion/MooreToCore.h" -#include "circt/Conversion/PipelineToCalyx.h" #include "circt/Conversion/PipelineToHW.h" #include "circt/Conversion/SCFToCalyx.h" -#include "circt/Conversion/StandardToHandshake.h" +#include "circt/Conversion/SeqToSV.h" +#include "circt/Conversion/VerifToSV.h" #include "mlir/IR/DialectRegistry.h" #include "mlir/Pass/Pass.h" #include "mlir/Pass/PassRegistry.h" diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 4580f9772bf2..89f29a98b480 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -19,16 +19,16 @@ include "mlir/Pass/PassBase.td" // AffineToPipeline //===----------------------------------------------------------------------===// -def AffineToPipeline : Pass<"convert-affine-to-pipeline", "mlir::func::FuncOp"> { - let summary = "Convert Affine dialect to Pipeline pipelines"; +def AffineToLoopSchedule : Pass<"convert-affine-to-loopschedule", "mlir::func::FuncOp"> { + let summary = "Convert Affine dialect to LoopSchedule scheduled loops"; let description = [{ This pass analyzes Affine loops and control flow, creates a Scheduling problem using the Calyx operator library, solves the problem, and lowers - the loops to a Pipeline pipeline. + the loops to a LoopSchedule. }]; - let constructor = "circt::createAffineToPipeline()"; + let constructor = "circt::createAffineToLoopSchedule()"; let dependentDialects = [ - "circt::pipeline::PipelineDialect", + "circt::loopschedule::LoopScheduleDialect", "mlir::arith::ArithDialect", "mlir::cf::ControlFlowDialect", "mlir::memref::MemRefDialect", @@ -92,6 +92,18 @@ def TestApplyLoweringOption : Pass<"test-apply-lowering-options", ]; } +def LegalizeAnonEnums : Pass<"legalize-anon-enums", "mlir::ModuleOp"> { + let summary = "Prepare anonymous enumeration types for ExportVerilog"; + let description = [{ + This pass transforms all anonymous enumeration types into typedecls to work + around difference in how anonymous enumerations work in SystemVerilog. + }]; + let constructor = "createLegalizeAnonEnumsPass()"; + let dependentDialects = [ + "circt::sv::SVDialect", "circt::comb::CombDialect", "circt::hw::HWDialect" + ]; +} + def PrepareForEmission : Pass<"prepare-for-emission", "hw::HWModuleOp"> { let summary = "Prepare IR for ExportVerilog"; @@ -162,17 +174,17 @@ def SCFToCalyx : Pass<"lower-scf-to-calyx", "mlir::ModuleOp"> { } //===----------------------------------------------------------------------===// -// PipelineToCalyx +// LoopScheduleToCalyx //===----------------------------------------------------------------------===// -def PipelineToCalyx : Pass<"lower-static-logic-to-calyx", "mlir::ModuleOp"> { - let summary = "Lower Pipeline to Calyx"; +def LoopScheduleToCalyx : Pass<"lower-loopschedule-to-calyx", "mlir::ModuleOp"> { + let summary = "Lower LoopSchedule to Calyx"; let description = [{ - This pass lowers Pipeline to Calyx. + This pass lowers LoopSchedule to Calyx. }]; - let constructor = "circt::createPipelineToCalyxPass()"; + let constructor = "circt::createLoopScheduleToCalyxPass()"; let dependentDialects = [ - "calyx::CalyxDialect", "::mlir::scf::SCFDialect", "hw::HWDialect", + "calyx::CalyxDialect", "loopschedule::LoopScheduleDialect", "hw::HWDialect", "comb::CombDialect" ]; let options = [ @@ -188,7 +200,14 @@ def PipelineToCalyx : Pass<"lower-static-logic-to-calyx", "mlir::ModuleOp"> { // PipelineTHW //===----------------------------------------------------------------------===// -def PipelineToHW : Pass<"lower-pipeline-to-hw", "hw::HWModuleOp"> { +// TODO: @mortbopet: There is a possible non-neglible speedup that can be achieved +// here by allowing this pass to run on a per-hwmodule/whatever container the +// pipeline is nested within-granularity. However, this conversion adds (and removes) +// new modules to the top-level mlir::ModuleOp scope, which technically violates +// hw::HWModuleLike's IsolatedFromAbove (and thus has previously caused +// concurrency issues via. concurrent additions and removals to the mlir::ModuleOp +// symboltable). +def PipelineToHW : Pass<"lower-pipeline-to-hw", "mlir::ModuleOp"> { let summary = "Lower Pipeline to HW"; let description = [{ This pass lowers `pipeline.rtp` operations to HW. @@ -197,6 +216,12 @@ def PipelineToHW : Pass<"lower-pipeline-to-hw", "hw::HWModuleOp"> { let dependentDialects = [ "hw::HWDialect", "comb::CombDialect", "seq::SeqDialect" ]; + let options = [ + Option<"clockGateRegs", "clock-gate-regs", "bool", "false", + "Clock gate each register instead of (default) input muxing (ASIC optimization).">, + Option<"enablePowerOnValues", "enable-poweron-values", "bool", "false", + "Add power-on values to the pipeline control registers"> + ]; } //===----------------------------------------------------------------------===// @@ -213,6 +238,25 @@ def CalyxToHW : Pass<"lower-calyx-to-hw", "mlir::ModuleOp"> { "seq::SeqDialect", "sv::SVDialect"]; } +//===----------------------------------------------------------------------===// +// CalyxNative +//===----------------------------------------------------------------------===// +def CalyxNative : Pass<"calyx-native", "mlir::ModuleOp"> { + let summary = "Callout to the Calyx native compiler and run a pass pipeline"; + let description = [{ + This pass calls out to the native, Rust-based Calyx compiler to run passes + with it and generate a new, valid, calyx dialect program. + }]; + let constructor = "circt::createCalyxNativePass()"; + let dependentDialects = ["calyx::CalyxDialect"]; + let options = [ + Option<"passPipeline", "pass-pipeline", "std::string", + "", "Passes to run with the native compiler">, + ]; +} + + + //===----------------------------------------------------------------------===// // CalyxToFSM //===----------------------------------------------------------------------===// @@ -330,7 +374,12 @@ def CalyxRemoveGroupsFromFSM : Pass<"calyx-remove-groups-fsm", "calyx::Component } ``` }]; - let dependentDialects = ["fsm::FSMDialect", "comb::CombDialect", "hw::HWDialect"]; + let dependentDialects = [ + "fsm::FSMDialect", + "comb::CombDialect", + "hw::HWDialect", + "seq::SeqDialect" + ]; let constructor = "circt::createRemoveGroupsFromFSMPass()"; } @@ -356,7 +405,8 @@ def LowerFIRRTLToHW : Pass<"lower-firrtl-to-hw", "mlir::ModuleOp"> { }]; let constructor = "circt::createLowerFIRRTLToHWPass()"; let dependentDialects = ["comb::CombDialect", "hw::HWDialect", - "seq::SeqDialect", "sv::SVDialect"]; + "seq::SeqDialect", "sv::SVDialect", + "ltl::LTLDialect", "verif::VerifDialect"]; let options = [ Option<"disableMemRandomization", "disable-mem-randomization", "bool", "false", "Disable emission of memory randomization code">, @@ -366,42 +416,59 @@ def LowerFIRRTLToHW : Pass<"lower-firrtl-to-hw", "mlir::ModuleOp"> { "bool", "false", "Emit warnings on unprocessed annotations during lower-to-hw pass">, Option<"emitChiselAssertsAsSVA", "emit-chisel-asserts-as-sva", - "bool", "false","Convert all Chisel asserts to SVA">, - Option<"addMuxPragmas", "add-mux-pragmas", "bool", "false", - "Annotate mux pragmas to multibit mux and subacess results"> + "bool", "false","Convert all Chisel asserts to SVA"> ]; } //===----------------------------------------------------------------------===// -// HandshakeToHW +// HandshakeToDC //===----------------------------------------------------------------------===// -def HandshakeToHW : Pass<"lower-handshake-to-hw", "mlir::ModuleOp"> { - let summary = "Lower Handshake to ESI/HW/Comb/Seq"; +def HandshakeToDC : Pass<"lower-handshake-to-dc", "mlir::ModuleOp"> { + let summary = "Lower Handshake to DC"; let description = [{ - Lower Handshake to ESI/HW/Comb/Seq. + Lower Handshake to DC operations. + Currently, a `handshake.func` will be converted into a `hw.module`. This + is principally an incorrect jump of abstraction - DC does not imply any + RTL/hardware semantics. However, DC does not define a container operation, + and there does not exist an e.g. `func.graph_func` which would be a generic + function with graph region behaviour. Thus, for now, we just use `hw.module` + as a container operation. }]; - let constructor = "circt::createHandshakeToHWPass()"; - let dependentDialects = ["hw::HWDialect", "esi::ESIDialect", "comb::CombDialect", - "seq::SeqDialect"]; + let constructor = "circt::createHandshakeToDCPass()"; + let dependentDialects = ["dc::DCDialect", "mlir::func::FuncDialect", "hw::HWDialect"]; } //===----------------------------------------------------------------------===// -// HandshakeToFIRRTL +// DCToHW //===----------------------------------------------------------------------===// -def HandshakeToFIRRTL : Pass<"lower-handshake-to-firrtl", "mlir::ModuleOp"> { - let summary = "Lower Handshake to FIRRTL"; +def DCToHW : Pass<"lower-dc-to-hw"> { + let summary = "Lower DC to HW"; let description = [{ - Lower Handshake to FIRRTL. + Lower DC to ESI/hw/comb/seq operations. + In case the IR contains DC operations that need to be clocked (fork, buffer), + there must exist a clock and reset signal in the parent `FunctionLike` + operation. These arguments are to be marked with a `dc.clock` and `dc.reset` + attribute, respectively. }]; - let constructor = "circt::createHandshakeToFIRRTLPass()"; - let dependentDialects = ["firrtl::FIRRTLDialect"]; - let options = [ - Option<"enableFlattening", "flatten", "bool", "false", - "Flattens the generated FIRRTL component by inlining all dataflow component" - " instantiations into the top module.">, - ]; + let constructor = "circt::createDCToHWPass()"; + let dependentDialects = ["dc::DCDialect", "esi::ESIDialect", "hw::HWDialect", + "comb::CombDialect", "seq::SeqDialect"]; +} + +//===----------------------------------------------------------------------===// +// HandshakeToHW +//===----------------------------------------------------------------------===// + +def HandshakeToHW : Pass<"lower-handshake-to-hw", "mlir::ModuleOp"> { + let summary = "Lower Handshake to ESI/HW/Comb/Seq"; + let description = [{ + Lower Handshake to ESI/HW/Comb/Seq. + }]; + let constructor = "circt::createHandshakeToHWPass()"; + let dependentDialects = ["hw::HWDialect", "esi::ESIDialect", "comb::CombDialect", + "seq::SeqDialect"]; } //===----------------------------------------------------------------------===// @@ -428,7 +495,10 @@ def ConvertLLHDToLLVM : Pass<"convert-llhd-to-llvm", "mlir::ModuleOp"> { This pass translates LLHD to LLVM. }]; let constructor = "circt::createConvertLLHDToLLVMPass()"; - let dependentDialects = ["mlir::LLVM::LLVMDialect"]; + let dependentDialects = [ + "mlir::arith::ArithDialect", + "mlir::LLVM::LLVMDialect" + ]; } //===----------------------------------------------------------------------===// @@ -444,19 +514,6 @@ def ConvertHWToLLVM : Pass<"convert-hw-to-llvm", "mlir::ModuleOp"> { let dependentDialects = ["mlir::LLVM::LLVMDialect"]; } -//===----------------------------------------------------------------------===// -// CombToLLVM -//===----------------------------------------------------------------------===// - -def ConvertCombToLLVM : Pass<"convert-comb-to-llvm", "mlir::ModuleOp"> { - let summary = "Convert Comb to LLVM"; - let description = [{ - This pass translates Comb to LLVM. - }]; - let constructor = "circt::createConvertCombToLLVMPass()"; - let dependentDialects = ["mlir::LLVM::LLVMDialect"]; -} - //===----------------------------------------------------------------------===// // HWArithToHW //===----------------------------------------------------------------------===// @@ -498,12 +555,39 @@ def ConvertHWToSystemC : Pass<"convert-hw-to-systemc", "mlir::ModuleOp"> { } //===----------------------------------------------------------------------===// -// StandardToHandshake +// HWToSV +//===----------------------------------------------------------------------===// + +def LowerHWToSV : Pass<"lower-hw-to-sv", "hw::HWModuleOp"> { + let summary = "Convert HW to SV"; + let description = [{ + This pass converts various HW contructs to SV. + }]; + let constructor = "circt::createLowerHWToSVPass()"; + let dependentDialects = ["sv::SVDialect"]; +} + +//===----------------------------------------------------------------------===// +// VerifToSV //===----------------------------------------------------------------------===// -def StandardToHandshake : Pass<"lower-std-to-handshake", "mlir::ModuleOp"> { - let summary = "Lower Standard MLIR into Handshake IR"; - let constructor = "circt::createStandardToHandshakePass()"; +def LowerVerifToSV : Pass<"lower-verif-to-sv", "hw::HWModuleOp"> { + let summary = "Convert Verif to SV"; + let description = [{ + This pass converts various Verif contructs to SV. + }]; + let constructor = "circt::createLowerVerifToSVPass()"; + let dependentDialects = [ + "sv::SVDialect", "hw::HWDialect", "comb::CombDialect"]; +} + +//===----------------------------------------------------------------------===// +// CFToHandshake +//===----------------------------------------------------------------------===// + +def CFToHandshake : Pass<"lower-cf-to-handshake", "mlir::ModuleOp"> { + let summary = "Lower func and CF into Handshake IR"; + let constructor = "circt::createCFToHandshakePass()"; let dependentDialects = ["handshake::HandshakeDialect"]; let options = [Option<"sourceConstants", "source-constants", "bool", "false", @@ -520,36 +604,6 @@ def HandshakeRemoveBlock : Pass<"handshake-remove-block-structure", "handshake:: let constructor = "circt::createHandshakeRemoveBlockPass()"; } -def InsertMergeBlocks : Pass<"insert-merge-blocks", "::mlir::ModuleOp"> { - let summary = "Insert explicit merge blocks"; - let description = [{ - This pass inserts additional merge blocks for each block with more than - two successors. A merge block is a block that only contains one operation, - a terminator, and has two predecessors. - The order successors are merged together mirrors the order different control - paths are created. Thus, each block with two successors will have a corresponding - merge block. - - This pass brings the CFG into a canonical form for further transformations. - - Treats loops and sub-CFGs with irregular control flow like single blocks. - }]; - let constructor = "circt::createInsertMergeBlocksPass()"; - let dependentDialects = ["mlir::cf::ControlFlowDialect", "mlir::func::FuncDialect"]; -} - -def MaximizeSSA : Pass<"maximize-ssa", "::mlir::ModuleOp"> { - let summary = "Convert every function in the module into maximal SSA form"; - let description = [{ - Convert the region within every function into maximal SSA form. This - ensures that every value used within a block is also defined within the - block, making dataflow explicit and removing block dominance-based dataflow - semantics. The pass achieves this by adding block arguments wherever - necessary to forward values to the block(s) where they are used. - }]; - let constructor = "circt::createMaximizeSSAPass()"; -} - //===----------------------------------------------------------------------===// // ConvertToArcs //===----------------------------------------------------------------------===// @@ -576,4 +630,54 @@ def ConvertCombToArith : Pass<"convert-comb-to-arith", "mlir::ModuleOp"> { let dependentDialects = ["mlir::arith::ArithDialect"]; } +//===----------------------------------------------------------------------===// +// ConvertArcToLLVM +//===----------------------------------------------------------------------===// + +def LowerArcToLLVM : Pass<"lower-arc-to-llvm", "mlir::ModuleOp"> { + let summary = "Lower state transfer arc representation to LLVM"; + let constructor = "circt::createLowerArcToLLVMPass()"; + let dependentDialects = [ + "arc::ArcDialect", + "mlir::cf::ControlFlowDialect", + "mlir::LLVM::LLVMDialect", + "mlir::scf::SCFDialect", + "mlir::func::FuncDialect" + ]; +} + +//===----------------------------------------------------------------------===// +// ConvertSeqToSV +//===----------------------------------------------------------------------===// + +def LowerSeqToSV: Pass<"lower-seq-to-sv", "mlir::ModuleOp"> { + let summary = "Lower sequential firrtl ops to SV."; + let constructor = "circt::createLowerSeqToSVPass()"; + let dependentDialects = ["circt::sv::SVDialect", "circt::hw::HWDialect"]; + let options = [ + Option<"disableRegRandomization", "disable-reg-randomization", "bool", "false", + "Disable emission of register randomization code">, + Option<"emitSeparateAlwaysBlocks", "emit-separate-always-blocks", "bool", "false", + "Emit assigments to registers in separate always blocks">, + Option<"lowerToAlwaysFF", "lower-to-always-ff", "bool", "true", + "Place assignments to registers into `always_ff` blocks"> + ]; + let statistics = [ + Statistic<"numSubaccessRestored", "num-subaccess-restored", + "Number of lhs subaccess operations restored "> + ]; +} + +def LowerFirMem : Pass<"lower-seq-firmem", "mlir::ModuleOp"> { + let summary = "Lower seq.firmem ops to instances of hw.module.generated ops"; + let constructor = "circt::createLowerFirMemPass()"; + let dependentDialects = ["circt::hw::HWDialect"]; +} + +def LowerSeqFIRRTLInitToSV: Pass<"lower-seq-firrtl-init-to-sv", "mlir::ModuleOp"> { + let summary = "Prep the module with macro definitions for firrtl registers."; + let constructor = "circt::createLowerSeqFIRRTLInitToSV()"; + let dependentDialects = ["circt::sv::SVDialect"]; +} + #endif // CIRCT_CONVERSION_PASSES_TD diff --git a/include/circt/Conversion/PipelineToHW.h b/include/circt/Conversion/PipelineToHW.h index 485159480337..b98747d29560 100644 --- a/include/circt/Conversion/PipelineToHW.h +++ b/include/circt/Conversion/PipelineToHW.h @@ -17,10 +17,14 @@ #include "circt/Support/LLVM.h" #include +#define GEN_PASS_DECL_PIPELINETOHW +#include "circt/Conversion/Passes.h.inc" + namespace circt { /// Create an SCF to Calyx conversion pass. -std::unique_ptr createPipelineToHWPass(); +std::unique_ptr +createPipelineToHWPass(const PipelineToHWOptions &options = {}); } // namespace circt diff --git a/include/circt/Conversion/SeqToSV.h b/include/circt/Conversion/SeqToSV.h new file mode 100644 index 000000000000..98b7cec32cc8 --- /dev/null +++ b/include/circt/Conversion/SeqToSV.h @@ -0,0 +1,31 @@ +//===- SeqToSV.h - SV conversion for seq ops ----------------===-*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares passes which lower `seq` to `sv` and `hw`. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_SEQTOSV_H +#define CIRCT_CONVERSION_SEQTOSV_H + +#include "circt/Support/LLVM.h" +#include + +namespace circt { + +#define GEN_PASS_DECL_LOWERSEQTOSV +#include "circt/Conversion/Passes.h.inc" + +std::unique_ptr +createLowerSeqToSVPass(const LowerSeqToSVOptions &options = {}); +std::unique_ptr createLowerFirMemPass(); +std::unique_ptr createLowerSeqFIRRTLInitToSV(); + +} // namespace circt + +#endif // CIRCT_CONVERSION_SEQTOSV_H diff --git a/include/circt/Conversion/PipelineToCalyx.h b/include/circt/Conversion/VerifToSV.h similarity index 52% rename from include/circt/Conversion/PipelineToCalyx.h rename to include/circt/Conversion/VerifToSV.h index 48577e885633..2b9313b66d5f 100644 --- a/include/circt/Conversion/PipelineToCalyx.h +++ b/include/circt/Conversion/VerifToSV.h @@ -1,4 +1,4 @@ -//===- PipelineToCalyx.h - Pipeline to Calyx pass entry point -----------*-===// +//===- VerifToSV.h - Verif to SV pass entry point ---------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,23 +6,25 @@ // //===----------------------------------------------------------------------===// // -// This header file defines prototypes that expose the PipelineToCalyx pass +// This header file defines prototypes that expose the VerifToSV pass // constructor. // //===----------------------------------------------------------------------===// -#ifndef CIRCT_CONVERSION_PIPELINETOCALYX_H -#define CIRCT_CONVERSION_PIPELINETOCALYX_H +#ifndef CIRCT_CONVERSION_VERIFTOSV_H +#define CIRCT_CONVERSION_VERIFTOSV_H #include "circt/Support/LLVM.h" -#include "mlir/Dialect/SCF/IR/SCF.h" #include namespace circt { +namespace hw { +class HWModuleOp; +} // namespace hw -/// Create a Pipeline to Calyx conversion pass. -std::unique_ptr> createPipelineToCalyxPass(); +/// Create the Verif to SV conversion pass. +std::unique_ptr> createLowerVerifToSVPass(); } // namespace circt -#endif // CIRCT_CONVERSION_PIPELINETOCALYX_H +#endif // CIRCT_CONVERSION_VERIFTOSV_H diff --git a/include/circt/Dialect/Arc/Arc.td b/include/circt/Dialect/Arc/Arc.td index d3920fe65295..df27f25c2251 100644 --- a/include/circt/Dialect/Arc/Arc.td +++ b/include/circt/Dialect/Arc/Arc.td @@ -11,8 +11,8 @@ include "mlir/IR/OpBase.td" -include "circt/Dialect/Arc/Dialect.td" -include "circt/Dialect/Arc/Types.td" -include "circt/Dialect/Arc/Ops.td" +include "circt/Dialect/Arc/ArcDialect.td" +include "circt/Dialect/Arc/ArcTypes.td" +include "circt/Dialect/Arc/ArcOps.td" #endif // CIRCT_DIALECT_ARC_ARC_TD diff --git a/include/circt/Dialect/Arc/Dialect.h b/include/circt/Dialect/Arc/ArcDialect.h similarity index 68% rename from include/circt/Dialect/Arc/Dialect.h rename to include/circt/Dialect/Arc/ArcDialect.h index c6f4f854b06f..bee9ce3409b0 100644 --- a/include/circt/Dialect/Arc/Dialect.h +++ b/include/circt/Dialect/Arc/ArcDialect.h @@ -1,4 +1,4 @@ -//===- Dialect.h - Arc dialect definition -----------------------*- C++ -*-===// +//===- ArcDialect.h - Arc dialect definition --------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_ARC_DIALECT_H -#define CIRCT_DIALECT_ARC_DIALECT_H +#ifndef CIRCT_DIALECT_ARC_ARCDIALECT_H +#define CIRCT_DIALECT_ARC_ARCDIALECT_H #include "circt/Support/LLVM.h" #include "mlir/IR/BuiltinOps.h" @@ -15,5 +15,6 @@ // Pull in the dialect definition. #include "circt/Dialect/Arc/ArcDialect.h.inc" +#include "circt/Dialect/Arc/ArcEnums.h.inc" -#endif // CIRCT_DIALECT_ARC_DIALECT_H +#endif // CIRCT_DIALECT_ARC_ARCDIALECT_H diff --git a/include/circt/Dialect/Arc/Dialect.td b/include/circt/Dialect/Arc/ArcDialect.td similarity index 63% rename from include/circt/Dialect/Arc/Dialect.td rename to include/circt/Dialect/Arc/ArcDialect.td index bfd37b8f0adf..efe78b4a83f5 100644 --- a/include/circt/Dialect/Arc/Dialect.td +++ b/include/circt/Dialect/Arc/ArcDialect.td @@ -1,4 +1,4 @@ -//===- Dialect.td - Arc dialect definition -----------------*- tablegen -*-===// +//===- ArcDialect.td - Arc dialect definition --------------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_ARC_DIALECT_TD -#define CIRCT_DIALECT_ARC_DIALECT_TD +#ifndef CIRCT_DIALECT_ARC_ARCDIALECT_TD +#define CIRCT_DIALECT_ARC_ARCDIALECT_TD def ArcDialect : Dialect { let name = "arc"; @@ -17,12 +17,17 @@ def ArcDialect : Dialect { in a circuit. }]; let cppNamespace = "circt::arc"; + let dependentDialects = ["circt::hw::HWDialect", "circt::seq::SeqDialect"]; + let hasConstantMaterializer = 1; let useDefaultTypePrinterParser = 1; + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + let extraClassDeclaration = [{ void registerTypes(); }]; } -#endif // CIRCT_DIALECT_ARC_DIALECT_TD +#endif // CIRCT_DIALECT_ARC_ARCDIALECT_TD diff --git a/include/circt/Dialect/Arc/ArcInterfaces.h b/include/circt/Dialect/Arc/ArcInterfaces.h new file mode 100644 index 000000000000..879752ff67e6 --- /dev/null +++ b/include/circt/Dialect/Arc/ArcInterfaces.h @@ -0,0 +1,52 @@ +//===- ArcInterfaces.h ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides registration functions for all external interfaces. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCINTERFACES_H +#define CIRCT_DIALECT_ARC_ARCINTERFACES_H + +#include "mlir/IR/DialectInterface.h" + +// Forward declarations. +namespace mlir { +class DialectRegistry; +} // namespace mlir + +namespace circt { +namespace arc { + +void registerCombRuntimeCostEstimateInterface(mlir::DialectRegistry ®istry); +void registerHWRuntimeCostEstimateInterface(mlir::DialectRegistry ®istry); +void registerSCFRuntimeCostEstimateInterface(mlir::DialectRegistry ®istry); + +inline void initAllExternalInterfaces(mlir::DialectRegistry ®istry) { + registerCombRuntimeCostEstimateInterface(registry); + registerHWRuntimeCostEstimateInterface(registry); + registerSCFRuntimeCostEstimateInterface(registry); +} + +/// A dialect interface to get runtime cost estimates of MLIR operations. This +/// is useful for implementing heuristics in optimization passes. +class RuntimeCostEstimateDialectInterface + : public mlir::DialectInterface::Base { +public: + RuntimeCostEstimateDialectInterface(mlir::Dialect *dialect) : Base(dialect) {} + + /// Returns a number indicating the expected number of cycles the given + /// operation will take to execute on hardware times 10 (to allow a bit more + /// fine tuning for high-throughput operations) + virtual uint32_t getCostEstimate(mlir::Operation *op) const = 0; +}; + +} // namespace arc +} // namespace circt + +#endif // CIRCT_DIALECT_ARC_ARCINTERFACES_H diff --git a/include/circt/Dialect/Arc/ArcInterfaces.td b/include/circt/Dialect/Arc/ArcInterfaces.td new file mode 100644 index 000000000000..7eb3e211a72a --- /dev/null +++ b/include/circt/Dialect/Arc/ArcInterfaces.td @@ -0,0 +1,51 @@ +//===- ArcInterfaces.td - Interfaces used in Arc -----------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCINTERFACES_TD +#define CIRCT_DIALECT_ARC_ARCINTERFACES_TD + +include "mlir/IR/OpBase.td" +include "mlir/Interfaces/CallInterfaces.td" + +def ClockedOpInterface : OpInterface<"ClockedOpInterface"> { + let description = [{ + This interface should be implemented by operations that have clocked + behavior. Don't use this interface for operations that are not themselves + clocked but only define a clocked region. + }]; + let cppNamespace = "::circt::arc"; + + let methods = [ + InterfaceMethod<[{ + For operations that are only clocked under dynamic conditions. Ideally, + this should not exist as a conditionally clocked operation can ussually + be split into two operations (one clocked, one non-clocked) as it makes + using this interface more complicated. + }], + "bool", "isClocked", (ins), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ return true; }]>, + InterfaceMethod<[{ + Returns the SSA value representing the clock signal. It is valid to + return a null value if the operation is inside a clocked region and thus + the clock is defined by the operation with the clocked region. + }], + "::mlir::Value", "getClock", (ins), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ return $_op.getClock(); }]>, + InterfaceMethod<[{ + Removes the clock value, e.g., used when moving a clocked operation into + a clocked region. + }], + "void", "eraseClock", (ins), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ return $_op.getClockMutable().clear(); }]>, + ]; +} + +#endif // CIRCT_DIALECT_ARC_ARCINTERFACES_TD diff --git a/include/circt/Dialect/Arc/Ops.h b/include/circt/Dialect/Arc/ArcOps.h similarity index 58% rename from include/circt/Dialect/Arc/Ops.h rename to include/circt/Dialect/Arc/ArcOps.h index cd14a5468058..f822322c7104 100644 --- a/include/circt/Dialect/Arc/Ops.h +++ b/include/circt/Dialect/Arc/ArcOps.h @@ -1,4 +1,4 @@ -//===- Ops.h - Arc dialect operations ---------------------------*- C++ -*-===// +//===- ArcOps.h - Arc dialect operations ------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,21 +6,25 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_ARC_OPS_H -#define CIRCT_DIALECT_ARC_OPS_H +#ifndef CIRCT_DIALECT_ARC_ARCOPS_H +#define CIRCT_DIALECT_ARC_ARCOPS_H -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/RegionKindInterface.h" #include "mlir/IR/SymbolTable.h" #include "mlir/Interfaces/CallInterfaces.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" -#include "circt/Dialect/Arc/Dialect.h" -#include "circt/Dialect/Arc/Types.h" +#include "circt/Dialect/Arc/ArcDialect.h" +#include "circt/Dialect/Arc/ArcTypes.h" +#include "circt/Dialect/Seq/SeqTypes.h" + +#include "circt/Dialect/Arc/ArcInterfaces.h.inc" #define GET_OP_CLASSES #include "circt/Dialect/Arc/Arc.h.inc" -#endif // CIRCT_DIALECT_ARC_OPS_H +#endif // CIRCT_DIALECT_ARC_ARCOPS_H diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td new file mode 100644 index 000000000000..ddfe77746586 --- /dev/null +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -0,0 +1,804 @@ +//===- ArcOps.td - Arc dialect operations ------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCOPS_TD +#define CIRCT_DIALECT_ARC_ARCOPS_TD + +include "circt/Dialect/Arc/ArcDialect.td" +include "circt/Dialect/Arc/ArcTypes.td" +include "circt/Dialect/Seq/SeqTypes.td" +include "circt/Dialect/Arc/ArcInterfaces.td" +include "mlir/IR/EnumAttr.td" +include "mlir/Interfaces/FunctionInterfaces.td" +include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/RegionKindInterface.td" +include "mlir/IR/SymbolInterfaces.td" +include "mlir/Interfaces/CallInterfaces.td" +include "mlir/Interfaces/ControlFlowInterfaces.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +class ArcOp traits = []> : + Op; + +def DefineOp : ArcOp<"define", [ + IsolatedFromAbove, + FunctionOpInterface, + Symbol, + RegionKindInterface, + SingleBlockImplicitTerminator<"arc::OutputOp">, + HasParent<"mlir::ModuleOp"> +]> { + let summary = "State transfer arc definition"; + let arguments = (ins + SymbolNameAttr:$sym_name, + TypeAttrOf:$function_type, + OptionalAttr:$arg_attrs, + OptionalAttr:$res_attrs + ); + let results = (outs); + let regions = (region SizedRegion<1>:$body); + let hasCustomAssemblyFormat = 1; + + let hasRegionVerifier = 1; + + let builders = [ + OpBuilder<(ins "mlir::StringAttr":$sym_name, + "mlir::TypeAttr":$function_type), [{ + build($_builder, $_state, sym_name, function_type, mlir::ArrayAttr(), + mlir::ArrayAttr()); + }]>, + OpBuilder<(ins "mlir::StringRef":$sym_name, + "mlir::FunctionType":$function_type), [{ + build($_builder, $_state, sym_name, function_type, mlir::ArrayAttr(), + mlir::ArrayAttr()); + }]>, + ]; + + let extraClassDeclaration = [{ + static mlir::RegionKind getRegionKind(unsigned index) { + return mlir::RegionKind::SSACFG; + } + + mlir::Block &getBodyBlock() { return getBody().front(); } + + // Get the arc's symbolic name. + mlir::StringAttr getNameAttr() { + return (*this)->getAttrOfType( + ::mlir::SymbolTable::getSymbolAttrName()); + } + + // Get the arc's symbolic name. + mlir::StringRef getName() { + return getNameAttr().getValue(); + } + + /// Returns the argument types of this function. + mlir::ArrayRef getArgumentTypes() { + return getFunctionType().getInputs(); + } + + /// Returns the result types of this function. + mlir::ArrayRef getResultTypes() { + return getFunctionType().getResults(); + } + + /// Verify the type attribute of this function. Returns failure and emits + /// an error if the attribute is invalid. + mlir::LogicalResult verifyType() { + auto type = getFunctionTypeAttr().getValue(); + if (!type.isa()) + return emitOpError("requires '") << getFunctionTypeAttrName() << + "' attribute of function type"; + return mlir::success(); + } + + /// Returns true if the arc returns the inputs directly and in the same + /// order, otherwise false. + bool isPassthrough(); + + //===------------------------------------------------------------------===// + // CallableOpInterface + //===------------------------------------------------------------------===// + + mlir::Region *getCallableRegion() { return &getBody(); } + }]; +} + +def OutputOp : ArcOp<"output", [ + Terminator, + ParentOneOf<["DefineOp", "LutOp", "ClockDomainOp"]>, + Pure, + ReturnLike +]> { + let summary = "Arc terminator"; + let arguments = (ins Variadic:$outputs); + let assemblyFormat = [{ + attr-dict ($outputs^ `:` qualified(type($outputs)))? + }]; + let builders = [OpBuilder<(ins), [{ + build($_builder, $_state, std::nullopt); + }]>]; + let hasVerifier = 1; +} + +def StateOp : ArcOp<"state", [ + MemRefsNormalizable, + CallOpInterface, + DeclareOpInterfaceMethods, + AttrSizedOperandSegments, + DeclareOpInterfaceMethods, +]> { + let summary = "State transfer arc"; + + let arguments = (ins + FlatSymbolRefAttr:$arc, + Optional:$clock, + Optional:$enable, + Optional:$reset, + I32Attr:$latency, + Variadic:$inputs); + let results = (outs Variadic:$outputs); + + let assemblyFormat = [{ + $arc `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)? + (`reset` $reset^)? `lat` $latency attr-dict + `:` functional-type($inputs, results) + }]; + + let hasFolder = 1; + let hasCanonicalizeMethod = 1; + + let builders = [ + OpBuilder<(ins "DefineOp":$arc, "mlir::Value":$clock, "mlir::Value":$enable, + "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs), [{ + build($_builder, $_state, mlir::SymbolRefAttr::get(arc), + arc.getFunctionType().getResults(), clock, enable, latency, + inputs); + }]>, + OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, + "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, + CArg<"mlir::ValueRange", "{}">:$inputs + ), [{ + build($_builder, $_state, arc, results, clock, enable, Value(), latency, + inputs); + }]>, + OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, + "mlir::Value":$clock, "mlir::Value":$enable, "mlir::Value":$reset, + "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs + ), [{ + if (clock) + $_state.addOperands(clock); + if (enable) + $_state.addOperands(enable); + if (reset) + $_state.addOperands(reset); + $_state.addOperands(inputs); + $_state.addAttribute("arc", arc); + $_state.addAttribute("latency", $_builder.getI32IntegerAttr(latency)); + $_state.addAttribute(getOperandSegmentSizeAttr(), + $_builder.getDenseI32ArrayAttr({ + clock ? 1 : 0, + enable ? 1 : 0, + reset ? 1 : 0, + static_cast(inputs.size())})); + $_state.addTypes(results); + }]>, + OpBuilder<(ins "mlir::StringAttr":$arc, "mlir::TypeRange":$results, + "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, + CArg<"mlir::ValueRange", "{}">:$inputs + ), [{ + build($_builder, $_state, mlir::SymbolRefAttr::get(arc), results, clock, + enable, latency, inputs); + }]>, + OpBuilder<(ins "mlir::StringRef":$arc, "mlir::TypeRange":$results, + "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, + CArg<"mlir::ValueRange", "{}">:$inputs + ), [{ + build($_builder, $_state, + mlir::StringAttr::get($_builder.getContext(), arc), + results, clock, enable, latency, inputs); + }]> + ]; + let skipDefaultBuilders = 1; + let hasVerifier = 1; + + let extraClassDeclaration = [{ + operand_range getArgOperands() { + return getInputs(); + } + MutableOperandRange getArgOperandsMutable() { + return getInputsMutable(); + } + + mlir::CallInterfaceCallable getCallableForCallee() { + return (*this)->getAttrOfType("arc"); + } + + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + (*this)->setAttr(getArcAttrName(), callee.get()); + } + }]; +} + +def CallOp : ArcOp<"call", [ + MemRefsNormalizable, Pure, + CallOpInterface, + DeclareOpInterfaceMethods +]> { + let summary = "calls an arc"; + + let arguments = (ins FlatSymbolRefAttr:$arc, Variadic:$inputs); + let results = (outs Variadic:$outputs); + + let assemblyFormat = [{ + $arc `(` $inputs `)` attr-dict `:` functional-type(operands, results) + }]; + + let extraClassDeclaration = [{ + operand_range getArgOperands() { + return {operand_begin(), operand_end()}; + } + MutableOperandRange getArgOperandsMutable() { + return getInputsMutable(); + } + + mlir::CallInterfaceCallable getCallableForCallee() { + return (*this)->getAttrOfType("arc"); + } + + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + (*this)->setAttr(getArcAttrName(), callee.get()); + } + }]; +} + +def ClockGateOp : ArcOp<"clock_gate", [Pure]> { + let summary = "Clock gate"; + let arguments = (ins ClockType:$input, I1:$enable); + let results = (outs ClockType:$output); + let assemblyFormat = [{ + $input `,` $enable attr-dict + }]; +} + +def MemoryOp : ArcOp<"memory", [MemoryEffects<[MemAlloc]>]> { + let summary = "Memory"; + let results = (outs MemoryType:$memory); + let assemblyFormat = "type($memory) attr-dict"; +} + +class MemoryAndDataTypesMatch : TypesMatchWith< + "memory and data types must match", mem, data, + "$_self.cast().getWordType()">; +class MemoryAndAddressTypesMatch : TypesMatchWith< + "memory and address types must match", mem, address, + "$_self.cast().getAddressType()">; + +def MemoryReadPortOp : ArcOp<"memory_read_port", [ + Pure, + MemoryAndDataTypesMatch<"memory", "data">, + MemoryAndAddressTypesMatch<"memory", "address"> +]> { + let summary = "Read port from a memory"; + let description = [{ + Represents a combinatorial memory read port. No memory read side-effect + trait is necessary because at the stage of the Arc lowering where this + operation is legal to be present, it is guaranteed that all reads from the + same address produce the same output. This is because all writes are + reordered to happen at the end of the cycle in LegalizeStateUpdates (or + alternatively produce the necessary temporaries). + }]; + let arguments = (ins + MemoryType:$memory, + AnyInteger:$address + ); + let results = (outs AnyInteger:$data); + + let assemblyFormat = [{ + $memory `[` $address `]` attr-dict `:` type($memory) + }]; +} + +def MemoryWritePortOp : ArcOp<"memory_write_port", [ + MemoryEffects<[MemWrite]>, + CallOpInterface, + DeclareOpInterfaceMethods, + AttrSizedOperandSegments, + ClockedOpInterface +]> { + let summary = "Write port to a memory"; + let arguments = (ins + MemoryType:$memory, + FlatSymbolRefAttr:$arc, + Variadic:$inputs, + Optional:$clock, + UnitAttr:$enable, + UnitAttr:$mask, + DefaultValuedAttr:$latency + ); + + let assemblyFormat = [{ + $memory `,` $arc `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)? + (`mask` $mask^)? `lat` $latency attr-dict `:` + type($memory) `,` type($inputs) + }]; + + let hasVerifier = 1; + + let extraClassDeclaration = [{ + SmallVector getArcResultTypes(); + static unsigned getAddressIdx() { return 0; } + static unsigned getDataIdx() { return 1; } + static unsigned getEnableIdx() { return 2; } + static unsigned getMaskIdx(bool hasEnable) { return hasEnable ? 3 : 2; } + + operand_range getArgOperands() { + return getInputs(); + } + MutableOperandRange getArgOperandsMutable() { + return getInputsMutable(); + } + + mlir::CallInterfaceCallable getCallableForCallee() { + return (*this)->getAttrOfType("arc"); + } + + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + (*this)->setAttr(getArcAttrName(), callee.get()); + } + }]; +} + +def MemoryReadOp : ArcOp<"memory_read", [ + MemoryEffects<[MemRead]>, + MemoryAndDataTypesMatch<"memory", "data">, + MemoryAndAddressTypesMatch<"memory", "address"> +]> { + let summary = "Read word from memory"; + let arguments = (ins + MemoryType:$memory, + AnyInteger:$address + ); + let results = (outs AnyInteger:$data); + + let assemblyFormat = [{ + $memory `[` $address `]` attr-dict `:` type($memory) + }]; +} + +def MemoryWriteOp : ArcOp<"memory_write", [ + MemoryEffects<[MemWrite]>, + MemoryAndDataTypesMatch<"memory", "data">, + MemoryAndAddressTypesMatch<"memory", "address"> +]> { + let summary = "Write word to memory"; + let arguments = (ins + MemoryType:$memory, + AnyInteger:$address, + Optional:$enable, + AnyInteger:$data + ); + + let assemblyFormat = [{ + $memory `[` $address `]` `,` $data (`if` $enable^)? + attr-dict `:` type($memory) + }]; + + let hasFolder = 1; + let hasCanonicalizeMethod = 1; +} + +//===----------------------------------------------------------------------===// +// Trigger Grouping +//===----------------------------------------------------------------------===// + +def ClockDomainOp : ArcOp<"clock_domain", [ + IsolatedFromAbove, + RegionKindInterface, + RecursiveMemoryEffects, + SingleBlockImplicitTerminator<"arc::OutputOp"> +]> { + let summary = "a clock domain"; + + let arguments = (ins Variadic:$inputs, ClockType:$clock); + let results = (outs Variadic:$outputs); + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + ` ` `(` $inputs `)` `clock` $clock attr-dict `:` + functional-type($inputs, results) $body + }]; + + let extraClassDeclaration = [{ + static mlir::RegionKind getRegionKind(unsigned index) { + return mlir::RegionKind::Graph; + } + mlir::Block &getBodyBlock() { return getBody().front(); } + }]; + + let hasRegionVerifier = 1; + let hasCanonicalizeMethod = 1; +} + +def ClockTreeOp : ArcOp<"clock_tree", [NoTerminator, NoRegionArguments]> { + let summary = "A clock tree"; + let arguments = (ins I1:$clock); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ + $clock attr-dict-with-keyword $body + }]; + let extraClassDeclaration = [{ + mlir::Block &getBodyBlock() { return getBody().front(); } + }]; +} + +def PassThroughOp : ArcOp<"passthrough", [NoTerminator, NoRegionArguments]> { + let summary = "Clock-less logic that is on the pass-through path"; + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ + attr-dict-with-keyword $body + }]; + let extraClassDeclaration = [{ + mlir::Block &getBodyBlock() { return getBody().front(); } + }]; +} + +//===----------------------------------------------------------------------===// +// Storage Allocation +//===----------------------------------------------------------------------===// + +def AllocStateOp : ArcOp<"alloc_state", [MemoryEffects<[MemAlloc]>]> { + let summary = "Allocate internal state"; + let arguments = (ins StorageType:$storage, UnitAttr:$tap); + let results = (outs StateType:$state); + let assemblyFormat = [{ + $storage (`tap` $tap^)? attr-dict `:` functional-type($storage, $state) + }]; +} + +def AllocMemoryOp : ArcOp<"alloc_memory", [MemoryEffects<[MemAlloc]>]> { + let summary = "Allocate a memory"; + let arguments = (ins StorageType:$storage); + let results = (outs MemoryType:$memory); + let assemblyFormat = [{ + $storage attr-dict `:` functional-type($storage, $memory) + }]; +} + +def AllocStorageOp : ArcOp<"alloc_storage", [MemoryEffects<[MemAlloc]>]> { + let summary = "Allocate contiguous storage space from a larger storage space"; + let arguments = (ins StorageType:$input, OptionalAttr:$offset); + let results = (outs StorageType:$output); + let assemblyFormat = [{ + $input (`[` $offset^ `]`)? attr-dict `:` functional-type($input, $output) + }]; +} + +def RootInputOp : ArcOp<"root_input", [ + DeclareOpInterfaceMethods +]> { + let summary = "A root input"; + let arguments = (ins StrAttr:$name, StorageType:$storage); + let results = (outs StateType:$state); + let assemblyFormat = [{ + $name `,` $storage attr-dict `:` functional-type($storage, $state) + }]; +} + +def RootOutputOp : ArcOp<"root_output", [ + DeclareOpInterfaceMethods +]> { + let summary = "A root output"; + let arguments = (ins StrAttr:$name, StorageType:$storage); + let results = (outs StateType:$state); + let assemblyFormat = [{ + $name `,` $storage attr-dict `:` functional-type($storage, $state) + }]; +} + +//===----------------------------------------------------------------------===// +// Storage Access +//===----------------------------------------------------------------------===// + +def AllocatableType : AnyTypeOf<[StateType, MemoryType, StorageType]>; + +def StorageGetOp : ArcOp<"storage.get", [Pure]> { + let summary = "Access an allocated state, memory, or storage slice"; + let arguments = (ins StorageType:$storage, I32Attr:$offset); + let results = (outs AllocatableType:$result); + let assemblyFormat = [{ + $storage `[` $offset `]` attr-dict + `:` qualified(type($storage)) `->` type($result) + }]; + let hasCanonicalizeMethod = 1; +} + +//===----------------------------------------------------------------------===// +// State Read/Write +//===----------------------------------------------------------------------===// + +class StateAndValueTypesMatch : TypesMatchWith< + "state and value types must match", state, value, + "$_self.cast().getType()">; + +def StateReadOp : ArcOp<"state_read", [ + MemoryEffects<[MemRead]>, + StateAndValueTypesMatch<"state", "value"> +]> { + let summary = "Get a state's current value"; + let arguments = (ins StateType:$state); + let results = (outs AnyInteger:$value); + let assemblyFormat = [{ + $state attr-dict `:` type($state) + }]; +} + +def StateWriteOp : ArcOp<"state_write", [ + MemoryEffects<[MemWrite]>, + StateAndValueTypesMatch<"state", "value"> +]> { + let summary = "Update a state's value"; + let description = [{ + Changes the value of a state. This operation is treated as a deferred + assignment by most transformation passes, which allows them to change the + order of `arc.state_read` and `arc.state_write` ops on the same state + without affecting the correctness of the model. The reads are always assumed + to produce the current value of the state and writes to be deferred until + all operations in the model have been executed for the current time step. + + The only exceptions to this are the state update legalization pass, which + inserts the necessary temporary variables such that writes can be performed + immediately without affecting correctness. This allows later lowering passes + to treat `arc.state_write` as an immediate assignment (without defering). + }]; + let arguments = (ins StateType:$state, AnyInteger:$value, + Optional:$condition); + let assemblyFormat = [{ + $state `=` $value (`if` $condition^)? attr-dict `:` type($state) + }]; +} + +//===----------------------------------------------------------------------===// +// Miscellaneous +//===----------------------------------------------------------------------===// + +def TapOp : ArcOp<"tap"> { + let summary = "A tracker op to observe a value under a given name"; + let arguments = (ins AnySignlessInteger:$value, StrAttr:$name); + let assemblyFormat = [{ $value attr-dict `:` type($value) }]; +} + +def ModelOp : ArcOp<"model", [RegionKindInterface, IsolatedFromAbove, + NoTerminator]> { + let summary = "A model with stratified clocks"; + let arguments = (ins StrAttr:$name); + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + $name attr-dict-with-keyword $body + }]; + + let extraClassDeclaration = [{ + static mlir::RegionKind getRegionKind(unsigned index) { + return mlir::RegionKind::Graph; + } + mlir::Block &getBodyBlock() { return getBody().front(); } + }]; + + let hasVerifier = 1; +} + +def LutOp : ArcOp<"lut", [ + IsolatedFromAbove, + SingleBlockImplicitTerminator<"arc::OutputOp">, + Pure +]> { + let summary = "A lookup-table."; + let description = [{ + Represents a lookup-table as one operation. The operations that map the + lookup/input values to the corresponding table-entry are collected inside + the body of this operation. + Note that the operation is marked to be isolated from above to guarantee + that all input values have to be passed as an operand. This allows for + simpler analyses and canonicalizations of the LUT as well as lowering. + Only combinational operations are allowed inside the LUT, i.e., no + side-effects, state, time delays, etc. + }]; + + let arguments = (ins Variadic:$inputs); + let results = (outs AnySignlessInteger:$output); + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + `(` $inputs `)` `:` functional-type($inputs, $output) + attr-dict-with-keyword $body + }]; + + let extraClassDeclaration = [{ + mlir::Block *getBodyBlock() { return &getBody().front(); } + }]; + + let hasVerifier = 1; +} + +def ZeroCountPredicateLeading : I32EnumAttrCase<"leading", 0>; +def ZeroCountPredicateTrailing : I32EnumAttrCase<"trailing", 1>; +def ZeroCountPredicate : I32EnumAttr< + "ZeroCountPredicate", "arc.zero_count predicate", + [ZeroCountPredicateLeading, ZeroCountPredicateTrailing]> { + let cppNamespace = "circt::arc"; +} + +def ZeroCountOp : ArcOp<"zero_count", [Pure, SameOperandsAndResultType]> { + let summary = "leading/trailing zero count operation"; + let arguments = (ins AnySignlessInteger:$input, + ZeroCountPredicate:$predicate); + let results = (outs AnySignlessInteger:$output); + let assemblyFormat = "$predicate $input attr-dict `:` type($input)"; +} + +def IntOr1DVectorOfInt : AnyTypeOf<[ + AnySignlessInteger, + VectorOfRankAndType<[1], [AnySignlessInteger]> +]>; + +def VectorizeOp : ArcOp<"vectorize", [ + IsolatedFromAbove, RecursiveMemoryEffects + ]> { + let summary = "isolated subgraph of operations to be vectorized"; + let description = [{ + This operation represents a vectorized computation DAG. It places a + convenient boundary between the subgraph to be vectorized and the + surrounding non-vectorizable parts of the original graph. + + This allows us to split the vectorization transformations into multiple + parts/passes: + * Finding an initial set of operations to be vectorized + * Optimizing this set by pulling in more operations into the nested block, + splitting it such that the vector width does not exceed a given limit, + applying a cost model and potentially reverting the decision to + vectorize this subgraph (e.g., because not enough ops could be pulled + in) + * Performing the actual vectorization by lowering this operation. This + operation allows to perform the lowering of the boundary and the body + separately and either via 1D `vector` types for SIMD vectorization or + plain integers for manual vectorization within a scalar register. + + For each block argument of the nested block, there is a list of operands + that represent the elements of the vector. If the boundary is already + vectorized each list will only contain a single SSA value of either vector + type or an integer representing the concatenation of all original operands + of that vector. + Only integer types can be vectorized, no arrays or structs are allowed. + + Example: + + Given the following two AND operations in the IR + ```mlir + %0 = arith.and %in0, %in1 : i1 + %1 = arith.and %in2, %in2 : i1 + ``` + they could be vectorized by putting one such AND operation in the body block + of the `arc.vectorize` operation and forwarding the operands accordingly. + ```mlir + %0:2 = arc.vectorize (%in0, %in1), (%in2, %in2) : + (i1, i1, i1, i1) -> (i1, i1) { + ^bb0(%arg0: i1, %arg1: i1): + %1 = arith.and %arg0, %arg1 : i1 + arc.output %1 : i1 + } + ``` + In a next step, the boundary could be lowered/vectorized. This can happen + in terms of integers for vectorization within scalar registers: + ```mlir + %0 = comb.concat %in0, %in1 : i1, i1 + %1 = comb.replicate %in2 : (i1) -> i2 + %2 = arc.vectorize (%0), (%1) : (i2, i2) -> (i2) { + ^bb0(%arg0: i1, %arg1: i1): + %1 = arith.and %arg0, %arg1 : i1 + arc.output %1 : i1 + } + %3 = comb.extract %2 from 1 : (i2) -> i1 + %4 = comb.extract %2 from 0 : (i2) -> i1 + ``` + Or via `vector` types for SIMD vectorization: + ```mlir + %cst = arith.constant dense<0> : vector<2xi1> + %0 = vector.insert %in0, %cst[0] : i1 into vector<2xi1> + %1 = vector.insert %in1, %0[1] : i1 into vector<2xi1> + %2 = vector.broadcast %in2 : i1 to vector<2xi1> + %3 = arc.vectorize (%1), (%2) : + (vector<2xi1>, vector<2xi1>) -> (vector<2xi1>) { + ^bb0(%arg0: i1, %arg1: i1): + %1 = arith.and %arg0, %arg1 : i1 + arc.output %1 : i1 + } + %4 = vector.extract %2[0] : vector<2xi1> + %5 = vector.extract %2[1] : vector<2xi1> + ``` + Alternatively, the body could be vectorized first. Again, as integers + ```mlir + %0:2 = arc.vectorize (%in0, %in1), (%in2, %in2) : + (i1, i1, i1, i1) -> (i1, i1) { + ^bb0(%arg0: i2, %arg1: i2): + %1 = arith.and %arg0, %arg1 : i2 + arc.output %1 : i2 + } + ``` + or SIMD vectors. + ```mlir + %0:2 = arc.vectorize (%in0, %in1), (%in2, %in3) : + (i1, i1, i1, i1) -> (i1, i1) { + ^bb0(%arg0: vector<2xi1>, %arg1: vector<2xi1>): + %1 = arith.and %arg0, %arg1 : vector<2xi1> + arc.output %1 : vector<2xi1> + } + ``` + Once both sides are lowered, the `arc.vectorize` op simply becomes a + passthrough for the operands and can be removed by inlining the nested + block. The integer based vectorization would then look like the following: + ```mlir + %0 = comb.concat %in0, %in1 : i1, i1 + %1 = comb.replicate %in2 : (i1) -> i2 + %2 = arith.and %0, %1 : i2 + %3 = comb.extract %2 from 1 : (i2) -> i1 + %4 = comb.extract %2 from 0 : (i2) -> i1 + ``` + The SIMD vector based lowering would result in the following IR: + ```mlir + %cst = arith.constant dense<0> : vector<2xi1> + %0 = vector.insert %in0, %cst[0] : i1 into vector<2xi1> + %1 = vector.insert %in1, %0[1] : i1 into vector<2xi1> + %2 = vector.broadcast %in2 : i1 to vector<2xi1> + %3 = arith.and %1, %2 : vector<2xi1> + %4 = vector.extract %3[0] : vector<2xi1> + %5 = vector.extract %3[1] : vector<2xi1> + ``` + }]; + + let arguments = (ins + VariadicOfVariadic:$inputs, + DenseI32ArrayAttr:$inputOperandSegments); + let results = (outs Variadic:$results); + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + $inputs attr-dict `:` functional-type($inputs, $results) $body + }]; + + let extraClassDeclaration = [{ + /// Returns whether the `arc.vectorize` boundary is already vectorized. + /// The boundary is vectorized if each input vector has only one element + /// (either of integer or vector type), i.e., the vector itself. + bool isBoundaryVectorized(); + + /// Returns whether the body block of the `arc.vectorize` is already + /// vectorized. The body is vectorized if the boundary is vectorized and + /// the body's argument and result types match those of the boundary, or + /// they are a vectorized version of the boundary types (i.e., a matching + /// vector type or integer with summed-up bitwidth). + bool isBodyVectorized(); + }]; + + let hasVerifier = 1; + let hasRegionVerifier = 1; +} + +def VectorizeReturnOp : ArcOp<"vectorize.return", [ + HasParent<"VectorizeOp">, Pure, ReturnLike, Terminator +]> { + let summary = "arc.vectorized terminator"; + let arguments = (ins IntOr1DVectorOfInt:$value); + let assemblyFormat = "operands attr-dict `:` qualified(type(operands))"; +} + +#endif // CIRCT_DIALECT_ARC_ARCOPS_TD diff --git a/include/circt/Dialect/Arc/ArcPasses.h b/include/circt/Dialect/Arc/ArcPasses.h new file mode 100644 index 000000000000..7a0df05ca3c3 --- /dev/null +++ b/include/circt/Dialect/Arc/ArcPasses.h @@ -0,0 +1,64 @@ +//===- ArcPasses.h - Arc dialect passes -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCPASSES_H +#define CIRCT_DIALECT_ARC_ARCPASSES_H + +#include "mlir/Pass/Pass.h" +#include +#include + +namespace mlir { +class Pass; +} // namespace mlir + +#include "circt/Dialect/Arc/ArcPassesEnums.h.inc" + +namespace circt { +namespace arc { + +#define GEN_PASS_DECL +#include "circt/Dialect/Arc/ArcPasses.h.inc" + +std::unique_ptr +createAddTapsPass(std::optional tapPorts = {}, + std::optional tapWires = {}, + std::optional tapNamedValues = {}); +std::unique_ptr createAllocateStatePass(); +std::unique_ptr createArcCanonicalizerPass(); +std::unique_ptr createDedupPass(); +std::unique_ptr createGroupResetsAndEnablesPass(); +std::unique_ptr +createInferMemoriesPass(std::optional tapPorts = {}); +std::unique_ptr createInferStatePropertiesPass(); +std::unique_ptr createInlineArcsPass(); +std::unique_ptr createInlineModulesPass(); +std::unique_ptr createIsolateClocksPass(); +std::unique_ptr createLatencyRetimingPass(); +std::unique_ptr createLegalizeStateUpdatePass(); +std::unique_ptr createLowerArcsToFuncsPass(); +std::unique_ptr createLowerClocksToFuncsPass(); +std::unique_ptr createLowerLUTPass(); +std::unique_ptr createLowerStatePass(); +std::unique_ptr createLowerVectorizationsPass( + LowerVectorizationsModeEnum mode = LowerVectorizationsModeEnum::Full); +std::unique_ptr createMakeTablesPass(); +std::unique_ptr createMuxToControlFlowPass(); +std::unique_ptr +createPrintStateInfoPass(llvm::StringRef stateFile = ""); +std::unique_ptr createSimplifyVariadicOpsPass(); +std::unique_ptr createSplitLoopsPass(); +std::unique_ptr createStripSVPass(); + +#define GEN_PASS_REGISTRATION +#include "circt/Dialect/Arc/ArcPasses.h.inc" + +} // namespace arc +} // namespace circt + +#endif // CIRCT_DIALECT_ARC_ARCPASSES_H diff --git a/include/circt/Dialect/Arc/ArcPasses.td b/include/circt/Dialect/Arc/ArcPasses.td new file mode 100644 index 000000000000..d1cd6b68a1ad --- /dev/null +++ b/include/circt/Dialect/Arc/ArcPasses.td @@ -0,0 +1,293 @@ +//===- ArcPasses.td - Arc dialect passes -------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCPASSES_TD +#define CIRCT_DIALECT_ARC_ARCPASSES_TD + +include "mlir/IR/EnumAttr.td" +include "mlir/Pass/PassBase.td" + +def AddTaps : Pass<"arc-add-taps", "mlir::ModuleOp"> { + let summary = "Add taps to ports and wires such that they remain observable"; + let constructor = "circt::arc::createAddTapsPass()"; + let dependentDialects = ["arc::ArcDialect", "seq::SeqDialect"]; + let options = [ + Option<"tapPorts", "ports", "bool", "true", "Make module ports observable">, + Option<"tapWires", "wires", "bool", "true", "Make wires observable">, + Option<"tapNamedValues", "named-values", "bool", "false", + "Make values with `sv.namehint` observable"> + ]; +} + +def AllocateState : Pass<"arc-allocate-state", "arc::ModelOp"> { + let summary = "Allocate and layout the global simulation state"; + let constructor = "circt::arc::createAllocateStatePass()"; + let dependentDialects = ["arc::ArcDialect"]; +} + +def ArcCanonicalizer : Pass<"arc-canonicalizer", "mlir::ModuleOp"> { + let summary = "Simulation centric canonicalizations"; + let constructor = "createArcCanonicalizerPass()"; + let dependentDialects = ["hw::HWDialect", + "comb::CombDialect", + "arc::ArcDialect"]; + let statistics = [ + Statistic<"numArcArgsRemoved", "num-arc-args-removed", + "Number of arguments removed from DefineOps">, + ]; +} + +def Dedup : Pass<"arc-dedup", "mlir::ModuleOp"> { + let summary = "Deduplicate identical arc definitions"; + let description = [{ + This pass deduplicates identical arc definitions. If two arcs differ only by + constants, the constants are outlined such that the arc can be deduplicated. + }]; + let constructor = "circt::arc::createDedupPass()"; + let dependentDialects = ["arc::ArcDialect"]; + let statistics = [ + Statistic<"dedupPassNumArcsDeduped", "dedupPassNumArcsDeduped", + "Number of arcs deduped">, + Statistic<"dedupPassTotalOps", "dedupPassTotalOps", + "Total number of ops deduped">, + ]; +} + +def GroupResetsAndEnables : Pass<"arc-group-resets-and-enables", + "mlir::ModuleOp"> { + let summary = "Group reset and enable conditions of lowered states"; + let constructor = "circt::arc::createGroupResetsAndEnablesPass()"; + let dependentDialects = ["arc::ArcDialect", "mlir::scf::SCFDialect"]; +} + +def InferMemories : Pass<"arc-infer-memories", "mlir::ModuleOp"> { + let summary = "Convert `FIRRTL_Memory` instances to dedicated memory ops"; + let constructor = "circt::arc::createInferMemoriesPass()"; + let dependentDialects = [ + "arc::ArcDialect", "comb::CombDialect", "seq::SeqDialect" + ]; + let options = [ + Option<"tapPorts", "tap-ports", "bool", "true", + "Make memory ports observable">, + ]; +} + +def InlineArcs : Pass<"arc-inline" , "mlir::ModuleOp"> { + let summary = "Inline very small arcs"; + let constructor = "circt::arc::createInlineArcsPass()"; + let statistics = [ + Statistic<"numInlinedArcs", "inlined-arcs", "Arcs inlined at a use site">, + Statistic<"numRemovedArcs", "removed-arcs", + "Arcs removed after full inlining">, + Statistic<"numTrivialArcs", "trivial-arcs", "Arcs with very few ops">, + Statistic<"numSingleUseArcs", "single-use-arcs", "Arcs with a single use">, + ]; + let options = [ + Option<"intoArcsOnly", "into-arcs-only", "bool", "false", + "Call operations to inline">, + Option<"maxNonTrivialOpsInBody", "max-body-ops", "unsigned", "3", + "Max number of non-trivial ops in the region to be inlined">, + ]; +} + +def InlineModules : Pass<"arc-inline-modules", "mlir::ModuleOp"> { + let summary = "Eagerly inline private modules"; + let description = [{ + This pass eagerly inlines private HW modules into their instantiation sites. + After outlining combinational logic and registers into arcs, module bodies + become fairly lightweight. Since arc definitions now fulfill the purpose of + code reuse by allowing a single definition to be called multiple times, the + module hierarchy degenerates into a purely cosmetic construct. At that point + it is beneficial to fully flatten the module hierarchy to simplify further + analysis and optimization of state transfer arcs. + }]; + let constructor = "circt::arc::createInlineModulesPass()"; +} + +def InferStateProperties : Pass<"arc-infer-state-properties", + "mlir::ModuleOp"> { + let summary = "Add resets and enables explicitly to the state operations"; + let constructor = "circt::arc::createInferStatePropertiesPass()"; + let dependentDialects = ["circt::hw::HWDialect", "circt::comb::CombDialect"]; +} + +def IsolateClocks : Pass<"arc-isolate-clocks", "mlir::ModuleOp"> { + let summary = "Group clocked operations into clock domains"; + let constructor = "circt::arc::createIsolateClocksPass()"; + let dependentDialects = ["arc::ArcDialect"]; +} + +def LatencyRetiming : Pass<"arc-latency-retiming", "mlir::ModuleOp"> { + let summary = "Push latencies through the design"; + let constructor = "circt::arc::createLatencyRetimingPass()"; + let dependentDialects = ["arc::ArcDialect"]; + + let statistics = [ + Statistic<"numOpsRemoved", "num-ops-removed", + "Number of zero-latency passthrough states removed">, + Statistic<"latencyUnitsSaved", "latency-units-saved", + "Number of latency units saved by merging them in a successor state"> + ]; +} + +def LegalizeStateUpdate : Pass<"arc-legalize-state-update", "mlir::ModuleOp"> { + let summary = "Insert temporaries such that state reads don't see writes"; + let constructor = "circt::arc::createLegalizeStateUpdatePass()"; + let dependentDialects = ["arc::ArcDialect"]; +} + +def LowerArcsToFuncs : Pass<"arc-lower-arcs-to-funcs", "mlir::ModuleOp"> { + let summary = "Lower arc definitions into functions"; + let constructor = "circt::arc::createLowerArcsToFuncsPass()"; + let dependentDialects = ["mlir::func::FuncDialect", "mlir::LLVM::LLVMDialect"]; +} + +def LowerClocksToFuncs : Pass<"arc-lower-clocks-to-funcs", "mlir::ModuleOp"> { + let summary = "Lower clock trees into functions"; + let constructor = "circt::arc::createLowerClocksToFuncsPass()"; + let dependentDialects = ["mlir::func::FuncDialect", "mlir::scf::SCFDialect"]; +} + +def LowerLUT : Pass<"arc-lower-lut", "arc::DefineOp"> { + let summary = "Lowers arc.lut into a comb and hw only representation."; + let constructor = "circt::arc::createLowerLUTPass()"; + let dependentDialects = ["hw::HWDialect", "comb::CombDialect"]; +} + +def LowerState : Pass<"arc-lower-state", "mlir::ModuleOp"> { + let summary = "Split state into read and write ops grouped by clock tree"; + let constructor = "circt::arc::createLowerStatePass()"; + let dependentDialects = [ + "arc::ArcDialect", "mlir::scf::SCFDialect", "mlir::func::FuncDialect", + "mlir::LLVM::LLVMDialect", "comb::CombDialect", "seq::SeqDialect" + ]; +} + +def LowerVectorizationsMode : I32EnumAttr< + "LowerVectorizationsModeEnum", "Lowering Mode", [ + I32EnumAttrCase<"Boundary", 0, "boundary">, + I32EnumAttrCase<"Body", 1, "body">, + I32EnumAttrCase<"InlineBody", 2, "inline-body">, + I32EnumAttrCase<"Full", 3>, + ]> { + let cppNamespace = "circt::arc"; +} + +def LowerVectorizations : Pass<"arc-lower-vectorizations", "mlir::ModuleOp"> { + let summary = "lower `arc.vectorize` operations"; + let description = [{ + This pass lowers `arc.vectorize` operations. By default, the operation will + be fully lowered (i.e., the op disappears in the IR). Alternatively, it can + be partially lowered. + + The "mode" pass option allows to only lower the boundary, only the body, or + only inline the body given that both the boundary and the body are already + lowered. + + The pass supports vectorization within scalar registers and SIMD + vectorization and prioritizes vectorization by packing the vector elements + into a scalar value if it can fit into 64 bits. + + Example: + ```mlir + hw.module @example(%in0: i8, %in1: i8, %in2: i8) -> (out0: i8, out1: i8) { + %0:2 = arc.vectorize (%in0, %in1), (%in2, %in2) : + (i8, i8, i8, i8) -> (i8, i8) { + ^bb0(%arg0: i8, %arg1: i8): + %1 = comb.and %arg0, %arg1 : i8 + arc.vectorize.return %1 : i8 + } + hw.output %0#0, %0#1 : i8, i8 + } + ``` + This piece of IR is lowered to the following fully vectorized IR: + ```mlir + hw.module @example(%in0: i8, %in1: i8, %in2: i8) -> (out0: i8, out1: i8) { + %0 = comb.concat %in0, %in1 : i8, i8 + %1 = comb.concat %in2, %in2 : i8, i8 + %2 = comb.and %0, %1 : i16 + %3 = comb.extract %2 from 0 : (i16) -> i8 + %4 = comb.extract %2 from 8 : (i16) -> i8 + hw.output %3, %4 : i8, i8 + } + ``` + }]; + let constructor = "circt::arc::createLowerVectorizationsPass()"; + + let options = [ + Option<"mode", "mode", "circt::arc::LowerVectorizationsModeEnum", + /*default=*/"circt::arc::LowerVectorizationsModeEnum::Full", + "Select what should be lowered.", + [{::llvm::cl::values( + clEnumValN(circt::arc::LowerVectorizationsModeEnum::Boundary, + "boundary", "Lower boundary only."), + clEnumValN(circt::arc::LowerVectorizationsModeEnum::Body, + "body", "Lower body only."), + clEnumValN(circt::arc::LowerVectorizationsModeEnum::InlineBody, + "inline-body", "Inline already vectorized ops only."), + clEnumValN(circt::arc::LowerVectorizationsModeEnum::Full, + "full", "Perform the complete lowering.") + )}]>, + ]; + + let dependentDialects = [ + "arc::ArcDialect", "circt::comb::CombDialect", "mlir::arith::ArithDialect", + "mlir::vector::VectorDialect", + ]; +} + +def MakeTables : Pass<"arc-make-tables", "mlir::ModuleOp"> { + let summary = "Transform appropriate arc logic into lookup tables"; + let constructor = "circt::arc::createMakeTablesPass()"; + let dependentDialects = ["arc::ArcDialect"]; +} + +def MuxToControlFlow : Pass<"arc-mux-to-control-flow", "mlir::ModuleOp"> { + let summary = "Convert muxes with large independent fan-ins to if-statements"; + let constructor = "circt::arc::createMuxToControlFlowPass()"; + let dependentDialects = ["mlir::scf::SCFDialect"]; +} + +def PrintStateInfo : Pass<"arc-print-state-info", "mlir::ModuleOp"> { + let summary = "Print the state storage layout in JSON format"; + let constructor = "circt::arc::createPrintStateInfoPass()"; + let options = [ + Option<"stateFile", "state-file", "std::string", "", + "Emit file with state description"> + ]; +} + +def SimplifyVariadicOps : Pass<"arc-simplify-variadic-ops", "mlir::ModuleOp"> { + let summary = "Convert variadic ops into distributed binary ops"; + let constructor = "circt::arc::createSimplifyVariadicOpsPass()"; + let statistics = [ + Statistic<"numOpsSkippedMultipleBlocks", "skipped-multiple-blocks", + "Ops skipped due to operands in different blocks">, + Statistic<"numOpsSimplified", "simplified", + "Ops simplified into binary ops">, + Statistic<"numOpsCreated", "created", + "Ops created as part of simplification">, + Statistic<"numOpsReordered", "reordered", + "Ops where simplification reordered operands">, + ]; +} + +def SplitLoops : Pass<"arc-split-loops", "mlir::ModuleOp"> { + let summary = "Split arcs to break zero latency loops"; + let constructor = "circt::arc::createSplitLoopsPass()"; + let dependentDialects = ["arc::ArcDialect"]; +} + +def StripSV : Pass<"arc-strip-sv", "mlir::ModuleOp"> { + let summary = "Remove SV wire, reg, and assigns"; + let constructor = "circt::arc::createStripSVPass()"; + let dependentDialects = ["arc::ArcDialect", "comb::CombDialect", + "hw::HWDialect", "seq::SeqDialect"]; +} + +#endif // CIRCT_DIALECT_ARC_ARCPASSES_TD diff --git a/include/circt/Dialect/Arc/ArcReductions.h b/include/circt/Dialect/Arc/ArcReductions.h new file mode 100644 index 000000000000..77e2823ded64 --- /dev/null +++ b/include/circt/Dialect/Arc/ArcReductions.h @@ -0,0 +1,29 @@ +//===- ArcReductions.h - Arc reduction interface declaration ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCREDUCTIONS_H +#define CIRCT_DIALECT_ARC_ARCREDUCTIONS_H + +#include "circt/Reduce/Reduction.h" + +namespace circt { +namespace arc { + +/// A dialect interface to provide reduction patterns to a reducer tool. +struct ArcReducePatternDialectInterface : public ReducePatternDialectInterface { + using ReducePatternDialectInterface::ReducePatternDialectInterface; + void populateReducePatterns(circt::ReducePatternSet &patterns) const override; +}; + +/// Register the Arc Reduction pattern dialect interface to the given registry. +void registerReducePatternDialectInterface(mlir::DialectRegistry ®istry); + +} // namespace arc +} // namespace circt + +#endif // CIRCT_DIALECT_ARC_ARCREDUCTIONS_H diff --git a/include/circt/Dialect/Arc/Types.h b/include/circt/Dialect/Arc/ArcTypes.h similarity index 71% rename from include/circt/Dialect/Arc/Types.h rename to include/circt/Dialect/Arc/ArcTypes.h index f217e400ff38..89b205607111 100644 --- a/include/circt/Dialect/Arc/Types.h +++ b/include/circt/Dialect/Arc/ArcTypes.h @@ -1,4 +1,4 @@ -//===- Types.h - Arc dialect types ------------------------------*- C++ -*-===// +//===- ArcTypes.h - Arc dialect types ---------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,8 +6,8 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_ARC_TYPES_H -#define CIRCT_DIALECT_ARC_TYPES_H +#ifndef CIRCT_DIALECT_ARC_ARCTYPES_H +#define CIRCT_DIALECT_ARC_ARCTYPES_H #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Types.h" @@ -15,4 +15,4 @@ #define GET_TYPEDEF_CLASSES #include "circt/Dialect/Arc/ArcTypes.h.inc" -#endif // CIRCT_DIALECT_ARC_TYPES_H +#endif // CIRCT_DIALECT_ARC_ARCTYPES_H diff --git a/include/circt/Dialect/Arc/Types.td b/include/circt/Dialect/Arc/ArcTypes.td similarity index 58% rename from include/circt/Dialect/Arc/Types.td rename to include/circt/Dialect/Arc/ArcTypes.td index 7aad71c91432..548cdec433f3 100644 --- a/include/circt/Dialect/Arc/Types.td +++ b/include/circt/Dialect/Arc/ArcTypes.td @@ -1,4 +1,4 @@ -//===- Types.td - Arc dialect types ------------------------*- tablegen -*-===// +//===- ArcTypes.td - Arc dialect types ---------------------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,10 +6,10 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_ARC_TYPES_TD -#define CIRCT_DIALECT_ARC_TYPES_TD +#ifndef CIRCT_DIALECT_ARC_ARCTYPES_TD +#define CIRCT_DIALECT_ARC_ARCTYPES_TD -include "circt/Dialect/Arc/Dialect.td" +include "circt/Dialect/Arc/ArcDialect.td" include "mlir/IR/AttrTypeBase.td" class ArcTypeDef : TypeDef { } @@ -23,13 +23,23 @@ def StateType : ArcTypeDef<"State"> { return $_get(type.getContext(), type); }]> ]; + + let extraClassDeclaration = [{ + unsigned getBitWidth() { return getType().getWidth(); } + unsigned getByteWidth() { return (getBitWidth() + 7) / 8; } + }]; } def MemoryType : ArcTypeDef<"Memory"> { let mnemonic = "memory"; - let parameters = (ins "unsigned":$numWords, "::mlir::IntegerType":$wordType, - OptionalParameter<"unsigned">:$stride); - let assemblyFormat = "`<` $numWords `x` $wordType (`,` $stride^)? `>`"; + let parameters = (ins "unsigned":$numWords, + "::mlir::IntegerType":$wordType, + "::mlir::IntegerType":$addressType); + let assemblyFormat = "`<` $numWords `x` $wordType `,` $addressType `>`"; + + let extraClassDeclaration = [{ + unsigned getStride(); + }]; } def StorageType : ArcTypeDef<"Storage"> { @@ -38,4 +48,4 @@ def StorageType : ArcTypeDef<"Storage"> { let assemblyFormat = "(`<` $size^ `>`)?"; } -#endif // CIRCT_DIALECT_ARC_TYPES_TD +#endif // CIRCT_DIALECT_ARC_ARCTYPES_TD diff --git a/include/circt/Dialect/Arc/CMakeLists.txt b/include/circt/Dialect/Arc/CMakeLists.txt index c2a41d228392..a55bc9b19f5e 100644 --- a/include/circt/Dialect/Arc/CMakeLists.txt +++ b/include/circt/Dialect/Arc/CMakeLists.txt @@ -1,7 +1,21 @@ add_circt_dialect(Arc arc) add_circt_dialect_doc(Arc arc) -set(LLVM_TARGET_DEFINITIONS Passes.td) -mlir_tablegen(Passes.h.inc -gen-pass-decls) +set(LLVM_TARGET_DEFINITIONS ArcPasses.td) +mlir_tablegen(ArcPasses.h.inc -gen-pass-decls) +mlir_tablegen(ArcPassesEnums.h.inc -gen-enum-decls) +mlir_tablegen(ArcPassesEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(CIRCTArcTransformsIncGen) -add_circt_doc(Passes ArcPasses -gen-pass-doc) +add_circt_doc(ArcPasses ArcPasses -gen-pass-doc) + +set(LLVM_TARGET_DEFINITIONS Arc.td) +mlir_tablegen(ArcEnums.h.inc -gen-enum-decls) +mlir_tablegen(ArcEnums.cpp.inc -gen-enum-defs) +add_public_tablegen_target(CIRCTArcEnumsIncGen) +add_dependencies(circt-headers CIRCTArcEnumsIncGen) + +set(LLVM_TARGET_DEFINITIONS ArcInterfaces.td) +mlir_tablegen(ArcInterfaces.h.inc -gen-op-interface-decls) +mlir_tablegen(ArcInterfaces.cpp.inc -gen-op-interface-defs) +add_public_tablegen_target(CIRCTArcInterfacesIncGen) +add_dependencies(circt-headers CIRCTArcInterfacesIncGen) diff --git a/include/circt/Dialect/Arc/Ops.td b/include/circt/Dialect/Arc/Ops.td deleted file mode 100644 index c9344bb5728e..000000000000 --- a/include/circt/Dialect/Arc/Ops.td +++ /dev/null @@ -1,444 +0,0 @@ -//===- Ops.td - Arc dialect operations ---------------------*- tablegen -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef CIRCT_DIALECT_ARC_OPS_TD -#define CIRCT_DIALECT_ARC_OPS_TD - -include "circt/Dialect/Arc/Dialect.td" -include "circt/Dialect/Arc/Types.td" -include "mlir/IR/FunctionInterfaces.td" -include "mlir/IR/OpAsmInterface.td" -include "mlir/IR/RegionKindInterface.td" -include "mlir/IR/SymbolInterfaces.td" -include "mlir/Interfaces/CallInterfaces.td" -include "mlir/Interfaces/ControlFlowInterfaces.td" -include "mlir/Interfaces/SideEffectInterfaces.td" - -class ArcOp traits = []> : - Op; - -def DefineOp : ArcOp<"define", [ - IsolatedFromAbove, - FunctionOpInterface, - Symbol, - RegionKindInterface, - SingleBlockImplicitTerminator<"arc::OutputOp">, - HasParent<"mlir::ModuleOp"> -]> { - let summary = "State transfer arc definition"; - let arguments = (ins - SymbolNameAttr:$sym_name, - TypeAttrOf:$function_type, - OptionalAttr:$arg_attrs, - OptionalAttr:$res_attrs - ); - let results = (outs); - let regions = (region SizedRegion<1>:$body); - let hasCustomAssemblyFormat = 1; - - let hasRegionVerifier = 1; - - let builders = [ - OpBuilder<(ins "mlir::StringAttr":$sym_name, "mlir::TypeAttr":$function_type), [{ - build($_builder, $_state, sym_name, function_type, mlir::ArrayAttr(), mlir::ArrayAttr()); - }]>, - OpBuilder<(ins "mlir::StringRef":$sym_name, "mlir::FunctionType":$function_type), [{ - build($_builder, $_state, sym_name, function_type, mlir::ArrayAttr(), mlir::ArrayAttr()); - }]>, - ]; - - let extraClassDeclaration = [{ - static mlir::RegionKind getRegionKind(unsigned index) { - return mlir::RegionKind::SSACFG; - } - - mlir::Block &getBodyBlock() { return getBody().front(); } - - // Get the arc's symbolic name. - mlir::StringAttr getNameAttr() { - return (*this)->getAttrOfType( - ::mlir::SymbolTable::getSymbolAttrName()); - } - - // Get the arc's symbolic name. - mlir::StringRef getName() { - return getNameAttr().getValue(); - } - - /// Returns the argument types of this function. - mlir::ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } - - /// Returns the result types of this function. - mlir::ArrayRef getResultTypes() { return getFunctionType().getResults(); } - - /// Verify the type attribute of this function. Returns failure and emits - /// an error if the attribute is invalid. - mlir::LogicalResult verifyType() { - auto type = getFunctionTypeAttr().getValue(); - if (!type.isa()) - return emitOpError("requires '") << getFunctionTypeAttrName() << - "' attribute of function type"; - return mlir::success(); - } - }]; -} - -def OutputOp : ArcOp<"output", [ - Terminator, - ParentOneOf<["DefineOp", "LutOp"]>, - Pure, - ReturnLike -]> { - let summary = "Arc terminator"; - let arguments = (ins Variadic:$outputs); - let assemblyFormat = [{ - attr-dict ($outputs^ `:` qualified(type($outputs)))? - }]; - let builders = [OpBuilder<(ins), [{ - build($_builder, $_state, std::nullopt); - }]>]; - let hasVerifier = 1; -} - -def StateOp : ArcOp<"state", [ - CallOpInterface, MemRefsNormalizable, - DeclareOpInterfaceMethods, - AttrSizedOperandSegments -]> { - let summary = "State transfer arc"; - - let arguments = (ins - FlatSymbolRefAttr:$arc, - Optional:$clock, - Optional:$enable, - I32Attr:$latency, - Variadic:$inputs); - let results = (outs Variadic); - - let assemblyFormat = [{ - $arc `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)? `lat` $latency attr-dict - `:` functional-type($inputs, results) - }]; - - let hasCanonicalizeMethod = 1; - - let builders = [ - OpBuilder<(ins "DefineOp":$arc, "mlir::Value":$clock, "mlir::Value":$enable, - "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs), [{ - build($_builder, $_state, mlir::SymbolRefAttr::get(arc), - arc.getFunctionType().getResults(), clock, enable, latency, - inputs); - }]>, - OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, - "mlir::Value":$enable, "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs - ), [{ - if (clock) - $_state.addOperands(clock); - if (enable) - $_state.addOperands(enable); - $_state.addOperands(inputs); - $_state.addAttribute("arc", arc); - $_state.addAttribute("latency", $_builder.getI32IntegerAttr(latency)); - $_state.addAttribute(getOperandSegmentSizeAttr(), - $_builder.getDenseI32ArrayAttr({ - clock ? 1 : 0, - enable ? 1 : 0, - static_cast(inputs.size())})); - $_state.addTypes(results); - }]>, - OpBuilder<(ins "mlir::StringAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, - "mlir::Value":$enable, "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs - ), [{ - build($_builder, $_state, mlir::SymbolRefAttr::get(arc), results, clock, enable, - latency, inputs); - }]>, - OpBuilder<(ins "mlir::StringRef":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, - "mlir::Value":$enable, "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs - ), [{ - build($_builder, $_state, mlir::StringAttr::get($_builder.getContext(), arc), - results, clock, enable, latency, inputs); - }]> - ]; - let skipDefaultBuilders = 1; - let hasVerifier = 1; - - let extraClassDeclaration = [{ - operand_range getArgOperands() { - return {operand_begin(), operand_end()}; - } - - mlir::CallInterfaceCallable getCallableForCallee() { - return (*this)->getAttrOfType("arc"); - } - }]; -} - -def ClockGateOp : ArcOp<"clock_gate", [Pure]> { - let summary = "Clock gate"; - let arguments = (ins I1:$input, I1:$enable); - let results = (outs I1:$output); - let assemblyFormat = [{ - $input `,` $enable attr-dict - }]; - let builders = [ - OpBuilder<(ins "mlir::Value":$input, "mlir::Value":$enable), [{ - build($_builder, $_state, input.getType(), input, enable); - }]> - ]; -} - -def MemoryOp : ArcOp<"memory", [MemoryEffects<[MemAlloc]>]> { - let summary = "Memory"; - let results = (outs MemoryType:$memory); - let assemblyFormat = [{ - type($memory) attr-dict - }]; -} - -class MemoryAndDataTypesMatch : TypesMatchWith< - "memory and data types must match", mem, data, - "$_self.cast().getWordType()">; - -def MemoryReadOp : ArcOp<"memory_read", [ - MemoryEffects<[MemRead]>, - MemoryAndDataTypesMatch<"memory", "data"> -]> { - let summary = "Read word from memory"; - let arguments = (ins - MemoryType:$memory, - AnyInteger:$address, - I1:$clock, - I1:$enable - ); - let results = (outs AnyInteger:$data); - let assemblyFormat = [{ - $memory `[` $address `]` `,` $clock `,` $enable - attr-dict `:` type($memory) `,` type($address) - }]; -} - -def MemoryWriteOp : ArcOp<"memory_write", [ - MemoryEffects<[MemWrite]>, - MemoryAndDataTypesMatch<"memory", "data">, - AttrSizedOperandSegments -]> { - let summary = "Write word to memory"; - let arguments = (ins - MemoryType:$memory, - AnyInteger:$address, - I1:$clock, - I1:$enable, - AnyInteger:$data, - Optional:$mask, - Variadic:$reads - ); - let assemblyFormat = [{ - $memory `[` $address `]` `,` $clock `,` $enable `,` $data - ( `mask` `(` $mask^ `:` type($mask) `)`)? - (` ` `(` `reads` $reads^ `:` type($reads) `)`)? - attr-dict `:` type($memory) `,` type($address) - }]; - - let hasVerifier = 1; -} - -//===----------------------------------------------------------------------===// -// Trigger Grouping -//===----------------------------------------------------------------------===// - -def ClockTreeOp : ArcOp<"clock_tree", [NoTerminator, NoRegionArguments]> { - let summary = "A clock tree"; - let arguments = (ins I1:$clock, Optional:$enable); - let regions = (region SizedRegion<1>:$body); - let assemblyFormat = [{ - $clock (`if` $enable^)? attr-dict-with-keyword $body - }]; - let extraClassDeclaration = [{ - mlir::Block &getBodyBlock() { return getBody().front(); } - }]; -} - -def PassThroughOp : ArcOp<"passthrough", [NoTerminator, NoRegionArguments]> { - let summary = "Clock-less logic that is on the pass-through path"; - let regions = (region SizedRegion<1>:$body); - let assemblyFormat = [{ - attr-dict-with-keyword $body - }]; - let extraClassDeclaration = [{ - mlir::Block &getBodyBlock() { return getBody().front(); } - }]; -} - -//===----------------------------------------------------------------------===// -// Storage Allocation -//===----------------------------------------------------------------------===// - -def AllocStateOp : ArcOp<"alloc_state", [MemoryEffects<[MemAlloc]>]> { - let summary = "Allocate internal state"; - let arguments = (ins StorageType:$storage, UnitAttr:$tap); - let results = (outs StateType:$state); - let assemblyFormat = [{ - $storage (`tap` $tap^)? attr-dict `:` functional-type($storage, $state) - }]; -} - -def AllocMemoryOp : ArcOp<"alloc_memory", [MemoryEffects<[MemAlloc]>]> { - let summary = "Allocate a memory"; - let arguments = (ins StorageType:$storage); - let results = (outs MemoryType:$memory); - let assemblyFormat = [{ - $storage attr-dict `:` functional-type($storage, $memory) - }]; -} - -def AllocStorageOp : ArcOp<"alloc_storage", [MemoryEffects<[MemAlloc]>]> { - let summary = "Allocate contiguous storage space from a larger storage space"; - let arguments = (ins StorageType:$input, OptionalAttr:$offset); - let results = (outs StorageType:$output); - let assemblyFormat = [{ - $input (`[` $offset^ `]`)? attr-dict `:` functional-type($input, $output) - }]; -} - -def RootInputOp : ArcOp<"root_input", [ - DeclareOpInterfaceMethods -]> { - let summary = "A root input"; - let arguments = (ins StrAttr:$name, StorageType:$storage); - let results = (outs StateType:$state); - let assemblyFormat = [{ - $name `,` $storage attr-dict `:` functional-type($storage, $state) - }]; -} - -def RootOutputOp : ArcOp<"root_output", [ - DeclareOpInterfaceMethods -]> { - let summary = "A root output"; - let arguments = (ins StrAttr:$name, StorageType:$storage); - let results = (outs StateType:$state); - let assemblyFormat = [{ - $name `,` $storage attr-dict `:` functional-type($storage, $state) - }]; -} - -//===----------------------------------------------------------------------===// -// State Read/Write -//===----------------------------------------------------------------------===// - -class StateAndValueTypesMatch : TypesMatchWith< - "state and value types must match", state, value, - "$_self.cast().getType()">; - -def StateReadOp : ArcOp<"state_read", [ - MemoryEffects<[MemRead]>, - StateAndValueTypesMatch<"state", "value"> -]> { - let summary = "Get a state's current value"; - let arguments = (ins StateType:$state); - let results = (outs AnyInteger:$value); - let assemblyFormat = [{ - $state attr-dict `:` type($state) - }]; - let builders = [ - OpBuilder<(ins "mlir::Value":$state), [{ - build($_builder, $_state, state.getType().cast().getType(), - state); - }]> - ]; -} - -def StateWriteOp : ArcOp<"state_write", [ - MemoryEffects<[MemWrite]>, - StateAndValueTypesMatch<"state", "value"> -]> { - let summary = "Update a state's value"; - let description = [{ - Changes the value of a state. This operation is treated as a deferred - assignment by most transformation passes, which allows them to change the - order of `arc.state_read` and `arc.state_write` ops on the same state - without affecting the correctness of the model. The reads are always assumed - to produce the current value of the state and writes to be deferred until - all operations in the model have been executed for the current time step. - - The only exceptions to this are the state update legalization pass, which - inserts the necessary temporary variables such that writes can be performed - immediately without affecting correctness. This allows later lowering passes - to treat `arc.state_write` as an immediate assignment (without defering). - }]; - let arguments = (ins StateType:$state, AnyInteger:$value, - Optional:$condition); - let assemblyFormat = [{ - $state `=` $value (`if` $condition^)? attr-dict `:` type($state) - }]; -} - -//===----------------------------------------------------------------------===// -// Miscellaneous -//===----------------------------------------------------------------------===// - -def TapOp : ArcOp<"tap"> { - let summary = "A tracker op to observe a value under a given name"; - let arguments = (ins AnySignlessInteger:$value, StrAttr:$name); - let assemblyFormat = [{ $value attr-dict `:` type($value) }]; -} - -def ModelOp : ArcOp<"model", [RegionKindInterface, IsolatedFromAbove, - NoTerminator]> { - let summary = "A model with stratified clocks"; - let arguments = (ins StrAttr:$name); - let regions = (region SizedRegion<1>:$body); - - let assemblyFormat = [{ - $name attr-dict-with-keyword $body - }]; - - let extraClassDeclaration = [{ - static mlir::RegionKind getRegionKind(unsigned index) { - return mlir::RegionKind::Graph; - } - mlir::Block &getBodyBlock() { return getBody().front(); } - }]; - - let hasVerifier = 1; -} - -def LutOp : ArcOp<"lut", [ - IsolatedFromAbove, - SingleBlockImplicitTerminator<"arc::OutputOp">, - Pure -]> { - let summary = "A lookup-table."; - let description = [{ - Represents a lookup-table as one operation. The operations that map the - lookup/input values to the corresponding table-entry are collected inside - the body of this operation. - Note that the operation is marked to be isolated from above to guarantee - that all input values have to be passed as an operand. This allows for - simpler analyses and canonicalizations of the LUT as well as lowering. - Only combinational operations are allowed inside the LUT, i.e., no - side-effects, state, time delays, etc. - }]; - - let arguments = (ins Variadic:$inputs); - let results = (outs AnySignlessInteger:$output); - let regions = (region SizedRegion<1>:$body); - - let assemblyFormat = [{ - `(` $inputs `)` `:` functional-type($inputs, $output) - attr-dict-with-keyword $body - }]; - - let extraClassDeclaration = [{ - mlir::Block *getBodyBlock() { return &getBody().front(); } - }]; - - let hasVerifier = 1; -} - -#endif // CIRCT_DIALECT_ARC_OPS_TD diff --git a/include/circt/Dialect/Arc/Passes.h b/include/circt/Dialect/Arc/Passes.h deleted file mode 100644 index fdea37624485..000000000000 --- a/include/circt/Dialect/Arc/Passes.h +++ /dev/null @@ -1,44 +0,0 @@ -//===- Passes.h - Arc dialect passes --------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef CIRCT_DIALECT_ARC_PASSES_H -#define CIRCT_DIALECT_ARC_PASSES_H - -#include "mlir/Pass/Pass.h" -#include - -namespace mlir { -class Pass; -} // namespace mlir - -namespace circt { -namespace arc { - -std::unique_ptr -createAddTapsPass(llvm::Optional tapPorts = {}, - llvm::Optional tapWires = {}); -std::unique_ptr createDedupPass(); -std::unique_ptr createInferMemoriesPass(); -std::unique_ptr createInlineArcsPass(); -std::unique_ptr createInlineModulesPass(); -std::unique_ptr createLowerLUTPass(); -std::unique_ptr createLowerStatePass(); -std::unique_ptr createMakeTablesPass(); -std::unique_ptr createRemoveUnusedArcArgumentsPass(); -std::unique_ptr createSimplifyVariadicOpsPass(); -std::unique_ptr createSinkInputsPass(); -std::unique_ptr createSplitLoopsPass(); -std::unique_ptr createStripSVPass(); - -#define GEN_PASS_REGISTRATION -#include "circt/Dialect/Arc/Passes.h.inc" - -} // namespace arc -} // namespace circt - -#endif // CIRCT_DIALECT_ARC_PASSES_H diff --git a/include/circt/Dialect/Arc/Passes.td b/include/circt/Dialect/Arc/Passes.td deleted file mode 100644 index 4cff718b6317..000000000000 --- a/include/circt/Dialect/Arc/Passes.td +++ /dev/null @@ -1,126 +0,0 @@ -//===- Passes.td - Arc dialect passes ----------------------*- tablegen -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#ifndef CIRCT_DIALECT_ARC_PASSES_TD -#define CIRCT_DIALECT_ARC_PASSES_TD - -include "mlir/Pass/PassBase.td" - -def AddTaps : Pass<"arc-add-taps", "mlir::ModuleOp"> { - let summary = "Add taps to ports and wires such that they remain observable"; - let constructor = "circt::arc::createAddTapsPass()"; - let dependentDialects = ["arc::ArcDialect"]; - let options = [ - Option<"tapPorts", "ports", "bool", "true", "Make module ports observable">, - Option<"tapWires", "wires", "bool", "true", "Make wires observable"> - ]; -} - -def Dedup : Pass<"arc-dedup", "mlir::ModuleOp"> { - let summary = "Deduplicate identical arc definitions"; - let description = [{ - This pass deduplicates identical arc definitions. If two arcs differ only by - constants, the constants are outlined such that the arc can be deduplicated. - }]; - let constructor = "circt::arc::createDedupPass()"; - let dependentDialects = ["arc::ArcDialect"]; -} - -def InferMemories : Pass<"arc-infer-memories", "mlir::ModuleOp"> { - let summary = "Convert `FIRRTL_Memory` instances to dedicated memory ops"; - let constructor = "circt::arc::createInferMemoriesPass()"; - let dependentDialects = [ - "arc::ArcDialect", "comb::CombDialect", "seq::SeqDialect" - ]; -} - -def InlineArcs : Pass<"arc-inline" , "mlir::ModuleOp"> { - let summary = "Inline very small arcs"; - let constructor = "circt::arc::createInlineArcsPass()"; - let statistics = [ - Statistic<"numInlinedArcs", "inlined-arcs", "Arcs inlined at a use site">, - Statistic<"numRemovedArcs", "removed-arcs", - "Arcs removed after full inlining">, - Statistic<"numTrivialArcs", "trivial-arcs", "Arcs with very few ops">, - Statistic<"numSingleUseArcs", "single-use-arcs", "Arcs with a single use">, - ]; -} - -def InlineModules : Pass<"arc-inline-modules", "mlir::ModuleOp"> { - let summary = "Eagerly inline private modules"; - let description = [{ - This pass eagerly inlines private HW modules into their instantiation sites. - After outlining combinational logic and registers into arcs, module bodies - become fairly lightweight. Since arc definitions now fulfill the purpose of - code reuse by allowing a single definition to be called multiple times, the - module hierarchy degenerates into a purely cosmetic construct. At that point - it is beneficial to fully flatten the module hierarchy to simplify further - analysis and optimization of state transfer arcs. - }]; - let constructor = "circt::arc::createInlineModulesPass()"; -} - -def LowerLUT : Pass<"arc-lower-lut", "arc::DefineOp"> { - let summary = "Lowers arc.lut into a comb and hw only representation."; - let constructor = "circt::arc::createLowerLUTPass()"; - let dependentDialects = ["hw::HWDialect", "comb::CombDialect"]; -} - -def LowerState : Pass<"arc-lower-state", "mlir::ModuleOp"> { - let summary = "Split state into read and write ops grouped by clock tree"; - let constructor = "circt::arc::createLowerStatePass()"; - let dependentDialects = ["arc::ArcDialect"]; -} - -def MakeTables : Pass<"arc-make-tables", "mlir::ModuleOp"> { - let summary = "Transform appropriate arc logic into lookup tables"; - let constructor = "circt::arc::createMakeTablesPass()"; - let dependentDialects = ["arc::ArcDialect"]; -} - -def RemoveUnusedArcArguments : Pass<"arc-remove-unused-arc-arguments", - "mlir::ModuleOp"> { - let summary = - "Remove unused arc args from the arc itself and the referencing states"; - let constructor = "circt::arc::createRemoveUnusedArcArgumentsPass()"; -} - -def SimplifyVariadicOps : Pass<"arc-simplify-variadic-ops", "mlir::ModuleOp"> { - let summary = "Convert variadic ops into distributed binary ops"; - let constructor = "circt::arc::createSimplifyVariadicOpsPass()"; - let statistics = [ - Statistic<"numOpsSkippedMultipleBlocks", "skipped-multiple-blocks", - "Ops skipped due to operands in different blocks">, - Statistic<"numOpsSimplified", "simplified", - "Ops simplified into binary ops">, - Statistic<"numOpsCreated", "created", - "Ops created as part of simplification">, - Statistic<"numOpsReordered", "reordered", - "Ops where simplification reordered operands">, - ]; -} - -def SinkInputs : Pass<"arc-sink-inputs", "mlir::ModuleOp"> { - let summary = "Sink constant inputs into arcs"; - let constructor = "circt::arc::createSinkInputsPass()"; - let dependentDialects = ["arc::ArcDialect"]; -} - -def SplitLoops : Pass<"arc-split-loops", "mlir::ModuleOp"> { - let summary = "Split arcs to break zero latency loops"; - let constructor = "circt::arc::createSplitLoopsPass()"; - let dependentDialects = ["arc::ArcDialect"]; -} - -def StripSV : Pass<"arc-strip-sv", "mlir::ModuleOp"> { - let summary = "Remove SV wire, reg, and assigns"; - let constructor = "circt::arc::createStripSVPass()"; - let dependentDialects = ["seq::SeqDialect", "arc::ArcDialect"]; -} - -#endif // CIRCT_DIALECT_ARC_PASSES_TD diff --git a/include/circt/Dialect/CMakeLists.txt b/include/circt/Dialect/CMakeLists.txt index 972ad22ce900..a30c22d83d61 100644 --- a/include/circt/Dialect/CMakeLists.txt +++ b/include/circt/Dialect/CMakeLists.txt @@ -9,6 +9,8 @@ add_subdirectory(Arc) add_subdirectory(Calyx) add_subdirectory(Comb) +add_subdirectory(DC) +add_subdirectory(Debug) add_subdirectory(ESI) add_subdirectory(FIRRTL) add_subdirectory(FSM) @@ -17,11 +19,15 @@ add_subdirectory(HW) add_subdirectory(HWArith) add_subdirectory(Interop) add_subdirectory(LLHD) +add_subdirectory(LoopSchedule) +add_subdirectory(LTL) add_subdirectory(Moore) add_subdirectory(MSFT) add_subdirectory(OM) add_subdirectory(Pipeline) +add_subdirectory(Ibis) add_subdirectory(Seq) add_subdirectory(SSP) add_subdirectory(SV) add_subdirectory(SystemC) +add_subdirectory(Verif) diff --git a/include/circt/Dialect/Calyx/Calyx.td b/include/circt/Dialect/Calyx/Calyx.td index bf2faca8a176..17e975793b48 100644 --- a/include/circt/Dialect/Calyx/Calyx.td +++ b/include/circt/Dialect/Calyx/Calyx.td @@ -18,7 +18,7 @@ include "mlir/IR/OpAsmInterface.td" include "mlir/IR/OpBase.td" include "mlir/IR/RegionKindInterface.td" include "mlir/IR/SymbolInterfaces.td" -include "mlir/IR/FunctionInterfaces.td" +include "mlir/Interfaces/FunctionInterfaces.td" include "circt/Dialect/Calyx/CalyxInterfaces.td" def CalyxDialect : Dialect { @@ -39,6 +39,9 @@ def CalyxDialect : Dialect { // Depends on the HWDialect to support external primitives using hw.module.extern let dependentDialects = ["circt::hw::HWDialect"]; let cppNamespace = "::circt::calyx"; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; } class SameTypeConstraint @@ -66,8 +69,8 @@ class CalyxContainer traits = []> : ])> { let assemblyFormat = "$body attr-dict"; let regions = (region SizedRegion<1>: $body); - - let extraClassDeclaration = [{ + + code commonClassDeclaration = [{ /// Returns the body of a Calyx container. Block *getBodyBlock() { Region* region = &getOperation()->getRegion(0); @@ -75,6 +78,7 @@ class CalyxContainer traits = []> : return ®ion->front(); } }]; + let extraClassDeclaration = commonClassDeclaration; } /// Base class for ports associated with a Calyx GroupOp. diff --git a/include/circt/Dialect/Calyx/CalyxControl.td b/include/circt/Dialect/Calyx/CalyxControl.td index 088f42d07d79..71d24b29e2f7 100644 --- a/include/circt/Dialect/Calyx/CalyxControl.td +++ b/include/circt/Dialect/Calyx/CalyxControl.td @@ -39,10 +39,15 @@ def ControlOp : CalyxContainer<"control", [ region->push_back(new Block()); }]> ]; + let extraClassDeclaration = commonClassDeclaration # [{ + /// Get the InvokeOp of the ControlOp. + SmallVector getInvokeOps(); + }]; } def IfOp : CalyxContainer<"if", [ - ControlLike + ControlLike, + DeclareOpInterfaceMethods ]> { let summary = "Calyx If"; let arguments = (ins @@ -76,36 +81,6 @@ def IfOp : CalyxContainer<"if", [ let assemblyFormat = "$cond (`with` $groupName^)? $thenRegion (`else` $elseRegion^)? attr-dict"; let hasVerifier = 1; let hasCanonicalizer = true; - let extraClassDeclaration = [{ - /// Checks whether the `then` body exists. - bool thenBodyExists() { - Region* region = &getOperation()->getRegion(0); - if (region == nullptr) - return false; - return !region->empty(); - } - /// Checks whether the `else` body exists. - bool elseBodyExists() { - Region* region = &getOperation()->getRegion(1); - if (region == nullptr) - return false; - return !region->empty(); - } - /// Gets the single basic block representing the `then` region. - Block *getThenBody() { - assert(thenBodyExists() && "Then region does not exist."); - Region* region = &getOperation()->getRegion(0); - assert(region->hasOneBlock() && "The Then body should have one Block."); - return ®ion->front(); - } - /// Gets the single basic block representing the `else` region. - Block *getElseBody() { - assert(elseBodyExists() && "Else region does not exist."); - Region* region = &getOperation()->getRegion(1); - assert(region->hasOneBlock() && "The Else body should have one Block."); - return ®ion->front(); - } - }]; let skipDefaultBuilders = true; let builders = [ OpBuilder<(ins @@ -125,6 +100,55 @@ def IfOp : CalyxContainer<"if", [ ]; } +def StaticIfOp : CalyxContainer<"static_if", [ + ControlLike, + DeclareOpInterfaceMethods + ]> { + let summary = "Calyx Static If"; + let arguments = (ins + I1:$cond + ); + let regions = (region SizedRegion<1>:$thenRegion, AnyRegion:$elseRegion); + let description = [{ + The "calyx.static_if" operation represents an if-then-else construct for + conditionally executing two Calyx groups. The operands to an if operation is + a 1-bit port. + + ```mlir + calyx.static_if %1 { + calyx.enable @G2 + ... + } else { + calyx.enable @G3 + ... + } + calyx.if %1 { + calyx.enable @G2 + ... + } + ``` + }]; + + let assemblyFormat = "$cond $thenRegion (`else` $elseRegion^)? attr-dict"; + let hasVerifier = 1; + let hasCanonicalizer = true; + let skipDefaultBuilders = true; + let builders = [ + OpBuilder<(ins + "Value":$cond, + CArg<"bool", "false">:$initializeElseBody), [{ + $_state.addOperands(cond); + + Region *thenRegion = $_state.addRegion(); + Region *elseRegion = $_state.addRegion(); + thenRegion->push_back(new Block()); + if (initializeElseBody) + elseRegion->push_back(new Block()); + }]> + ]; +} + + def SeqOp : CalyxContainer<"seq", [ ControlLike ]> { @@ -151,6 +175,34 @@ def SeqOp : CalyxContainer<"seq", [ let hasCanonicalizer = true; } + +def StaticSeqOp : CalyxContainer<"static_seq", [ + ControlLike + ]> { + let summary = "Calyx Static Seq"; + let description = [{ + The "calyx.static_seq" operation executes the + control within its region sequentially. + + ```mlir + calyx.static_seq { + // G2 will not begin execution until G1 is done. + calyx.enable @G1 + calyx.enable @G2 + } + ``` + }]; + let skipDefaultBuilders = true; + let builders = [ + OpBuilder<(ins), [{ + Region* region = $_state.addRegion(); + region->push_back(new Block()); + }]> + ]; + let hasCanonicalizer = true; + let hasVerifier = 1; +} + def ParOp : CalyxContainer<"par", [ ControlLike ]> { @@ -180,6 +232,36 @@ def ParOp : CalyxContainer<"par", [ let hasVerifier = 1; } +def StaticParOp : CalyxContainer<"static_par", [ + ControlLike + ]> { + let summary = "Calyx Static Parallel"; + let description = [{ + The "calyx.static_par" operation executes the + control within its region in parallel. + + ```mlir + calyx.static_par { + // G1 and G2 will execute in parallel. + // The region is complete when both + // G1 and G2 are done. + calyx.enable @G1 + calyx.enable @G2 + } + ``` + }]; + let skipDefaultBuilders = true; + let builders = [ + OpBuilder<(ins), [{ + Region* region = $_state.addRegion(); + region->push_back(new Block()); + }]> + ]; + let hasCanonicalizer = true; + let hasVerifier = 1; +} + + def EnableOp : CalyxOp<"enable", [ ControlLike ]> { @@ -258,3 +340,118 @@ def WhileOp : CalyxContainer<"while", [ ]; } + +def StaticRepeatOp : CalyxContainer<"static_repeat", [ + ControlLike + ]> { + let summary = "Calyx Static Repeat"; + let arguments = (ins + I32Attr:$count + ); + let description = [{ + The "calyx.static_repeat" operation represents the repeated execution of + the control within its region. All control within the region must be static. + + ```mlir + calyx.static_repeat 10 { + calyx.enable @G1 + ... + } + ``` + }]; + let hasVerifier = 1; + let hasCanonicalizer = true; + let assemblyFormat = "$count $body attr-dict"; + let skipDefaultBuilders = true; + let builders = [ + OpBuilder<(ins + "uint32_t":$count), [{ + $_state.addAttribute("count", + mlir::IntegerAttr::get(IntegerType::get($_state.getContext(), 32), count) + ); + + Region *body = $_state.addRegion(); + body->push_back(new Block()); + }]> + ]; + +} + +def RepeatOp : CalyxContainer<"repeat", [ + ControlLike + ]> { + let summary = "Calyx Dynamic Repeat"; + let arguments = (ins + I32Attr:$count + ); + let description = [{ + The "calyx.repeat" operation represents the repeated execution of + the control within its region. + The key difference with static repeat is that the body (unlike with static + repeat) can be dynamically timed. + + ```mlir + calyx.repeat 10 { + calyx.enable @G1 + ... + } + ``` + }]; + let hasCanonicalizer = true; + let assemblyFormat = "$count $body attr-dict"; + let skipDefaultBuilders = true; + let builders = [ + OpBuilder<(ins + "uint32_t":$count), [{ + $_state.addAttribute("count", + mlir::IntegerAttr::get(IntegerType::get($_state.getContext(), 32), count) + ); + + Region *body = $_state.addRegion(); + body->push_back(new Block()); + }]> + ]; +} + +def InvokeOp : CalyxOp<"invoke", [ + ControlLike, + SameVariadicOperandSize + ]> { + let summary = "Calyx Invoke"; + let description = [{ + calyx.invoke is similar to the behavior of a function + call, which invokes a given component. + + The 'callee' attribute is the name of the component, + the 'ports' attribute The ports attribute specifies + the input port of the component when it is invoked, + and the 'inputs' attribute specifies the assignment on + the corresponding port. + + ```mlir + %id.in, %id.out, ... = calyx.instance @id of @identity : i32, i32, ... + %r.in, ... = calyx.register @r : i32, ... + ... + calyx.control { + calyx.seq { + calyx.invoke @id(%id.in = %c1_10, %r.in = %id.out) -> (i32, i32) + } + } + ``` + }]; + + let arguments = (ins FlatSymbolRefAttr:$callee, + Variadic:$ports, + Variadic:$inputs, + ArrayAttr:$portNames, + ArrayAttr:$inputNames); + let results = (outs); + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + let extraClassDeclaration = [{ + // Get the go port of the invoked component. + Value getInstGoValue(); + // Get the done port of the invoked component. + Value getInstDoneValue(); + }]; +} diff --git a/include/circt/Dialect/Calyx/CalyxHelpers.h b/include/circt/Dialect/Calyx/CalyxHelpers.h index ae3665ce8786..0e4661853968 100644 --- a/include/circt/Dialect/Calyx/CalyxHelpers.h +++ b/include/circt/Dialect/Calyx/CalyxHelpers.h @@ -17,6 +17,7 @@ #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Support/LLVM.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" #include @@ -34,6 +35,16 @@ hw::ConstantOp createConstant(Location loc, OpBuilder &builder, ComponentOp component, size_t width, size_t value); +/// A helper function to create calyx.instance operation. +calyx::InstanceOp createInstance(Location loc, OpBuilder &builder, + ComponentOp component, + SmallVectorImpl &resultTypes, + StringRef instanceName, + StringRef componentName); + +/// A helper function to get the instance name. +std::string getInstanceName(mlir::func::CallOp callOp); + // Returns whether this operation is a leaf node in the Calyx control. // TODO(github.com/llvm/circt/issues/1679): Add Invoke. bool isControlLeafNode(Operation *op); diff --git a/include/circt/Dialect/Calyx/CalyxInterfaces.td b/include/circt/Dialect/Calyx/CalyxInterfaces.td index 8351394fdd53..46fcca5bd2f0 100644 --- a/include/circt/Dialect/Calyx/CalyxInterfaces.td +++ b/include/circt/Dialect/Calyx/CalyxInterfaces.td @@ -269,3 +269,69 @@ def ComponentOpInterface : OpInterface<"ComponentInterface"> { let verify = [{ return verifyComponent(op); }]; } + +def IfOpInterface : OpInterface<"IfInterface"> { + let cppNamespace = "::circt::calyx"; + + let description = [{ + This is an op interface for Calyx If control ops. Implemented + by Dynamic and Static If ops. + }]; + + let methods = [ + InterfaceMethod< + "This returns true if the then body exists.", + "bool", + "thenBodyExists", + (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + Region* region = &$_op->getRegion(0); + if (region == nullptr) + return false; + return !region->empty(); + }] + >, + InterfaceMethod< + "This returns true if the else body exists.", + "bool", + "elseBodyExists", + (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + Region* region = &$_op->getRegion(1); + if (region == nullptr) + return false; + return !region->empty(); + }] + >, + InterfaceMethod< + "This returns the then body block.", + "Block*", + "getThenBody", + (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + assert(thenBodyExists() && "Then region does not exist."); + Region* region = &$_op->getRegion(0); + assert(region->hasOneBlock() && "The Then body should have one Block."); + return ®ion->front(); + }] + >, + InterfaceMethod< + "This returns the else body block.", + "Block*", + "getElseBody", + (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + assert(elseBodyExists() && "Else region does not exist."); + Region* region = &$_op->getRegion(1); + assert(region->hasOneBlock() && "The Else body should have one Block."); + return ®ion->front(); + }] + > + ]; + + let verify = [{ return verifyIf(op); }]; +} diff --git a/include/circt/Dialect/Calyx/CalyxLoweringUtils.h b/include/circt/Dialect/Calyx/CalyxLoweringUtils.h index b14cc62599d2..a8c0ce521568 100644 --- a/include/circt/Dialect/Calyx/CalyxLoweringUtils.h +++ b/include/circt/Dialect/Calyx/CalyxLoweringUtils.h @@ -17,7 +17,6 @@ #include "circt/Dialect/Calyx/CalyxHelpers.h" #include "circt/Dialect/Calyx/CalyxOps.h" #include "circt/Dialect/Comb/CombOps.h" -#include "circt/Dialect/Pipeline/Pipeline.h" #include "circt/Support/LLVM.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" @@ -85,11 +84,13 @@ void buildAssignmentsForRegisterWrite(OpBuilder &builder, // A structure representing a set of ports which act as a memory interface for // external memories. struct MemoryPortsImpl { - Value readData; - Value done; - Value writeData; + std::optional readData; + std::optional readEn; + std::optional readDone; + std::optional writeData; + std::optional writeEn; + std::optional writeDone; SmallVector addrPorts; - Value writeEn; }; // Represents the interface of memory in Calyx. The various lowering passes @@ -99,22 +100,31 @@ struct MemoryInterface { MemoryInterface(); explicit MemoryInterface(const MemoryPortsImpl &ports); explicit MemoryInterface(calyx::MemoryOp memOp); + explicit MemoryInterface(calyx::SeqMemoryOp memOp); // Getter methods for each memory interface port. Value readData(); - Value done(); + Value readEn(); + Value readDone(); Value writeData(); Value writeEn(); + Value writeDone(); + std::optional readDataOpt(); + std::optional readEnOpt(); + std::optional readDoneOpt(); + std::optional writeDataOpt(); + std::optional writeEnOpt(); + std::optional writeDoneOpt(); ValueRange addrPorts(); private: - std::variant impl; + std::variant impl; }; -// A common interface for loop operations that need to be lowered to Calyx. -class LoopInterface { +// A common interface for any loop operation that needs to be lowered to Calyx. +class BasicLoopInterface { public: - virtual ~LoopInterface(); + virtual ~BasicLoopInterface(); // Returns the arguments to this loop operation. virtual Block::BlockArgListType getBodyArgs() = 0; @@ -122,17 +132,22 @@ class LoopInterface { // Returns body of this loop operation. virtual Block *getBodyBlock() = 0; + // Returns the location of the loop interface. + virtual Location getLoc() = 0; + + // Returns the number of iterations the loop will conduct if known. + virtual std::optional getBound() = 0; +}; + +// A common interface for loop operations that have conditionals (e.g., while +// loops) that need to be lowered to Calyx. +class LoopInterface : BasicLoopInterface { +public: // Returns the Block in which the condition exists. virtual Block *getConditionBlock() = 0; // Returns the condition as a Value. virtual Value getConditionValue() = 0; - - // Returns the number of iterations the loop will conduct if known. - virtual std::optional getBound() = 0; - - // Returns the location of the loop interface. - virtual Location getLoc() = 0; }; // Provides an interface for the control flow `while` operation across different @@ -155,6 +170,26 @@ class WhileOpInterface : LoopInterface { T impl; }; +// Provides an interface for the control flow `forOp` operation across different +// dialects. +template +class RepeatOpInterface : BasicLoopInterface { + static_assert(std::is_convertible_v); + +public: + explicit RepeatOpInterface(T op) : impl(op) {} + explicit RepeatOpInterface(Operation *op) : impl(dyn_cast_or_null(op)) {} + + // Returns the operation. + T getOperation() { return impl; } + + // Returns the source location of the operation. + Location getLoc() override { return impl->getLoc(); } + +private: + T impl; +}; + /// Holds common utilities used for scheduling when lowering to Calyx. template class SchedulerInterface { @@ -198,7 +233,7 @@ class SchedulerInterface { // several lowering patterns. template class LoopLoweringStateInterface { - static_assert(std::is_base_of_v); + static_assert(std::is_base_of_v); public: ~LoopLoweringStateInterface() = default; @@ -242,6 +277,22 @@ class LoopLoweringStateInterface { return it->second; } + /// Registers groups to be the loop init groups of `op`. + void setLoopInitGroups(Loop op, SmallVector groups) { + Operation *operation = op.getOperation(); + assert(loopInitGroups.count(operation) == 0 && + "Init group(s) was already set for this loopOp"); + loopInitGroups[operation] = std::move(groups); + } + + /// Retrieve the loop init groups registered for `op`. + SmallVector getLoopInitGroups(Loop op) { + auto it = loopInitGroups.find(op.getOperation()); + assert(it != loopInitGroups.end() && + "No init group(s) was set for this loopOp"); + return it->second; + } + /// Creates a new group that assigns the 'ops' values to the iter arg /// registers of the loop operation. calyx::GroupOp buildLoopIterArgAssignments(OpBuilder &builder, Loop op, @@ -272,6 +323,10 @@ class LoopLoweringStateInterface { /// finishing a loop body. The execution of this group will write the /// yield'ed loop body values to the iteration argument registers. DenseMap loopLatchGroups; + + /// Loop init groups are to be scheduled before the while operation. These + /// groups should set the initial value(s) of the loop init_args register(s). + DenseMap> loopInitGroups; }; // Handles state during the lowering of a Calyx component. This provides common @@ -339,6 +394,12 @@ class ComponentLoweringStateInterface { /// the original function maps to. unsigned getFuncOpResultMapping(unsigned funcReturnIdx); + /// The instance is obtained from the name of the callee. + InstanceOp getInstance(StringRef calleeName); + + /// Put the name of the callee and the instance of the call into map. + void addInstance(StringRef calleeName, InstanceOp instanceOp); + /// Return the group which evaluates the value v. Optionally, caller may /// specify the expected type of the group. template @@ -397,6 +458,9 @@ class ComponentLoweringStateInterface { /// A mapping between the source funcOp result indices and the corresponding /// output port indices of this componentOp. DenseMap funcOpResultMapping; + + /// A mapping between the callee and the instance. + llvm::StringMap instanceMap; }; /// An interface for conversion passes that lower Calyx programs. This handles @@ -679,6 +743,17 @@ class BuildReturnRegs : public calyx::FuncOpPartialLoweringPattern { PatternRewriter &rewriter) const override; }; +/// Builds instance for the calyx.invoke and calyx.group in order to initialize +/// the instance. +class BuildCallInstance : public calyx::FuncOpPartialLoweringPattern { + using FuncOpPartialLoweringPattern::FuncOpPartialLoweringPattern; + + LogicalResult + partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, + PatternRewriter &rewriter) const override; + ComponentOp getCallComponent(mlir::func::CallOp callOp) const; +}; + } // namespace calyx } // namespace circt diff --git a/include/circt/Dialect/Calyx/CalyxOps.h b/include/circt/Dialect/Calyx/CalyxOps.h index 9466b664fb1b..6d64506aee34 100644 --- a/include/circt/Dialect/Calyx/CalyxOps.h +++ b/include/circt/Dialect/Calyx/CalyxOps.h @@ -16,10 +16,10 @@ #include "circt/Dialect/Calyx/CalyxDialect.h" #include "circt/Dialect/HW/HWOps.h" #include "mlir/IR/BuiltinOps.h" -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/RegionKindInterface.h" #include "mlir/IR/SymbolTable.h" +#include "mlir/Interfaces/FunctionInterfaces.h" #include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" @@ -120,6 +120,9 @@ LogicalResult verifyCell(Operation *op); /// A helper function to verify each operation with the Group Interface trait. LogicalResult verifyGroupInterface(Operation *op); +/// A helper function to verify each operation with the If trait. +LogicalResult verifyIf(Operation *op); + /// Returns port information for the block argument provided. PortInfo getPortInfo(BlockArgument arg); diff --git a/include/circt/Dialect/Calyx/CalyxPrimitives.td b/include/circt/Dialect/Calyx/CalyxPrimitives.td index 2b9ef0eef5c7..e00a15db48c9 100644 --- a/include/circt/Dialect/Calyx/CalyxPrimitives.td +++ b/include/circt/Dialect/Calyx/CalyxPrimitives.td @@ -18,6 +18,38 @@ class CalyxPrimitive traits = []> : let skipDefaultBuilders = 1; } +/// The n-bit, undef op which only provides the out signal +def UndefLibOp: CalyxPrimitive<"undefined", []> { + let summary = "An undefined signal"; + let description = [{ + The "calyx.undef" op defines an undefined signal. An undefined signal can be + replaced with any value representable in n-bits. + ```mlir + // A 32-bit undefined value + %undef.out = calyx.undef @undef : i32 + ``` + }]; + let arguments = (ins SymbolNameAttr:$sym_name); + let builders = [ + OpBuilder<(ins "StringRef":$sym_name, "::mlir::TypeRange":$resultTypes), [{ + $_state.addAttribute(mlir::SymbolTable::getSymbolAttrName(), $_builder.getStringAttr(sym_name)); + $_state.addTypes(resultTypes); + }]> + ]; + let results = (outs AnyType:$out); + let extraClassDefinition = [{ + bool $cppClass::isCombinational() { return false; } + SmallVector $cppClass::portNames() { return {"out"}; } + SmallVector $cppClass::portDirections() { return {Output}; } + void $cppClass::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + getCellAsmResultNames(setNameFn, *this, this->portNames()); + } + SmallVector $cppClass::portAttributes() { + return {DictionaryAttr::get(getContext())}; + } + }]; +} + /// Trait used to mark certain operations in the Calyx dialect as combinational. def Combinational : NativeOpTrait<"Combinational"> { let cppNamespace = "::circt::calyx"; @@ -60,7 +92,7 @@ def MemoryOp : CalyxPrimitive<"memory", []> { dimension in `$sizes`. The `$width` attribute dictates the width of a single element. - See https://capra.cs.cornell.edu/docs/calyx/libraries/core.html#memories for + See https://docs.calyxir.org/libraries/core.html#memories for more information. ```mlir @@ -114,6 +146,82 @@ def MemoryOp : CalyxPrimitive<"memory", []> { Value readData() { return getResult(getNumDimensions() + 3); } Value done() { return getResult(getNumDimensions() + 4); } }]; + + let extraClassDefinition = [{ + bool $cppClass::isCombinational() { return false; } + }]; +} + +def SeqMemoryOp : CalyxPrimitive<"seq_mem", []> { + let summary = "Defines a memory with sequential read"; + let description = [{ + The "calyx.seq_mem" op defines a memory with sequential reads. Memories can + have any number of dimensions, as specified by the length of the `$sizes` and + `$addrSizes` arrays. The `$addrSizes` specify the bitwidth of each dimension's + address, and should be wide enough to address the range of the corresponding + dimension in `$sizes`. The `$width` attribute dictates the width of a single + element. + + See https://docs.calyxir.org/libraries/core.html#memories for + more information. + + ```mlir + // A 1-dimensional, 32-bit memory with size dimension 1. Equivalent representation in the native compiler: + // `m1 = seq_mem_d1(32, 1, 1)` + %m1.addr0, %m1.write_data, %m1.write_en, %m1.write_done, %m1.clk, %m1.read_data, %m1.read_en, %m1.read_done = calyx.memory @m1 <[1] x 32> [1] : i1, i32, i1, i1, i32, i1 + + // A 2-dimensional, 8-bit memory with size dimensions 64 x 64. Equivalent representation in the native compiler: + // `m2 = seq_mem_d2(8, 64, 64, 6, 6)` + %m2.addr0, %m2.addr1, %m2.write_data, %m2.write_en, %m2.write_done, %m2.clk, %m2.read_data, %m2.read_en, %m2.read_done = calyx.memory @m2 <[64, 64] x 8> [6, 6] : i6, i6, i8, i1, i1, i8, i1 + ``` + }]; + + let arguments = (ins + SymbolNameAttr:$sym_name, + I64Attr:$width, + ArrayAttr:$sizes, + ArrayAttr:$addrSizes + ); + + let results = (outs + Variadic:$results + ); + + let assemblyFormat = [{ + $sym_name ` ` `<` $sizes `x` $width `>` $addrSizes attr-dict `:` qualified(type($results)) + }]; + + let hasVerifier = 1; + + let builders = [ + OpBuilder<(ins + "StringRef":$sym_name, + "int64_t":$width, + "ArrayRef":$sizes, + "ArrayRef":$addrSizes + )> + ]; + + let extraClassDeclaration = [{ + size_t getNumDimensions() { return getAddrSizes().size(); } + + ValueRange addrPorts() { return getResults().take_front(getNumDimensions()); } + Value addrPort(size_t i) { + assert(i < getNumDimensions() && "index greater than number of memory address ports."); + return getResults()[i]; + } + Value writeData() { return getResult(getNumDimensions()); } + Value writeEn() { return getResult(getNumDimensions() + 1); } + Value writeDone() { return getResult(getNumDimensions() + 2); } + Value clk() { return getResult(getNumDimensions() + 3); } + Value readData() { return getResult(getNumDimensions() + 4); } + Value readEn() { return getResult(getNumDimensions() + 5); } + Value readDone() { return getResult(getNumDimensions() + 6); } + }]; + + let extraClassDefinition = [{ + bool $cppClass::isCombinational() { return false; } + }]; } class CalyxLibraryOp traits = []> : @@ -191,6 +299,28 @@ def AndLibOp : CombinationalArithBinaryLibraryOp<"and"> {} def OrLibOp : CombinationalArithBinaryLibraryOp<"or"> {} def XorLibOp : CombinationalArithBinaryLibraryOp<"xor"> {} +def MuxLibOp : CalyxLibraryOp<"mux", [ + Combinational, SameTypeConstraint<"tru", "fal">, SameTypeConstraint<"tru", "out"> + ]> { + let results = (outs I1:$cond, AnyType:$tru, AnyType:$fal, AnyType:$out); + let extraClassDefinition = [{ + SmallVector $cppClass::portNames() { + return {"cond", "tru", "fal", "out"}; + } + SmallVector $cppClass::portDirections() { + return {Input, Input, Input, Output}; + } + void $cppClass::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + getCellAsmResultNames(setNameFn, *this, this->portNames()); + } + bool $cppClass::isCombinational() { return true; } + SmallVector $cppClass::portAttributes() { + return {DictionaryAttr::get(getContext()), DictionaryAttr::get(getContext()), + DictionaryAttr::get(getContext()), DictionaryAttr::get(getContext())}; + } + }]; +} + class ArithBinaryPipeLibraryOp : ArithBinaryLibraryOp ]> { @@ -228,4 +358,4 @@ def WireLibOp : UnaryLibraryOp<"wire", [SameTypeConstraint<"in", "out">]> { ]; } -def ExtSILibOp : UnaryLibraryOp<"extsi"> {} +def ExtSILibOp : UnaryLibraryOp<"signext"> {} diff --git a/include/circt/Dialect/Calyx/CalyxStructure.td b/include/circt/Dialect/Calyx/CalyxStructure.td index b8e39019e17d..fdf7acacf557 100644 --- a/include/circt/Dialect/Calyx/CalyxStructure.td +++ b/include/circt/Dialect/Calyx/CalyxStructure.td @@ -95,6 +95,11 @@ def ComponentOp : CalyxOp<"component", [ /// Returns the result types of this function. ArrayRef getResultTypes() { return getFunctionType().getResults(); } + /// Returns the region on the current operation that is callable. + ::mlir::Region *getCallableRegion() { + return &getBody(); + } + /// Verify the type attribute of this function. Returns failure and emits /// an error if the attribute is invalid. LogicalResult verifyType() { @@ -201,6 +206,11 @@ def CombComponentOp : CalyxOp<"comb_component", [ /// Returns the result types of this function. ArrayRef getResultTypes() { return getFunctionType().getResults(); } + /// Returns the region on the current operation that is callable. + ::mlir::Region *getCallableRegion() { + return &getBody(); + } + /// Verify the type attribute of this function. Returns failure and emits /// an error if the attribute is invalid. LogicalResult verifyType() { @@ -465,9 +475,79 @@ def CombGroupOp : CalyxOp<"comb_group", [ ]; } +def StaticGroupOp : CalyxOp<"static_group", [ + HasParent<"WiresOp">, + DeclareOpInterfaceMethods, + NoRegionArguments, + RegionKindInterface, + SingleBlock, + Symbol, + NoTerminator + ]> { + let summary = "Calyx Static Group"; + let description = [{ + Represents a Calyx static group, which is a collection + of assignments that are only active when the group + is run from the control execution schedule. A group + signifies its termination with a special port named + a "done" port. + + ```mlir + calyx.static_group latency<1> @MyStaticGroup { + calyx.assign %1 = %2 : i32 + } + ``` + }]; + + let arguments = (ins SymbolNameAttr: $sym_name, + I64Attr: $latency); + + let extraClassDeclaration = [{ + // Implement RegionKindInterface. + static RegionKind getRegionKind(unsigned index) { return RegionKind::Graph; } + + /// Returns the GroupGoOp for this group. + GroupGoOp getGoOp(); + + /// Returns the body of a Calyx component. + Block *getBodyBlock() { + Region* region = &getOperation()->getRegion(0); + assert(region->hasOneBlock() && "The body should have one Block."); + return ®ion->front(); + } + }]; + + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = "`latency` `<` $latency `>` $sym_name $body attr-dict"; + let skipDefaultBuilders = true; + let builders = [ + OpBuilder<(ins "StringRef":$sym_name, "uint64_t":$latency), [{ + $_state.addAttribute( + mlir::SymbolTable::getSymbolAttrName(), + StringAttr::get($_state.getContext(), sym_name) + ); + $_state.addAttribute( + getLatencyAttrName($_state.name), + mlir::IntegerAttr::get(IntegerType::get($_state.getContext(), 64), latency) + ); + Region* region = $_state.addRegion(); + region->push_back(new Block()); + }]>, + OpBuilder<(ins "StringAttr":$sym_name, "uint64_t":$latency), [{ + $_state.addAttribute(mlir::SymbolTable::getSymbolAttrName(), sym_name); + $_state.addAttribute( + getLatencyAttrName($_state.name), + mlir::IntegerAttr::get(IntegerType::get($_state.getContext(), 64), latency) + ); + Region* region = $_state.addRegion(); + region->push_back(new Block()); + }]> + ]; +} + def AssignOp : CalyxOp<"assign", [ SameTypeConstraint<"dest", "src">, - ParentOneOf<["GroupOp", "CombGroupOp", "WiresOp"]> + ParentOneOf<["GroupOp", "CombGroupOp", "StaticGroupOp","WiresOp"]> ]> { let summary = "Calyx Assignment"; let description = [{ @@ -547,3 +627,52 @@ def GroupGoOp : CalyxGroupPort<"group_go", [ }]> ]; } + +def CycleOp : CalyxOp<"cycle", [ + HasParent<"StaticGroupOp">, +]> { + let summary = "Calyx Static Cycle Op"; + let description = [{ + Returns an I1 signal that is active `start` cycles after the static + group starts, and inactive all other times. Optional `end` + attribute allows the signal to be active from until `end` cycles + after the static group starts. Must be used within a static group. + + ```mlir + calyx.static_group latency<2> @MyStaticGroup { + %1 = calyx.cycle 1 : i1 + } + ``` + }]; + let arguments = (ins + I32Attr:$start, + OptionalAttr:$end + ); + let results = (outs + I1:$active + ); + let extraClassDeclaration = [{ + /// Returns the latency of the parent static group. + uint32_t getGroupLatency(); + }]; + let builders = [ + OpBuilder<(ins "uint32_t":$start), [{ + $_state.addAttribute( + getStartAttrName($_state.name), + mlir::IntegerAttr::get(IntegerType::get($_state.getContext(), 32), start) + ); + }]>, + OpBuilder<(ins "uint32_t":$start, "uint32_t":$end), [{ + $_state.addAttribute( + getStartAttrName($_state.name), + mlir::IntegerAttr::get(IntegerType::get($_state.getContext(), 32), start) + ); + $_state.addAttribute( + getEndAttrName($_state.name), + mlir::IntegerAttr::get(IntegerType::get($_state.getContext(), 32), end) + ); + }]> + ]; + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; +} diff --git a/include/circt/Dialect/Comb/CMakeLists.txt b/include/circt/Dialect/Comb/CMakeLists.txt index 7fa70fec1470..a7bb1a6251f1 100644 --- a/include/circt/Dialect/Comb/CMakeLists.txt +++ b/include/circt/Dialect/Comb/CMakeLists.txt @@ -5,3 +5,10 @@ set(LLVM_TARGET_DEFINITIONS Comb.td) mlir_tablegen(CombEnums.h.inc -gen-enum-decls) mlir_tablegen(CombEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(MLIRCombEnumsIncGen) + +set(LLVM_TARGET_DEFINITIONS Passes.td) +mlir_tablegen(Passes.h.inc -gen-pass-decls) +add_public_tablegen_target(CIRCTCombTransformsIncGen) + +# Generate Pass documentation. +add_circt_doc(Passes CombPasses -gen-pass-doc) diff --git a/include/circt/Dialect/Comb/Comb.td b/include/circt/Dialect/Comb/Comb.td index 8f4d1d6f154e..b69ce57d5fbb 100644 --- a/include/circt/Dialect/Comb/Comb.td +++ b/include/circt/Dialect/Comb/Comb.td @@ -28,6 +28,10 @@ def CombDialect : Dialect { }]; let hasConstantMaterializer = 1; let cppNamespace = "::circt::comb"; + + // This will be the default after next LLVM bump. + let usePropertiesForAttributes = 1; + } // Base class for the operation in this dialect. diff --git a/include/circt/Dialect/Comb/CombOps.h b/include/circt/Dialect/Comb/CombOps.h index 8e8c5f59cc8f..b1fd1acf568e 100644 --- a/include/circt/Dialect/Comb/CombOps.h +++ b/include/circt/Dialect/Comb/CombOps.h @@ -16,9 +16,10 @@ #include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/HW/HWTypes.h" #include "circt/Support/LLVM.h" +#include "mlir/Bytecode/BytecodeOpInterface.h" #include "mlir/IR/BuiltinOps.h" -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/IR/OpImplementation.h" +#include "mlir/Interfaces/FunctionInterfaces.h" #include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" @@ -51,8 +52,10 @@ Value createOrFoldSExt(Location loc, Value value, Type destTy, Value createOrFoldSExt(Value value, Type destTy, ImplicitLocOpBuilder &builder); /// Create a ``Not'' gate on a value. -Value createOrFoldNot(Location loc, Value value, OpBuilder &builder); -Value createOrFoldNot(Value value, ImplicitLocOpBuilder &builder); +Value createOrFoldNot(Location loc, Value value, OpBuilder &builder, + bool twoState = false); +Value createOrFoldNot(Value value, ImplicitLocOpBuilder &builder, + bool twoState = false); } // namespace comb } // namespace circt diff --git a/include/circt/Dialect/Comb/CombPasses.h b/include/circt/Dialect/Comb/CombPasses.h new file mode 100644 index 000000000000..c41b577cd768 --- /dev/null +++ b/include/circt/Dialect/Comb/CombPasses.h @@ -0,0 +1,32 @@ +//===- Passes.h - Comb pass entry points ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This header file defines prototypes that expose pass constructors. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_COMB_COMBPASSES_H +#define CIRCT_DIALECT_COMB_COMBPASSES_H + +#include "mlir/Pass/Pass.h" +#include "mlir/Pass/PassRegistry.h" +#include +#include + +namespace circt { +namespace comb { + +/// Generate the code for registering passes. +#define GEN_PASS_DECL +#define GEN_PASS_REGISTRATION +#include "circt/Dialect/Comb/Passes.h.inc" + +} // namespace comb +} // namespace circt + +#endif // CIRCT_DIALECT_COMB_COMBPASSES_H diff --git a/include/circt/Dialect/Comb/Combinational.td b/include/circt/Dialect/Comb/Combinational.td index 61487205a6b4..8e53c843a132 100644 --- a/include/circt/Dialect/Comb/Combinational.td +++ b/include/circt/Dialect/Comb/Combinational.td @@ -122,7 +122,7 @@ def ICmpOp : CombOp<"icmp", [Pure, SameTypeOperands]> { bit wide result. ``` - %r = hw.icmp eq %a, %b : i4 + %r = comb.icmp eq %a, %b : i4 ``` }]; @@ -231,6 +231,7 @@ def ConcatOp : CombOp<"concat", [InferTypeOpInterface, Pure]> { std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results); }]; @@ -285,3 +286,33 @@ def MuxOp : CombOp<"mux", let hasFolder = true; let hasCanonicalizer = true; } + +def TruthTableOp : CombOp<"truth_table", [Pure]> { + let summary = "Return a true/false based on a lookup table"; + let description = [{ + ``` + %a = ... : i1 + %b = ... : i1 + %0 = comb.truth_table %a, %b -> [false, true, true, false] + ``` + + This operation assumes a fully elaborated table -- 2^n entries. Inputs are + sorted MSB -> LSB from left to right and the offset into `lookupTable` is + computed from them. The table is sorted from 0 -> (2^n - 1) from left to + right. + + No difference from array_get into an array of constants except for xprop + behavior. If one of the inputs is unknown, but said input doesn't make a + difference in the output (based on the lookup table) the result should not + be 'x' -- it should be the well-known result. + }]; + + let arguments = (ins Variadic:$inputs, BoolArrayAttr:$lookupTable); + let results = (outs I1:$result); + + let assemblyFormat = [{ + $inputs `->` $lookupTable attr-dict + }]; + + let hasVerifier = 1; +} diff --git a/include/circt/Dialect/Comb/Passes.td b/include/circt/Dialect/Comb/Passes.td new file mode 100644 index 000000000000..5ecaa997dfa6 --- /dev/null +++ b/include/circt/Dialect/Comb/Passes.td @@ -0,0 +1,25 @@ +//===-- Passes.td - Comb pass definition file --------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_COMB_PASSES_TD +#define CIRCT_DIALECT_COMB_PASSES_TD + +include "mlir/Pass/PassBase.td" + +def LowerComb : Pass<"lower-comb", "mlir::ModuleOp"> { + let summary = "Lowers the some of the comb ops"; + let description = [{ + Some operations in the comb dialect (e.g. `comb.truth_table`) are not + directly supported by ExportVerilog. They need to be lowered into ops which + are supported. There are many ways to lower these ops so we do this in a + separate pass. This also allows the lowered form to participate in + optimizations like the comb canonicalizers. + }]; +} + +#endif // CIRCT_DIALECT_COMB_PASSES_TD diff --git a/include/circt/Dialect/DC/CMakeLists.txt b/include/circt/Dialect/DC/CMakeLists.txt new file mode 100644 index 000000000000..267f1f15a891 --- /dev/null +++ b/include/circt/Dialect/DC/CMakeLists.txt @@ -0,0 +1,8 @@ +add_circt_dialect(DC dc) +add_circt_dialect_doc(DC dc) + +set(LLVM_TARGET_DEFINITIONS DC.td) +set(LLVM_TARGET_DEFINITIONS DCPasses.td) +mlir_tablegen(DCPasses.h.inc -gen-pass-decls) +add_public_tablegen_target(CIRCTDCTransformsIncGen) +add_circt_doc(DCPasses DCPasses -gen-pass-doc) diff --git a/include/circt/Dialect/DC/DC.td b/include/circt/Dialect/DC/DC.td new file mode 100644 index 000000000000..2e3435ccd79f --- /dev/null +++ b/include/circt/Dialect/DC/DC.td @@ -0,0 +1,18 @@ +//===- DC.td - DC dialect definition -----------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_DC_TD +#define CIRCT_DIALECT_DC_DC_TD + +include "mlir/IR/OpBase.td" + +include "circt/Dialect/DC/DCDialect.td" +include "circt/Dialect/DC/DCTypes.td" +include "circt/Dialect/DC/DCOps.td" + +#endif // CIRCT_DIALECT_DC_DC_TD diff --git a/include/circt/Dialect/DC/DCDialect.h b/include/circt/Dialect/DC/DCDialect.h new file mode 100644 index 000000000000..6c2ac032e279 --- /dev/null +++ b/include/circt/Dialect/DC/DCDialect.h @@ -0,0 +1,19 @@ +//===- DCDialect.h - DC dialect definition ----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_DCDIALECT_H +#define CIRCT_DIALECT_DC_DCDIALECT_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Dialect.h" + +// Pull in the dialect definition. +#include "circt/Dialect/DC/DCDialect.h.inc" + +#endif // CIRCT_DIALECT_DC_DCDIALECT_H diff --git a/include/circt/Dialect/DC/DCDialect.td b/include/circt/Dialect/DC/DCDialect.td new file mode 100644 index 000000000000..b14c027f53ab --- /dev/null +++ b/include/circt/Dialect/DC/DCDialect.td @@ -0,0 +1,34 @@ +//===- DCDialect.td - DC dialect definition ----------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_DIALECT_TD +#define CIRCT_DIALECT_DC_DIALECT_TD + +include "mlir/IR/OpBase.td" + +def DCDialect : Dialect { + let name = "dc"; + let summary = "Dynamic Control"; + let description = [{ + This is the `dc` dialect, used to represent dynamic control constructs with + handshaking semantics. + }]; + let cppNamespace = "circt::dc"; + + let useDefaultTypePrinterParser = 1; + let dependentDialects = ["circt::esi::ESIDialect"]; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + + let extraClassDeclaration = [{ + void registerTypes(); + }]; +} + +#endif // CIRCT_DIALECT_DC_DIALECT_TD diff --git a/include/circt/Dialect/DC/DCOps.h b/include/circt/Dialect/DC/DCOps.h new file mode 100644 index 000000000000..ae777469b3fb --- /dev/null +++ b/include/circt/Dialect/DC/DCOps.h @@ -0,0 +1,37 @@ +//===- DCOps.h - DC dialect operations --------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_DCOPS_H +#define CIRCT_DIALECT_DC_DCOPS_H + +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/RegionKindInterface.h" +#include "mlir/IR/SymbolTable.h" +#include "mlir/Interfaces/CallInterfaces.h" +#include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" + +#include "circt/Dialect/DC/DCDialect.h" +#include "circt/Dialect/DC/DCTypes.h" +#include "circt/Dialect/ESI/ESIOps.h" +#include "circt/Dialect/ESI/ESITypes.h" + +namespace circt { +namespace dc { + +// Returns true if 't'is a `dc.value` type. +bool isI1ValueType(Type t); + +} // namespace dc +} // namespace circt + +#define GET_OP_CLASSES +#include "circt/Dialect/DC/DC.h.inc" + +#endif // CIRCT_DIALECT_DC_DCOPS_H diff --git a/include/circt/Dialect/DC/DCOps.td b/include/circt/Dialect/DC/DCOps.td new file mode 100644 index 000000000000..4826f793d6f0 --- /dev/null +++ b/include/circt/Dialect/DC/DCOps.td @@ -0,0 +1,253 @@ +//===- DCOps.td - DC dialect operations --------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_OPS_TD +#define CIRCT_DIALECT_DC_OPS_TD + +include "mlir/Interfaces/InferTypeOpInterface.td" +include "circt/Dialect/DC/DCDialect.td" +include "circt/Dialect/DC/DCTypes.td" +include "mlir/Interfaces/FunctionInterfaces.td" +include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/RegionKindInterface.td" +include "mlir/IR/SymbolInterfaces.td" +include "mlir/Interfaces/CallInterfaces.td" +include "mlir/Interfaces/ControlFlowInterfaces.td" +include "mlir/Interfaces/SideEffectInterfaces.td" +include "mlir/IR/BuiltinAttributes.td" +include "circt/Dialect/ESI/ESITypes.td" + +class DCOp traits = []> : + Op; + +class SameTypeConstraint + : TypesMatchWith<"lhs and rhs types should be equivalent", + lhs, rhs, [{ $_self }]>; + +def BufferOp : DCOp<"buffer", + [SameTypeConstraint<"input", "output">]> { + let summary = "Buffer operation"; + let description = [{ + The buffer operation may buffer a `dc.value` or `dc.token` typed SSA value. + In practice, this provides a mechanism to buffer data-side values in a + control-sensitive manner. + + Example: + ```mlir + %value_out = dc.buffer [2] %value : !dc.value + ``` + + **Hardware/CIRCT context note**: buffers have no dialect-side notion of + cycles/stages/implementation. It is up to the generating pass to interpret + buffer semantics - some may want to add attributes to a single buffer, some + may want to stagger `dc.buffer`s sequentially. + }]; + + let arguments = (ins + ValueOrTokenType:$input, + ConfinedAttr]>:$size, + OptionalAttr:$initValues + ); + let results = (outs ValueOrTokenType:$output); + + let assemblyFormat = "`[` $size `]` $input ($initValues^)? attr-dict `:` type($input)"; + let hasVerifier = 1; + let builders = [OpBuilder<( + ins "Value":$input, "size_t":$size), [{ + build($_builder, $_state, input.getType(), input, $_builder.getI64IntegerAttr(size), {}); + }]>]; + + let extraClassDeclaration = [{ + // Returns the data type of this buffer, if any. + std::optional getInnerType() { + if(auto type = getInput().getType().dyn_cast()) + return type.getInnerType(); + return std::nullopt; + } + + // Returns the initial values of this buffer as a vector of int64's. + FailureOr> getInitValueArray(); + }]; +} + +def JoinOp : DCOp<"join", [Commutative]> { + let summary = "Synchronizes the incoming tokens with the outgoing token"; + let description = [{ + This operator synchronizes all incoming tokens. Synchronization implies applying + join semantics in between all in- and output ports. + + Example: + ```mlir + %0 = dc.join %a, %b + ``` + }]; + + let arguments = (ins Variadic:$tokens); + let results = (outs TokenType:$output); + + let assemblyFormat = "$tokens attr-dict"; + let hasFolder = 1; +} + +def ForkOp : DCOp<"fork"> { + let summary = "Splits the incoming token into multiple outgoing tokens"; + let description = [{ + This operator splits the incoming token into multiple outgoing tokens. + + Example: + ```mlir + %0, %1 = dc.fork [2] %a : !dc.token, !dc.token + ``` + }]; + + let arguments = (ins TokenType:$token); + let results = (outs Variadic:$outputs); + let hasCustomAssemblyFormat = 1; + let hasCanonicalizer = 1; + let hasFolder = 1; + + let builders = [OpBuilder<( + ins "Value":$token, "size_t":$numOutputs), [{ + llvm::SmallVector outputTypes(numOutputs, $_builder.getType()); + build($_builder, $_state, outputTypes, token); + }]>]; +} + +def BranchOp : DCOp<"branch"> { + let summary = "Branch operation"; + let description = [{ + The incoming select token is propagated to the selected output based on + the value of the condition. + }]; + + let arguments = (ins I1ValueType:$condition); + let results = (outs TokenType:$trueToken, TokenType:$falseToken); + + let assemblyFormat = "$condition attr-dict"; +} + +def SelectOp : DCOp<"select"> { + let summary = "Select operation"; + let description = [{ + An input token is selected based on the value of the incoming select + signal, and propagated to the single output. Only the condition value, + the selected input, and the output will be transacted. + }]; + + let arguments = (ins I1ValueType:$condition, TokenType:$trueToken, TokenType:$falseToken); + let results = (outs TokenType:$output); + let assemblyFormat = "$condition `,` $trueToken `,` $falseToken attr-dict"; + let hasCanonicalizer = 1; +} + +def SinkOp : DCOp<"sink"> { + let summary = "Sink operation"; + let description = [{ + The sink operation will always accept any incoming tokens, and + discard them. + }]; + + let arguments = (ins TokenType:$token); + let results = (outs); + let assemblyFormat = "$token attr-dict"; +} + +def SourceOp : DCOp<"source"> { + let summary = "Source operation"; + let description = [{ + The source operation will always produce a token. + }]; + + let arguments = (ins); + let results = (outs TokenType:$output); + let assemblyFormat = "attr-dict"; + let builders = [OpBuilder<(ins), [{ + build($_builder, $_state, $_builder.getType()); + }]>]; +} + +def PackOp : DCOp<"pack", [ + DeclareOpInterfaceMethods +]> { + let summary = "Pack operation"; + let description = [{ + An operation which packs together a !dc.token value with some other + value. + + Typically, a `dc.pack` op will be used to facilitate data-dependent + control flow, wherein a `dc.value` is to be generated as a select + signal for either a `dc.branch` or `dc.select` operation. + }]; + + let arguments = (ins TokenType:$token, AnyType:$input); + let results = (outs ValueType:$output); + let assemblyFormat = "$token `,` $input attr-dict `:` type($input)"; + let hasFolder = 1; +} + +def UnpackOp : DCOp<"unpack", [ + DeclareOpInterfaceMethods +]> { + let summary = "Unpack operation"; + let description = [{ + An operation which unpacks a !dc.value value into a !dc.token value + and its constituent values. + }]; + + let arguments = (ins ValueType:$input); + let results = (outs TokenType:$token, AnyType:$output); + let assemblyFormat = "$input attr-dict `:` qualified(type($input))"; + let hasCanonicalizer = 1; + let hasFolder = 1; +} + +def MergeOp : DCOp<"merge"> { + let summary = "Merge operation"; + let description = [{ + Select one of the incoming tokens and emits an output stating which token + was selected. If multiple tokens are ready to transact at the same time, + the tokens are selected with priority, from first to last (i.e. left to right + in the IR). This property ensures deterministic behavior. + }]; + + let arguments = (ins TokenType:$first, TokenType:$second); + let results = (outs I1ValueType:$output); + let assemblyFormat = "$first `,` $second attr-dict"; +} + +def ToESIOp : DCOp<"to_esi", [ + DeclareOpInterfaceMethods +]> { + let summary = "Convert a DC-typed value to an ESI-typed value"; + let description = [{ + Convert a `dc.token/dc.value` to an ESI channel. + }]; + + let arguments = (ins + ValueOrTokenType:$input + ); + let results = (outs ChannelType:$output); + let assemblyFormat = "$input attr-dict `:` type($input)"; +} + +def FromESIOp : DCOp<"from_esi", [ + DeclareOpInterfaceMethods +]> { + let summary = "Convert an ESI-typed value to a DC-typed value"; + let description = [{ + Convert an ESI channel to a `dc.token/dc.value`. + }]; + + let arguments = (ins ChannelType:$input); + let results = (outs ValueOrTokenType:$output); + let assemblyFormat = "$input attr-dict `:` type($input)"; +} + +#endif // CIRCT_DIALECT_DC_OPS_TD diff --git a/include/circt/Dialect/DC/DCPasses.h b/include/circt/Dialect/DC/DCPasses.h new file mode 100644 index 000000000000..d6f6f1ea8a6a --- /dev/null +++ b/include/circt/Dialect/DC/DCPasses.h @@ -0,0 +1,31 @@ +//===- Passes.h - DC dialect passes --------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_DCPASSES_H +#define CIRCT_DIALECT_DC_DCPASSES_H + +#include "mlir/Pass/Pass.h" +#include + +namespace mlir { +class Pass; +} // namespace mlir + +namespace circt { +namespace dc { + +std::unique_ptr createDCMaterializeForksSinksPass(); +std::unique_ptr createDCDematerializeForksSinksPass(); + +#define GEN_PASS_REGISTRATION +#include "circt/Dialect/DC/DCPasses.h.inc" + +} // namespace dc +} // namespace circt + +#endif // CIRCT_DIALECT_DC_DCPASSES_H diff --git a/include/circt/Dialect/DC/DCPasses.td b/include/circt/Dialect/DC/DCPasses.td new file mode 100644 index 000000000000..884b266edfba --- /dev/null +++ b/include/circt/Dialect/DC/DCPasses.td @@ -0,0 +1,34 @@ +//===- Passes.td - DC dialect passes ----------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_PASSES_TD +#define CIRCT_DIALECT_DC_PASSES_TD + +include "mlir/Pass/PassBase.td" + +def DCMaterializeForksSinks : InterfacePass<"dc-materialize-forks-sinks", "mlir::FunctionOpInterface"> { + let summary = "Materialize fork and sink operations."; + let description = [{ + This pass analyses a function-like operation and inserts fork and sink + operations ensuring that all values have exactly one use. + }]; + let constructor = "circt::dc::createDCMaterializeForksSinksPass()"; + let dependentDialects = ["dc::DCDialect"]; +} + +def DCDematerializeForksSinks : InterfacePass<"dc-dematerialize-forks-sinks", "mlir::FunctionOpInterface"> { + let summary = "Dematerialize fork and sink operations."; + let description = [{ + This pass analyses a function-like operation and removes all fork and sink + operations. + }]; + let constructor = "circt::dc::createDCDematerializeForksSinksPass()"; + let dependentDialects = ["dc::DCDialect"]; +} + +#endif // CIRCT_DIALECT_DC_PASSES_TD diff --git a/include/circt/Dialect/DC/DCTypes.h b/include/circt/Dialect/DC/DCTypes.h new file mode 100644 index 000000000000..7314c65d20cc --- /dev/null +++ b/include/circt/Dialect/DC/DCTypes.h @@ -0,0 +1,18 @@ +//===- DCTypes.h - DC dialect types -----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_DCTYPES_H +#define CIRCT_DIALECT_DC_DCTYPES_H + +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Types.h" + +#define GET_TYPEDEF_CLASSES +#include "circt/Dialect/DC/DCTypes.h.inc" + +#endif // CIRCT_DIALECT_DC_DCTYPES_H diff --git a/include/circt/Dialect/DC/DCTypes.td b/include/circt/Dialect/DC/DCTypes.td new file mode 100644 index 000000000000..ff7e2d387c76 --- /dev/null +++ b/include/circt/Dialect/DC/DCTypes.td @@ -0,0 +1,50 @@ +//===- DCTypes.td - DC dialect types -----------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DC_TYPES_TD +#define CIRCT_DIALECT_DC_TYPES_TD + +include "circt/Dialect/DC/DCDialect.td" +include "mlir/IR/AttrTypeBase.td" + +class DCTypeDef : TypeDef { } + +def TokenType : DCTypeDef<"Token"> { + let mnemonic = "token"; + let parameters = (ins ); + let assemblyFormat = ""; + let description = [{ + A `!dc.token`-typed value represents a control value with handshake semantics. + }]; +} + +// A value type is a value which is wrapped with token semantics. +def ValueType : DCTypeDef<"Value"> { + let mnemonic = "value"; + let parameters = (ins "Type":$innerType); + let assemblyFormat = "`<`$innerType`>`"; + let description = [{ + A `!dc.value`-typed value represents a value which is wrapped with + `!dc.token` semantics. This type is used to attach control semantics to + values. The inner value may be of any type. + }]; +} + +def I1ValueType : Type< + CPred<"circt::dc::isI1ValueType($_self)">, "must be a !dc.value type">, + BuildableType<"$_builder.getType($_builder.getI1Type())"> { +} + +def ValueOrTokenType : Type< + CPred<"$_self.isa()">, + "must be a !dc.value or !dc.token type"> { +} + + + +#endif // CIRCT_DIALECT_DC_TYPES_TD diff --git a/include/circt/Dialect/Debug/CMakeLists.txt b/include/circt/Dialect/Debug/CMakeLists.txt new file mode 100644 index 000000000000..aeb5daa09fec --- /dev/null +++ b/include/circt/Dialect/Debug/CMakeLists.txt @@ -0,0 +1,5 @@ +add_circt_dialect(Debug dbg) +add_circt_doc(DebugOps Dialects/DebugOps -gen-op-doc) +add_circt_doc(DebugTypes Dialects/DebugTypes -gen-typedef-doc -dialect dbg) + +add_dependencies(circt-headers MLIRDebugIncGen) diff --git a/include/circt/Dialect/Debug/Debug.td b/include/circt/Dialect/Debug/Debug.td new file mode 100644 index 000000000000..e9cb723d4e30 --- /dev/null +++ b/include/circt/Dialect/Debug/Debug.td @@ -0,0 +1,16 @@ +//===- Debug.td - Debug dialect ----------------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DEBUG_DEBUG_TD +#define CIRCT_DIALECT_DEBUG_DEBUG_TD + +include "circt/Dialect/Debug/DebugDialect.td" +include "circt/Dialect/Debug/DebugOps.td" +include "circt/Dialect/Debug/DebugTypes.td" + +#endif // CIRCT_DIALECT_DEBUG_DEBUG_TD diff --git a/include/circt/Dialect/Debug/DebugDialect.h b/include/circt/Dialect/Debug/DebugDialect.h new file mode 100644 index 000000000000..b644a0b45455 --- /dev/null +++ b/include/circt/Dialect/Debug/DebugDialect.h @@ -0,0 +1,17 @@ +//===- DebugDialect.h - Debug dialect definition ----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DEBUG_DEBUGDIALECT_H +#define CIRCT_DIALECT_DEBUG_DEBUGDIALECT_H + +#include "mlir/IR/Dialect.h" + +// Dialect definition generated from `DebugDialect.td` +#include "circt/Dialect/Debug/DebugDialect.h.inc" + +#endif // CIRCT_DIALECT_DEBUG_DEBUGDIALECT_H diff --git a/include/circt/Dialect/Debug/DebugDialect.td b/include/circt/Dialect/Debug/DebugDialect.td new file mode 100644 index 000000000000..ffc4c5d94e99 --- /dev/null +++ b/include/circt/Dialect/Debug/DebugDialect.td @@ -0,0 +1,28 @@ +//===- DebugDialect.td - Debug dialect definition ----------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DEBUG_DEBUGDIALECT_TD +#define CIRCT_DIALECT_DEBUG_DEBUGDIALECT_TD + +include "mlir/IR/DialectBase.td" +include "mlir/IR/OpBase.td" + +def DebugDialect : Dialect { + let name = "dbg"; + let summary = "Facilities to represent debug information"; + let cppNamespace = "circt::debug"; + + let useDefaultTypePrinterParser = 1; + + let extraClassDeclaration = [{ + void registerOps(); + void registerTypes(); + }]; +} + +#endif // CIRCT_DIALECT_DEBUG_DEBUGDIALECT_TD diff --git a/include/circt/Dialect/Debug/DebugOps.h b/include/circt/Dialect/Debug/DebugOps.h new file mode 100644 index 000000000000..91459d4ca522 --- /dev/null +++ b/include/circt/Dialect/Debug/DebugOps.h @@ -0,0 +1,24 @@ +//===- DebugOps.h - Debug dialect operations ====----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DEBUG_DEBUGOPS_H +#define CIRCT_DIALECT_DEBUG_DEBUGOPS_H + +#include "mlir/Bytecode/BytecodeOpInterface.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" + +#include "circt/Dialect/Debug/DebugDialect.h" +#include "circt/Dialect/Debug/DebugTypes.h" + +// Operation definitions generated from `Debug.td` +#define GET_OP_CLASSES +#include "circt/Dialect/Debug/Debug.h.inc" + +#endif // CIRCT_DIALECT_DEBUG_DEBUGOPS_H diff --git a/include/circt/Dialect/Debug/DebugOps.td b/include/circt/Dialect/Debug/DebugOps.td new file mode 100644 index 000000000000..712d6f53ab82 --- /dev/null +++ b/include/circt/Dialect/Debug/DebugOps.td @@ -0,0 +1,81 @@ +//===- DebugOps.td - Debug dialect operations --------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DEBUG_DEBUGOPS_TD +#define CIRCT_DIALECT_DEBUG_DEBUGOPS_TD + +include "circt/Dialect/Debug/DebugDialect.td" +include "circt/Dialect/Debug/DebugTypes.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" +include "mlir/IR/OpBase.td" + +class DebugOp traits = []> : + Op; + + +def VariableOp : DebugOp<"variable"> { + let summary = "A named value to be captured in debug info"; + let description = [{ + Marks a value to be tracked in DI under the given name. The `dbg.variable` + operation is useful to represent named values in a source language. For + example, ports, constants, parameters, variables, nodes, or name aliases can + all be represented as a variable. In combination with `dbg.array` and + `dbg.struct`, complex aggregate source language values can be described and + reconstituted from individual IR values. The `dbg.variable` operation acts + as a tracker that follows the evolution of its assigned value throughout the + compiler's pass pipelines. The debug info analysis uses this op to populate + a module's scope with named source language values, and to establish how + these source language values can be reconstituted from the actual IR values + present at the end of compilation. + + See the rationale for examples and details. + }]; + let arguments = (ins StrAttr:$name, AnyType:$value); + let assemblyFormat = [{ $name `,` $value attr-dict `:` type($value) }]; +} + + +def StructOp : DebugOp<"struct", [ + Pure, + PredOpTrait<"number of fields and names match", + CPred<"$fields.size() == $names.size()">> +]> { + let summary = "Aggregate values into a struct"; + let description = [{ + Creates a struct aggregate from a list of names and values. The `dbg.struct` + operation allows for struct-like source language values to be captured in + the debug info. This includes structs, unions, bidirectional bundles, + interfaces, classes, and other similar structures. + + See the rationale for examples and details. + }]; + let arguments = (ins Variadic:$fields, StrArrayAttr:$names); + let results = (outs StructType:$result); + let hasCustomAssemblyFormat = 1; +} + + +def ArrayOp : DebugOp<"array", [Pure, SameTypeOperands]> { + let summary = "Aggregate values into an array"; + let description = [{ + Creates an array aggregate from a list of values. The first operand is + placed at array index 0. The last operand is placed at the highest array + index. The `dbg.array` operation allows for array-like source language + values to be captured in the debug info. This includes arrays, or in the + case of SystemVerilog, packed and unpacked arrays, lists, sequences, queues, + FIFOs, channels, and vectors. + + See the rationale for examples and details. + }]; + let arguments = (ins Variadic:$elements); + let results = (outs ArrayType:$result); + let hasCustomAssemblyFormat = 1; +} + +#endif // CIRCT_DIALECT_DEBUG_DEBUGOPS_TD diff --git a/include/circt/Dialect/Debug/DebugTypes.h b/include/circt/Dialect/Debug/DebugTypes.h new file mode 100644 index 000000000000..52b6ee5c9d4e --- /dev/null +++ b/include/circt/Dialect/Debug/DebugTypes.h @@ -0,0 +1,18 @@ +//===- DebugTypes.h - Debug dialect types -----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DEBUG_DEBUGTYPES_H +#define CIRCT_DIALECT_DEBUG_DEBUGTYPES_H + +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Types.h" + +#define GET_TYPEDEF_CLASSES +#include "circt/Dialect/Debug/DebugTypes.h.inc" + +#endif // CIRCT_DIALECT_DEBUG_DEBUGTYPES_H diff --git a/include/circt/Dialect/Debug/DebugTypes.td b/include/circt/Dialect/Debug/DebugTypes.td new file mode 100644 index 000000000000..37faa171a3a4 --- /dev/null +++ b/include/circt/Dialect/Debug/DebugTypes.td @@ -0,0 +1,29 @@ +//===- DebugTypes.td - Debug dialect types -----------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_DEBUG_DEBUGTYPES_TD +#define CIRCT_DIALECT_DEBUG_DEBUGTYPES_TD + +include "circt/Dialect/Debug/DebugDialect.td" +include "mlir/IR/AttrTypeBase.td" + +class DebugTypeDef : TypeDef { } + +def StructType : DebugTypeDef<"Struct"> { + let mnemonic = "struct"; + let summary = "debug struct aggregate"; + let description = "The result of a `dbg.struct` operation."; +} + +def ArrayType : DebugTypeDef<"Array"> { + let mnemonic = "array"; + let summary = "debug array aggregate"; + let description = "The result of a `dbg.array` operation."; +} + +#endif // CIRCT_DIALECT_DEBUG_DEBUGTYPES_TD diff --git a/include/circt/Dialect/ESI/APIUtilities.h b/include/circt/Dialect/ESI/APIUtilities.h new file mode 100644 index 000000000000..2b60caa1728a --- /dev/null +++ b/include/circt/Dialect/ESI/APIUtilities.h @@ -0,0 +1,68 @@ +//===- APIUtilities.h - ESI general-purpose API utilities -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Utilities and classes applicable to all ESI API generators. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ESI_APIUTILITIES_H +#define CIRCT_DIALECT_ESI_APIUTILITIES_H + +#include "circt/Dialect/ESI/ESIOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/Support/IndentedOstream.h" +#include "llvm/ADT/MapVector.h" + +#include + +namespace circt { +namespace esi { + +/// Every time we implement a breaking change in the schema generation, +/// increment this number. It is a seed for all the schema hashes. +constexpr uint64_t esiApiVersion = 1; + +// Base type for all Cosim-implementing type emitters. +class ESIAPIType { +public: + using FieldInfo = hw::StructType::FieldInfo; + + ESIAPIType(mlir::Type); + virtual ~ESIAPIType() = default; + bool operator==(const ESIAPIType &) const; + + /// Get the type back. + mlir::Type getType() const { return type; } + + /// Returns true if the type is currently supported. + virtual bool isSupported() const; + + llvm::ArrayRef getFields() const { return fieldTypes; } + + // API-safe name for this type which should work with most languages. + StringRef name() const; + + uint64_t size() const; + + // Capnproto-safe type id for this type. + uint64_t typeID() const; + +protected: + /// Cosim requires that everything be contained in a struct. ESI doesn't so + /// we wrap non-struct types in a struct. + llvm::SmallVector fieldTypes; + + mlir::Type type; + mutable std::string cachedName; + mutable std::optional cachedID; +}; + +} // namespace esi +} // namespace circt + +#endif // CIRCT_DIALECT_ESI_APIUTILITIES_H diff --git a/include/circt/Dialect/ESI/AppID.h b/include/circt/Dialect/ESI/AppID.h new file mode 100644 index 000000000000..bc91ff75bb0e --- /dev/null +++ b/include/circt/Dialect/ESI/AppID.h @@ -0,0 +1,86 @@ +//===- AppID.h - AppID related code -----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Application IDs are paths through the instance hierarchy with some +// application-specific meaning. They allow designers and users to avoid some of +// the design's implementation details. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ESI_APPID_H +#define CIRCT_DIALECT_ESI_APPID_H + +#include "circt/Dialect/ESI/ESIAttributes.h" +#include "circt/Dialect/HW/HWSymCache.h" + +#include "mlir/IR/Operation.h" +#include "llvm/ADT/DenseMap.h" + +namespace circt { +namespace esi { + +/// Get the AppID of a particular operation. Returns null if the operation does +/// not have one. +AppIDAttr getAppID(Operation *op); + +/// An index for resolving AppIDPaths to dynamic instances. +class AppIDIndex { +public: + AppIDIndex(Operation *mlirTop); + ~AppIDIndex(); + + // If invalid, construction failed for some reason (which was emitted via an + // error). Since we want to be able to call this class as an analysis, all of + // the index construction occurs in the constructor, which doesn't allow for + // a LogicalResult return. (This is where exceptions would be useful.) + bool isValid() const { return valid; } + + // Return an array of AppIDAttrs which are contained in the module. + ArrayAttr getChildAppIDsOf(hw::HWModuleLike) const; + + /// Walk the AppID hierarchy rooted at the specified module. + LogicalResult + walk(hw::HWModuleLike top, + function_ref)> fn) const; + LogicalResult + walk(StringRef top, + function_ref)> fn) const; + + /// Return an array of InnerNameRefAttrs representing the relative path to + /// 'appid' from 'fromMod'. + FailureOr getAppIDPathAttr(hw::HWModuleLike fromMod, + AppIDAttr appid, Location loc) const; + +private: + /// Walk the AppID hierarchy rooted at the specified module. + LogicalResult + walk(hw::HWModuleLike top, hw::HWModuleLike current, + SmallVectorImpl &pathStack, + SmallVectorImpl &opStack, + function_ref)> fn) const; + + //===--------------------------------------------------------------------===// + // Index construction and storage. + //===--------------------------------------------------------------------===// + class ModuleAppIDs; + + /// Construct the index for a module. + FailureOr buildIndexFor(hw::HWModuleLike modToProcess); + + // Map modules to their cached child app ID indexes. + DenseMap containerAppIDs; + + bool valid; + hw::HWSymbolCache symCache; + Operation *mlirTop; +}; + +} // namespace esi +} // namespace circt + +#endif // CIRCT_DIALECT_ESI_APPID_H diff --git a/include/circt/Dialect/ESI/CMakeLists.txt b/include/circt/Dialect/ESI/CMakeLists.txt index fb07f3df783f..b2515cbb48c4 100644 --- a/include/circt/Dialect/ESI/CMakeLists.txt +++ b/include/circt/Dialect/ESI/CMakeLists.txt @@ -1,15 +1,20 @@ add_circt_dialect(ESI esi) -add_circt_dialect_doc(ESI esi) -set(LLVM_TARGET_DEFINITIONS ESI.td) +set(LLVM_TARGET_DEFINITIONS ESIPasses.td) mlir_tablegen(ESIPasses.h.inc -gen-pass-decls) add_public_tablegen_target(MLIRESITransformsIncGen) -add_circt_doc(ESI ESIPasses -gen-pass-doc) +add_circt_doc(ESIPasses ESIPasses -gen-pass-doc) set(LLVM_TARGET_DEFINITIONS ESI.td) mlir_tablegen(ESIEnums.h.inc -gen-enum-decls) mlir_tablegen(ESIEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(MLIRESIEnumsIncGen) +add_dependencies(circt-headers MLIRESIEnumsIncGen) + +mlir_tablegen(ESIAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect=esi) +mlir_tablegen(ESIAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect=esi) +add_public_tablegen_target(MLIRESIAttrsIncGen) +add_dependencies(circt-headers MLIRESIAttrsIncGen) set(LLVM_TARGET_DEFINITIONS ESIInterfaces.td) mlir_tablegen(ESIInterfaces.h.inc -gen-op-interface-decls) @@ -17,4 +22,12 @@ mlir_tablegen(ESIInterfaces.cpp.inc -gen-op-interface-defs) add_public_tablegen_target(MLIRESIInterfacesIncGen) add_dependencies(circt-headers MLIRESIInterfacesIncGen) -add_subdirectory(cosim) +add_circt_doc(ESIChannels Dialects/ESIAppID -gen-attrdef-doc) +add_circt_doc(ESIChannels Dialects/ESIChannels -gen-op-doc) +add_circt_doc(ESIChannels Dialects/ESIChannelTypes -gen-typedef-doc) +add_circt_doc(ESIServices Dialects/ESIServices -gen-op-doc) +add_circt_doc(ESIStdServices Dialects/ESIStdServices -gen-op-doc) +add_circt_doc(ESIStructure Dialects/ESIStructure -gen-op-doc) + +add_circt_doc(ESIInterfaces Dialects/ESIInterfaces -gen-op-interface-docs) +add_circt_doc(ESITypes Dialects/ESITypes -gen-typedef-doc) diff --git a/include/circt/Dialect/ESI/ESI.td b/include/circt/Dialect/ESI/ESI.td index e6b8c0a8aaaa..256872b8702a 100644 --- a/include/circt/Dialect/ESI/ESI.td +++ b/include/circt/Dialect/ESI/ESI.td @@ -16,44 +16,15 @@ include "mlir/IR/AttrTypeBase.td" include "mlir/IR/OpBase.td" -include "mlir/IR/SymbolInterfaces.td" -include "mlir/Interfaces/SideEffectInterfaces.td" -def ESI_Dialect : Dialect { - let name = "esi"; - let cppNamespace = "::circt::esi"; - - let hasConstantMaterializer = 1; - let useDefaultTypePrinterParser = 1; - - let extraClassDeclaration = [{ - /// Register all ESI types. - void registerTypes(); - }]; -} - -class ESI_Op traits = []> : - Op; - -// Divide all ESI ops into abstract and physical. Abstract ops are ones which -// need to be lowered to physical ops before they get lowered further (into -// HW). The abstract to physical lowering may not be fully constrained -- this -// is where decisions will have to be made concerning implementations. Physical -// ops are generally trivial to lower to HW -- they represent concrete -// constructs with no decisions left to be made. - -class ESI_Abstract_Op traits = []> : - ESI_Op; -class ESI_Physical_Op traits = []> : - ESI_Op; +include "circt/Dialect/ESI/ESIDialect.td" +include "circt/Dialect/ESI/ESIManifest.td" include "circt/Dialect/ESI/ESIInterfaces.td" -include "circt/Dialect/ESI/ESIPorts.td" include "circt/Dialect/ESI/ESITypes.td" -include "circt/Dialect/ESI/ESIOps.td" +include "circt/Dialect/ESI/ESIChannels.td" include "circt/Dialect/ESI/ESIServices.td" include "circt/Dialect/ESI/ESIStdServices.td" -include "circt/Dialect/ESI/ESIPasses.td" include "circt/Dialect/ESI/ESIStructure.td" #endif // ESI_TD diff --git a/include/circt/Dialect/ESI/ESIAttributes.h b/include/circt/Dialect/ESI/ESIAttributes.h new file mode 100644 index 000000000000..b8cbaeca0e76 --- /dev/null +++ b/include/circt/Dialect/ESI/ESIAttributes.h @@ -0,0 +1,23 @@ +//===- ESIAttributes.h - attributes for the ESI dialect ---------*- C++ -*-===// +// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ESI_ESIATTRIBUTES_H +#define CIRCT_DIALECT_ESI_ESIATTRIBUTES_H + +#include "circt/Dialect/HW/HWTypes.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/Dialect.h" +#include "mlir/IR/Operation.h" + +#include "ESIDialect.h" + +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/ESI/ESIAttributes.h.inc" + +#endif diff --git a/include/circt/Dialect/ESI/ESIChannels.td b/include/circt/Dialect/ESI/ESIChannels.td new file mode 100644 index 000000000000..59096700f6a5 --- /dev/null +++ b/include/circt/Dialect/ESI/ESIChannels.td @@ -0,0 +1,516 @@ +//===- ESIChannels.td - All ESI ops related to app channels -- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is where any operations for the ESI dialect live. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ESI_CHANNELS_TD +#define CIRCT_DIALECT_ESI_CHANNELS_TD + +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/EnumAttr.td" +include "mlir/IR/OpAsmInterface.td" +include "mlir/Interfaces/InferTypeOpInterface.td" + +include "circt/Dialect/HW/HWTypes.td" +include "circt/Dialect/Seq/SeqTypes.td" + +include "circt/Dialect/ESI/ESIDialect.td" +include "circt/Dialect/ESI/ESIInterfaces.td" +include "circt/Dialect/ESI/ESITypes.td" + +//===----------------------------------------------------------------------===// +// ESI channel type +//===----------------------------------------------------------------------===// + +// Standard valid-ready signaling. +def ChannelSignalingValidReady : I32EnumAttrCase<"ValidReady", 0>; +// FIFO interface with a read latency of zero. Very similiar to valid-ready, but +// cannot assert ready if data is not available. +def ChannelSignalingFIFO0 : I32EnumAttrCase<"FIFO0", 1>; + +def ChannelSignaling : I32EnumAttr< + "ChannelSignaling", + "ESI channel wire signaling standard", + [ChannelSignalingValidReady, ChannelSignalingFIFO0]>{ + let cppNamespace = "::circt::esi"; +} + +def ChannelTypeImpl : ESI_Type<"Channel"> { + let summary = "An ESI-compatible channel port"; + let description = [{ + An ESI port kind which models a latency-insensitive, unidirectional, + point-to-point data stream. Channels are typed (like all of ESI). Said + type can be any MLIR type, but must be lowered to something a backend + knows how to output (i.e. something emitVerilog knows about). + + Example: + + ```mlir + hw.module.extern @Sender() -> (%x: !esi.channel) + hw.module @Reciever(%a: !esi.channel>) { } + ``` + }]; + + let mnemonic = "channel"; + let parameters = (ins + "Type":$inner, + DefaultValuedParameter< + "::circt::esi::ChannelSignaling", + "::circt::esi::ChannelSignaling::ValidReady">:$signaling); + + let assemblyFormat = "`<` $inner (`,` $signaling^)? `>`"; + + let builders = [ + TypeBuilder<(ins "Type":$type), [{ + return Base::get(type.getContext(), type, + ::circt::esi::ChannelSignaling::ValidReady); + }]>, + ]; +} + +//===----------------------------------------------------------------------===// +// Wrap / unwrap channels to their signaling protocols. +//===----------------------------------------------------------------------===// + +def WrapValidReadyOp : ESI_Op<"wrap.vr", [ + DeclareOpInterfaceMethods + ]> { + let summary = "Wrap a value into an ESI port"; + let description = [{ + Wrapping a value into an ESI port type allows modules to send values down + an ESI port. Wrap data with valid bit, result is the ESI channel and the + ready signal from the other end of the channel. + }]; + + let arguments = (ins AnyType:$rawInput, I1:$valid); + let results = (outs ChannelType:$chanOutput, I1:$ready); + let hasCustomAssemblyFormat = 1; + let hasFolder = 1; + let hasVerifier = 1; + + let builders = [ + OpBuilder<(ins "mlir::Value":$data, "mlir::Value":$valid)> + ]; +} + +def UnwrapValidReadyOp : ESI_Op<"unwrap.vr", [ + DeclareOpInterfaceMethods + ]> { + let summary = "Unwrap a value from an ESI port"; + let description = [{ + Unwrapping a value allows operations on the contained value. Unwrap the + channel along with a ready signal that you generate. Result is the data + along with a valid signal. + }]; + + let arguments = (ins ChannelType:$chanInput, I1:$ready); + let results = (outs AnyType:$rawOutput, I1:$valid); + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + + let builders = [ + OpBuilder<(ins "mlir::Value":$inChan, "mlir::Value":$ready)> + ]; +} + +def WrapFIFOOp : ESI_Op<"wrap.fifo", [ + DeclareOpInterfaceMethods + ]> { + let summary = "Wrap a value into an ESI port with FIFO signaling"; + + let arguments = (ins AnyType:$data, I1:$empty); + let results = (outs ChannelType:$chanOutput, I1:$rden); + let hasCanonicalizeMethod = true; + let hasFolder = true; + let hasVerifier = 1; + + let assemblyFormat = [{ + $data `,` $empty attr-dict `:` + custom(type($data), type($chanOutput)) + }]; +} + +def UnwrapFIFOOp : ESI_Op<"unwrap.fifo", [ + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods + ]> { + let summary = "Unwrap a value from an ESI port into a FIFO interface"; + + let arguments = (ins ChannelType:$chanInput, I1:$rden); + let results = (outs AnyType:$data, I1:$empty); + let hasCanonicalizeMethod = true; + let hasVerifier = 1; + + let assemblyFormat = [{ + $chanInput `,` $rden attr-dict `:` qualified(type($chanInput)) + }]; + + let extraClassDeclaration = [{ + static LogicalResult mergeAndErase(UnwrapFIFOOp, WrapFIFOOp, PatternRewriter&); + }]; +} + +def ModportType: + Type()">, "sv.interface">; + +def WrapSVInterfaceOp: ESI_Op<"wrap.iface", [ + DeclareOpInterfaceMethods + ]> { + let summary = "Wrap an SV interface into an ESI port"; + let description = [{ + Wrap a SystemVerilog interface into an ESI channel. Interface MUST look + like an interface produced by ESI meaning it MUST contain valid, ready, + and data signals. Any other signals will be discarded. + }]; + + let arguments = (ins ModportType:$interfaceSink); + let results = (outs ChannelType:$output); + + let assemblyFormat = [{ + $interfaceSink attr-dict `:` qualified(type($interfaceSink)) `->` qualified(type($output)) + }]; + + let hasVerifier = 1; +} + +def UnwrapSVInterfaceOp : ESI_Op<"unwrap.iface", [ + DeclareOpInterfaceMethods + ]> { + let summary = "Unwrap an SV interface from an ESI port"; + let description = [{ + Unwrap an ESI channel into a SystemVerilog interface containing valid, + ready, and data signals. + }]; + + let arguments = (ins ChannelType:$chanInput, ModportType:$interfaceSource); + let results = (outs); + + let assemblyFormat = [{ + $chanInput `into` $interfaceSource attr-dict `:` `(` qualified(type($chanInput)) `,` qualified(type($interfaceSource)) `)` + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// Channel bundles +//===----------------------------------------------------------------------===// + +// Used to indicate the presence of a keyword in asm. Should be used within +// optional parens in the declarative assembly format. +// Upstreaming: https://github.com/llvm/llvm-project/pull/65438 +def UnitParameter : AttrOrTypeParameter<"::mlir::UnitAttr", "boolean flag"> { + let printer = ""; + let parser = "::mlir::UnitAttr::get($_parser.getContext())"; + let defaultValue = "::mlir::UnitAttr()"; +} + +def ChannelDirection : I32EnumAttr<"ChannelDirection", + "Direction of channel (see ChannelBundleImpl for details)", [ + I32EnumAttrCase<"to", 1>, + I32EnumAttrCase<"from", 2>, + ]> { + let cppNamespace = "::circt::esi"; +} + + +def ChannelBundleType : ESI_Type<"ChannelBundle"> { + let summary = "a bundle of channels"; + + let description = [{ + A channel bundle (sometimes referred to as just "bundle") is a set of + channels of associated signals, along with per-channel names and directions. + The prototypical example for a bundle is a request-response channel pair. + + The direction terminology is a bit confusing. Let us designate the module + which is outputting the bundle as the "sender" module and a module which has + a bundle as an input as the "receiver". The directions "from" and "to" are + from the senders perspective. So, the "to" direction means that channel is + transmitting messages from the sender to the receiver. Then, "from" means + that the sender is getting messages from the receiver (typically responses). + + When requesting a bundle from a service, the client is always considered the + sender. So the "to" direction is for the client to send messages to the + service. + }]; + + let mnemonic = "bundle"; + let parameters = (ins ArrayRefParameter<"BundledChannel">:$channels, + UnitParameter:$resettable); + let assemblyFormat = [{ + `<` `[` $channels `]` (`reset` $resettable^)? `>` + }]; + + let extraClassDeclaration = [{ + // Get a channel bundle with the same types and names but the directions + // reversed. + ChannelBundleType getReversed() const; + + // Return the opposite direction. + static ChannelDirection flip(ChannelDirection dir) { + return dir == ChannelDirection::from + ? ChannelDirection::to + : ChannelDirection::from; + } + }]; +} + +def PackBundleOp : ESI_Op<"bundle.pack", [ + DeclareOpInterfaceMethods + ]> { + let summary = "pack channels into a bundle"; + + let arguments = (ins Variadic:$toChannels); + let results = (outs ChannelBundleType:$bundle, + Variadic:$fromChannels); + let assemblyFormat = [{ + $toChannels attr-dict `:` custom( + type($toChannels), type($fromChannels), type($bundle)) + }]; + + let hasVerifier = 1; + let hasCanonicalizeMethod = 1; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "ChannelBundleType":$bundleType, + "mlir::ValueRange":$toChannels)> + ]; +} + +def UnpackBundleOp : ESI_Op<"bundle.unpack", [ + DeclareOpInterfaceMethods + ]> { + let summary = "unpack channels from a bundle"; + + let arguments = (ins ChannelBundleType:$bundle, + Variadic:$fromChannels); + let results = (outs Variadic:$toChannels); + let assemblyFormat = [{ + $fromChannels `from` $bundle attr-dict `:` custom( + type($toChannels), type($fromChannels), type($bundle)) + }]; + + let hasVerifier = 1; + let hasCanonicalizeMethod = 1; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "Value":$bundle, "mlir::ValueRange":$fromChannels)> + ]; +} + +//===----------------------------------------------------------------------===// +// Data windows +//===----------------------------------------------------------------------===// + +def ESIWindowType : ESI_Type<"Window"> { + let summary = "a data window"; + let description = [{ + A 'data window' allows designers to break up large messages into multiple + frames (aka phits) spread across multiple cycles. Windows are specified in + terms of a mapping of struct fields to frames. The width of a window is the + maximum frame size + the union tag (log2(#frames)). + + A data window does NOT imply an ESI channel. + + Current restrictions: + - A field may only appear once. + - Fields may only be re-ordered (wrt the original message) within a frame. + - Array fields whose array length is not evenly divisible by 'numItems' will + have an implicit frame inserted directly after containing the leftover array + items. + - Array fields with an array length MUST be in their own frame. + }]; + + let mnemonic = "window"; + let genVerifyDecl = 1; + + let parameters = (ins "StringAttr":$name, "mlir::Type":$into, + ArrayRefParameter<"WindowFrameType">:$frames); + let assemblyFormat = [{ `<` $name `,` $into `,` `[` $frames `]` `>` }]; + + let extraClassDeclaration = [{ + /// Get the union which realizes this window during lowering. + circt::hw::UnionType getLoweredType() const; + }]; +} + +def WindowFrameType : ESI_Type<"WindowFrame"> { + let summary = "Declare a data window frame"; + let description = [{ + A named list of fields which should appear in a given frame. + }]; + let mnemonic = "window.frame"; + + let parameters = (ins "StringAttr":$name, + ArrayRefParameter<"WindowFieldType">:$members); + + let assemblyFormat = [{ + `<` $name `,` `[` $members `]` `>` + }]; +} + +def WindowFieldType : ESI_Type<"WindowField"> { + let summary = "a field-in-frame specifier"; + let description = [{ + Specify that a field should appear within the enclosing frame. + }]; + let mnemonic = "window.field"; + + let parameters = (ins + "StringAttr":$fieldName, + OptionalParameter<"uint64_t", "# of items in arrays or lists">:$numItems); + + let assemblyFormat = [{ + `<` $fieldName (`,` $numItems^)? `>` + }]; +} + +def WrapWindow : ESI_Physical_Op<"window.wrap", [Pure]> { + let summary = "wrap a union into a data window"; + + let arguments = (ins UnionType:$frame); + let results = (outs ESIWindowType:$window); + let hasVerifier = 1; + let hasFolder = 1; + + let assemblyFormat = [{ + $frame attr-dict `:` custom(type($frame), type($window)) + }]; +} + +def UnwrapWindow : ESI_Physical_Op<"window.unwrap", [ + Pure, + DeclareOpInterfaceMethods + ]> { + let summary = "unwrap a data window into a union"; + + let arguments = (ins ESIWindowType:$window); + let results = (outs UnionType:$frame); + let hasFolder = 1; + + let assemblyFormat = [{ + $window attr-dict `:` qualified(type($window)) + }]; +} + +//===----------------------------------------------------------------------===// +// Channel buffering and pipelining. +//===----------------------------------------------------------------------===// + +def ChannelBufferOp : ESI_Abstract_Op<"buffer", [ + Pure, + DeclareOpInterfaceMethods + ]> { + let summary = "Control options for an ESI channel."; + let description = [{ + A channel buffer (`buffer`) is essentially a set of options on a channel. + It always adds at least one cycle of latency (pipeline stage) to the + channel, but this is configurable. + + This operation is inserted on an ESI dataflow edge. It must exist + previous to SystemVerilog emission but can be added in a lowering pass. + + A `stages` attribute may be provided to specify a specific number of cycles + (pipeline stages) to use on this channel. Must be greater than 0. + + A `name` attribute may be provided to assigned a name to a buffered + connection. + + Example: + + ```mlir + %esiChan = hw.instance "sender" @Sender () : () -> (!esi.channel) + // Allow automatic selection of options. + %bufferedChan = esi.buffer %esiChan : i1 + hw.instance "recv" @Reciever (%bufferedChan) : (!esi.channel) -> () + + // Alternatively, specify the number of stages. + %fourStageBufferedChan = esi.buffer %esiChan { stages = 4 } : i1 + ``` + }]; + + let arguments = (ins ClockType:$clk, I1:$rst, ChannelType:$input, + OptionalAttr]>>:$stages, + OptionalAttr:$name); + let results = (outs ChannelType:$output); + let hasCustomAssemblyFormat = 1; +} + +def PipelineStageOp : ESI_Physical_Op<"stage", [ + Pure, + DeclareOpInterfaceMethods + ]> { + let summary = "An elastic buffer stage."; + let description = [{ + An individual elastic pipeline register. Generally lowered to from a + ChannelBuffer ('buffer'), though can be inserted anywhere to add an + additional pipeline stage. Adding individually could be useful for + late-pass latency balancing. + }]; + + let arguments = (ins ClockType:$clk, I1:$rst, ChannelType:$input); + let results = (outs ChannelType:$output); + let hasCustomAssemblyFormat = 1; +} + +//===----------------------------------------------------------------------===// +// Misc operations +//===----------------------------------------------------------------------===// + +def CosimToHostEndpointOp : ESI_Physical_Op<"cosim.to_host", []> { + let summary = "Co-simulation endpoint sending data to the host."; + let description = [{ + A co-simulation endpoint is a connection from the simulation to some + outside process, usually a software application responsible for driving + the simulation (driver). + + It is uni-directional, in this case sending data from the simulation to the + host. + }]; + + let arguments = (ins ClockType:$clk, I1:$rst, + ChannelType:$toHost, StrAttr:$name); + + let assemblyFormat = [{ + $clk `,` $rst `,` $toHost`,` $name attr-dict `:` qualified(type($toHost)) + }]; +} + +def CosimFromHostEndpointOp : ESI_Physical_Op<"cosim.from_host", []> { + let summary = "Co-simulation endpoint receiving data from the host"; + let description = [{ + A co-simulation endpoint is a connection from the simulation to some + outside process, usually a software application responsible for driving + the simulation (driver). + + It is uni-directional, in this case receiving data from the host for the + simulation. + }]; + + let arguments = (ins ClockType:$clk, I1:$rst, StrAttr:$name); + let results = (outs ChannelType:$fromHost); + + let assemblyFormat = [{ + $clk `,` $rst `,` $name attr-dict `:` qualified(type($fromHost)) + }]; +} + +def NullSourceOp : ESI_Physical_Op<"null", [Pure]> { + let summary = "An op which never produces messages."; + + let arguments = (ins); + let results = (outs ChannelType:$out); + + let assemblyFormat = [{ attr-dict `:` qualified(type($out)) }]; +} + +#endif // CIRCT_DIALECT_ESI_CHANNELS_TD diff --git a/include/circt/Dialect/ESI/ESIDialect.h b/include/circt/Dialect/ESI/ESIDialect.h index bfed67edd8e6..9897b63edbae 100644 --- a/include/circt/Dialect/ESI/ESIDialect.h +++ b/include/circt/Dialect/ESI/ESIDialect.h @@ -28,14 +28,6 @@ namespace circt { namespace esi { void registerESIPasses(); -void registerESITranslations(); -LogicalResult exportCosimSchema(ModuleOp module, llvm::raw_ostream &os); - -/// A triple of signals which represent a latency insensitive interface with -/// valid/ready semantics. -struct ESIPortValidReadyMapping { - hw::PortInfo data, valid, ready; -}; /// Name of dialect attribute which governs whether or not to bundle (i.e. use /// SystemVerilog interfaces) channel signal wires on external modules. @@ -62,21 +54,6 @@ constexpr StringRef extModPortRdenSuffix = "esi.portRdenSuffix"; /// only to FIFO channels. constexpr StringRef extModPortEmptySuffix = "esi.portEmptySuffix"; -/// Find all the port triples on a module which fit the -/// /_valid/_ready pattern. Ready must be the opposite -/// direction of the other two. -void findValidReadySignals(Operation *modOp, - SmallVectorImpl &names); - -/// Given a list of logical port names, find the data/valid/ready port triples. -void resolvePortNames(Operation *modOp, ArrayRef portNames, - SmallVectorImpl &portTriples); - -/// Build an ESI module wrapper, converting the wires with latency-insensitive -/// semantics to ESI channels and passing through the rest. -Operation *buildESIWrapper(OpBuilder &b, Operation *mod, - ArrayRef esiPortNames); - } // namespace esi } // namespace circt diff --git a/include/circt/Dialect/ESI/ESIDialect.td b/include/circt/Dialect/ESI/ESIDialect.td new file mode 100644 index 000000000000..1881daa21586 --- /dev/null +++ b/include/circt/Dialect/ESI/ESIDialect.td @@ -0,0 +1,60 @@ +//===- ESIDialect.td - ESI dialect definition --------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ESI_DIALECT_TD +#define CIRCT_DIALECT_ESI_DIALECT_TD + +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/OpBase.td" + +include "mlir/Interfaces/SideEffectInterfaces.td" + +def ESI_Dialect : Dialect { + let name = "esi"; + let cppNamespace = "::circt::esi"; + + let hasConstantMaterializer = 1; + let useDefaultTypePrinterParser = 1; + let useDefaultAttributePrinterParser = 1; + + let extraClassDeclaration = [{ + /// Register all ESI attributes. + void registerAttributes(); + /// Register all ESI types. + void registerTypes(); + }]; + + let dependentDialects = [ + "circt::seq::SeqDialect", "circt::hw::HWDialect", "circt::sv::SVDialect", + "circt::comb::CombDialect" + ]; +} + +class ESI_Op traits = []> : + Op; + +// Divide ESI ops into abstract and physical. Abstract ops are ones which +// need to be lowered to physical ops before they get lowered further (into +// HW). The abstract to physical lowering may not be fully constrained -- this +// is where decisions will have to be made concerning implementations. Physical +// ops are generally trivial to lower to HW -- they represent concrete +// constructs with no decisions left to be made. +class ESI_Abstract_Op traits = []> : + ESI_Op; +class ESI_Physical_Op traits = []> : + ESI_Op; + +class ESI_Type : TypeDef { } + +class ESI_Attr traits = [], + string baseCppClass = "::mlir::Attribute"> + : AttrDef { + let mnemonic = ?; +} + +#endif // CIRCT_DIALECT_ESI_DIALECT_TD diff --git a/include/circt/Dialect/ESI/ESIInterfaces.td b/include/circt/Dialect/ESI/ESIInterfaces.td index 8205688cff31..c5057345471b 100644 --- a/include/circt/Dialect/ESI/ESIInterfaces.td +++ b/include/circt/Dialect/ESI/ESIInterfaces.td @@ -10,6 +10,9 @@ // //===----------------------------------------------------------------------===// +#ifndef CIRCT_DIALECT_ESI_INTERFACES_TD +#define CIRCT_DIALECT_ESI_INTERFACES_TD + include "mlir/IR/OpBase.td" def ChannelOpInterface : OpInterface<"ChannelOpInterface"> { @@ -36,6 +39,57 @@ def ChannelOpInterface : OpInterface<"ChannelOpInterface"> { ]; } +def HasAppIDOpInterface : OpInterface<"HasAppID"> { + let cppNamespace = "circt::esi"; + let description = [{ + Op can be identified by an AppID. + }]; + + let methods = [ + InterfaceMethod< + "Returns the AppID of this operation.", + "::circt::esi::AppIDAttr", "getAppID", (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + return $_op.getAppID(); + }] + >, + ]; +} + +def IsManifestData : OpInterface<"IsManifestData"> { + let cppNamespace = "circt::esi"; + let description = [{ + Op's attributes should be represented in the manifest. + }]; + + let methods = [ + StaticInterfaceMethod< + "Get the class name for this op.", + "StringRef", "getManifestClass", (ins) + >, + InterfaceMethod< + "Populate results with the manifest data.", + "void", "getDetails", (ins "SmallVectorImpl&":$results), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + auto attrs = $_op->getAttrs(); + results.append(attrs.begin(), attrs.end()); + }] + >, + InterfaceMethod< + "Get the manifest data from this op as an attribute.", + "DictionaryAttr", "getDetailsAsDict", (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + SmallVector attrs; + $_op.getDetails(attrs); + return DictionaryAttr::get($_op.getContext(), attrs); + }] + >, + ]; +} + //===----------------------------------------------------------------------===// // // Service-related interfaces. @@ -56,11 +110,21 @@ def ServiceDeclOpInterface : OpInterface<"ServiceDeclOpInterface"> { (ins "llvm::SmallVectorImpl&":$ports) >, InterfaceMethod< - "Validate this service request against this service decl.", - "mlir::LogicalResult", "validateRequest", (ins "mlir::Operation*":$req), - /*methodBody=*/"", - /*defaultImplementation=*/[{ - return circt::esi::validateServiceConnectionRequest($_op, req); - }]>, + "Get info on a particular port.", + "FailureOr", "getPortInfo", + (ins "StringAttr":$portName), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + SmallVector ports; + $_op.getPortList(ports); + auto f = llvm::find_if(ports, [&](ServicePortInfo port) { + return port.port.getName() == portName; + }); + if (f != ports.end()) return *f; + return failure(); + }] + > ]; } + +#endif // CIRCT_DIALECT_ESI_INTERFACES_TD diff --git a/include/circt/Dialect/ESI/ESIManifest.td b/include/circt/Dialect/ESI/ESIManifest.td new file mode 100644 index 000000000000..7813aa762c5a --- /dev/null +++ b/include/circt/Dialect/ESI/ESIManifest.td @@ -0,0 +1,222 @@ +//===- ESIManifest.td - System manifest stuff ---------------- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ESI_MANIFEST_TD +#define CIRCT_DIALECT_ESI_MANIFEST_TD + +include "circt/Dialect/ESI/ESIChannels.td" +include "circt/Dialect/HW/HWAttributesNaming.td" + +include "mlir/IR/AttrTypeBase.td" + +def AppIDAttr : ESI_Attr<"AppID"> { + let summary = "An application relevant instance identifier"; + let description = [{ + Identifies an instance which is visible through multiple hierarchy levels. + Indended to make locating an instance easier in the instance hierarchy. + }]; + + let parameters = (ins "StringAttr":$name, + OptionalParameter<"std::optional">:$index); + let mnemonic = "appid"; + let assemblyFormat = [{ + `<` $name (`[` $index^ `]`)? `>` + }]; + + let extraClassDeclaration = [{ + static constexpr StringRef AppIDAttrName = "esi.appid"; + }]; +} + +def AppIDArrayAttr : + TypedArrayAttrBase; + +def AppIDPathAttr : ESI_Attr<"AppIDPath"> { + let summary = "An application-specific hierarchical path through a design"; + let description = [{ + A list of AppID components which specifies a specific dynamic instance + in the design. + }]; + + let parameters = (ins "FlatSymbolRefAttr":$root, + ArrayRefParameter<"AppIDAttr">:$path); + let mnemonic = "appid_path"; + let assemblyFormat = [{ + `<` $root `[` $path `]` `>` + }]; + + let extraClassDeclaration = [{ + // Get an AppIDPathAttr without the last component. Returns None if the path + // is simply the root. + AppIDPathAttr getParent(); + }]; +} + +def BundleDirection : I32EnumAttr<"BundleDirection", + "Direction of original request", [ + I32EnumAttrCase<"toServer", 1>, + I32EnumAttrCase<"toClient", 2>, + ]> { + let cppNamespace = "::circt::esi"; +} + +def ServiceRequestRecordOp : ESI_Op<"manifest.req", [ + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { + let summary = "Record of a service request"; + let description = [{ + A record of a service request, including the requestor, the service + requested, and the parameters of the request. Emitted before connecting the + service to preserve metadata about the original request. + }]; + + let arguments = (ins AppIDAttr:$requestor, + InnerRefAttr:$servicePort, + BundleDirection:$direction, + TypeAttrOf:$bundleType); + + let assemblyFormat = [{ + qualified($requestor) `,` $servicePort `,` $direction `,` $bundleType + attr-dict + }]; + + let extraClassDeclaration = [{ + AppIDAttr getAppID() { + return getRequestor(); + } + // Misc information about the original request which must appear in the + // manifest. + void getDetails(SmallVectorImpl &results); + }]; +} + +def ServiceImplRecordOp : ESI_Op<"manifest.service_impl", [ + NoTerminator, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { + let summary = "Record of a service implementation"; + let description = [{ + A record of a service implementation. Optionally emitted by the service + implementation. Contains information necessary to connect to the service and + service clients. + }]; + + let arguments = (ins AppIDAttr:$appID, + OptionalAttr:$service, + StrAttr:$serviceImplName, + DictionaryAttr:$implDetails); + let regions = (region SizedRegion<1>:$reqDetails); + let assemblyFormat = [{ + qualified($appID) (`svc` $service^)? `by` $serviceImplName `with` $implDetails + attr-dict-with-keyword $reqDetails + }]; + + let extraClassDeclaration = [{ + // Get information which needs to appear in the manifest for the host to + // connect to this service. + void getDetails(SmallVectorImpl &results); + }]; +} + +def ServiceImplClientRecordOp : ESI_Op<"manifest.impl_conn", [ + DeclareOpInterfaceMethods, + HasParent<"ServiceImplRecordOp">]> { + let summary = "Details of a service implementation client connection"; + let description = [{ + A record containing all the necessary details of how to connect to a client + which the parent service record is servicing. + }]; + + let arguments = (ins AppIDArrayAttr:$relAppIDPath, + InnerRefAttr:$servicePort, + TypeAttrOf:$bundleType, + DictionaryAttr:$implDetails); + let assemblyFormat = [{ + $relAppIDPath `req` $servicePort `(` $bundleType `)` + `with` $implDetails attr-dict + }]; + + let extraClassDeclaration = [{ + // Get information which needs to appear in the manifest for the host to + // connect to this client through the parent service. + void getDetails(SmallVectorImpl &results); + }]; +} + +def AppIDHierRootOp : ESI_Op<"manifest.hier_root", + [HasParent<"mlir::ModuleOp">, + NoTerminator, SingleBlock]> { + let summary = "The root of an appid instance hierarchy"; + + let arguments = (ins FlatSymbolRefAttr:$topModuleRef); + let regions = (region SizedRegion<1>:$children); + + let assemblyFormat = [{ + $topModuleRef attr-dict-with-keyword $children + }]; +} + +def AppIDHierNodeOp : ESI_Op<"manifest.hier_node", [ + ParentOneOf<["circt::esi::AppIDHierRootOp", + "circt::esi::AppIDHierNodeOp"]>, + NoTerminator, SingleBlock]> { + let summary = "A node in the AppID hierarchy"; + + let arguments = (ins AppIDAttr:$appID, FlatSymbolRefAttr:$moduleRef); + let regions = (region SizedRegion<1>:$children); + + let assemblyFormat = [{ + qualified($appID) `mod` $moduleRef attr-dict-with-keyword $children + }]; +} + +def SymbolMetadataOp : ESI_Op<"manifest.sym", [ + DeclareOpInterfaceMethods]> { + let summary = "Metadata about a symbol"; + let description = [{ + Metadata about a symbol, including its name, repository, commit hash, + version, and summary. All are optional, but strongly encouraged. Any + additional metadata which users wish to attach should go as discardable + attributes. + }]; + + let arguments = (ins FlatSymbolRefAttr:$symbolRef, + OptionalAttr:$name, + OptionalAttr:$repo, + OptionalAttr:$commitHash, + OptionalAttr:$version, + OptionalAttr:$summary); + + let assemblyFormat = [{ + $symbolRef + (`name` $name^)? + (`repo` $repo^)? + (`commit` $commitHash^)? + (`version` $version^)? + (`summary` $summary^)? + attr-dict + }]; +} + +def BlobAttr : ESI_Attr<"Blob"> { + let summary = "A binary blob"; + + let parameters = (ins ArrayRefParameter<"char">:$data); + let mnemonic = "blob"; + let hasCustomAssemblyFormat = 1; +} + +def CompressedManifestOp : ESI_Op<"manifest.compressed", []> { + let summary = "A zlib-compressed JSON manifest"; + let arguments = (ins BlobAttr:$compressedManifest); + let assemblyFormat = [{ + $compressedManifest attr-dict + }]; +} + +#endif // CIRCT_DIALECT_ESI_MANIFEST_TD diff --git a/include/circt/Dialect/ESI/ESIOps.h b/include/circt/Dialect/ESI/ESIOps.h index 735ec195778a..54f40959cc7d 100644 --- a/include/circt/Dialect/ESI/ESIOps.h +++ b/include/circt/Dialect/ESI/ESIOps.h @@ -13,10 +13,12 @@ #ifndef CIRCT_DIALECT_ESI_ESIOPS_H #define CIRCT_DIALECT_ESI_ESIOPS_H +#include "circt/Dialect/ESI/ESIAttributes.h" #include "circt/Dialect/ESI/ESIDialect.h" #include "circt/Dialect/ESI/ESITypes.h" #include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Dialect/Seq/SeqTypes.h" #include "mlir/IR/OpImplementation.h" #include "mlir/Interfaces/InferTypeOpInterface.h" @@ -27,16 +29,18 @@ namespace esi { /// Describes a service port. In the unidirection case, either (but not both) /// type fields will be null. struct ServicePortInfo { - StringAttr name; - Type toServerType; - Type toClientType; + enum class Direction { toClient, toServer }; + + hw::InnerRefAttr port; + Direction direction; + ChannelBundleType type; + + StringRef directionAsString() { + return direction == ServicePortInfo::Direction::toClient ? "toClient" + : "toServer"; + } }; -class ServiceDeclOpInterface; -/// Validate a connection request against a service decl by comparing against -/// the port list. -LogicalResult validateServiceConnectionRequest(ServiceDeclOpInterface decl, - Operation *reqOp); } // namespace esi } // namespace circt diff --git a/include/circt/Dialect/ESI/ESIOps.td b/include/circt/Dialect/ESI/ESIOps.td deleted file mode 100644 index 8fe362193db6..000000000000 --- a/include/circt/Dialect/ESI/ESIOps.td +++ /dev/null @@ -1,131 +0,0 @@ -//===- ESIOps.td - ESI dialect operation definitions --------- tablegen -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This is where any operations for the ESI dialect live. -// -//===----------------------------------------------------------------------===// - -def ChannelBufferOp : ESI_Abstract_Op<"buffer", [ - Pure, - DeclareOpInterfaceMethods - ]> { - let summary = "Control options for an ESI channel."; - let description = [{ - A channel buffer (`buffer`) is essentially a set of options on a channel. - It always adds at least one cycle of latency (pipeline stage) to the - channel, but this is configurable. - - This operation is inserted on an ESI dataflow edge. It must exist - previous to SystemVerilog emission but can be added in a lowering pass. - - A `stages` attribute may be provided to specify a specific number of cycles - (pipeline stages) to use on this channel. Must be greater than 0. - - A `name` attribute may be provided to assigned a name to a buffered - connection. - - Example: - - ```mlir - %esiChan = hw.instance "sender" @Sender () : () -> (!esi.channel) - // Allow automatic selection of options. - %bufferedChan = esi.buffer %esiChan : i1 - hw.instance "recv" @Reciever (%bufferedChan) : (!esi.channel) -> () - - // Alternatively, specify the number of stages. - %fourStageBufferedChan = esi.buffer %esiChan { stages = 4 } : i1 - ``` - }]; - - let arguments = (ins I1:$clk, I1:$rst, ChannelType:$input, - OptionalAttr]>>:$stages, - OptionalAttr:$name); - let results = (outs ChannelType:$output); - let hasCustomAssemblyFormat = 1; -} - -def PipelineStageOp : ESI_Physical_Op<"stage", [ - Pure, - DeclareOpInterfaceMethods - ]> { - let summary = "An elastic buffer stage."; - let description = [{ - An individual elastic pipeline register. Generally lowered to from a - ChannelBuffer ('buffer'), though can be inserted anywhere to add an - additional pipeline stage. Adding individually could be useful for - late-pass latency balancing. - }]; - - let arguments = (ins I1:$clk, I1:$rst, ChannelType:$input); - let results = (outs ChannelType:$output); - let hasCustomAssemblyFormat = 1; -} - -def CosimEndpointOp : ESI_Physical_Op<"cosim", []> { - let summary = "Co-simulation endpoint"; - let description = [{ - A co-simulation endpoint is a connection from the simulation to some - outside process, usually a software application responsible for driving - the simulation (driver). - - ESI uses a serialization protocol called Cap'n Proto (capnp for short). - The advantage of capnp is the decoding overhead: for value types (ints, - structs, etc.) there is none! This stands in contrast to Protocol Buffers - and Bond as their messages contain metadata for each field which must be - interpreted. - - The advantage of using a well-supported serialization protocol is - language support -- driver applications can be written in any language - supported by the specific protocol. - }]; - - let arguments = (ins I1:$clk, I1:$rst, ChannelType:$send, StrAttr:$name); - let results = (outs ChannelType:$recv); - - let assemblyFormat = [{ - $clk `,` $rst `,` $send `,` $name attr-dict - `:` qualified(type($send)) `->` qualified(type($recv)) - }]; -} - -def RtlBitArrayType : Type()" - " && $_self.cast<::circt::hw::ArrayType>().getElementType() ==" - " ::mlir::IntegerType::get($_self.getContext(), 1)">, "an HW bit array">; - -def CapnpDecodeOp : ESI_Physical_Op<"decode.capnp", [Pure]> { - let summary = "Translate bits in Cap'nProto messages to HW typed data"; - - let arguments = (ins I1:$clk, I1:$valid, RtlBitArrayType:$capnpBits); - let results = (outs AnyType:$decodedData); - - let assemblyFormat = [{ - $clk $valid $capnpBits attr-dict `:` qualified(type($capnpBits)) `->` - qualified(type($decodedData)) - }]; -} - -def CapnpEncodeOp : ESI_Physical_Op<"encode.capnp", [Pure]> { - let summary = "Translate HW typed data to Cap'nProto"; - - let arguments = (ins I1:$clk, I1:$valid, AnyType:$dataToEncode); - let results = (outs RtlBitArrayType:$capnpBits); - - let assemblyFormat = [{ - $clk $valid $dataToEncode attr-dict `:` qualified(type($dataToEncode)) - `->` qualified(type($capnpBits)) - }]; -} - -def NullSourceOp : ESI_Physical_Op<"null", [Pure]> { - let summary = "An op which never produces messages."; - - let arguments = (ins); - let results = (outs ChannelType:$out); - - let assemblyFormat = [{ attr-dict `:` qualified(type($out)) }]; -} diff --git a/include/circt/Dialect/ESI/ESIPasses.h b/include/circt/Dialect/ESI/ESIPasses.h index 44d8cec60922..fa1ff0308500 100644 --- a/include/circt/Dialect/ESI/ESIPasses.h +++ b/include/circt/Dialect/ESI/ESIPasses.h @@ -22,11 +22,21 @@ namespace circt { namespace esi { -std::unique_ptr> createESIEmitCollateralPass(); +/// This should eventually become a set of functions to define the various +/// platform-specific lowerings. +struct Platform { + static constexpr char cosim[] = "cosim"; +}; + std::unique_ptr> createESIPhysicalLoweringPass(); +std::unique_ptr> createESIBundleLoweringPass(); std::unique_ptr> createESIPortLoweringPass(); +std::unique_ptr> createESITypeLoweringPass(); std::unique_ptr> createESItoHWPass(); std::unique_ptr> createESIConnectServicesPass(); +std::unique_ptr> createESICleanMetadataPass(); +std::unique_ptr> createESIBuildManifestPass(); +std::unique_ptr> createESIAppIDHierPass(); /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION diff --git a/include/circt/Dialect/ESI/ESIPasses.td b/include/circt/Dialect/ESI/ESIPasses.td index 2fb6acd4e0d6..60bfb5097cce 100644 --- a/include/circt/Dialect/ESI/ESIPasses.td +++ b/include/circt/Dialect/ESI/ESIPasses.td @@ -16,6 +16,8 @@ include "mlir/Pass/PassBase.td" +include "circt/Dialect/ESI/ESIDialect.td" + def ESIConnectServices : Pass<"esi-connect-services", "mlir::ModuleOp"> { let summary = "Connect up ESI service requests to service providers"; let constructor = "circt::esi::createESIConnectServicesPass()"; @@ -24,35 +26,65 @@ def ESIConnectServices : Pass<"esi-connect-services", "mlir::ModuleOp"> { "circt::comb::CombDialect"]; } -def ESIEmitCollateral: Pass<"esi-emit-collateral", "mlir::ModuleOp"> { - let summary = "Emit all the neccessary collateral"; - let constructor = "circt::esi::createESIEmitCollateralPass()"; - let dependentDialects = ["circt::sv::SVDialect"]; +def ESIAppIDHier : Pass<"esi-appid-hier", "mlir::ModuleOp"> { + let summary = "Build an AppID based hierarchy rooted at top module 'top'"; + let constructor = "circt::esi::createESIAppIDHierPass()"; + let options = [ + Option<"top", "top", "std::string", + "", "Root module of the instance hierarchy"> + ]; +} + +def ESIBuildManifest : Pass<"esi-build-manifest", "mlir::ModuleOp"> { + let summary = "Build a manifest of an ESI system"; + let constructor = "circt::esi::createESIBuildManifestPass()"; + let dependentDialects = ["circt::hw::HWDialect", "circt::sv::SVDialect"]; let options = [ - Option<"schemaFile", "schema-file", "std::string", - "", "File to output capnp schema into">, - ListOption<"tops", "tops", "std::string", - "List of top modules to export Tcl for", - "llvm::cl::ZeroOrMore,"> + Option<"toFile", "to-file", "std::string", + "", "Write the manifest JSON directly to this file">, + Option<"top", "top", "std::string", + "", "Root module of the instance hierarchy"> ]; } +def ESICleanMetadata : Pass<"esi-clean-metadata", "mlir::ModuleOp"> { + let summary = "Clean up ESI service metadata"; + let constructor = "circt::esi::createESICleanMetadataPass()"; + let dependentDialects = ["circt::hw::HWDialect"]; +} + def LowerESIToPhysical: Pass<"lower-esi-to-physical", "mlir::ModuleOp"> { let summary = "Lower ESI abstract Ops to ESI physical ops."; let constructor = "circt::esi::createESIPhysicalLoweringPass()"; let dependentDialects = ["circt::hw::HWDialect"]; } +def LowerESIBundles: Pass<"lower-esi-bundles", "mlir::ModuleOp"> { + let summary = "Lower ESI bundles to channels."; + let constructor = "circt::esi::createESIBundleLoweringPass()"; + let dependentDialects = ["circt::hw::HWDialect"]; +} + def LowerESIPorts: Pass<"lower-esi-ports", "mlir::ModuleOp"> { let summary = "Lower ESI input and/or output ports."; let constructor = "circt::esi::createESIPortLoweringPass()"; let dependentDialects = ["circt::comb::CombDialect", "circt::sv::SVDialect"]; } +def LowerESITypes: Pass<"lower-esi-types", "mlir::ModuleOp"> { + let summary = "Lower ESI high level types."; + let constructor = "circt::esi::createESITypeLoweringPass()"; + let dependentDialects = ["circt::hw::HWDialect"]; +} + def LowerESItoHW: Pass<"lower-esi-to-hw", "mlir::ModuleOp"> { let summary = "Lower ESI to HW where possible and SV elsewhere."; let constructor = "circt::esi::createESItoHWPass()"; let dependentDialects = ["circt::comb::CombDialect", "circt::hw::HWDialect"]; + let options = [ + Option<"platform", "platform", "std::string", + "", "Target this platform"> + ]; } #endif // CIRCT_DIALECT_ESI_ESIPASSES_TD diff --git a/include/circt/Dialect/ESI/ESIPorts.td b/include/circt/Dialect/ESI/ESIPorts.td deleted file mode 100644 index 90aeedf423c8..000000000000 --- a/include/circt/Dialect/ESI/ESIPorts.td +++ /dev/null @@ -1,190 +0,0 @@ -//===- ESIPorts.td - ESI port specifications ---------------*- tablegen -*-===// -// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// ESI ports are the primary mechanism on which the ESI dialect operates. These -// types wrap other types to specify the port as an ESI port. They represent -// ESI communication flows of various kinds. At first, ESI will only support -// 'channels' -- unidirectional, point-to-point, latency-insensitive streams. -// -//===----------------------------------------------------------------------===// - -include "mlir/IR/AttrTypeBase.td" -include "mlir/IR/EnumAttr.td" -include "mlir/Interfaces/InferTypeOpInterface.td" - - -class ESI_Port : TypeDef {} - -// Standard valid-ready signaling. -def ChannelSignalingValidReady : I32EnumAttrCase<"ValidReady", 0>; -// FIFO interface with a read latency of zero. Very similiar to valid-ready, but -// cannot assert ready if data is not available. -def ChannelSignalingFIFO0 : I32EnumAttrCase<"FIFO0", 1>; -def ChannelSignaling : I32EnumAttr< - "ChannelSignaling", - "ESI channel wire signaling standard", - [ChannelSignalingValidReady, ChannelSignalingFIFO0]>{ - let cppNamespace = "::circt::esi"; -} - -def ChannelType : ESI_Port<"Channel"> { - let summary = "An ESI-compatible channel port"; - let description = [{ - An ESI port kind which models a latency-insensitive, unidirectional, - point-to-point data stream. Channels are typed (like all of ESI). Said - type can be any MLIR type, but must be lowered to something a backend - knows how to output (i.e. something emitVerilog knows about). - - Example: - - ```mlir - hw.module.extern @Sender() -> (%x: !esi.channel) - hw.module @Reciever(%a: !esi.channel>) { } - ``` - }]; - - let mnemonic = "channel"; - let parameters = (ins - "Type":$inner, - DefaultValuedParameter< - "::circt::esi::ChannelSignaling", - "::circt::esi::ChannelSignaling::ValidReady">:$signaling); - - let assemblyFormat = "`<` $inner (`,` $signaling^)? `>`"; - - let builders = [ - TypeBuilder<(ins "Type":$type), [{ - return Base::get(type.getContext(), type, - ::circt::esi::ChannelSignaling::ValidReady); - }]>, - ]; -} - -//========= -// Operations on ports. - -def WrapValidReadyOp : ESI_Op<"wrap.vr", [ - DeclareOpInterfaceMethods - ]> { - let summary = "Wrap a value into an ESI port"; - let description = [{ - Wrapping a value into an ESI port type allows modules to send values down - an ESI port. Wrap data with valid bit, result is the ESI channel and the - ready signal from the other end of the channel. - }]; - - let arguments = (ins AnyType:$rawInput, I1:$valid); - let results = (outs ChannelType:$chanOutput, I1:$ready); - let hasCustomAssemblyFormat = 1; - let hasFolder = 1; - let hasVerifier = 1; - - let builders = [ - OpBuilder<(ins "mlir::Value":$data, "mlir::Value":$valid)> - ]; -} - -def UnwrapValidReadyOp : ESI_Op<"unwrap.vr", [ - DeclareOpInterfaceMethods - ]> { - let summary = "Unwrap a value from an ESI port"; - let description = [{ - Unwrapping a value allows operations on the contained value. Unwrap the - channel along with a ready signal that you generate. Result is the data - along with a valid signal. - }]; - - let arguments = (ins ChannelType:$chanInput, I1:$ready); - let results = (outs AnyType:$rawOutput, I1:$valid); - let hasCustomAssemblyFormat = 1; - let hasVerifier = 1; - - let builders = [ - OpBuilder<(ins "mlir::Value":$inChan, "mlir::Value":$ready)> - ]; -} - -def WrapFIFOOp : ESI_Op<"wrap.fifo", [ - DeclareOpInterfaceMethods - ]> { - let summary = "Wrap a value into an ESI port with FIFO signaling"; - - let arguments = (ins AnyType:$data, I1:$empty); - let results = (outs ChannelType:$chanOutput, I1:$rden); - let hasCanonicalizeMethod = true; - let hasFolder = true; - let hasVerifier = 1; - - let assemblyFormat = [{ - $data `,` $empty attr-dict `:` - custom(type($data), type($chanOutput)) - }]; -} - -def UnwrapFIFOOp : ESI_Op<"unwrap.fifo", [ - DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods - ]> { - let summary = "Unwrap a value from an ESI port into a FIFO interface"; - - let arguments = (ins ChannelType:$chanInput, I1:$rden); - let results = (outs AnyType:$data, I1:$empty); - let hasCanonicalizeMethod = true; - let hasVerifier = 1; - - let assemblyFormat = [{ - $chanInput `,` $rden attr-dict `:` qualified(type($chanInput)) - }]; - - let extraClassDeclaration = [{ - static LogicalResult mergeAndErase(UnwrapFIFOOp, WrapFIFOOp, PatternRewriter&); - }]; -} - -def ModportType: - Type()">, "sv.interface">; - -def WrapSVInterfaceOp: ESI_Op<"wrap.iface", [ - DeclareOpInterfaceMethods - ]> { - let summary = "Wrap an SV interface into an ESI port"; - let description = [{ - Wrap a SystemVerilog interface into an ESI channel. Interface MUST look - like an interface produced by ESI meaning it MUST contain valid, ready, - and data signals. Any other signals will be discarded. - }]; - - let arguments = (ins ModportType:$interfaceSink); - let results = (outs ChannelType:$output); - - let assemblyFormat = [{ - $interfaceSink attr-dict `:` qualified(type($interfaceSink)) `->` qualified(type($output)) - }]; - - let hasVerifier = 1; -} - -def UnwrapSVInterfaceOp : ESI_Op<"unwrap.iface", [ - DeclareOpInterfaceMethods - ]> { - let summary = "Unwrap an SV interface from an ESI port"; - let description = [{ - Unwrap an ESI channel into a SystemVerilog interface containing valid, - ready, and data signals. - }]; - - let arguments = (ins ChannelType:$chanInput, ModportType:$interfaceSource); - let results = (outs); - - let assemblyFormat = [{ - $chanInput `into` $interfaceSource attr-dict `:` `(` qualified(type($chanInput)) `,` qualified(type($interfaceSource)) `)` - }]; - - let hasVerifier = 1; -} diff --git a/include/circt/Dialect/ESI/ESIServices.h b/include/circt/Dialect/ESI/ESIServices.h index ef6a5c76ed52..9a371e74af4f 100644 --- a/include/circt/Dialect/ESI/ESIServices.h +++ b/include/circt/Dialect/ESI/ESIServices.h @@ -14,6 +14,7 @@ #include "mlir/Pass/Pass.h" #include +#include namespace circt { namespace esi { @@ -33,10 +34,9 @@ class ServiceGeneratorDispatcher { ServiceGeneratorDispatcher( DenseMap genLookupTable, bool failIfNotFound) - : genLookupTable(genLookupTable), failIfNotFound(failIfNotFound) {} - ServiceGeneratorDispatcher(const ServiceGeneratorDispatcher &that) - : genLookupTable(that.genLookupTable), - failIfNotFound(that.failIfNotFound) {} + : genLookupTable(std::move(genLookupTable)), + failIfNotFound(failIfNotFound) {} + ServiceGeneratorDispatcher(const ServiceGeneratorDispatcher &that) = default; /// Get the global dispatcher. static ServiceGeneratorDispatcher &globalDispatcher(); @@ -46,7 +46,7 @@ class ServiceGeneratorDispatcher { LogicalResult generate(ServiceImplementReqOp, ServiceDeclOpInterface); /// Add a generator to this registry. - void registerGenerator(StringRef name, ServiceGeneratorFunc func); + void registerGenerator(StringRef implType, ServiceGeneratorFunc gen); private: DenseMap genLookupTable; diff --git a/include/circt/Dialect/ESI/ESIServices.td b/include/circt/Dialect/ESI/ESIServices.td index 06b7feb18132..fcf0e858a4ae 100644 --- a/include/circt/Dialect/ESI/ESIServices.td +++ b/include/circt/Dialect/ESI/ESIServices.td @@ -6,8 +6,19 @@ // //===----------------------------------------------------------------------===// -include "circt/Dialect/HW/HWTypes.td" +#ifndef CIRCT_DIALECT_ESI_SERVICES_TD +#define CIRCT_DIALECT_ESI_SERVICES_TD + include "mlir/IR/RegionKindInterface.td" +include "mlir/IR/SymbolInterfaces.td" + +include "circt/Dialect/ESI/ESIChannels.td" +include "circt/Dialect/ESI/ESIInterfaces.td" +include "circt/Dialect/ESI/ESIManifest.td" +include "circt/Dialect/ESI/ESITypes.td" + +include "circt/Dialect/HW/HWAttributesNaming.td" +include "circt/Dialect/HW/HWTypes.td" def CustomServiceDeclOp : ESI_Op<"service.decl", [SingleBlock, NoTerminator, HasParent<"::mlir::ModuleOp">, @@ -37,9 +48,10 @@ def CustomServiceDeclOp : ESI_Op<"service.decl", def ToServerOp : ESI_Op<"service.to_server", [HasParent<"::circt::esi::CustomServiceDeclOp">]> { - let summary = "An ESI service port headed to the service"; + let summary = "An ESI service bundle being sent to the service"; - let arguments = (ins SymbolNameAttr:$inner_sym, TypeAttr:$toServerType); + let arguments = (ins SymbolNameAttr:$inner_sym, + TypeAttrOf:$toServerType); let assemblyFormat = [{ $inner_sym attr-dict `:` $toServerType }]; @@ -47,27 +59,32 @@ def ToServerOp : ESI_Op<"service.to_server", def ToClientOp : ESI_Op<"service.to_client", [HasParent<"::circt::esi::CustomServiceDeclOp">]> { - let summary = "An ESI service port headed to a particular client"; + let summary = "An ESI service bundle being received by the client"; - let arguments = (ins SymbolNameAttr:$inner_sym, TypeAttr:$toClientType); + let arguments = (ins SymbolNameAttr:$inner_sym, + TypeAttrOf:$toClientType); let assemblyFormat = [{ $inner_sym attr-dict `:` $toClientType }]; } -def ServiceDeclInOutOp : ESI_Op<"service.inout", - [HasParent<"::circt::esi::CustomServiceDeclOp">]> { - let summary = "An ESI service port which has both directions"; +def ESIAnyType : ESI_Type<"Any"> { + let summary = "any type"; + let description = [{ + Used to state that any type is accepted in a service port declaration. The + specific type will be determined later in compilation. + }]; - let arguments = (ins SymbolNameAttr:$inner_sym, - TypeAttr:$toServerType, TypeAttr:$toClientType); - let assemblyFormat = [{ - $inner_sym attr-dict - `:` qualified($toServerType) `->` qualified($toClientType) + let mnemonic = "any"; + let assemblyFormat = ""; + + let extraClassDeclaration = [{ + static AnyType get(MLIRContext *context); }]; } -def ServiceInstanceOp : ESI_Op<"service.instance"> { +def ServiceInstanceOp : ESI_Op<"service.instance", [ + DeclareOpInterfaceMethods]> { let summary = "Instantiate a server module"; let description = [{ Instantiate a service adhering to a service declaration interface. @@ -89,18 +106,21 @@ def ServiceInstanceOp : ESI_Op<"service.instance"> { implementation to use for this service. }]; - let arguments = (ins OptionalAttr:$service_symbol, + let arguments = (ins AppIDAttr:$appID, + OptionalAttr:$service_symbol, StrAttr:$impl_type, OptionalAttr:$impl_opts, Variadic:$inputs); let results = (outs Variadic); let assemblyFormat = [{ - (`svc` $service_symbol^)? `impl` `as` $impl_type (`opts` $impl_opts^)? - `(` $inputs `)` attr-dict `:` functional-type($inputs, results) + qualified($appID) (`svc` $service_symbol^)? `impl` `as` $impl_type + (`opts` $impl_opts^)? `(` $inputs `)` + attr-dict `:` functional-type($inputs, results) }]; } -def ServiceImplementReqOp : ESI_Op<"service.impl_req", [NoTerminator]> { +def ServiceImplementReqOp : ESI_Op<"service.impl_req", [ + NoTerminator, DeclareOpInterfaceMethods]> { let summary = "Request for a service to be implemented"; let description = [{ The connect services pass replaces `service.instance`s with this op. The @@ -113,81 +133,65 @@ def ServiceImplementReqOp : ESI_Op<"service.impl_req", [NoTerminator]> { implementation. }]; - let arguments = (ins OptionalAttr:$service_symbol, + let arguments = (ins AppIDAttr:$appID, + OptionalAttr:$service_symbol, StrAttr:$impl_type, OptionalAttr:$impl_opts, Variadic:$inputs); let results = (outs Variadic:$outputs); let regions = (region SizedRegion<1>:$portReqs); - let extraClassDeclaration = [{ - /// Find pairs of toServer and toClient requests with the same client path. - /// In cases where there doesn't exist a pair, one of the two entries will - /// be null. Should never contain a pair with both entries null. - void gatherPairedReqs( - llvm::SmallVectorImpl>&); - }]; - let assemblyFormat = [{ - (`svc` $service_symbol^)? `impl` `as` $impl_type (`opts` $impl_opts^)? - `(` $inputs `)` attr-dict `:` functional-type($inputs, results) + qualified($appID) (`svc` $service_symbol^)? `impl` `as` $impl_type + (`opts` $impl_opts^)? `(` $inputs `)` + attr-dict `:` functional-type($inputs, results) $portReqs }]; } -def RequestToServerConnectionOp : ESI_Op<"service.req.to_server", [ +def ServiceImplementConnReqOp : ESI_Op<"service.impl_req.req", [ DeclareOpInterfaceMethods]> { - let summary = "Request a connection to send data"; + let summary = "The canonical form of a connection request"; - let arguments = (ins HWInnerRefAttr:$servicePort, - ChannelType:$toServer, StrArrayAttr:$clientNamePath); + let arguments = (ins InnerRefAttr:$servicePort, + AppIDArrayAttr:$relativeAppIDPath); + let results = (outs ChannelBundleType:$toClient); let assemblyFormat = [{ - $toServer `->` $servicePort `(` $clientNamePath `)` - attr-dict `:` qualified(type($toServer)) + $servicePort `(` $relativeAppIDPath `)` + attr-dict `:` qualified(type($toClient)) }]; -} - -def RequestToClientConnectionOp : ESI_Op<"service.req.to_client", [ - DeclareOpInterfaceMethods]> { - let summary = "Request a connection to receive data"; - let arguments = (ins HWInnerRefAttr:$servicePort, - StrArrayAttr:$clientNamePath); - let results = (outs ChannelType:$toClient); - let assemblyFormat = [{ - $servicePort `(` $clientNamePath `)` - attr-dict `:` qualified(type($toClient)) + let extraClassDeclaration = [{ + ChannelBundleType getBundleType() { return getToClient().getType(); } }]; } -def RequestInOutChannelOp : ESI_Op<"service.req.inout", [ +def RequestToServerConnectionOp : ESI_Op<"service.req.to_server", [ + DeclareOpInterfaceMethods, DeclareOpInterfaceMethods]> { - let summary = "Request a bidirectional channel"; - - let arguments = (ins HWInnerRefAttr:$servicePort, - ChannelType:$toServer, - StrArrayAttr:$clientNamePath); - let results = (outs ChannelType:$toClient); + let summary = "Request a connection to send data"; + let arguments = (ins InnerRefAttr:$servicePort, + ChannelBundleType:$toServer, + AppIDAttr:$appID); let assemblyFormat = [{ - $toServer `->` $servicePort `(` $clientNamePath `)` attr-dict `:` - qualified(type($toServer)) `->` qualified(type($toClient)) + $toServer `->` $servicePort `(` qualified($appID) `)` + attr-dict `:` qualified(type($toServer)) }]; } -def ServiceHierarchyMetadataOp : ESI_Op<"service.hierarchy.metadata", [ +def RequestToClientConnectionOp : ESI_Op<"service.req.to_client", [ + DeclareOpInterfaceMethods, DeclareOpInterfaceMethods]> { - let summary = "Metadata about a service in the service hierarchy"; + let summary = "Request a connection to receive data"; - let arguments = (ins OptionalAttr:$service_symbol, - ArrayAttr:$serverNamePath, - StrAttr:$impl_type, - OptionalAttr:$impl_details, - ArrayAttr:$clients); + let arguments = (ins InnerRefAttr:$servicePort, + AppIDAttr:$appID); + let results = (outs ChannelBundleType:$toClient); let assemblyFormat = [{ - `path` $serverNamePath (`implementing` $service_symbol^)? - `impl` `as` $impl_type (`opts` $impl_details^)? - `clients` $clients attr-dict + $servicePort `(` qualified($appID) `)` + attr-dict `:` qualified(type($toClient)) }]; } + +#endif // CIRCT_DIALECT_ESI_SERVICES_TD diff --git a/include/circt/Dialect/ESI/ESIStdServices.td b/include/circt/Dialect/ESI/ESIStdServices.td index c58a44002d8b..a3e839bfa4c5 100644 --- a/include/circt/Dialect/ESI/ESIStdServices.td +++ b/include/circt/Dialect/ESI/ESIStdServices.td @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +include "circt/Dialect/ESI/ESIServices.td" include "circt/Dialect/HW/HWTypes.td" def RandomAccessMemoryDeclOp: ESI_Op<"mem.ram", @@ -31,4 +32,11 @@ def RandomAccessMemoryDeclOp: ESI_Op<"mem.ram", let assemblyFormat = [{ $sym_name $innerType `x` $depth attr-dict }]; + + let extraClassDeclaration = [{ + ServicePortInfo readPortInfo(); + ServicePortInfo writePortInfo(); + static constexpr auto ReqDirChannelIdx = 0; + static constexpr auto RespDirChannelIdx = 0; + }]; } diff --git a/include/circt/Dialect/ESI/ESIStructure.td b/include/circt/Dialect/ESI/ESIStructure.td index 2b01819ad15c..1f8b93326c7e 100644 --- a/include/circt/Dialect/ESI/ESIStructure.td +++ b/include/circt/Dialect/ESI/ESIStructure.td @@ -10,12 +10,20 @@ // //===----------------------------------------------------------------------===// -include "circt/Dialect/HW/HWOpInterfaces.td" +#ifndef CIRCT_DIALECT_ESI_STRUCTURE_TD +#define CIRCT_DIALECT_ESI_STRUCTURE_TD + include "mlir/IR/OpBase.td" +include "mlir/IR/RegionKindInterface.td" +include "mlir/IR/SymbolInterfaces.td" + +include "circt/Dialect/ESI/ESIDialect.td" +include "circt/Dialect/HW/HWOpInterfaces.td" def ESIPureModuleOp : ESI_Op<"pure_module", [Symbol, NoTerminator, RegionKindInterface, NoRegionArguments, SingleBlock, HasParent<"mlir::ModuleOp">, + DeclareOpInterfaceMethods, DeclareOpInterfaceMethods]> { let summary = "ESI pure module"; let description = [{ @@ -36,6 +44,7 @@ def ESIPureModuleOp : ESI_Op<"pure_module", let extraClassDeclaration = [{ // Implement RegionKindInterface. static RegionKind getRegionKind(unsigned index) { return RegionKind::Graph;} + }]; } @@ -88,3 +97,5 @@ def ESIPureModuleParamOp : ESI_Op<"pure_module.param", let results = (outs); let assemblyFormat = [{ $name `:` $type attr-dict }]; } + +#endif // CIRCT_DIALECT_ESI_STRUCTURE_TD diff --git a/include/circt/Dialect/ESI/ESITypes.h b/include/circt/Dialect/ESI/ESITypes.h index bf9d658b788a..fd6a68c06c92 100644 --- a/include/circt/Dialect/ESI/ESITypes.h +++ b/include/circt/Dialect/ESI/ESITypes.h @@ -1,6 +1,5 @@ //===- ESITypes.h - types for the ESI dialect -------------------*- C++ -*-===// // -// // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception @@ -22,7 +21,38 @@ #include "ESIDialect.h" +namespace circt { +namespace esi { +struct BundledChannel; +} // namespace esi +} // namespace circt + #define GET_TYPEDEF_CLASSES #include "circt/Dialect/ESI/ESITypes.h.inc" +namespace circt { +namespace esi { + +struct BundledChannel { + StringAttr name; + ChannelDirection direction; + ChannelType type; + + int operator==(const BundledChannel &that) const { + return name == that.name && direction == that.direction && + type == that.type; + } +}; + +// NOLINTNEXTLINE(readability-identifier-naming) +inline llvm::hash_code hash_value(const BundledChannel channel) { + return llvm::hash_combine(channel.name, channel.direction, channel.type); +} + +// If 'type' is an esi:ChannelType, will return the inner type of said channel. +// Else, returns 'type'. +mlir::Type innerType(mlir::Type type); +} // namespace esi +} // namespace circt + #endif diff --git a/include/circt/Dialect/ESI/ESITypes.td b/include/circt/Dialect/ESI/ESITypes.td index 199854de45db..5e87347b9c14 100644 --- a/include/circt/Dialect/ESI/ESITypes.td +++ b/include/circt/Dialect/ESI/ESITypes.td @@ -7,24 +7,33 @@ // //===----------------------------------------------------------------------===// // -// ESI will have a rich, high level type system. Currently, it is much more -// minimal. +// ESI extends the HW types by adding a List type. // //===----------------------------------------------------------------------===// -class ESI_Type : TypeDef { } +#ifndef CIRCT_DIALECT_ESI_TYPES_TD +#define CIRCT_DIALECT_ESI_TYPES_TD -def ESIAnyType : ESI_Type<"Any"> { - let summary = "any type"; +include "circt/Dialect/ESI/ESIDialect.td" + +def ChannelType : DialectType($_self)">, + "an ESI channel", + "::circt::esi::ChannelType">; + +def ESIListType : ESI_Type<"List"> { + let summary = "a runtime-variably sized list"; let description = [{ - Used to state that any type is accepted. The specific type will be - determined later in compilation. + In software, a chunk of memory with runtime-specified length. In hardware, a + stream of runtime-specified amount of data transmitted over many cycles in + compile-time specified specified windows (chunks). }]; - let mnemonic = "any"; - let assemblyFormat = ""; - - let extraClassDeclaration = [{ - static AnyType get(MLIRContext *context); + let mnemonic = "list"; + let parameters = (ins "Type":$elementType); + let assemblyFormat = [{ + `<` $elementType `>` }]; } + +#endif // CIRCT_DIALECT_ESI_TYPES_TD diff --git a/include/circt/Dialect/ESI/cosim/CMakeLists.txt b/include/circt/Dialect/ESI/cosim/CMakeLists.txt deleted file mode 100644 index f81d78ae6f4b..000000000000 --- a/include/circt/Dialect/ESI/cosim/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -##===- CMakeLists.txt - Cosim DPI library headers -------------*- cmake -*-===// -## -## Generate the Capnp headers if Capnp is found. -## -##===----------------------------------------------------------------------===// - -if(CapnProto_FOUND) - option(ESI_COSIM "Enable ESI Cosimulation" ON) - message("-- Enabling ESI cosim") - - file(READ CosimDpi.capnp EsiCosimSchema) - set(COSIM_SCHEMA_HDR ${CIRCT_BINARY_DIR}/include/circt/Dialect/ESI/CosimSchema.h) - configure_file(CosimSchema.h.in ${COSIM_SCHEMA_HDR}) - - if (MSVC) - string(REPLACE "/EHs-c-" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHa") - else () - string(REPLACE "-fno-exceptions" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - endif () - - add_definitions(${CAPNP_DEFINITIONS}) - capnp_generate_cpp(COSIM_CAPNP_SRCS COSIM_CANPN_HDRS CosimDpi.capnp) - add_library(EsiCosimCapnp - ${COSIM_CAPNP_HDRS} - ${COSIM_CAPNP_SRCS} - ${COSIM_SCHEMA_HDR}) - target_link_libraries(EsiCosimCapnp - PRIVATE - CapnProto::capnp - CapnProto::capnp-rpc) -endif() diff --git a/include/circt/Dialect/ESI/cosim/CosimSchema.h.in b/include/circt/Dialect/ESI/cosim/CosimSchema.h.in deleted file mode 100644 index 34ba8c40e06c..000000000000 --- a/include/circt/Dialect/ESI/cosim/CosimSchema.h.in +++ /dev/null @@ -1,28 +0,0 @@ -//===- CosimSchema.h.in - template for the ESI cosim schema ----*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// CMake configures this file to fill the Cosim Capnp RPC schema. -// -//===----------------------------------------------------------------------===// - -#ifndef CIRCT_DIALECT_ESI_COSIM_COSIMSCHEMA_H -#define CIRCT_DIALECT_ESI_COSIM_COSIMSCHEMA_H - -namespace circt { -namespace esi { -namespace cosim { - -constexpr char CosimSchema[] = R"""( -@EsiCosimSchema@ -)"""; - -} // namespace cosim -} // namespace esi -} // namespace circt - -#endif diff --git a/include/circt/Dialect/FIRRTL/AnnotationDetails.h b/include/circt/Dialect/FIRRTL/AnnotationDetails.h index af7e2e12a951..759740256708 100644 --- a/include/circt/Dialect/FIRRTL/AnnotationDetails.h +++ b/include/circt/Dialect/FIRRTL/AnnotationDetails.h @@ -28,6 +28,7 @@ constexpr const char *rawAnnotations = "rawAnnotations"; // Annotation Class Names //===----------------------------------------------------------------------===// +constexpr const char *conventionAnnoClass = "circt.ConventionAnnotation"; constexpr const char *dontTouchAnnoClass = "firrtl.transforms.DontTouchAnnotation"; constexpr const char *enumComponentAnnoClass = @@ -89,8 +90,12 @@ constexpr const char *verifBlackBoxAnnoClass = constexpr const char *metadataDirectoryAttrName = "sifive.enterprise.firrtl.MetadataDirAnnotation"; constexpr const char *noDedupAnnoClass = "firrtl.transforms.NoDedupAnnotation"; +constexpr const char *dedupGroupAnnoClass = + "firrtl.transforms.DedupGroupAnnotation"; constexpr const char *dftTestModeEnableAnnoClass = "sifive.enterprise.firrtl.DFTTestModeEnableAnnotation"; +constexpr const char *dftClockDividerBypassAnnoClass = + "sifive.enterprise.firrtl.DFTClockDividerBypassAnnotation"; // Grand Central Annotations constexpr const char *serializedViewAnnoClass = @@ -209,6 +214,9 @@ constexpr const char *wiringSinkAnnoClass = constexpr const char *wiringSourceAnnoClass = "firrtl.passes.wiring.SourceAnnotation"; +// Attribute annotations. +constexpr const char *attributeAnnoClass = "firrtl.AttributeAnnotation"; + } // namespace firrtl } // namespace circt diff --git a/include/circt/Dialect/FIRRTL/CHIRRTL.td b/include/circt/Dialect/FIRRTL/CHIRRTL.td index 0e4c2443659d..d74ccc90796e 100644 --- a/include/circt/Dialect/FIRRTL/CHIRRTL.td +++ b/include/circt/Dialect/FIRRTL/CHIRRTL.td @@ -21,6 +21,7 @@ include "circt/Dialect/FIRRTL/FIRRTLTypes.td" include "circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td" include "circt/Dialect/FIRRTL/FIRRTLAttributes.td" include "circt/Dialect/FIRRTL/FIRRTLEnums.td" +include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Dialect/HW/HWTypes.td" include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/IR/SymbolInterfaces.td" @@ -37,6 +38,9 @@ def CHIRRTLDialect : Dialect { let dependentDialects = ["circt::firrtl::FIRRTLDialect"]; let useDefaultTypePrinterParser = 1; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; } //===----------------------------------------------------------------------===// @@ -102,7 +106,7 @@ def CMemoryPortType : TypeDef { class CHIRRTLOp traits = []> : Op; -def CombMemOp : CHIRRTLOp<"combmem", [HasCustomSSAName, FNamableOp]> { +def CombMemOp : CHIRRTLOp<"combmem", [HasCustomSSAName, DeclareOpInterfaceMethods, FNamableOp]> { let summary = "Define a new combinational memory"; let description = [{ Define a new behavioral combinational memory. Combinational memories have a @@ -119,12 +123,12 @@ def CombMemOp : CHIRRTLOp<"combmem", [HasCustomSSAName, FNamableOp]> { OpBuilder<(ins "firrtl::FIRRTLBaseType":$elementType, "uint64_t":$numElements, "mlir::StringRef":$name, "firrtl::NameKindEnum":$nameKind, "ArrayAttr":$annotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym, + CArg<"StringAttr", "StringAttr()">:$innerSym, CArg<"firrtl::MemoryInitAttr", "firrtl::MemoryInitAttr{}">:$init)> ]; } -def SeqMemOp : CHIRRTLOp<"seqmem", [HasCustomSSAName, FNamableOp]> { +def SeqMemOp : CHIRRTLOp<"seqmem", [HasCustomSSAName, DeclareOpInterfaceMethods, FNamableOp]> { let summary = "Define a new sequential memory"; let description = [{ Define a new behavioral sequential memory. Sequential memories have a @@ -139,9 +143,9 @@ def SeqMemOp : CHIRRTLOp<"seqmem", [HasCustomSSAName, FNamableOp]> { custom(attr-dict) `:` qualified(type($result))}]; let builders = [ OpBuilder<(ins "firrtl::FIRRTLBaseType":$elementType, "uint64_t":$numElements, - "RUWAttr":$ruw, "mlir::StringRef":$name, + "firrtl::RUWAttr":$ruw, "mlir::StringRef":$name, "firrtl::NameKindEnum":$nameKind, "ArrayAttr":$annotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym, + CArg<"StringAttr", "StringAttr()">:$innerSym, CArg<"firrtl::MemoryInitAttr", "firrtl::MemoryInitAttr{}">:$init)> ]; } @@ -173,7 +177,7 @@ def MemoryPortOp : CHIRRTLOp<"memoryport", [InferTypeOpInterface, let builders = [ OpBuilder<(ins "::mlir::Type":$dataType, "::mlir::Value":$memory, - "MemDirAttr":$direction, CArg<"StringRef", "{}">:$name, + "firrtl::MemDirAttr":$direction, CArg<"StringRef", "{}">:$name, CArg<"ArrayRef","{}">:$annotations)> ]; @@ -188,6 +192,7 @@ def MemoryPortOp : CHIRRTLOp<"memoryport", [InferTypeOpInterface, std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results); }]; @@ -244,6 +249,7 @@ def MemoryDebugPortOp : CHIRRTLOp<"debugport", [InferTypeOpInterface, std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results); }]; diff --git a/include/circt/Dialect/FIRRTL/CMakeLists.txt b/include/circt/Dialect/FIRRTL/CMakeLists.txt index 23a45386a5fe..2dc3b2b6abc6 100644 --- a/include/circt/Dialect/FIRRTL/CMakeLists.txt +++ b/include/circt/Dialect/FIRRTL/CMakeLists.txt @@ -24,6 +24,12 @@ mlir_tablegen(FIRRTLOpInterfaces.cpp.inc -gen-op-interface-defs) add_public_tablegen_target(CIRCTFIRRTLOpInterfacesIncGen) add_dependencies(circt-headers CIRCTFIRRTLOpInterfacesIncGen) +set(LLVM_TARGET_DEFINITIONS FIRRTLTypeInterfaces.td) +mlir_tablegen(FIRRTLTypeInterfaces.h.inc -gen-type-interface-decls) +mlir_tablegen(FIRRTLTypeInterfaces.cpp.inc -gen-type-interface-defs) +add_public_tablegen_target(CIRCTFIRRTLTypeInterfacesIncGen) +add_dependencies(circt-headers CIRCTFIRRTLTypeInterfacesIncGen) + # Generate Dialect documentation. add_circt_doc(FIRRTLAttributes Dialects/FIRRTLAttributes -gen-attrdef-doc) add_circt_doc(FIRRTLStructure Dialects/FIRRTLStructureOps -gen-op-doc) diff --git a/include/circt/Dialect/FIRRTL/FIREmitter.h b/include/circt/Dialect/FIRRTL/FIREmitter.h index 099a6e064dda..c191e368ee54 100644 --- a/include/circt/Dialect/FIRRTL/FIREmitter.h +++ b/include/circt/Dialect/FIRRTL/FIREmitter.h @@ -19,7 +19,11 @@ namespace circt { namespace firrtl { -mlir::LogicalResult exportFIRFile(mlir::ModuleOp module, llvm::raw_ostream &os); +struct FIRVersion; + +mlir::LogicalResult exportFIRFile(mlir::ModuleOp module, llvm::raw_ostream &os, + std::optional targetLineLength, + FIRVersion version); void registerToFIRFileTranslation(); diff --git a/include/circt/Dialect/FIRRTL/FIRParser.h b/include/circt/Dialect/FIRRTL/FIRParser.h index 7df2a47441e2..1177b4ca51ec 100644 --- a/include/circt/Dialect/FIRRTL/FIRParser.h +++ b/include/circt/Dialect/FIRRTL/FIRParser.h @@ -29,13 +29,25 @@ namespace circt { namespace firrtl { struct FIRParserOptions { - /// If this is set to true, the @info locators are ignored, and the locations - /// are set to the location in the .fir file. - bool ignoreInfoLocators = false; + /// Specify how @info locators should be handled. + enum class InfoLocHandling { + /// If this is set to true, the @info locators are ignored, and the + /// locations are set to the location in the .fir file. + IgnoreInfo, + /// Prefer @info locators, fallback to .fir locations. + PreferInfo, + /// Attach both @info locators (when present) and .fir locations. + FusedInfo + }; + + InfoLocHandling infoLocatorHandling = InfoLocHandling::PreferInfo; + /// The number of annotation files that were specified on the command line. /// This, along with numOMIRFiles provides structure to the buffers in the /// source manager. unsigned numAnnotationFiles; + bool scalarizeTopModule = false; + bool scalarizeExtModules = false; }; mlir::OwningOpRef importFIRFile(llvm::SourceMgr &sourceMgr, @@ -64,6 +76,46 @@ maybeStringToLocation(llvm::StringRef spelling, bool skipParsing, void registerFromFIRFileTranslation(); +/// The FIRRTL specification version. +struct FIRVersion { + constexpr FIRVersion(uint16_t major, uint16_t minor, uint16_t patch) + : major{major}, minor{minor}, patch{patch} {} + + explicit constexpr operator uint64_t() const { + return uint64_t(major) << 32 | uint64_t(minor) << 16 | uint64_t(patch); + } + + constexpr bool operator<(FIRVersion rhs) const { + return uint64_t(*this) < uint64_t(rhs); + } + + constexpr bool operator>(FIRVersion rhs) const { + return uint64_t(*this) > uint64_t(rhs); + } + + constexpr bool operator<=(FIRVersion rhs) const { + return uint64_t(*this) <= uint64_t(rhs); + } + + constexpr bool operator>=(FIRVersion rhs) const { + return uint64_t(*this) >= uint64_t(rhs); + } + + uint16_t major; + uint16_t minor; + uint16_t patch; +}; + +constexpr FIRVersion minimumFIRVersion(0, 2, 0); +constexpr FIRVersion nextFIRVersion(3, 3, 0); +constexpr FIRVersion exportFIRVersion = nextFIRVersion; +constexpr FIRVersion defaultFIRVersion(1, 0, 0); + +template +T &operator<<(T &os, FIRVersion version) { + return os << version.major << "." << version.minor << "." << version.patch; +} + } // namespace firrtl } // namespace circt diff --git a/include/circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h b/include/circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h index 9301752a8d7b..838b0dddf4f9 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h @@ -16,7 +16,8 @@ #include "circt/Dialect/FIRRTL/CHIRRTLDialect.h" #include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h" #include "circt/Dialect/FIRRTL/FIRRTLOps.h" -#include "circt/Dialect/FIRRTL/Namespace.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/HW/InnerSymbolNamespace.h" #include "llvm/ADT/TypeSwitch.h" namespace circt { @@ -72,6 +73,69 @@ struct AnnoPathValue { } }; +template +static T &operator<<(T &os, const AnnoPathValue &path) { + os << "~" << path.ref.getModule()->getParentOfType().getName() + << "|"; + + if (path.isLocal()) { + os << path.ref.getModule().getModuleName(); + } else { + os << path.instances.front() + ->getParentOfType() + .getModuleName(); + } + for (auto inst : path.instances) + os << "/" << inst.getName() << ":" << inst.getModuleName(); + if (!path.isOpOfType()) { + os << ">" << path.ref; + auto type = dyn_cast(path.ref.getType()); + if (!type) + return os; + auto targetFieldID = path.fieldIdx; + while (targetFieldID) { + FIRRTLTypeSwitch(type) + .Case([&](FVectorType vector) { + auto index = vector.getIndexForFieldID(targetFieldID); + os << "[" << index << "]"; + type = vector.getElementType(); + targetFieldID -= vector.getFieldID(index); + }) + .template Case([&](BundleType bundle) { + auto index = bundle.getIndexForFieldID(targetFieldID); + os << "." << bundle.getElementName(index); + type = bundle.getElementType(index); + targetFieldID -= bundle.getFieldID(index); + }) + .Default([&](auto) { targetFieldID = 0; }); + } + } + return os; +} + +template +static T &operator<<(T &os, const OpAnnoTarget &target) { + os << target.getOp()->getAttrOfType("name").getValue(); + return os; +} + +template +static T &operator<<(T &os, const PortAnnoTarget &target) { + os << target.getModule().getPortName(target.getPortNo()); + return os; +} + +template +static T &operator<<(T &os, const AnnoTarget &target) { + if (auto op = target.dyn_cast()) + os << op; + else if (auto port = target.dyn_cast()) + os << port; + else + os << "<>"; + return os; +} + /// Cache AnnoTargets for a module's named things. struct AnnoTargetCache { AnnoTargetCache() = delete; @@ -189,10 +253,6 @@ std::optional resolvePath(StringRef rawPath, CircuitOp circuit, SymbolTable &symTbl, CircuitTargetCache &cache); -/// Return true if an Annotation's class name is handled by the LowerAnnotations -/// pass. -bool isAnnoClassLowered(StringRef className); - /// A representation of a deferred Wiring problem consisting of a source that /// should be connected to a sink. struct WiringProblem { @@ -245,32 +305,53 @@ struct ModuleModifications { SmallVector uturns; }; +/// A cache of existing HierPathOps, mostly used to facilitate HierPathOp reuse. +struct HierPathCache { + HierPathCache(Operation *op, SymbolTable &symbolTable); + + hw::HierPathOp getOpFor(ArrayAttr attr); + + StringAttr getSymFor(ArrayAttr attr) { + return getOpFor(attr).getSymNameAttr(); + } + + FlatSymbolRefAttr getRefFor(ArrayAttr attr) { + return FlatSymbolRefAttr::get(getSymFor(attr)); + } + +private: + OpBuilder builder; + DenseMap cache; + SymbolTable &symbolTable; +}; + /// State threaded through functions for resolving and applying annotations. struct ApplyState { using AddToWorklistFn = llvm::function_ref; ApplyState(CircuitOp circuit, SymbolTable &symTbl, AddToWorklistFn addToWorklistFn, - InstancePathCache &instancePathCache) + InstancePathCache &instancePathCache, bool noRefTypePorts) : circuit(circuit), symTbl(symTbl), addToWorklistFn(addToWorklistFn), - instancePathCache(instancePathCache) {} + instancePathCache(instancePathCache), hierPathCache(circuit, symTbl), + noRefTypePorts(noRefTypePorts) {} CircuitOp circuit; SymbolTable &symTbl; CircuitTargetCache targetCaches; AddToWorklistFn addToWorklistFn; InstancePathCache &instancePathCache; - DenseMap instPathToNLAMap; + HierPathCache hierPathCache; size_t numReusedHierPaths = 0; + // Options that control annotation lowering. + bool noRefTypePorts; + DenseSet wiringProblemInstRefs; DenseMap legacyWiringProblems; SmallVector wiringProblems; - ModuleNamespace &getNamespace(FModuleLike module) { - auto &ptr = namespaces[module]; - if (!ptr) - ptr = std::make_unique(module); - return *ptr; + hw::InnerSymbolNamespace &getNamespace(FModuleLike module) { + return namespaces[module]; } IntegerAttr newID() { @@ -279,7 +360,7 @@ struct ApplyState { }; private: - DenseMap> namespaces; + hw::InnerSymbolNamespaceCollection namespaces; unsigned annotationID = 0; }; @@ -325,7 +406,7 @@ A tryGetAs(DictionaryAttr &dict, const Attribute &root, StringRef key, return nullptr; } // Check that the value has the correct type. - auto valueA = value.dyn_cast_or_null(); + auto valueA = dyn_cast_or_null(value); if (!valueA) { SmallString<128> msg; if (path.isTriviallyEmpty()) @@ -346,11 +427,85 @@ A tryGetAs(DictionaryAttr &dict, const Attribute &root, StringRef key, /// Add ports to the module and all its instances and return the clone for /// `instOnPath`. This does not connect the new ports to anything. Replace /// the old instances with the new cloned instance in all the caches. -InstanceOp addPortsToModule( - FModuleLike mod, InstanceOp instOnPath, FIRRTLType portType, Direction dir, - StringRef newName, InstancePathCache &instancePathcache, - llvm::function_ref getNamespace, - CircuitTargetCache *targetCaches = nullptr); +InstanceOp addPortsToModule(FModuleLike mod, InstanceOp instOnPath, + FIRRTLType portType, Direction dir, + StringRef newName, + InstancePathCache &instancePathcache, + CircuitTargetCache *targetCaches = nullptr); + +///===----------------------------------------------------------------------===// +/// LowerAnnotations +///===----------------------------------------------------------------------===// + +/// Annotation resolver and handler. +struct AnnoRecord { + llvm::function_ref(DictionaryAttr, ApplyState &)> + resolver; + llvm::function_ref + applier; +}; + +/// Register external annotation records. +LogicalResult registerAnnotationRecord( + StringRef annoClass, AnnoRecord annoRecord, + const std::function &errorHandler = {}); + +///===----------------------------------------------------------------------===// +/// Standard Utility Resolvers +///===----------------------------------------------------------------------===// + +/// (SFC) FIRRTL SingleTargetAnnotation resolver. Uses the 'target' field of +/// the annotation with standard parsing to resolve the path. This requires +/// 'target' to exist and be normalized (per docs/FIRRTLAnnotations.md). +std::optional stdResolve(DictionaryAttr anno, ApplyState &state); + +/// Resolves with target, if it exists. If not, resolves to the circuit. +std::optional tryResolve(DictionaryAttr anno, ApplyState &state); + +///===----------------------------------------------------------------------===// +/// Standard Utility Appliers +///===----------------------------------------------------------------------===// + +/// An applier which puts the annotation on the target and drops the 'target' +/// field from the annotation. Optionally handles non-local annotations. +LogicalResult applyWithoutTargetImpl(const AnnoPathValue &target, + DictionaryAttr anno, ApplyState &state, + bool allowNonLocal); + +/// An applier which puts the annotation on the target and drops the 'target' +/// field from the annotation. Optionally handles non-local annotations. +/// Ensures the target resolves to an expected type of operation. +template +static LogicalResult applyWithoutTarget(const AnnoPathValue &target, + DictionaryAttr anno, + ApplyState &state) { + if (target.ref.isa()) { + if (!allowPortAnnoTarget) + return failure(); + } else if (!target.isOpOfType()) + return failure(); + + return applyWithoutTargetImpl(target, anno, state, allowNonLocal); +} + +template +static LogicalResult applyWithoutTarget(const AnnoPathValue &target, + DictionaryAttr anno, + ApplyState &state) { + return applyWithoutTarget(target, anno, + state); +} + +/// An applier which puts the annotation on the target and drops the 'target' +/// field from the annotaiton. Optionally handles non-local annotations. +template +static LogicalResult applyWithoutTarget(const AnnoPathValue &target, + DictionaryAttr anno, + ApplyState &state) { + return applyWithoutTargetImpl(target, anno, state, allowNonLocal); +} } // namespace firrtl } // namespace circt diff --git a/include/circt/Dialect/FIRRTL/FIRRTLAnnotations.h b/include/circt/Dialect/FIRRTL/FIRRTLAnnotations.h index 14f79b72dbae..26823b4d6e2e 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLAnnotations.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLAnnotations.h @@ -19,6 +19,9 @@ #include "llvm/Support/PointerLikeTypeTraits.h" namespace circt { +namespace hw { +struct InnerSymbolNamespace; +} // namespace hw namespace firrtl { class AnnotationSetIterator; @@ -26,7 +29,6 @@ class FModuleOp; class FModuleLike; class MemOp; class InstanceOp; -struct ModuleNamespace; class FIRRTLType; /// Return the name of the attribute used for annotations on FIRRTL ops. @@ -91,6 +93,10 @@ class Annotation { void removeMember(StringAttr name); void removeMember(StringRef name); + /// Returns true if this is an annotation which can be safely deleted without + /// consequence. + bool canBeDeleted(); + using iterator = llvm::ArrayRef::iterator; iterator begin() const { return getDict().begin(); } iterator end() const { return getDict().end(); } @@ -261,6 +267,10 @@ class AnnotationSet { static bool addDontTouch(Operation *op); static bool removeDontTouch(Operation *op); + /// Check if every annotation can be deleted. + bool canBeDeleted() const; + static bool canBeDeleted(Operation *op); + bool operator==(const AnnotationSet &other) const { return annotations == other.annotations; } @@ -424,12 +434,8 @@ struct AnnoTarget { /// Get the parent module of the target. FModuleLike getModule() const; - /// Get the inner_sym attribute of an op. If there is no attached inner_sym, - /// then one will be created and attached to the op. - StringAttr getInnerSym(ModuleNamespace &moduleNamespace) const; - /// Get a reference to this target suitable for use in an NLA. - Attribute getNLAReference(ModuleNamespace &moduleNamespace) const; + Attribute getNLAReference(hw::InnerSymbolNamespace &moduleNamespace) const; /// Get the type of the target. FIRRTLType getType() const; @@ -448,8 +454,7 @@ struct OpAnnoTarget : public AnnoTarget { AnnotationSet getAnnotations() const; void setAnnotations(AnnotationSet annotations) const; - StringAttr getInnerSym(ModuleNamespace &moduleNamespace) const; - Attribute getNLAReference(ModuleNamespace &moduleNamespace) const; + Attribute getNLAReference(hw::InnerSymbolNamespace &moduleNamespace) const; FIRRTLType getType() const; static bool classof(const AnnoTarget &annoTarget) { @@ -470,8 +475,7 @@ struct PortAnnoTarget : public AnnoTarget { AnnotationSet getAnnotations() const; void setAnnotations(AnnotationSet annotations) const; - StringAttr getInnerSym(ModuleNamespace &moduleNamespace) const; - Attribute getNLAReference(ModuleNamespace &moduleNamespace) const; + Attribute getNLAReference(hw::InnerSymbolNamespace &moduleNamespace) const; FIRRTLType getType() const; static bool classof(const AnnoTarget &annoTarget) { diff --git a/include/circt/Dialect/FIRRTL/FIRRTLAttributes.td b/include/circt/Dialect/FIRRTL/FIRRTLAttributes.td index e22db58e99ee..093f93fc5c89 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLAttributes.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLAttributes.td @@ -28,10 +28,10 @@ def PortAnnotationsAttr : ArrayAttrBase< // Guarantee this is an ArrayAttr first CPred<"$_self.isa<::mlir::ArrayAttr>()">, // Guarantee all elements are an array or a dictionary. - CPred<"::llvm::all_of($_self.cast<::mlir::ArrayAttr>(), " - "[&](::mlir::Attribute attr) { return attr.isa<" + CPred<"::llvm::all_of(cast($_self), " + "[&](::mlir::Attribute attr) { return isa<" "::mlir::ArrayAttr," - "::mlir::DictionaryAttr>();})">]>, + "::mlir::DictionaryAttr>(attr);})">]>, "Port annotations attribute"> { let constBuilderCall = "$_builder.getArrayAttr($0)"; } @@ -181,12 +181,34 @@ def MemoryInitAttr : AttrDef { "firrtl.annotations.LoadMemoryFromFile" and "firrtl.annotations.MemoryFileInlineAnnotation". }]; - let parameters = ( - ins "::mlir::StringAttr":$filename, - "::mlir::BoolAttr":$isBinary, - "::mlir::BoolAttr":$isInline + let parameters = (ins + "::mlir::StringAttr":$filename, + "bool":$isBinary, + "bool":$isInline ); let assemblyFormat = "`<` $filename `,` $isBinary `,` $isInline `>`"; } +/// An attribute holding internal path for ref-type ports. +def InternalPathAttr : AttrDef { + let summary = "Internal path for ref-type ports"; + let mnemonic = "internalpath"; + let parameters = (ins OptionalParameter<"::mlir::StringAttr">:$path); + let assemblyFormat = "(`<` $path^ `>`)?"; + let builders = [ + AttrBuilderWithInferredContext<(ins "::mlir::StringAttr":$path), [{ + return $_get(path.getContext(), path); + }]>, + AttrBuilder<(ins "::llvm::StringRef":$path), [{ + return $_get($_ctxt, ::mlir::StringAttr::get($_ctxt, path)); + }]>, + AttrBuilder<(ins), [{ + return $_get($_ctxt, ::mlir::StringAttr()); + }]> + ]; +} + +def InternalPathArrayAttr + : TypedArrayAttrBase; + #endif // CIRCT_DIALECT_FIRRTL_FIRRTLATTRIBUTES_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLCanonicalization.td b/include/circt/Dialect/FIRRTL/FIRRTLCanonicalization.td index 42a6aa79a168..6d66380b1b74 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLCanonicalization.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLCanonicalization.td @@ -42,40 +42,66 @@ def NonEmptyAttr : Constraint>; def NullAttr : Constraint>; // Constraint that enforces equal types -def EqualTypes : Constraint>; +def EqualTypes : Constraint>; + +// Constraint that enforces equal type sizes +def EqualIntSize : Constraint($0.getType()).getWidth() == type_cast($1.getType()).getWidth()">>; + +// sizeof(0) >= sizeof(1) +def IntTypeWidthGEQ32 : Constraint($0.getType()).getBitWidthOrSentinel() >= type_cast($1.getType()).getBitWidthOrSentinel()">>; + +// sizeof(0) > sizeof(1) +def IntTypeWidthGT32 : Constraint($0.getType()).getBitWidthOrSentinel() > type_cast($1.getType()).getBitWidthOrSentinel()">>; // Constraint that enforces int types -def IntTypes : Constraint()">>; +def IntTypes : Constraint($0.getType())">>; // Constraint that enforces sint types -def SIntTypes : Constraint()">>; +def SIntTypes : Constraint($0.getType())">>; // Constraint that enforces uint types -def UIntTypes : Constraint()">>; +def UIntTypes : Constraint($0.getType())">>; // Constraint that enforces types mismatches -def mismatchTypes : Constraint>; +def mismatchTypes : Constraint>; // Constraint that enforces types of known width def KnownWidth : Constraint() && - !$0.getType().cast().getRecursiveTypeProperties().hasUninferredWidth + type_isa($0.getType()) && + !type_cast($0.getType()).getRecursiveTypeProperties().hasUninferredWidth }]>>; +/// Constraint that matches a false SpecialConstantOp +def FalseSpecialConstantOp : Constraint() &&" + "$0.getDefiningOp().getValue() == false">>; + +/// Constraint that matches a true SpecialConstantOp +def TrueSpecialConstantOp : Constraint() &&" + "$0.getDefiningOp().getValue() == true">>; + /// Constraint that matches a zero ConstantOp or SpecialConstantOp. def ZeroConstantOp : Constraint() &&" "$0.getDefiningOp().getValue().isZero()">, - CPred<"$0.getDefiningOp() &&" - "$0.getDefiningOp().getValue() == false"> + FalseSpecialConstantOp.predicate ]>>; /// Constraint that matches a one ConstantOp or SpecialConstantOp. def OneConstantOp : Constraint() &&" "$0.getDefiningOp().getValue().isOne()">, - CPred<"$0.getDefiningOp() &&" - "$0.getDefiningOp().getValue() == true"> + TrueSpecialConstantOp.predicate +]>>; + +/// Constraint that matches a not-all-ones ConstantOp or SpecialConstantOp. +def NotAllOneConstantOp : Constraint() &&" + "!$0.getDefiningOp().getValue().isAllOnes()">, + FalseSpecialConstantOp.predicate ]>>; /// Constraint that matches an all ones ConstantOp. @@ -83,7 +109,7 @@ def AllOneConstantOp : Constraint() && $0.ge // Delta between type widths def TypeWidthAdjust32 : NativeCodeCall< - "$_builder.getI32IntegerAttr($0.getType().cast().getBitWidthOrSentinel() - $1.getType().cast().getBitWidthOrSentinel())">; + "$_builder.getI32IntegerAttr(type_cast($0.getType()).getBitWidthOrSentinel() - type_cast($1.getType()).getBitWidthOrSentinel())">; /// Drop the writer to the first argument and passthrough the second def DropWrite : NativeCodeCall<"dropWrite($_builder, $0, $1)">; @@ -133,14 +159,14 @@ def ConnectSameType : Pat< def ConnectExtension : Pat< (ConnectOp $dst, $src), (StrictConnectOp $dst, (PadPrimOp $src, - (NativeCodeCall<"$0.getType().cast().getBitWidthOrSentinel()"> $dst))), + (NativeCodeCall<"type_cast($0.getType()).getBitWidthOrSentinel()"> $dst))), [(IntType $dst), (IntType $src), (KnownWidth $dst), (KnownWidth $src), (mismatchTypes $src, $dst)]>; def LimitConstant32 : NativeCodeCall< "$_builder.getI32IntegerAttr($0.getValue().getLimitedValue(1ULL << 31))">; def TypeWidth32 : NativeCodeCall< - "$_builder.getI32IntegerAttr($0.getType().cast().getBitWidthOrSentinel())">; + "$_builder.getI32IntegerAttr(type_cast($0.getType()).getBitWidthOrSentinel())">; // dshl(a, const) -> shl(a, const) def DShlOfConstant : Pat< @@ -243,7 +269,7 @@ def SubFromZeroUnsigned : Pat < //sub(a,a) -> 0 def SubOfSelf : Pat < (SubPrimOp:$old $x, $x), - (NativeCodeCall<"$_builder.create($0.getLoc(), $0.getType().cast(), getIntZerosAttr($0.getType()))"> $old), + (NativeCodeCall<"$_builder.create($0.getLoc(), type_cast($0.getType()), getIntZerosAttr($0.getType()))"> $old), [(KnownWidth $x)]>; // sub((pad a, n), b) -> pad(sub(a, b), n) @@ -261,7 +287,7 @@ def SubOfPadR : Pat < // and(x, 0) -> 0, fold can't handle all cases def AndOfZero : Pat < (AndPrimOp:$old $x, (ConstantOp:$zcst $cst)), - (NativeCodeCall<"$_builder.create($0.getLoc(), $0.getType().cast(), getIntZerosAttr($0.getType()))"> $old), + (NativeCodeCall<"$_builder.create($0.getLoc(), type_cast($0.getType()), getIntZerosAttr($0.getType()))"> $old), [(KnownWidth $x), (ZeroConstantOp $zcst)]>; // and(x, -1) -> x, fold can't handle all cases @@ -282,6 +308,16 @@ def AndOfPad : Pat < (MoveNameHint $old, (PadPrimOp (AndPrimOp (TailPrimOp $x, (TypeWidthAdjust32 $x, $y)), $y), $n)), [(KnownWidth $x), (UIntType $x), (EqualTypes $x, $pad)]>; +def AndOfAsSIntL : Pat< + (AndPrimOp:$old (AsSIntPrimOp $x), $y), + (MoveNameHint $old, (AndPrimOp $x, (AsUIntPrimOp $y))), + [(KnownWidth $x), (EqualIntSize $x, $y)]>; + +def AndOfAsSIntR : Pat< + (AndPrimOp:$old $x, (AsSIntPrimOp $y)), + (MoveNameHint $old, (AndPrimOp (AsUIntPrimOp $x), $y)), + [(KnownWidth $x), (EqualIntSize $x, $y)]>; + // or(x, 0) -> x, fold can't handle all cases def OrOfZero : Pat < (OrPrimOp:$old $x, (ConstantOp:$zcst $cst)), @@ -291,7 +327,7 @@ def OrOfZero : Pat < // or(x, -1) -> -1, fold can't handle all cases def OrOfAllOne : Pat < (OrPrimOp:$old $x, (ConstantOp:$ocst $cst)), - (NativeCodeCall<"$_builder.create($0.getLoc(), $0.getType().cast(), getIntOnesAttr($0.getType()))"> $old), + (NativeCodeCall<"$_builder.create($0.getLoc(), type_cast($0.getType()), getIntOnesAttr($0.getType()))"> $old), [(KnownWidth $x), (EqualTypes $x, $ocst), (AllOneConstantOp $ocst)]>; /// or(x,x) -> x, fold can't handle all cases @@ -316,7 +352,7 @@ def XorOfZero : Pat < /// xor(x,x) -> 0, fold can't handle all cases def XorOfSelf : Pat < (XorPrimOp:$old $x, $x), - (NativeCodeCall<"$_builder.create($0.getLoc(), $0.getType().cast(), getIntZerosAttr($0.getType()))"> $old), + (NativeCodeCall<"$_builder.create($0.getLoc(), type_cast($0.getType()), getIntZerosAttr($0.getType()))"> $old), [(KnownWidth $x)]>; /// xor(x, pad(y, n)) -> cat(head(x), xor(tail(x), y)) @@ -326,6 +362,114 @@ def XorOfPad : Pat < (XorPrimOp (TailPrimOp $x, (TypeWidthAdjust32 $x, $y)), $y))), [(KnownWidth $x), (UIntType $x), (EqualTypes $x, $pad)]>; +/// orr(asSInt(x)) -> orr(x) +def OrRasSInt : Pat < + (OrRPrimOp:$old (AsSIntPrimOp $x)), + (MoveNameHint $old, (OrRPrimOp $x)), + [(KnownWidth $x), (IntTypes $x)]>; + +/// orr(asUInt(x)) -> orr(x) +def OrRasUInt : Pat < + (OrRPrimOp:$old (AsUIntPrimOp $x)), + (MoveNameHint $old, (OrRPrimOp $x)), + [(KnownWidth $x), (IntTypes $x)]>; + +/// orr(cat(0,x)) -> orr(x) +def OrRCatZeroH : Pat < + (OrRPrimOp:$old (CatPrimOp $c, $x)), + (MoveNameHint $old, (OrRPrimOp $x)), + [(KnownWidth $x), (ZeroConstantOp $c)]>; + +/// orr(cat(x,0)) -> orr(x) +def OrRCatZeroL : Pat < + (OrRPrimOp:$old (CatPrimOp $x, $c)), + (MoveNameHint $old, (OrRPrimOp $x)), + [(KnownWidth $x), (ZeroConstantOp $c)]>; + +/// orr(pad(x,n)) -> orr(x) +def OrRPadU : Pat < + (OrRPrimOp:$old (PadPrimOp:$pad $x, $n)), + (MoveNameHint $old, (OrRPrimOp $x)), + [(KnownWidth $x), (UIntTypes $x), (IntTypeWidthGEQ32 $pad, $x)]>; + +/// xorr(asSInt(x)) -> xorr(x) +def XorRasSInt : Pat < + (XorRPrimOp:$old (AsSIntPrimOp $x)), + (MoveNameHint $old, (XorRPrimOp $x)), + [(KnownWidth $x), (IntTypes $x)]>; + +/// xorr(asUInt(x)) -> xorr(x) +def XorRasUInt : Pat < + (XorRPrimOp:$old (AsUIntPrimOp $x)), + (MoveNameHint $old, (XorRPrimOp $x)), + [(KnownWidth $x), (IntTypes $x)]>; + +/// xorr(cat(0,x)) -> xorr(x) +def XorRCatZeroH : Pat < + (XorRPrimOp:$old (CatPrimOp $c, $x)), + (MoveNameHint $old, (XorRPrimOp $x)), + [(KnownWidth $x), (IntTypes $x), (ZeroConstantOp $c)]>; + +/// xorr(cat(x,0)) -> xorr(x) +def XorRCatZeroL : Pat < + (XorRPrimOp:$old (CatPrimOp $x, $c)), + (MoveNameHint $old, (XorRPrimOp $x)), + [(KnownWidth $x), (IntTypes $x), (ZeroConstantOp $c)]>; + +/// xorr(pad(x,n)) -> xorr(x) +def XorRPadU : Pat < + (XorRPrimOp:$old (PadPrimOp:$pad $x, $n)), + (MoveNameHint $old, (XorRPrimOp $x)), + [(KnownWidth $x), (UIntTypes $x), (IntTypeWidthGEQ32 $pad, $x)]>; + +/// andr(asSInt(x)) -> andr(x) +def AndRasSInt : Pat < + (AndRPrimOp:$old (AsSIntPrimOp $x)), + (MoveNameHint $old, (AndRPrimOp $x)), + [(KnownWidth $x), (IntTypes $x)]>; + +/// andr(asUInt(x)) -> andr(x) +def AndRasUInt : Pat < + (AndRPrimOp:$old (AsUIntPrimOp $x)), + (MoveNameHint $old, (AndRPrimOp $x)), + [(KnownWidth $x), (IntTypes $x)]>; + +/// andr(cat(*0*, x)) -> 0 +def AndRCatZeroL : Pat < + (AndRPrimOp:$old (CatPrimOp $z, $x)), + (NativeCodeCall<"$_builder.create($0.getLoc(), cast($0.getType()), getIntZerosAttr($0.getType()))"> $old), + [(NotAllOneConstantOp $z)]>; + +/// andr(cat(x, *0*)) -> 0 +def AndRCatZeroR : Pat < + (AndRPrimOp:$old (CatPrimOp $x, $z)), + (NativeCodeCall<"$_builder.create($0.getLoc(), type_cast($0.getType()), getIntZerosAttr($0.getType()))"> $old), + [(NotAllOneConstantOp $z)]>; + +/// andr(cat(1, x)) -> andr(x) +def AndRCatOneL : Pat < + (AndRPrimOp:$old (CatPrimOp $z, $x)), + (MoveNameHint $old, (AndRPrimOp $x)), + [(AllOneConstantOp $z)]>; + +/// andr(cat(x, 1)) -> andr(x) +def AndRCatOneR : Pat < + (AndRPrimOp:$old (CatPrimOp $x, $z)), + (MoveNameHint $old, (AndRPrimOp $x)), + [(AllOneConstantOp $z)]>; + +/// andr(pad(x:U,n)) -> 0 (where pad is doing something) +def AndRPadU : Pat < + (AndRPrimOp:$old (PadPrimOp:$pad $x, $n)), + (NativeCodeCall<"$_builder.create($0.getLoc(), type_cast($0.getType()), getIntZerosAttr($0.getType()))"> $old), + [(KnownWidth $x), (UIntTypes $x), (IntTypeWidthGT32 $pad, $x)]>; + +/// andr(pad(x:S,n)) -> andr(x) +def AndRPadS : Pat < + (AndRPrimOp:$old (PadPrimOp:$pad $x, $n)), + (MoveNameHint $old, (AndRPrimOp $x)), + [(KnownWidth $x), (SIntTypes $x)]>; + // bits(bits(x, ...), ...) -> bits(x, ...) def BitsOfBits : Pat< (BitsPrimOp:$old (BitsPrimOp $x, I32Attr:$iHigh, I32Attr:$iLow), I32Attr:$oHigh, I32Attr:$oLow), @@ -334,12 +478,39 @@ def BitsOfBits : Pat< (NativeCodeCall<"$_builder.getI32IntegerAttr($0.getValue().getZExtValue() + $1.getValue().getZExtValue())"> $oLow, $iLow))), []>; +// bits(mux(c,x,y)) -> mux(c, bits(x), bits(y)) +// This isn't obviously good if the mux has multiple uses, so limit it to constants. +def BitsOfMux : Pat< + (BitsPrimOp:$old (MuxPrimOp $c, $x, $y), I32Attr:$oHigh, I32Attr:$oLow), + (MoveNameHint $old, (MuxPrimOp $c, (BitsPrimOp $x, $oHigh, $oLow), (BitsPrimOp $y, $oHigh, $oLow))), + [(AnyConstantOp $x), (AnyConstantOp $y)]>; + +// bits(asUInt) -> bits +def BitsOfAsUInt : Pat< + (BitsPrimOp:$old (AsUIntPrimOp $x), I32Attr:$oHigh, I32Attr:$oLow), + (MoveNameHint $old, (BitsPrimOp $x, $oHigh, $oLow)), + [(KnownWidth $x)]>; + +// bits(asUInt) -> bits +def BitsOfAnd : Pat< + (BitsPrimOp:$old (AndPrimOp $x, $y), I32Attr:$oHigh, I32Attr:$oLow), + (MoveNameHint $old, (AndPrimOp (BitsPrimOp $x, $oHigh, $oLow), (BitsPrimOp $y, $oHigh, $oLow))), + [(KnownWidth $x), (EqualTypes $x, $y)]>; + +// bits(pad(x)) -> x when they cancel out +def AttrIsZero : Constraint>; +def BitsOfPad : Pat< + (BitsPrimOp:$old (PadPrimOp $x, I32Attr:$n), I32Attr:$oHigh, I32Attr:$oLow), + (AsUIntPrimOp $x), + [(KnownWidth $x), (EqualIntSize $old, $x), (AttrIsZero $oLow)]>; + +def IndexInBound : Constraint($0).getAPSInt().getExtValue() < static_cast(type_cast($1.getType()).getNumElements())">>; + // subaccess a, cst -> subindex a, cst -// TODO: only enable if cst is inside a. Subaccess and subindex behave differently for out-of-bounds indexes. def SubaccessOfConstant : Pat< (SubaccessOp:$old $array, (ConstantOp $cst)), - (MoveNameHint $old, (SubindexOp $array, (NativeCodeCall<"$_builder.getI32IntegerAttr($0.cast().getAPSInt().getExtValue())"> $cst))), - []>; + (MoveNameHint $old, (SubindexOp $array, (NativeCodeCall<"$_builder.getI32IntegerAttr(cast($0).getAPSInt().getExtValue())"> $cst))), + [(IndexInBound $cst, $array)]>; // mux(cond, 0, 1) -> ~cond def MuxNot : Pat< @@ -373,6 +544,24 @@ def NarrowMuxRHS : Pat < (MoveNameHint $old, (CatPrimOp (MuxPrimOp $cond, $a, $c), $b)), [(EqualTypes $lhs, $rhs), (KnownWidth $lhs)]>; +// mux(eq(a, b), a, b) -> b +def MuxEQOperands : Pat< + (MuxPrimOp (EQPrimOp $a, $b), $a, $b), + (replaceWithValue $b), + [(EqualTypes $a, $b), (KnownWidth $a)]>; + +// mux(eq(b, a), a, b) -> b +def MuxEQOperandsSwapped : Pat< + (MuxPrimOp (EQPrimOp $b, $a), $a, $b), + (replaceWithValue $b), + [(EqualTypes $a, $b), (KnownWidth $a)]>; + +// mux(neq(a, b), x, y) -> mux(eq(a, b), y, x) +def MuxNEQ : Pat< + (MuxPrimOp:$old (NEQPrimOp $a, $b), $x, $y), + (MoveNameHint $old, (MuxPrimOp (EQPrimOp $a, $b), $y, $x)), + [(EqualTypes $x, $y), (KnownWidth $x)]>; + def CatDoubleConst : Pat < (CatPrimOp:$old $cst1, (CatPrimOp $cst2, $v)), (MoveNameHint $old, (CatPrimOp (CatPrimOp $cst1, (AsUIntPrimOp $cst2)), (AsUIntPrimOp $v))), @@ -380,13 +569,36 @@ def CatDoubleConst : Pat < // regreset(clock, constant_zero, resetValue) -> reg(clock) def RegResetWithZeroReset : Pat< - (RegResetOp $clock, $reset, $_, $name, $nameKind, $annotations, $inner_sym), - (RegOp $clock, $name, $nameKind, $annotations, $inner_sym), [(ZeroConstantOp $reset)]>; - -// regreset(clock, constant_one, resetValue) -> node(resetValue) -def RegResetWithOneReset : Pat< - (RegResetOp:$reg $clock, $reset, $resetVal, $name, $nameKind, $annotations, $inner_sym), - (DropWrite $reg, (NodeOp $resetVal, $name, $nameKind, $annotations, $inner_sym)), - [(OneConstantOp $reset)]>; + (RegResetOp $clock, $reset, $_, $name, $nameKind, $annotations, $inner_sym, $forceable), + (RegOp $clock, $name, $nameKind, $annotations, $inner_sym, $forceable), [(ZeroConstantOp $reset)]>; + +// ref.resolve(ref.send(a)) -> a +def RefResolveOfRefSend : Pat< + (RefResolveOp:$resolve (RefSendOp $base)), + (replaceWithValue $base), [(EqualTypes $base, $resolve)] +>; + +def UtoStoU : Pat< + (AsSIntPrimOp (AsUIntPrimOp $x)), + (replaceWithValue $x), + []>; + +def StoUtoS : Pat< + (AsUIntPrimOp (AsSIntPrimOp $x)), + (replaceWithValue $x), + []>; + +def CVTSigned : Pat< + (CvtPrimOp $x), + (replaceWithValue $x), + [(SIntType $x)]>; + +def CVTUnSigned : Pat< + (CvtPrimOp:$old $x), + (MoveNameHint $old, + (AsSIntPrimOp + (PadPrimOp $x, + (NativeCodeCall<"$_builder.getI32IntegerAttr(type_cast($0.getType()).getBitWidthOrSentinel())"> $old)))), + [(UIntType $x), (KnownWidth $old)]>; #endif // CIRCT_DIALECT_FIRRTL_FIRRTLCANONICALIZATION_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td index ea79c6f9de82..eab3a67f299f 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td @@ -23,15 +23,27 @@ include "circt/Dialect/HW/HWTypes.td" include "mlir/IR/OpBase.td" include "mlir/IR/SymbolInterfaces.td" include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" +/// A declaration that can be referred to by it's name/identity. class ReferableDeclOp traits = []> : - FIRRTLOp { - + FIRRTLOp, + FNamableOp]> { } -def InstanceOp : ReferableDeclOp<"instance", [HasParent<"firrtl::FModuleOp, firrtl::WhenOp">, +/// A referable declaration of some actual hardware object, which must be +/// located in a hardware-creating context, such as the body of a module. +class HardwareDeclOp traits = []> : + ReferableDeclOp]> {} + +def InstanceOp : HardwareDeclOp<"instance", [ DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods]> { + DeclareOpInterfaceMethods, + ]> { let summary = "Instantiate an instance of a module"; let description = [{ This represents an instance of a module. The results are the modules inputs @@ -64,7 +76,7 @@ def InstanceOp : ReferableDeclOp<"instance", [HasParent<"firrtl::FModuleOp, firr CArg<"ArrayRef", "{}">:$annotations, CArg<"ArrayRef", "{}">:$portAnnotations, CArg<"bool","false">:$lowerToBind, - CArg<"StringAttr", "StringAttr()">:$inner_sym)>, + CArg<"StringAttr", "StringAttr()">:$innerSym)>, OpBuilder<(ins "::mlir::TypeRange":$resultTypes, "::mlir::StringRef":$moduleName, "::mlir::StringRef":$name, @@ -74,7 +86,7 @@ def InstanceOp : ReferableDeclOp<"instance", [HasParent<"firrtl::FModuleOp, firr "ArrayRef":$annotations, "ArrayRef":$portAnnotations, "bool":$lowerToBind, - "hw::InnerSymAttr":$inner_sym)>, + "hw::InnerSymAttr":$innerSym)>, /// Constructor when you have the target module in hand. OpBuilder<(ins "FModuleLike":$module, @@ -83,14 +95,10 @@ def InstanceOp : ReferableDeclOp<"instance", [HasParent<"firrtl::FModuleOp, firr CArg<"ArrayRef", "{}">:$annotations, CArg<"ArrayRef", "{}">:$portAnnotations, CArg<"bool","false">:$lowerToBind, - CArg<"StringAttr", "StringAttr()">:$inner_sym)> + CArg<"StringAttr", "StringAttr()">:$innerSym)> ]; let extraClassDeclaration = [{ - /// Lookup the module or extmodule for the symbol. This returns null on - /// invalid IR. - FModuleLike getReferencedModule(SymbolTable& symtbl); - /// Return the port direction for the specified result number. Direction getPortDirection(size_t resultNo) { return direction::get(getPortDirections()[resultNo]); @@ -98,7 +106,7 @@ def InstanceOp : ReferableDeclOp<"instance", [HasParent<"firrtl::FModuleOp, firr /// Return the port name for the specified result number. StringAttr getPortName(size_t resultNo) { - return getPortNames()[resultNo].cast(); + return cast(getPortNames()[resultNo]); } StringRef getPortNameStr(size_t resultNo) { return getPortName(resultNo).getValue(); @@ -117,10 +125,15 @@ def InstanceOp : ReferableDeclOp<"instance", [HasParent<"firrtl::FModuleOp, firr /// conjuction with adding ports to the referenced module. This will emit /// the new InstanceOp to the same location. InstanceOp cloneAndInsertPorts(ArrayRef> ports); + + //===------------------------------------------------------------------===// + // PortList Methods + //===------------------------------------------------------------------===// + SmallVector<::circt::hw::PortInfo> getPortList(); }]; } -def MemOp : ReferableDeclOp<"mem"> { +def MemOp : HardwareDeclOp<"mem"> { let summary = "Define a new mem"; let arguments = (ins ConfinedAttr]>:$readLatency, @@ -130,8 +143,8 @@ def MemOp : ReferableDeclOp<"mem"> { AnnotationArrayAttr:$annotations, PortAnnotationsAttr:$portAnnotations, OptionalAttr:$inner_sym, - OptionalAttr:$groupID, - OptionalAttr:$init); + OptionalAttr:$init, + OptionalAttr:$prefix); let results = (outs Variadic:$results); let assemblyFormat = [{ @@ -148,13 +161,13 @@ def MemOp : ReferableDeclOp<"mem"> { CArg<"NameKindEnum", "NameKindEnum::DroppableName">:$nameKind, CArg<"ArrayRef", "{}">:$annotations, CArg<"ArrayRef", "{}">:$portAnnotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym)>, + CArg<"StringAttr", "StringAttr()">:$innerSym)>, OpBuilder<(ins "::mlir::TypeRange":$resultTypes, "uint32_t":$readLatency, "uint32_t":$writeLatency, "uint64_t":$depth, "RUWAttr":$ruw, "ArrayRef":$portNames, "StringRef":$name, "NameKindEnum":$nameKind, "ArrayRef":$annotations, "ArrayRef":$portAnnotations, - "hw::InnerSymAttr":$inner_sym)> + "hw::InnerSymAttr":$innerSym)> ]; let hasVerifier = 1; @@ -166,6 +179,9 @@ def MemOp : ReferableDeclOp<"mem"> { using NamedPort = std::pair; + /// Return the address width of the memory. + size_t getAddrBits(); + /// Return the type of a port given the memory depth, type, and kind static FIRRTLType getTypeForPort(uint64_t depth, FIRRTLBaseType dataType, PortKind portKind, size_t maskBits = 0); @@ -214,8 +230,10 @@ def MemOp : ReferableDeclOp<"mem"> { }]; } -def NodeOp : ReferableDeclOp<"node", - [SameOperandsAndResultType, InferTypeOpInterface]> { +def NodeOp : HardwareDeclOp<"node", [ + AllTypesMatch<["input","result"]>, + DeclareOpInterfaceMethods, + Forceable]> { let summary = "No-op to name a value"; let description = [{ A node is simply a named intermediate value in a circuit. The node must @@ -231,39 +249,44 @@ def NodeOp : ReferableDeclOp<"node", let arguments = (ins PassiveType:$input, StrAttr:$name, NameKindAttr:$nameKind, AnnotationArrayAttr:$annotations, - OptionalAttr:$inner_sym); - let results = (outs FIRRTLBaseType:$result); + OptionalAttr:$inner_sym, + UnitAttr:$forceable); // ReferenceKinds + let results = (outs FIRRTLBaseType:$result, Optional:$ref); let builders = [ - OpBuilder<(ins "::mlir::Type":$elementType, "::mlir::Value":$input, - CArg<"StringRef", "{}">:$name, - CArg<"NameKindEnum", "NameKindEnum::DroppableName">:$nameKind, - CArg<"ArrayRef", "{}">:$annotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym), [{ - return build($_builder, $_state, elementType, input, name, nameKind, + OpBuilder<(ins "::mlir::Value":$input, + CArg<"StringRef", "{}">:$name, + CArg<"NameKindEnum", "NameKindEnum::DroppableName">:$nameKind, + CArg<"ArrayRef", "{}">:$annotations, + CArg<"StringAttr", "StringAttr()">:$innerSym, + CArg<"bool", "false">:$forceable), [{ + return build($_builder, $_state, input, name, nameKind, $_builder.getArrayAttr(annotations), - inner_sym ? hw::InnerSymAttr::get(inner_sym) : hw::InnerSymAttr()); + innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr(), + forceable); }]>, - OpBuilder<(ins "::mlir::Type":$elementType, "::mlir::Value":$input, - "StringRef":$name, "NameKindEnum":$nameKind, - "::mlir::ArrayAttr":$annotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym), [{ - return build($_builder, $_state, elementType, input, name, nameKind, + OpBuilder<(ins "::mlir::Value":$input, + "StringRef":$name, "NameKindEnum":$nameKind, + "::mlir::ArrayAttr":$annotations, + CArg<"StringAttr", "StringAttr()">:$innerSym, + CArg<"bool", "false">:$forceable), [{ + return build($_builder, $_state, input, name, nameKind, annotations, - inner_sym ? hw::InnerSymAttr::get(inner_sym) : hw::InnerSymAttr()); + innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr(), + forceable); }]> ]; let assemblyFormat = [{ (`sym` $inner_sym^)? `` custom($nameKind) - $input `` custom(attr-dict) `:` qualified(type($input)) + $input (`forceable` $forceable^)? `` custom(attr-dict) `:` qualified(type($input)) }]; let hasCanonicalizer = true; let hasFolder = 1; } -def RegOp : ReferableDeclOp<"reg"> { +def RegOp : HardwareDeclOp<"reg", [Forceable]> { let summary = "Define a new register"; let description = [{ Declare a new register: @@ -276,36 +299,86 @@ def RegOp : ReferableDeclOp<"reg"> { let arguments = ( ins ClockType:$clockVal, StrAttr:$name, NameKindAttr:$nameKind, AnnotationArrayAttr:$annotations, - OptionalAttr:$inner_sym); - let results = (outs AnyRegisterType:$result); + OptionalAttr:$inner_sym, + UnitAttr:$forceable); + let results = (outs AnyRegisterType:$result, Optional:$ref); + let skipDefaultBuilders = true; let builders = [ + OpBuilder<(ins "::mlir::TypeRange":$resultTypes, "::mlir::ValueRange":$operands, + CArg<"::llvm::ArrayRef<::mlir::NamedAttribute>","{}">:$attributes), [{ + assert(operands.size() == 1u && "mismatched number of parameters"); + odsState.addOperands(operands); + odsState.addAttributes(attributes); + assert(resultTypes.size() >= 1u && "mismatched number of return types"); + odsState.addTypes(resultTypes); + }]>, + OpBuilder<(ins "::mlir::Type":$elementType, + "::mlir::Value":$clockVal, + "::mlir::StringAttr":$name, + "::circt::firrtl::NameKindEnumAttr":$nameKind, + "::mlir::ArrayAttr":$annotations, + "::circt::hw::InnerSymAttr":$innerSym, + "::mlir::UnitAttr":$forceable), [{ + $_state.addOperands(clockVal); + $_state.addAttribute(getNameAttrName($_state.name), name); + $_state.addAttribute(getNameKindAttrName($_state.name), nameKind); + $_state.addAttribute(getAnnotationsAttrName($_state.name), annotations); + if (innerSym) { + $_state.addAttribute(getInnerSymAttrName($_state.name), innerSym); + } + $_state.addTypes(elementType); + if (forceable) { + $_state.addAttribute(getForceableAttrName($_state.name), forceable); + if (auto type = detail::getForceableResultType(true, elementType)) + $_state.addTypes(type); + } + }]>, + OpBuilder<(ins "::mlir::Type":$elementType, + "::mlir::Value":$clockVal, + "::llvm::StringRef":$name, + "::circt::firrtl::NameKindEnum":$nameKind, + "::mlir::ArrayAttr":$annotations, + "::circt::hw::InnerSymAttr":$innerSym, + "bool":$forceable), [{ + return build($_builder, $_state, elementType, clockVal, + $_builder.getStringAttr(name), + ::circt::firrtl::NameKindEnumAttr::get(odsBuilder.getContext(), nameKind), + annotations, innerSym, + forceable ? $_builder.getUnitAttr() : nullptr); + }]>, OpBuilder<(ins "::mlir::Type":$elementType, "::mlir::Value":$clockVal, CArg<"StringRef", "{}">:$name, CArg<"NameKindEnum", "NameKindEnum::DroppableName">:$nameKind, CArg<"ArrayRef","{}">:$annotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym), [{ - return build($_builder, $_state, elementType, clockVal, name, - nameKind, $_builder.getArrayAttr(annotations), - inner_sym ? hw::InnerSymAttr::get(inner_sym) : hw::InnerSymAttr()); + CArg<"StringAttr", "StringAttr()">:$innerSym, + CArg<"bool", "false">:$forceable), [{ + return build($_builder, $_state, elementType, + clockVal, $_builder.getStringAttr(name), nameKind, + $_builder.getArrayAttr(annotations), + innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr(), + forceable); }]>, OpBuilder<(ins "::mlir::Type":$elementType, "::mlir::Value":$clockVal, "StringRef":$name, "NameKindEnum":$nameKind, - "::mlir::ArrayAttr":$annotation, "StringAttr":$inner_sym), [{ - return build($_builder, $_state, elementType, clockVal, name, - nameKind, annotation, - inner_sym ? hw::InnerSymAttr::get(inner_sym) : hw::InnerSymAttr()); + "::mlir::ArrayAttr":$annotation, "StringAttr":$innerSym, + CArg<"bool", "false">:$forceable), [{ + return build($_builder, $_state, elementType, + clockVal, $_builder.getStringAttr(name), nameKind, annotation, + innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr(), + forceable); }]> ]; let assemblyFormat = [{ (`sym` $inner_sym^)? `` custom($nameKind) - operands `` custom(attr-dict) `:` type($clockVal) `,` qualified(type($result)) + operands (`forceable` $forceable^)? `` custom(attr-dict) `:` type($clockVal) `,` qualified(type($result)) (`,` qualified(type($ref))^)? + }]; let hasCanonicalizeMethod = true; } -def RegResetOp : ReferableDeclOp<"regreset"> { +def RegResetOp : HardwareDeclOp<"regreset", [Forceable]> { let summary = "Define a new register with a reset"; let description = [{ Declare a new register: @@ -316,45 +389,89 @@ def RegResetOp : ReferableDeclOp<"regreset"> { let arguments = ( ins ClockType:$clockVal, AnyResetType:$resetSignal, - AnyRegisterType:$resetValue, + FIRRTLBaseType:$resetValue, StrAttr:$name, NameKindAttr:$nameKind, AnnotationArrayAttr:$annotations, - OptionalAttr:$inner_sym); - let results = (outs AnyRegisterType:$result); + OptionalAttr:$inner_sym, + UnitAttr:$forceable); + let results = (outs AnyRegisterType:$result, Optional:$ref); + let skipDefaultBuilders = true; let builders = [ + OpBuilder<(ins "::mlir::TypeRange":$resultTypes, "::mlir::ValueRange":$operands, + CArg<"::llvm::ArrayRef<::mlir::NamedAttribute>","{}">:$attributes), [{ + assert(operands.size() == 3u && "mismatched number of parameters"); + odsState.addOperands(operands); + odsState.addAttributes(attributes); + assert(resultTypes.size() >= 1u && "mismatched number of return types"); + odsState.addTypes(resultTypes); + }]>, + OpBuilder<(ins "::mlir::Type":$elementType, + "::mlir::Value":$clockVal, + "::mlir::Value":$resetSignal, "::mlir::Value":$resetValue, + "::mlir::StringAttr":$name, + "::circt::firrtl::NameKindEnumAttr":$nameKind, + "::mlir::ArrayAttr":$annotations, + "::circt::hw::InnerSymAttr":$innerSym, + "::mlir::UnitAttr":$forceable), [{ + $_state.addOperands(clockVal); + odsState.addOperands(resetSignal); + odsState.addOperands(resetValue); + $_state.addAttribute(getNameAttrName($_state.name), name); + $_state.addAttribute(getNameKindAttrName($_state.name), nameKind); + $_state.addAttribute(getAnnotationsAttrName($_state.name), annotations); + if (innerSym) { + $_state.addAttribute(getInnerSymAttrName($_state.name), innerSym); + } + $_state.addTypes(elementType); + if (forceable) { + $_state.addAttribute(getForceableAttrName($_state.name), forceable); + if (auto type = detail::getForceableResultType(true, elementType)) + $_state.addTypes(type); + } + }]>, OpBuilder<(ins "::mlir::Type":$elementType, "::mlir::Value":$clockVal, "::mlir::Value":$resetSignal, "::mlir::Value":$resetValue, CArg<"StringRef", "{}">:$name, CArg<"NameKindEnum", "NameKindEnum::DroppableName">:$nameKind, CArg<"ArrayRef","{}">:$annotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym), [{ - return build($_builder, $_state, elementType, clockVal, resetSignal, - resetValue, name, nameKind, + CArg<"StringAttr", "StringAttr()">:$innerSym, + CArg<"bool", "false">:$forceable), [{ + return build($_builder, $_state, elementType, + clockVal, resetSignal, resetValue, + $_builder.getStringAttr(name), + ::circt::firrtl::NameKindEnumAttr::get(odsBuilder.getContext(), nameKind), $_builder.getArrayAttr(annotations), - inner_sym ? hw::InnerSymAttr::get(inner_sym) : hw::InnerSymAttr()); + innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr(), + forceable ? $_builder.getUnitAttr() : nullptr); }]>, OpBuilder<(ins "::mlir::Type":$elementType, "::mlir::Value":$clockVal, "::mlir::Value":$resetSignal, "::mlir::Value":$resetValue, "StringRef":$name, "NameKindEnum":$nameKind, - "::mlir::ArrayAttr":$annotation, "StringAttr":$inner_sym), [{ - return build($_builder, $_state, elementType, clockVal, resetSignal, - resetValue, name, nameKind, annotation, - inner_sym ? hw::InnerSymAttr::get(inner_sym) : hw::InnerSymAttr()); + "::mlir::ArrayAttr":$annotation, "StringAttr":$innerSym, + CArg<"bool", "false">:$forceable), [{ + return build($_builder, $_state, elementType, + clockVal, resetSignal, resetValue, + $_builder.getStringAttr(name), + ::circt::firrtl::NameKindEnumAttr::get(odsBuilder.getContext(), nameKind), + annotation, + innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr(), + forceable ? $_builder.getUnitAttr() : nullptr); }]> ]; let assemblyFormat = [{ (`sym` $inner_sym^)? `` custom($nameKind) - operands `` custom(attr-dict) - `:` type($clockVal) `,` qualified(type($resetSignal)) `,` qualified(type($resetValue)) `,` qualified(type($result)) + operands (`forceable` $forceable^)? `` custom(attr-dict) + `:` type($clockVal) `,` qualified(type($resetSignal)) `,` qualified(type($resetValue)) `,` qualified(type($result)) (`,` qualified(type($ref))^)? + }]; let hasCanonicalizer = true; let hasVerifier = 1; } -def WireOp : ReferableDeclOp<"wire"> { +def WireOp : HardwareDeclOp<"wire", [Forceable]> { let summary = "Define a new wire"; let description = [{ Declare a new wire: @@ -365,33 +482,153 @@ def WireOp : ReferableDeclOp<"wire"> { let arguments = (ins StrAttr:$name, NameKindAttr:$nameKind, AnnotationArrayAttr:$annotations, - OptionalAttr:$inner_sym); - let results = (outs AnyType:$result); + OptionalAttr:$inner_sym, + UnitAttr:$forceable); // ReferenceKinds + let results = (outs AnyType:$result, Optional:$ref); let hasCanonicalizer = true; + let skipDefaultBuilders = true; let builders = [ + OpBuilder<(ins "::mlir::TypeRange":$resultTypes, "::mlir::ValueRange":$operands, + CArg<"::llvm::ArrayRef<::mlir::NamedAttribute>","{}">:$attributes), [{ + assert(operands.size() == 0u && "mismatched number of parameters"); + odsState.addOperands(operands); + odsState.addAttributes(attributes); + assert(resultTypes.size() >= 1u && "mismatched number of return types"); + odsState.addTypes(resultTypes); + }]>, + OpBuilder<(ins "::mlir::Type":$elementType, + "::mlir::StringAttr":$name, + "::circt::firrtl::NameKindEnumAttr":$nameKind, + "::mlir::ArrayAttr":$annotations, + "::circt::hw::InnerSymAttr":$innerSym, + "::mlir::UnitAttr":$forceable), [{ + $_state.addAttribute(getNameAttrName($_state.name), name); + $_state.addAttribute(getNameKindAttrName($_state.name), nameKind); + $_state.addAttribute(getAnnotationsAttrName($_state.name), annotations); + if (innerSym) { + $_state.addAttribute(getInnerSymAttrName($_state.name), innerSym); + } + $_state.addTypes(elementType); + if (forceable) { + $_state.addAttribute(getForceableAttrName($_state.name), forceable); + if (auto type = detail::getForceableResultType(true, elementType)) + $_state.addTypes(type); + } + }]>, + OpBuilder<(ins "::mlir::Type":$elementType, + "::llvm::StringRef":$name, + "::circt::firrtl::NameKindEnum":$nameKind, + "::mlir::ArrayAttr":$annotations, + "::circt::hw::InnerSymAttr":$innerSym, + "bool":$forceable), [{ + return build($_builder, $_state, elementType, + $_builder.getStringAttr(name), + ::circt::firrtl::NameKindEnumAttr::get(odsBuilder.getContext(), nameKind), + annotations, innerSym, + forceable ? $_builder.getUnitAttr() : nullptr); + }]>, OpBuilder<(ins "::mlir::Type":$elementType, CArg<"StringRef", "{}">:$name, CArg<"NameKindEnum", "NameKindEnum::DroppableName">:$nameKind, CArg<"ArrayRef","{}">:$annotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym), [{ - return build($_builder, $_state, elementType, name, nameKind, + CArg<"StringAttr", "StringAttr()">:$innerSym, + CArg<"bool", "false">:$forceable), [{ + return build($_builder, $_state, elementType, + name, nameKind, $_builder.getArrayAttr(annotations), - inner_sym ? hw::InnerSymAttr::get(inner_sym) : hw::InnerSymAttr()); + innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr(), + forceable); }]>, OpBuilder<(ins "::mlir::Type":$elementType, "StringRef":$name, "NameKindEnum":$nameKind, "::mlir::ArrayAttr":$annotations, - CArg<"StringAttr", "StringAttr()">:$inner_sym), [{ - return build($_builder, $_state, elementType, name, nameKind, annotations, - inner_sym ? hw::InnerSymAttr::get(inner_sym) : hw::InnerSymAttr()); + CArg<"StringAttr", "StringAttr()">:$innerSym, + CArg<"bool", "false">:$forceable), [{ + return build($_builder, $_state, elementType, + name, nameKind, annotations, + innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr(), + forceable); }]> ]; let assemblyFormat = [{ - (`sym` $inner_sym^)? `` custom($nameKind) `` - custom(attr-dict) `:` - qualified(type($result)) + (`sym` $inner_sym^)? `` custom($nameKind) + (`forceable` $forceable^)? `` custom(attr-dict) `:` + qualified(type($result)) (`,` qualified(type($ref))^)? + }]; +} + +//===----------------------------------------------------------------------===// +// Clock Gate Intrinsic +//===----------------------------------------------------------------------===// + +def ClockGateIntrinsicOp : FIRRTLOp<"int.clock_gate", [Pure]> { + let summary = "Safely gates a clock with an enable signal"; + let description = [{ + The `int.clock_gate` enables and disables a clock safely, without glitches, + based on a boolean enable value. If the enable input is 1, the output clock + produced by the clock gate is identical to the input clock. If the enable + input is 0, the output clock is a constant zero. + + The enable input is sampled at the rising edge of the input clock; any + changes on the enable before or after that edge are ignored and do not + affect the output clock. + }]; + + let arguments = (ins NonConstClockType:$input, + NonConstUInt1Type:$enable, + Optional:$test_enable); + let results = (outs NonConstClockType:$output); + let hasFolder = 1; + let hasCanonicalizeMethod = 1; + let assemblyFormat = [{ + $input `,` $enable (`,` $test_enable^)? attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// Property Ops +//===----------------------------------------------------------------------===// + +def ObjectOp : FIRRTLOp<"object", [ + ParentOneOf<["firrtl::FModuleOp, firrtl::ClassOp"]>, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods +]> { + let summary = "Instantiate a class to produce an object"; + let description = [{ + This represents an instance of a class. The results is the instantiated + object. + + Examples: + ```mlir + %0 = firrtl.object @Foo(in io: !firrtl.uint) + ``` + }]; + let arguments = (ins StrAttr:$name); + let results = (outs ClassType:$result); + let hasVerifier = 1; + let builders = [ + OpBuilder<(ins "ClassLike":$klass, "StringRef":$name)> + ]; + let extraClassDeclaration = [{ + /// Lookup the module or extmodule for the symbol. This returns null on + /// invalid IR. + ClassLike getReferencedClass(SymbolTable& symbolTable); + + StringAttr getClassNameAttr(); + StringRef getClassName(); + }]; + let assemblyFormat = [{ + `` custom(attr-dict) custom(type($result)) }]; } diff --git a/include/circt/Dialect/FIRRTL/FIRRTLDialect.h b/include/circt/Dialect/FIRRTL/FIRRTLDialect.h index 385795948725..8bae5802d681 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLDialect.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLDialect.h @@ -14,6 +14,7 @@ #define CIRCT_DIALECT_FIRRTL_DIALECT_H #include "circt/Dialect/HW/HWDialect.h" +#include "circt/Dialect/OM/OMDialect.h" #include "circt/Support/LLVM.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/Dialect.h" diff --git a/include/circt/Dialect/FIRRTL/FIRRTLDialect.td b/include/circt/Dialect/FIRRTL/FIRRTLDialect.td index 650d416baf1f..6ba70cce99ac 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLDialect.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLDialect.td @@ -34,8 +34,12 @@ def FIRRTLDialect : Dialect { let useDefaultTypePrinterParser = 0; let useDefaultAttributePrinterParser = 1; + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + let dependentDialects = [ - "circt::hw::HWDialect" + "circt::hw::HWDialect", + "circt::om::OMDialect" ]; let extraClassDeclaration = [{ diff --git a/include/circt/Dialect/FIRRTL/FIRRTLEnums.td b/include/circt/Dialect/FIRRTL/FIRRTLEnums.td index 6ce66c3ef273..0db76979fb04 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLEnums.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLEnums.td @@ -16,6 +16,8 @@ include "FIRRTLDialect.td" include "mlir/IR/EnumAttr.td" +let cppNamespace = "::circt::firrtl" in { + //===----------------------------------------------------------------------===// // Name Kinds //===----------------------------------------------------------------------===// @@ -28,11 +30,34 @@ def InterestingName: I32EnumAttrCase<"InterestingName", 1, "interesting_name">; def NameKindEnumImpl: I32EnumAttr<"NameKindEnum", "name kind", [DroppableName, InterestingName]> { let genSpecializedAttr = 0; - let cppNamespace = "::circt::firrtl"; } def NameKindAttr: EnumAttr; +//===----------------------------------------------------------------------===// +// Module Instantiation Conventions +//===----------------------------------------------------------------------===// + +def Convention : I32EnumAttr<"Convention", "lowering convention", [ + I32EnumAttrCase<"Internal", 0, "internal">, + I32EnumAttrCase<"Scalarized", 1, "scalarized">]> { + let genSpecializedAttr = 0; +} + +def ConventionAttr : EnumAttr; + +//===----------------------------------------------------------------------===// +// Group Lowering Conventions +//===----------------------------------------------------------------------===// + +def GroupConvention : I32EnumAttr<"GroupConvention", "group convention", [ + I32EnumAttrCase<"Bind", 0, "bind">]> { + + let genSpecializedAttr = 0; +} + +def GroupConventionAttr : EnumAttr; + //===----------------------------------------------------------------------===// // Read Under Write Behaviour //===----------------------------------------------------------------------===// @@ -71,4 +96,18 @@ def AtEdge : I32EnumAttrCase<"AtEdge", 2, "edge">; def EventControlAttr : I32EnumAttr<"EventControl", "edge control trigger", [AtPosEdge, AtNegEdge, AtEdge]> {} +//===----------------------------------------------------------------------===// +// OM Targets +//===----------------------------------------------------------------------===// + +def DontTouch : I32EnumAttrCase<"DontTouch", 0, "dont_touch">; +def Instance : I32EnumAttrCase<"Instance", 1, "instance">; +def MemberInstance: I32EnumAttrCase<"MemberInstance", 2, "member_instance">; +def MemberReference: I32EnumAttrCase<"MemberReference", 3, "member_reference">; +def Reference: I32EnumAttrCase<"Reference", 4, "reference">; +def TargetKind : I32EnumAttr<"TargetKind", "object model target kind", + [DontTouch, Instance, MemberInstance, MemberReference, Reference]> {} + +} + #endif // CIRCT_DIALECT_FIRRTL_FIRRTLENUMS_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td index a482a5fa8ae8..7066dda5be82 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td @@ -15,6 +15,9 @@ include "FIRRTLDialect.td" include "FIRRTLTypes.td" +include "FIRRTLEnums.td" +include "circt/Dialect/HW/HWAttributesNaming.td" +include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Dialect/HW/HWTypes.td" include "circt/Types.td" include "mlir/Interfaces/InferTypeOpInterface.td" @@ -24,11 +27,6 @@ def SameOperandsIntTypeKind : NativeOpTrait<"SameOperandsIntTypeKind"> { let cppNamespace = "::circt::firrtl"; } -class IndexConstraint - : TypesMatchWith<"type should be element of vector type", - value, resultValue, - "firrtl::getVectorElementType($_self)">; - // A common base class for operations that implement type inference and parsed // argument validation. class FIRRTLExprOp traits = []> : @@ -71,10 +69,11 @@ class FIRRTLExprOp traits = []> : std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results) { - return impl::inferReturnTypes(context, loc, operands, attrs, regions, - results, &inferReturnType); + return impl::inferReturnTypes(context, loc, operands, attrs, properties, + regions, results, &inferReturnType); } /// Check that the parser has consumed the correct number of operands and @@ -86,6 +85,18 @@ class FIRRTLExprOp traits = []> : return {}; return inferReturnType(operands, attrs, loc); } + + // Returns when two result types are compatible for this op; method used by + // InferTypeOpInterface. + static bool isCompatibleReturnTypes(TypeRange l, TypeRange r) { + if (l.size () != r.size()) + return false; + for (auto [l, r] : llvm::zip(l, r)) { + if (!areAnonymousTypesEquivalent(l, r)) + return false; + } + return true; + } }]; } @@ -195,6 +206,43 @@ def VectorCreateOp : FIRRTLOp<"vectorcreate"> { let hasFolder = 1; } +def FEnumCreateOp : FIRRTLOp<"enumcreate"> { + let summary = "Produce a enum value"; + let description = [{ + Create an enum from tag and value. + ```mlir + %result = firrtl.enumcreate field-name(%input) : !firrtl.enum, Some: uint<8>> + + ``` + }]; + + let arguments = (ins I32Attr:$fieldIndex, FIRRTLBaseType:$input); + let results = (outs FEnumType:$result); + + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + + let builders = [ + OpBuilder<(ins "FEnumType":$resultType, "StringRef":$fieldName, "Value":$input), [{ + auto fieldIndex = resultType.getElementIndex(fieldName); + assert(fieldIndex.has_value() && "invalid type constructor"); + return build($_builder, $_state, resultType, (uint32_t)*fieldIndex, input); + }]> + ]; + + let extraClassDeclaration = [{ + /// Return the name attribute of the accessed field. + StringAttr getFieldNameAttr() { + return getResult().getType().get().getElementNameAttr(getFieldIndex()); + } + + /// Return the name of the accessed field. + StringRef getFieldName() { + return getFieldNameAttr().getValue(); + } + }]; +} + def InvalidValueOp : FIRRTLOp<"invalidvalue", [MemoryEffects<[MemAlloc]>, HasCustomSSAName]> { @@ -217,26 +265,24 @@ def InvalidValueOp : FIRRTLOp<"invalidvalue", let assemblyFormat = "attr-dict `:` qualified(type($result))"; } -def SubfieldOp : FIRRTLExprOp<"subfield"> { +class BaseSubfieldOp : FIRRTLExprOp { let summary = "Extract a subfield of another value"; let description = [{ The subfield expression refers to a subelement of an expression with a bundle type. ``` - %result = firrtl.subfield %input[field-name] : !input-type + %result = firrtl.}] # name # [{ %input[field-name] : !input-type ``` }]; - let arguments = (ins BundleType:$input, I32Attr:$fieldIndex); - let results = (outs FIRRTLBaseType:$result); - let hasFolder = 1; - let hasCanonicalizer = 1; + let arguments = (ins btype:$input, I32Attr:$fieldIndex); + let results = (outs rtype:$result); let hasVerifier = 1; let hasCustomAssemblyFormat = 1; let builders = [ OpBuilder<(ins "Value":$input, "StringRef":$fieldName), [{ - auto bundleType = input.getType().cast(); + auto bundleType = firrtl::type_cast<}] # btype # [{>(input.getType()); auto fieldIndex = bundleType.getElementIndex(fieldName); assert(fieldIndex.has_value() && "subfield operation to unknown field"); return build($_builder, $_state, input, *fieldIndex); @@ -244,26 +290,31 @@ def SubfieldOp : FIRRTLExprOp<"subfield"> { ]; let firrtlExtraClassDeclaration = [{ + using InputType = }] # btype # [{; + /// Return true if the specified field is flipped. bool isFieldFlipped(); /// Return a `FieldRef` to the accessed field. FieldRef getAccessedField() { - return FieldRef(getInput(), getInput().getType().cast() + return FieldRef(getInput(), firrtl::type_cast(getInput().getType()) .getFieldID(getFieldIndex())); } /// Return the name of the accessed field. StringRef getFieldName() { - return getInput().getType().cast() - .getElementName(getFieldIndex()); + return firrtl::type_cast(getInput().getType()).getElementName(getFieldIndex()); } }]; } -def SubindexOp : FIRRTLExprOp<"subindex", [ - IndexConstraint<"input", "result"> - ]> { +def SubfieldOp : BaseSubfieldOp<"subfield", BundleType, FIRRTLBaseType> { + let hasFolder = 1; + let hasCanonicalizer = 1; +} +def OpenSubfieldOp : BaseSubfieldOp<"opensubfield", OpenBundleType, FIRRTLType>; + +def SubindexOp : FIRRTLExprOp<"subindex"> { let summary = "Extract an element of a vector value"; let description = [{ The subindex expression statically refers, by index, to a subelement @@ -280,6 +331,33 @@ def SubindexOp : FIRRTLExprOp<"subindex", [ let hasFolder = 1; let hasCanonicalizer = 1; + let assemblyFormat = + "$input `[` $index `]` attr-dict `:` qualified(type($input))"; + + let firrtlExtraClassDeclaration = [{ + /// Return a `FieldRef` to the accessed field. + FieldRef getAccessedField() { + return FieldRef(getInput(), getInput().getType().get().getFieldID(getIndex())); + } + using InputType = FVectorType; + }]; +} + +def OpenSubindexOp : FIRRTLExprOp<"opensubindex"> { + let summary = "Extract an element of a vector value"; + let description = [{ + The subindex expression statically refers, by index, to a subelement + of an expression with a vector type. The index must be a non-negative + integer and cannot be equal to or exceed the length of the vector it + indexes. + ``` + %result = firrtl.opensubindex %input[index] : t1 + ``` + }]; + + let arguments = (ins OpenVectorType:$input, I32Attr:$index); + let results = (outs FIRRTLType:$result); + let assemblyFormat = "$input `[` $index `]` attr-dict `:` qualified(type($input))"; @@ -288,12 +366,12 @@ def SubindexOp : FIRRTLExprOp<"subindex", [ FieldRef getAccessedField() { return FieldRef(getInput(), getInput().getType().getFieldID(getIndex())); } + + using InputType = OpenVectorType; }]; } -def SubaccessOp : FIRRTLExprOp<"subaccess", [ - IndexConstraint<"input", "result"> - ]> { +def SubaccessOp : FIRRTLExprOp<"subaccess"> { let summary = "Extract a dynamic element of a vector value"; let description = [{ The subaccess expression dynamically refers to a subelement of a @@ -313,6 +391,110 @@ def SubaccessOp : FIRRTLExprOp<"subaccess", [ let hasCanonicalizer = true; } +def IsTagOp : FIRRTLExprOp<"istag"> { + let summary = "Test the active variant of an enumeration"; + let description = [{ + This operation is used to test the active variant of an enumeration. The + tag tested for must be one of the possible variants of the input type. If + the tag is the currently active variant the result will be 1, otherwise the + result will be 0. + + Example: + ```mlir + %0 = firrtl.istag A %v : !firrtl.enum, B: UInt<0>> + ``` + }]; + let arguments = (ins FEnumType:$input, I32Attr:$fieldIndex); + let results = (outs UInt1Type:$result); + let hasVerifier = 1; + let hasCustomAssemblyFormat = 1; + let builders = [ + OpBuilder<(ins "Value":$input, "StringRef":$fieldName), [{ + auto enumType = firrtl::type_cast(input.getType()); + auto fieldIndex = enumType.getElementIndex(fieldName); + assert(fieldIndex.has_value() && "subtag operation to unknown field"); + return build($_builder, $_state, input, *fieldIndex); + }]> + ]; + + let firrtlExtraClassDeclaration = [{ + /// Return a `FieldRef` to the accessed field. + FieldRef getAccessedField() { + return FieldRef(getInput(), firrtl::type_cast(getInput().getType()) + .getFieldID(getFieldIndex())); + } + + /// Return the name of the accessed field. + StringAttr getFieldNameAttr() { + return firrtl::type_cast(getInput().getType()) + .getElementNameAttr(getFieldIndex()); + } + + /// Return the name of the accessed field. + StringRef getFieldName() { + return getFieldNameAttr().getValue(); + } + }]; +} + +def SubtagOp : FIRRTLExprOp<"subtag"> { + let summary = "Extract an element of a enum value"; + let description = [{ + The subtag expression refers to a subelement of a + enum-typed expression. + ``` + %result = firrtl.subtag %input[field-name] : !input-type + ``` + }]; + + let arguments = (ins FEnumType:$input, I32Attr:$fieldIndex); + let results = (outs FIRRTLBaseType:$result); + let hasVerifier = 1; + let hasCustomAssemblyFormat = 1; + + let builders = [ + OpBuilder<(ins "Value":$input, "StringRef":$fieldName), [{ + auto enumType = firrtl::type_cast(input.getType()); + auto fieldIndex = enumType.getElementIndex(fieldName); + assert(fieldIndex.has_value() && "subtag operation to unknown field"); + return build($_builder, $_state, input, *fieldIndex); + }]> + ]; + + let firrtlExtraClassDeclaration = [{ + /// Return a `FieldRef` to the accessed field. + FieldRef getAccessedField() { + return FieldRef(getInput(), getInput().getType().get() + .getFieldID(getFieldIndex())); + } + + /// Return the name of the accessed field. + StringAttr getFieldNameAttr() { + return getInput().getType().get().getElementNameAttr(getFieldIndex()); + } + + /// Return the name of the accessed field. + StringRef getFieldName() { + return getFieldNameAttr().getValue(); + } + }]; +} + +def TagExtractOp : FIRRTLExprOp<"tagextract", [InferTypeOpInterface]> { + let summary = "Extract the tag from a value"; + let description = [{ + The tagextract expression returns the binary value of the current tag of an + enum value. + ``` + %result = firrtl.tagextract %input : !input-type + ``` + }]; + + let arguments = (ins FEnumType:$input); + let results = (outs UIntType:$result); + let assemblyFormat = "$input attr-dict `:` qualified(type($input))"; +} + def MultibitMuxOp : FIRRTLExprOp<"multibit_mux"> { let summary = "Multibit multiplexer"; let description = [{ @@ -372,8 +554,8 @@ class BinaryPrimOp attrs, std::optional loc) { - return $_infer(operands[0].getType().cast(), - operands[1].getType().cast(), + return $_infer(firrtl::type_cast(operands[0].getType()), + firrtl::type_cast(operands[1].getType()), loc); } }]); @@ -416,6 +598,18 @@ let inferType = "impl::inferBitwiseResult" in { } } +// Element-wise and/or/xor operations whose result and operands are 1d vectors with +// a same type. Apply bitwise operation to each element. +let inferType = "impl::inferElementwiseResult" in +let hasFolder = 0 in { + def ElementwiseAndPrimOp : BinaryPrimOp<"elementwise_and", 1DVecIntType, + 1DVecIntType, 1DVecUIntType, [Commutative]>; + def ElementwiseOrPrimOp : BinaryPrimOp<"elementwise_or", 1DVecIntType, + 1DVecIntType, 1DVecUIntType, [Commutative]>; + def ElementwiseXorPrimOp : BinaryPrimOp<"elementwise_xor", 1DVecUIntType, + 1DVecIntType, 1DVecUIntType, [Commutative]>; +} + // Comparison Operations let inferType = "impl::inferComparisonResult" in { let hasCanonicalizer = true in { @@ -477,7 +671,7 @@ class UnaryPrimOp attrs, std::optional loc) { - return $_infer(operands[0].getType().cast(), loc); + return $_infer(firrtl::type_cast(operands[0].getType()), loc); } }]); @@ -486,37 +680,41 @@ class UnaryPrimOp; -def AsSIntPrimOp : UnaryPrimOp<"asSInt", FIRRTLBaseType, SIntType>; -def AsUIntPrimOp : UnaryPrimOp<"asUInt", FIRRTLBaseType, UIntType>; +let hasCanonicalizer=1 in { + def AsSIntPrimOp : UnaryPrimOp<"asSInt", FIRRTLBaseType, SIntType>; + def AsUIntPrimOp : UnaryPrimOp<"asUInt", FIRRTLBaseType, UIntType>; + def CvtPrimOp : UnaryPrimOp<"cvt", IntType, SIntType>; +} def AsAsyncResetPrimOp : UnaryPrimOp<"asAsyncReset", OneBitCastableType, AsyncResetType>; def AsClockPrimOp : UnaryPrimOp<"asClock", OneBitCastableType, ClockType>; -def CvtPrimOp : UnaryPrimOp<"cvt", IntType, SIntType>; def NegPrimOp : UnaryPrimOp<"neg", IntType, SIntType>; let hasCanonicalizer = true in def NotPrimOp : UnaryPrimOp<"not", IntType, UIntType>; let inferType = "impl::inferReductionResult" in { - def AndRPrimOp : UnaryPrimOp<"andr", IntType, UInt1Type> { - let description = [{ - Horizontally reduce a value to one bit, using the 'and' operation to merge - bits. `andr(x)` is equivalent to `concat(x, 1b1) == ~0`. As such, it - returns 1 for zero-bit-wide operands. - }]; - } - def OrRPrimOp : UnaryPrimOp<"orr", IntType, UInt1Type> { - let description = [{ - Horizontally reduce a value to one bit, using the 'or' operation to merge - bits. `orr(x)` is equivalent to `concat(x, 1b0) != 0`. As such, it - returns 0 for zero-bit-wide operands. - }]; - } - def XorRPrimOp : UnaryPrimOp<"xorr", IntType, UInt1Type> { - let description = [{ - Horizontally reduce a value to one bit, using the 'xor' operation to merge - bits. `xorr(x)` is equivalent to `popcount(concat(x, 1b0)) & 1`. As - such, it returns 0 for zero-bit-wide operands. - }]; + let hasCanonicalizer = 1 in { + def AndRPrimOp : UnaryPrimOp<"andr", IntType, UInt1Type> { + let description = [{ + Horizontally reduce a value to one bit, using the 'and' operation to merge + bits. `andr(x)` is equivalent to `concat(x, 1b1) == ~0`. As such, it + returns 1 for zero-bit-wide operands. + }]; + } + def OrRPrimOp : UnaryPrimOp<"orr", IntType, UInt1Type> { + let description = [{ + Horizontally reduce a value to one bit, using the 'or' operation to merge + bits. `orr(x)` is equivalent to `concat(x, 1b0) != 0`. As such, it + returns 0 for zero-bit-wide operands. + }]; + } + def XorRPrimOp : UnaryPrimOp<"xorr", IntType, UInt1Type> { + let description = [{ + Horizontally reduce a value to one bit, using the 'xor' operation to merge + bits. `xorr(x)` is equivalent to `popcount(concat(x, 1b0)) & 1`. As + such, it returns 0 for zero-bit-wide operands. + }]; + } } } @@ -553,7 +751,8 @@ def HeadPrimOp : PrimOp<"head"> { } def MuxPrimOp : PrimOp<"mux"> { - let arguments = (ins UInt1Type:$sel, PassiveType:$high, PassiveType:$low); + let arguments = (ins UInt1OrUnsizedType:$sel, PassiveType:$high, + PassiveType:$low); let results = (outs PassiveType:$result); let assemblyFormat = @@ -623,11 +822,55 @@ def TailPrimOp : PrimOp<"tail"> { let parseValidator = "impl::validateOneOperandOneConst"; } +//===----------------------------------------------------------------------===// +// Target Specific Intrinsics +//===----------------------------------------------------------------------===// + +def Mux2CellIntrinsicOp : PrimOp<"int.mux2cell"> { + let summary = [{ + an intrinsic lowered into 2-to-1 MUX cell in synthesis tools. + }]; + + let description = [{ + This intrinsic exposes a low-level API to use 2-to-1 MUX cell in backend + synthesis tool. At FIRRTL level, this operation participates + the inference process in the same way as a normal mux operation. + }]; + + let arguments = (ins UInt1OrUnsizedType:$sel, PassiveType:$high, + PassiveType:$low); + let results = (outs PassiveType:$result); + + let assemblyFormat = + "`(` operands `)` attr-dict `:` functional-type(operands, $result)"; +} + +def Mux4CellIntrinsicOp : PrimOp<"int.mux4cell"> { + let summary = [{ + an intrinsic lowered into 4-to-1 MUX cell in synthesis tools. + }]; + + let description = [{ + This intrinsic exposes a low-level API to use 4-to-1 MUX cell in backend + synthesis tool. At FIRRTL level, this operation participates + the inference process as a sugar of mux operation chains. + }]; + + let arguments = (ins UInt2OrUnsizedType:$sel, PassiveType:$v3, + PassiveType:$v2, PassiveType:$v1, + PassiveType:$v0); + let results = (outs PassiveType:$result); + + let assemblyFormat = + "`(` operands `)` attr-dict `:` functional-type(operands, $result)"; +} + //===----------------------------------------------------------------------===// // Verif and SV specific //===----------------------------------------------------------------------===// -def IsXIntrinsicOp : FIRRTLExprOp<"int.isX"> { +def IsXIntrinsicOp : FIRRTLOp<"int.isX", + [HasCustomSSAName, Pure]> { let summary = "Test for 'x"; let description = [{ The `int.isX` expression checks that the operand is not a verilog literal @@ -637,26 +880,43 @@ def IsXIntrinsicOp : FIRRTLExprOp<"int.isX"> { }]; let arguments = (ins FIRRTLBaseType:$arg); - let results = (outs UInt1Type:$result); + let results = (outs NonConstUInt1Type:$result); let hasFolder = 1; let assemblyFormat = "$arg attr-dict `:` type($arg)"; } -def PlusArgsTestIntrinsicOp : FIRRTLExprOp<"int.plusargs.test"> { +def PlusArgsTestIntrinsicOp : FIRRTLOp<"int.plusargs.test", + [HasCustomSSAName, Pure]> { let summary = "SystemVerilog `$test$plusargs` call"; let arguments = (ins StrAttr:$formatString); - let results = (outs UInt1Type:$found); + let results = (outs NonConstUInt1Type:$found); let assemblyFormat = "$formatString attr-dict"; } -def PlusArgsValueIntrinsicOp -: FIRRTLOp<"int.plusargs.value", [HasCustomSSAName, Pure]> { +def PlusArgsValueIntrinsicOp : FIRRTLOp<"int.plusargs.value", + [HasCustomSSAName, Pure]> { let summary = "SystemVerilog `$value$plusargs` call"; let arguments = (ins StrAttr:$formatString); - let results = (outs UInt1Type:$found, AnyType:$result); - let assemblyFormat = "$formatString attr-dict `:` type($found) `,` type($result)"; + let results = (outs NonConstUInt1Type:$found, AnyType:$result); + let assemblyFormat = "$formatString attr-dict `:` type($result)"; +} + +def HasBeenResetIntrinsicOp : FIRRTLOp<"int.has_been_reset", [Pure]> { + let summary = "Check that a proper reset has been seen."; + let description = [{ + The result of `firrtl.int.has_been_reset` reads as 0 immediately after simulation + startup and after each power-cycle in a power-aware simulation. The result + remains 0 before and during reset and only switches to 1 after the reset is + deasserted again. + + See the corresponding `verif.has_been_reset` operation. + }]; + let arguments = (ins NonConstClockType:$clock, AnyResetType:$reset); + let results = (outs NonConstUInt1Type:$result); + let hasFolder = 1; + let assemblyFormat = "$clock `,` $reset attr-dict `:` type($reset)"; } //===----------------------------------------------------------------------===// @@ -730,6 +990,42 @@ def VerbatimWireOp : FIRRTLOp<"verbatim.wire", ]; } +//===----------------------------------------------------------------------===// +// Special inference Operations +//===----------------------------------------------------------------------===// + +// This assumes operands are ground types without explicitly checking +class SameGroundTypeOperandConstness + : PredOpTrait< + "operand constness must match", + CPred<"isConst($" # a # ".getType()) == isConst($" # b # ".getType())">>; + +def UninferredResetCastOp : FIRRTLOp<"resetCast", [HasCustomSSAName, Pure, SameGroundTypeOperandConstness<"input", "result">]> { + let description = [{ + Cast between reset types. This is used to enable strictconnects early in + the pipeline by isolating all uninferred reset connections to a single op. + }]; + let arguments = (ins AnyResetType:$input); + let results = (outs AnyResetType:$result); + let hasFolder = 1; + let assemblyFormat = + "$input attr-dict `:` functional-type($input, $result)"; +} + +def ConstCastOp : FIRRTLOp<"constCast", [HasCustomSSAName, Pure]> { + let description = [{ + Cast from a 'const' to a non-'const' type. + ``` + %result = firrtl.constCast %in : (!firrtl.const.t1) -> !firrtl.t1 + ``` + }]; + let arguments = (ins PassiveType:$input); + let results = (outs PassiveType:$result); + let hasVerifier = 1; + let assemblyFormat = + "$input attr-dict `:` functional-type($input, $result)"; +} + //===----------------------------------------------------------------------===// // Conversions to/from fixed-width signless integer types in standard dialect. //===----------------------------------------------------------------------===// @@ -760,6 +1056,198 @@ def BitCastOp: FIRRTLOp<"bitcast", [Pure]> { let assemblyFormat = "$input attr-dict `:` functional-type($input, $result)"; } +//===----------------------------------------------------------------------===// +// Properties +//===----------------------------------------------------------------------===// + +// The object.subfield op is different from other expressions, because its +// result corresponds to a port, which can have foreign (not firrtl) types. +// For this reason, the object.subfield op is incompatible with the +// FIRRTLExprOp base class. +def ObjectSubfieldOp : FIRRTLOp<"object.subfield", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Extract an element of an object"; + let description = [{ + The object.subfield expression refers to a subelement of an object. + ```mlir + %field = firrtl.object.subfield %object[field] : !firrt.class<@Class(field: !firrtl.string)> + ``` + }]; + let arguments = (ins ClassType:$input, I32Attr:$index); + let results = (outs PropertyType:$result); + let hasCustomAssemblyFormat = true; + let hasVerifier = false; + let extraClassDeclaration = [{ + /// Infer the return type of this operation. + /// Note: In contrast to other ops, this function infers a generic Type, + /// in order to support foreign types in ports. + static Type inferReturnType(ValueRange operands, + ArrayRef attrs, + std::optional loc); + + /// Return a `FieldRef` to the accessed field. + FieldRef getAccessedField() { + auto input = getInput(); + auto index = getIndex(); + auto inputType = input.getType(); + auto fieldID = inputType.getFieldID(index); + return FieldRef(input, fieldID); + } + }]; +} + +def ObjectAnyRefCastOp : FIRRTLOp<"object.anyref_cast", [Pure]> { + let summary = "Cast object reference to anyref."; + let description = [{ + Cast any object reference to AnyRef type. This is needed for passing objects + of a known class to sinks that accept any reference. + + Example + ``` + %0= firrtl.object.anyref_cast %object : !firrtl.class<@Foo()> + ``` + }]; + + let arguments = (ins ClassType:$input); + let results = (outs AnyRefType:$result); + + let assemblyFormat = + "$input attr-dict `:` type($input)"; +} + +def StringConstantOp : FIRRTLOp<"string", [Pure, ConstantLike]> { + let summary = "Produce a constant string value"; + let description = [{ + Produces a constant value of string type. + + Example: + ```mlir + %0 = firrtl.string "hello world" + ``` + }]; + let arguments = (ins StrAttr:$value); + let results = (outs StringType:$result); + let hasFolder = 1; + let assemblyFormat = "$value attr-dict"; +} + +def FIntegerConstantOp : FIRRTLOp<"integer", [Pure, ConstantLike]> { + let summary = "Produce a constant integer value"; + let description = [{ + Produces a constant value of integer type. + + Example: + ```mlir + %0 = firrtl.integer 42 + ``` + }]; + let arguments = (ins APSIntAttr:$value); + let results = (outs FIntegerType:$result); + let hasFolder = 1; + let hasCustomAssemblyFormat = true; +} + +def UnresolvedPathOp : FIRRTLOp<"unresolved_path", [Pure]> { + let summary = "Produce a path value"; + let description = [{ + Produces a value which represents a path to the target in a design. + + Example: + ```mlir + 0 = firrtl.unresolved_path "~Circuit|Module>w" + ``` + }]; + let arguments = (ins StrAttr:$target); + let results = (outs PathType:$result); + let assemblyFormat = "$target attr-dict"; +} + +def PathOp : FIRRTLOp<"path", [Pure]> { + let summary = "Produce a path value"; + let description = [{ + Produces a value which represents a path to the target in a design. + + Example: + ```mlir + hw.hierpath @Path [@Foo::@bar, @Bar] + %wire = firrtl.wire {annotations = [ {class = "circt.tracker", id = distinct[0]<>, circt.nonlocal = @Path} ]} : !firrtl.uint<1> + %0 = firrtl.path reference distinct[0]<> + ``` + }]; + let arguments = (ins TargetKind:$targetKind, + DistinctAttr:$target); + let results = (outs PathType:$result); + let assemblyFormat = "$targetKind $target attr-dict"; +} + +def ListCreateOp : FIRRTLOp<"list.create", [Pure, SameTypeOperands]> { + let summary = "Produce a list value"; + let description = [{ + Produces a value of list type containing the provided elements. + + Example: + ```mlir + %3 = firrtl.list.create %0, %1, %2 : !firrtl.list + ``` + }]; + + let arguments = (ins Variadic:$elements); + let results = (outs ListType:$result); + + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; +} + +def MapCreateOp : FIRRTLOp<"map.create", [Pure, SameVariadicOperandSize]> { + let summary = "Produce a map value"; + let description = [{ + Produces a value of map type containing the provided key/value elements. + + Example: + ```mlir + %4 = firrtl.map.create (%0 -> %1, %2 -> %3) : !firrtl.map + ``` + }]; + + let arguments = (ins Variadic:$keys, Variadic:$values); + let results = (outs MapType:$result); + + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; +} + +def BoolConstantOp : FIRRTLOp<"bool", [Pure, ConstantLike]> { + let summary = "Produce a constant boolean value"; + let description = [{ + Produces a constant value of boolean type. + + Example: + ```mlir + %0 = firrtl.bool true + ``` + }]; + let arguments = (ins BoolAttr:$value); + let results = (outs BoolType:$result); + let hasFolder = 1; + let assemblyFormat = "$value attr-dict"; +} + +def DoubleConstantOp : FIRRTLOp<"double", [Pure, ConstantLike]> { + let summary = "Produce a constant double value"; + let description = [{ + Produces a constant value of double type. + + Example: + ```mlir + %0 = firrtl.double 3.2 + ``` + }]; + let arguments = (ins DoubleAttr:$value); + let results = (outs DoubleType:$result); + let hasFolder = 1; + let assemblyFormat = "$value attr-dict"; +} + //===----------------------------------------------------------------------===// // RefOperations: Operations on the RefType. // @@ -783,22 +1271,31 @@ def BitCastOp: FIRRTLOp<"bitcast", [Pure]> { //===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===// -// Constraints on RefOps +// RefOps //===----------------------------------------------------------------------===// -class RefTypeConstraint - : TypesMatchWith<"reference base type should match", - ref, base, - "$_self.cast().getType()">; +def RefCastOp : FIRRTLOp<"ref.cast", + [HasCustomSSAName, + Pure, + CompatibleRefTypes<"result","input">]> { + let summary = "Cast between compatible reference types"; + let description = [{ + Losslessly cast between compatible reference types. + Source and destination must be recursively identical or destination + has uninferred variants of the corresponding element in source. + ``` + %result = firrtl.ref.cast %ref : (t1) -> t2 + ``` + }]; -class RefResultTypeConstraint - : TypesMatchWith<"reference base type should match", - base, ref, - "RefType::get($_self.cast())">; + let arguments = (ins RefType:$input); + let results = (outs RefType:$result); -//===----------------------------------------------------------------------===// -// RefOps -//===----------------------------------------------------------------------===// + let hasFolder = 1; + + let assemblyFormat = + "$input attr-dict `:` functional-type($input, $result)"; +} def RefResolveOp: FIRRTLExprOp<"ref.resolve", [RefTypeConstraint<"ref","result">]> { @@ -812,6 +1309,8 @@ def RefResolveOp: FIRRTLExprOp<"ref.resolve", let arguments = (ins RefType:$ref); let results = (outs FIRRTLBaseType:$result); + let hasCanonicalizer = true; + let assemblyFormat = "$ref attr-dict `:` qualified(type($ref))"; } @@ -845,6 +1344,89 @@ def RefSubOp : FIRRTLExprOp<"ref.sub"> { let assemblyFormat = "$input `[` $index `]` attr-dict `:` qualified(type($input))"; + + let firrtlExtraClassDeclaration = [{ + /// Return a `FieldRef` to the accessed field. + FieldRef getAccessedField() { + auto inputBaseType = getInput().getType().getType(); + size_t fieldID = ::circt::hw::FieldIdImpl::getFieldID(inputBaseType, getIndex()); + return FieldRef(getInput(), fieldID); + } + }]; +} + +def RWProbeOp : FIRRTLOp<"ref.rwprobe", + [DeclareOpInterfaceMethods, + HasCustomSSAName, + Pure + ]> { + let summary = "FIRRTL RWProbe"; + let description = [{ + Create a RWProbe for the target. + Target must be local. + ``` + %result = firrtl.ref.rwprobe @mod::@sym : firrtl.rwprobe + ``` + }]; + + let arguments = (ins InnerRefAttr:$target); + let results = (outs RWProbeConcreteReset:$result); + let assemblyFormat = "$target attr-dict `:` type($result)"; +} + +//===----------------------------------------------------------------------===// +// LTL Dialect Sequence and Property Intrinsics +//===----------------------------------------------------------------------===// + +class LTLIntrinsicOp traits = []> : + FIRRTLOp<"int.ltl." # mnemonic, traits # [Pure]> { + let summary = "FIRRTL variant of `ltl." # mnemonic # "`"; + let description = "See `ltl." # mnemonic # "` op in the LTL dialect."; + let assemblyFormat = [{ + operands attr-dict `:` functional-type(operands, results) + }]; + let results = (outs UInt1Type:$result); +} + +class UnaryLTLIntrinsicOp traits = []> : + LTLIntrinsicOp { + let arguments = (ins UInt1Type:$input); +} + +class BinaryLTLIntrinsicOp traits = []> : + LTLIntrinsicOp { + let arguments = (ins UInt1Type:$lhs, UInt1Type:$rhs); +} + + +// Generic +def LTLAndIntrinsicOp : BinaryLTLIntrinsicOp<"and", [Commutative]>; +def LTLOrIntrinsicOp : BinaryLTLIntrinsicOp<"or", [Commutative]>; + +// Sequences +def LTLDelayIntrinsicOp : LTLIntrinsicOp<"delay"> { + let arguments = (ins UInt1Type:$input, + I64Attr:$delay, + OptionalAttr:$length); + let assemblyFormat = [{ + $input `,` $delay (`,` $length^)? attr-dict `:` + functional-type(operands, results) + }]; +} +def LTLConcatIntrinsicOp : BinaryLTLIntrinsicOp<"concat">; + +// Properties +def LTLNotIntrinsicOp : UnaryLTLIntrinsicOp<"not">; +def LTLImplicationIntrinsicOp : BinaryLTLIntrinsicOp<"implication">; +def LTLEventuallyIntrinsicOp : UnaryLTLIntrinsicOp<"eventually">; + +// Clocking +def LTLClockIntrinsicOp : LTLIntrinsicOp<"clock"> { + let arguments = (ins UInt1Type:$input, ClockType:$clock); + let assemblyFormat = [{ + $input `,` $clock attr-dict `:` functional-type(operands, results) + }]; } +def LTLDisableIntrinsicOp : BinaryLTLIntrinsicOp<"disable">; #endif // CIRCT_DIALECT_FIRRTL_FIRRTLEXPRESSIONS_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h b/include/circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h index 02f83315c3f7..3f662ce3609d 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h @@ -14,7 +14,7 @@ #define CIRCT_DIALECT_FIRRTL_FIRRTLINSTANCEGRAPH_H #include "circt/Dialect/FIRRTL/FIRRTLOps.h" -#include "circt/Dialect/HW/InstanceGraphBase.h" +#include "circt/Support/InstanceGraph.h" #include "circt/Support/LLVM.h" #include "llvm/ADT/GraphTraits.h" #include "llvm/ADT/STLExtras.h" @@ -22,9 +22,9 @@ namespace circt { namespace firrtl { -using InstanceRecord = hw::InstanceRecord; -using InstanceGraphNode = hw::InstanceGraphNode; -using InstancePathCache = hw::InstancePathCache; +using InstanceRecord = igraph::InstanceRecord; +using InstanceGraphNode = igraph::InstanceGraphNode; +using InstancePathCache = igraph::InstancePathCache; /// This graph tracks modules and where they are instantiated. This is intended /// to be used as a cached analysis on FIRRTL circuits. This class can be used @@ -32,16 +32,16 @@ using InstancePathCache = hw::InstancePathCache; /// /// To use this class, retrieve a cached copy from the analysis manager: /// auto &instanceGraph = getAnalysis(getOperation()); -class InstanceGraph : public hw::InstanceGraphBase { +class InstanceGraph : public igraph::InstanceGraph { public: /// Create a new module graph of a circuit. This must be called on a FIRRTL /// CircuitOp or MLIR ModuleOp. explicit InstanceGraph(Operation *operation); /// Get the node corresponding to the top-level module of a circuit. - InstanceGraphNode *getTopLevelNode() override { return topLevelNode; } + igraph::InstanceGraphNode *getTopLevelNode() override { return topLevelNode; } - /// Get the module corresponding to the top-lebel module of a circuit. + /// Get the module corresponding to the top-level module of a circuit. FModuleLike getTopLevelModule() { return cast(*getTopLevelNode()->getModule()); } @@ -57,12 +57,12 @@ bool allUnder(ArrayRef nodes, InstanceGraphNode *top); template <> struct llvm::GraphTraits - : public llvm::GraphTraits {}; + : public llvm::GraphTraits {}; template <> struct llvm::DOTGraphTraits - : public llvm::DOTGraphTraits { - using llvm::DOTGraphTraits::DOTGraphTraits; + : public llvm::DOTGraphTraits { + using llvm::DOTGraphTraits::DOTGraphTraits; }; #endif // CIRCT_DIALECT_FIRRTL_FIRRTLINSTANCEGRAPH_H diff --git a/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.h b/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.h index 15712d094335..7d9a19868c25 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.h @@ -25,10 +25,17 @@ #include "mlir/IR/SymbolTable.h" #include "llvm/ADT/TypeSwitch.h" +namespace mlir { +class PatternRewriter; +} // end namespace mlir + namespace circt { namespace firrtl { class FIRRTLType; +class Forceable; +class ClassLike; +class ClassType; /// This holds the name and type that describes the module's ports. struct PortInfo { @@ -43,40 +50,16 @@ struct PortInfo { /// Return true if this is a simple output-only port. If you want the /// direction of the port, use the \p direction parameter. - bool isOutput() { - if (direction != Direction::Out) - return false; - if (type.isa()) - return !isInOut(); - return true; - } + bool isOutput() const { return direction == Direction::Out && !isInOut(); } /// Return true if this is a simple input-only port. If you want the /// direction of the port, use the \p direction parameter. - bool isInput() { - if (direction != Direction::In) - return false; - if (type.isa()) - return !isInOut(); - return true; - } + bool isInput() const { return direction == Direction::In && !isInOut(); } /// Return true if this is an inout port. This will be true if the port /// contains either bi-directional signals or analog types. - bool isInOut() { - auto flags = TypeSwitch(type) - .Case([](auto base) { - return base.getRecursiveTypeProperties(); - }) - .Case([](auto ref) { - return ref.getType().getRecursiveTypeProperties(); - }) - .Default([](auto) { - llvm_unreachable("unsupported type"); - return RecursiveTypeProperties{}; - }); - return !flags.isPassive || flags.containsAnalog; - } + /// Non-HW types (e.g., ref types) are never considered InOut. + bool isInOut() const { return isTypeInOut(type); } /// Default constructors PortInfo(StringAttr name, Type type, Direction dir, StringAttr symName = {}, @@ -96,9 +79,43 @@ struct PortInfo { annotations(annos) {} }; +enum class ConnectBehaviorKind { + /// Classic FIRRTL connections: last connect 'wins' across paths; + /// conditionally applied under 'when'. + LastConnect, + /// Exclusive connection to the destination, unconditional. + StaticSingleConnect, +}; + /// Verification hook for verifying module like operations. LogicalResult verifyModuleLikeOpInterface(FModuleLike module); +namespace detail { +/// Return null or forceable reference result type. +RefType getForceableResultType(bool forceable, Type type); +/// Verify a Forceable op. +LogicalResult verifyForceableOp(Forceable op); +/// Replace a Forceable op with equivalent, changing whether forceable. +/// No-op if already has specified forceability. +Forceable +replaceWithNewForceability(Forceable op, bool forceable, + ::mlir::PatternRewriter *rewriter = nullptr); +} // end namespace detail + +//===----------------------------------------------------------------------===// +// ClassLike Helpers +//===----------------------------------------------------------------------===// + +namespace detail { +ClassType getInstanceTypeForClassLike(ClassLike classOp); + +/// Assuming that the classOp is the source of truth, verify that the type +/// accurately matches the signature of the class. +LogicalResult +verifyTypeAgainstClassLike(ClassLike classOp, ClassType type, + function_ref emitError); +} // namespace detail + } // namespace firrtl } // namespace circt diff --git a/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td b/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td index 7935e5acc6d5..fdcfd31e36d5 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td @@ -14,24 +14,13 @@ #define CIRCT_DIALECT_FIRRTL_FIRRTLOPINTERFACES_TD include "mlir/IR/OpBase.td" +include "circt/Dialect/HW/HWOpInterfaces.td" -def FModuleLike : OpInterface<"FModuleLike"> { +def FModuleLike : OpInterface<"FModuleLike", [Symbol, PortList, InstanceGraphModuleOpInterface]> { let cppNamespace = "circt::firrtl"; let description = "Provide common module information."; let methods = [ - //===------------------------------------------------------------------===// - // Module Name - //===------------------------------------------------------------------===// - - InterfaceMethod<"Get the module name", - "StringRef", "moduleName", (ins), - /*methodBody=*/[{ return $_op.getName(); }]>, - - InterfaceMethod<"Get the module name", - "StringAttr", "moduleNameAttr", (ins), - /*methodBody=*/[{ return $_op.getNameAttr(); }]>, - //===------------------------------------------------------------------===// // Parameters //===------------------------------------------------------------------===// @@ -40,6 +29,16 @@ def FModuleLike : OpInterface<"FModuleLike"> { "ArrayAttr", "getParameters", (ins), /*methodBody=*/[{ return $_op.getParameters(); }]>, + //===------------------------------------------------------------------===// + // Instantiation Convention + //===------------------------------------------------------------------===// + + InterfaceMethod<"Get the module's instantiation convention", + "ConventionAttr", "getConventionAttr">, + + InterfaceMethod<"Get the module's instantiation convention", + "Convention", "getConvention">, + //===------------------------------------------------------------------===// // Port Directions //===------------------------------------------------------------------===// @@ -186,6 +185,15 @@ def FModuleLike : OpInterface<"FModuleLike"> { !syms[portIndex].template cast().empty()); }]>, + InterfaceMethod<"Get the port symbol attribute", + "circt::hw::InnerSymAttr", "getPortSymbolAttr", (ins "size_t":$portIndex), [{}], [{ + auto syms = $_op.getPortSymbols(); + if (syms.empty() || + syms[portIndex].template cast().empty()) + return hw::InnerSymAttr(); + return syms[portIndex].template cast(); + }]>, + // Setters InterfaceMethod<"Set the symbols of all ports and their fields", "void", "setPortSymbols", (ins "ArrayRef":$symbols), [{}], [{ @@ -341,16 +349,39 @@ def FModuleLike : OpInterface<"FModuleLike"> { static_assert( ConcreteOp::template hasTrait<::mlir::SymbolOpInterface::Trait>(), "expected operation to be a symbol"); - static_assert( - ConcreteOp::template hasTrait<::circt::hw::HWModuleLike::Trait>(), - "expected operation to be also be a hardware module"); static_assert( ConcreteOp::template hasTrait(), "expected operation to be an inner symbol table"); - return verifyModuleLikeOpInterface(op); + return verifyModuleLikeOpInterface(cast<::circt::firrtl::FModuleLike>(op)); }]; } +def ClassLike : OpInterface<"ClassLike", [FModuleLike]> { + let cppNamespace = "circt::firrtl"; + let description = "Provide common class information."; + let methods = [ + InterfaceMethod< + "Get the type for instances of this class", + "ClassType", "getInstanceType", (ins), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ + return detail::getInstanceTypeForClassLike($_op); + }] + >, + InterfaceMethod< + "Verify that the given type agrees with this class", + "LogicalResult", "verifyType", (ins + "::circt::firrtl::ClassType":$type, + "::llvm::function_ref<::mlir::InFlightDiagnostic()>":$emitError + ), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ + return detail::verifyTypeAgainstClassLike($_op, type, emitError); + }] + > + ]; +} + def FConnectLike : OpInterface<"FConnectLike"> { let cppNamespace = "circt::firrtl"; let description = "Provide common connection information."; @@ -360,6 +391,15 @@ def FConnectLike : OpInterface<"FConnectLike"> { "Value", "getDest", (ins)>, InterfaceMethod<"Return a source of connection.", "Value", "getSrc", (ins)>, + StaticInterfaceMethod<"Return connection behavior kind.", + "ConnectBehaviorKind", "getConnectBehaviorKind", (ins), [{}], + /*defaultImplementation=*/[{ return ConnectBehaviorKind::LastConnect; }]>, + StaticInterfaceMethod<"Returns true if ConnectBehavior is StaticSingleConnect.", + "bool", "hasStaticSingleConnectBehavior", (ins), + /*methodBody=*/[{ return getConnectBehaviorKind() == ConnectBehaviorKind::StaticSingleConnect; }]>, + StaticInterfaceMethod<"Returns true if ConnectBehavior is LastConnect.", + "bool", "hasLastConnectBehavior", (ins), + /*methodBody=*/[{ return getConnectBehaviorKind() == ConnectBehaviorKind::LastConnect; }]> ]; } @@ -375,11 +415,12 @@ def FNamableOp : OpInterface<"FNamableOp"> { getAttrOfType("nameKind").getValue() == NameKindEnum::DroppableName; }]>, - InterfaceMethod<"Make the name droppable.", + InterfaceMethod<"Completely drop a name.", "void", "dropName", (ins), [{}], /*defaultImplementation=*/[{ this->getOperation()->setAttr("nameKind", NameKindEnumAttr::get(this->getOperation()->getContext(), NameKindEnum::DroppableName)); + this->getOperation()->setAttr("name", StringAttr::get(this->getOperation()->getContext(), "")); }]>, InterfaceMethod<"Set a namekind.", "void", "setNameKindAttr", (ins "firrtl::NameKindEnumAttr":$nameKind), @@ -400,4 +441,42 @@ def FNamableOp : OpInterface<"FNamableOp"> { ]; } +def Forceable : OpInterface<"Forceable"> { + let cppNamespace = "circt::firrtl"; + let description = [{ + Interaction with declarations of forceable hardware components, + and managing references to them. + }]; + + let methods = [ + InterfaceMethod<"Return true if the operation is forceable.", + "bool", "isForceable", (ins), [{}], /*defaultImplementation=*/[{ + return $_op.getForceable(); + }]>, + InterfaceMethod<"Return data value that will be targeted.", + "mlir::TypedValue", "getData", (ins), [{}], /*defaultImplementation=*/[{ + return llvm::cast>($_op.getDataRaw()); + }]>, + InterfaceMethod<"Return raw data value that will be targeted.", + "Value", "getDataRaw", (ins), [{}], /*defaultImplementation=*/[{ + return $_op.getResult(); + }]>, + InterfaceMethod<"Return data type that will be referenced.", + "FIRRTLBaseType", "getDataType", (ins), [{}], /*defaultImplementation=*/[{ + return firrtl::type_cast($_op.getDataRaw().getType()); + }]>, + InterfaceMethod<"Return reference result, or null if not active.", + "mlir::TypedValue", "getDataRef", (ins), [{}], /*defaultImplementation=*/[{ + return llvm::cast_or_null>($_op.getRef()); + }]>, + ]; + let extraClassDeclaration = [{ + /// Attribute name for 'forceable' tracking. + static StringRef getForceableAttrName() { return "forceable"; } + }]; + let verify = [{ + return detail::verifyForceableOp(cast<::circt::firrtl::Forceable>($_op)); + }]; +} + #endif // CIRCT_DIALECT_FIRRTL_FIRRTLOPINTERFACES_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLOps.h b/include/circt/Dialect/FIRRTL/FIRRTLOps.h index c9bf9a4b64f5..010a317a5209 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLOps.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLOps.h @@ -15,16 +15,16 @@ #include "circt/Dialect/FIRRTL/FIRRTLDialect.h" #include "circt/Dialect/FIRRTL/FIRRTLOpInterfaces.h" -#include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOpInterfaces.h" #include "circt/Dialect/HW/HWTypes.h" #include "circt/Dialect/HW/InnerSymbolTable.h" +#include "circt/Dialect/Seq/SeqAttributes.h" #include "circt/Support/FieldRef.h" #include "mlir/IR/Builders.h" -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/RegionKindInterface.h" #include "mlir/IR/SymbolTable.h" +#include "mlir/Interfaces/FunctionInterfaces.h" #include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" @@ -57,11 +57,19 @@ bool isConstant(Value value); /// pairwise connect. bool isDuplexValue(Value val); -enum class Flow { Source, Sink, Duplex }; +enum class Flow : uint8_t { None, Source, Sink, Duplex }; /// Get a flow's reverse. Flow swapFlow(Flow flow); +constexpr bool isValidSrc(Flow flow) { + return uint8_t(flow) & uint8_t(Flow::Source); +} + +constexpr bool isValidDst(Flow flow) { + return uint8_t(flow) & uint8_t(Flow::Sink); +} + /// Compute the flow for a Value, \p val, as determined by the FIRRTL /// specification. This recursively walks backwards from \p val to the /// declaration. The resulting flow is a combination of the declaration flow @@ -134,8 +142,8 @@ LogicalResult verifySameOperandsIntTypeKind(Operation *op); // Type inference adaptor for FIRRTL operations. LogicalResult inferReturnTypes( MLIRContext *context, std::optional loc, ValueRange operands, - DictionaryAttr attrs, mlir::RegionRange regions, - SmallVectorImpl &results, + DictionaryAttr attrs, mlir::OpaqueProperties properties, + mlir::RegionRange regions, SmallVectorImpl &results, llvm::function_ref, std::optional)> callback); @@ -145,6 +153,8 @@ FIRRTLType inferAddSubResult(FIRRTLType lhs, FIRRTLType rhs, std::optional loc); FIRRTLType inferBitwiseResult(FIRRTLType lhs, FIRRTLType rhs, std::optional loc); +FIRRTLType inferElementwiseResult(FIRRTLType lhs, FIRRTLType rhs, + std::optional loc); FIRRTLType inferComparisonResult(FIRRTLType lhs, FIRRTLType rhs, std::optional loc); FIRRTLType inferReductionResult(FIRRTLType arg, std::optional loc); @@ -182,13 +192,13 @@ struct FirMemory { size_t readLatency; size_t writeLatency; size_t maskBits; - size_t readUnderWrite; - hw::WUW writeUnderWrite; + seq::RUW readUnderWrite; + seq::WUW writeUnderWrite; SmallVector writeClockIDs; StringAttr modName; bool isMasked; - uint32_t groupID; MemoryInitAttr init; + StringAttr prefix; // Location is carried along but not considered part of the identity of this. Location loc; @@ -198,16 +208,13 @@ struct FirMemory { // The original MemOp, only used in LowerToHW. Also not part of the identity. Operation *op = nullptr; - std::tuple, uint32_t, StringRef, bool, - bool> - getTuple() const { + auto getTuple() const { return std::make_tuple( numReadPorts, numWritePorts, numReadWritePorts, dataWidth, depth, readLatency, writeLatency, maskBits, readUnderWrite, writeUnderWrite, - writeClockIDs, groupID, init ? init.getFilename().getValue() : "", - init ? init.getIsBinary().getValue() : false, - init ? init.getIsInline().getValue() : false); + writeClockIDs, init ? init.getFilename().getValue() : "", + init ? init.getIsBinary() : false, init ? init.getIsInline() : false, + prefix ? prefix.getValue() : ""); } bool operator<(const FirMemory &rhs) const { return getTuple() < rhs.getTuple(); @@ -222,121 +229,15 @@ struct FirMemory { * * The following conditions must hold: * 1. read latency and write latency of one. - * 2. only one readwrite port or write port. - * 3. zero or one read port. - * 4. undefined read-under-write behavior. + * 2. undefined read-under-write behavior. */ bool isSeqMem() const { if (readLatency != 1 || writeLatency != 1) return false; - if (numWritePorts + numReadWritePorts != 1) - return false; - if (numReadPorts > 1) - return false; return dataWidth > 0; } }; -// Record of the inner sym name, the module name and the corresponding -// operation. Also the port index, if the symbol is on a module port. -// This is used to record the operations or ports, that have an inner sym. -// The operation and portIdx, can be null, when we have an InnerRefAttr, that is -// the module name and sym name, but we don't yet have a handle to the operation -// which has the Reference. So, InnerRefRecord can be used to construct illegal -// InnerRefAttr, which do not exist in the circt. That is the reason, the -// comparison operators here, only care for module name and symbol name. -struct InnerRefRecord { - mlir::StringAttr mod, innerSym; - mlir::Operation *op = nullptr; - unsigned portIdx = 0; - InnerRefRecord(StringAttr mod, StringAttr innerSym, Operation *op) - : mod(mod), innerSym(innerSym), op(op) {} - InnerRefRecord(StringAttr mod, StringAttr innerSym, Operation *op, - unsigned portIdx) - : mod(mod), innerSym(innerSym), op(op), portIdx(portIdx) {} - InnerRefRecord(hw::InnerRefAttr ref) - : mod(ref.getModule()), innerSym(ref.getName()) {} - bool operator<(const InnerRefRecord &rhs) const { - return (innerSym.getValue() < rhs.innerSym.getValue() || - (innerSym == rhs.innerSym && mod.getValue() < rhs.mod)); - } - bool operator==(const InnerRefRecord &rhs) const { - return (innerSym == rhs.innerSym && mod == rhs.mod); - } - bool operator!=(const InnerRefRecord &rhs) const { return !(*this == rhs); } -}; - -// A data structure to record and lookup an InnerSym and the corresponding -// operation. Can be used when the list is populated first, then sorted and then -// only used for lookup. This does not handle duplicate entries explicitly. The -// list must be sorted, before any lookup. This is based on an observation that -// Dense arrays can be efficient lookup structures. Especially when we have -// insert-phase and lookup-phase based code. -// TODO: Generalize this data structure. -struct InnerRefList { - InnerRefList(MLIRContext *context) - : InnerSymAttr(StringAttr::get(context, "inner_sym")) {} - - void sort() { - llvm::sort(list); - sorted = true; - } - int search(const InnerRefRecord &key) const { - assert(sorted && "Sort the list before search"); - if (!sorted || list.empty()) - return -1; - const auto *iter = std::lower_bound(list.begin(), list.end(), key); - if (iter == list.end()) - return -1; - return (iter - list.begin()); - } - bool exists(const InnerRefRecord &key) const { return search(key) != -1; } - Operation *getOpIfExists(const hw::InnerRefAttr ref) const { - auto index = search(InnerRefRecord(ref)); - if (index != -1 && list[index].op != nullptr) - return list[index].op; - return nullptr; - } - const InnerRefRecord *getRecordIfExists(const hw::InnerRefAttr ref) const { - auto index = search(InnerRefRecord(ref)); - if (index != -1) - return &list[index]; - return nullptr; - } - void pushBack(InnerRefRecord &key) { - list.push_back(key); - sorted = false; - } - // Inesrt the op with the module modName, if it has an inner sym. - bool insert(Operation *op, StringAttr modName) { - if (op == nullptr) - return false; - auto innerSym = op->getAttrOfType(InnerSymAttr); - if (!innerSym) - return false; - list.emplace_back(modName, innerSym, op); - sorted = false; - return true; - } - // Insert all the ports for the op, if they have the inner sym. - bool insert(FModuleLike op) { - StringAttr modName = op.moduleNameAttr(); - bool inserted = false; - for (auto sym : llvm::enumerate(op.getPortSymbols())) - if (sym.value()) { - list.emplace_back(modName, sym.value().cast(), op, - sym.index()); - inserted = true; - } - sorted = !inserted; - return inserted; - } - -private: - StringAttr InnerSymAttr; - SmallVector list; - bool sorted = false; -}; } // namespace firrtl } // namespace circt diff --git a/include/circt/Dialect/FIRRTL/FIRRTLReductions.h b/include/circt/Dialect/FIRRTL/FIRRTLReductions.h new file mode 100644 index 000000000000..181c289ff421 --- /dev/null +++ b/include/circt/Dialect/FIRRTL/FIRRTLReductions.h @@ -0,0 +1,31 @@ +//===- FIRRTLReductions.h - FIRRTL reduction interf. decl. ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_FIRRTL_FIRRTLREDUCTIONS_H +#define CIRCT_DIALECT_FIRRTL_FIRRTLREDUCTIONS_H + +#include "circt/Reduce/Reduction.h" + +namespace circt { +namespace firrtl { + +/// A dialect interface to provide reduction patterns to a reducer tool. +struct FIRRTLReducePatternDialectInterface + : public ReducePatternDialectInterface { + using ReducePatternDialectInterface::ReducePatternDialectInterface; + void populateReducePatterns(circt::ReducePatternSet &patterns) const override; +}; + +/// Register the FIRRTL Reduction pattern dialect interface to the given +/// registry. +void registerReducePatternDialectInterface(mlir::DialectRegistry ®istry); + +} // namespace firrtl +} // namespace circt + +#endif // CIRCT_DIALECT_FIRRTL_FIRRTLREDUCTIONS_H diff --git a/include/circt/Dialect/FIRRTL/FIRRTLStatements.td b/include/circt/Dialect/FIRRTL/FIRRTLStatements.td index e63fabdd5a5b..ba39a0a9a4a9 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLStatements.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLStatements.td @@ -17,6 +17,8 @@ include "FIRRTLDialect.td" include "FIRRTLEnums.td" include "FIRRTLOpInterfaces.td" include "FIRRTLTypes.td" +include "circt/Dialect/HW/HWTypes.td" +include "mlir/IR/SymbolInterfaces.td" include "mlir/Interfaces/SideEffectInterfaces.td" def AttachOp : FIRRTLOp<"attach"> { @@ -50,45 +52,61 @@ def ConnectOp : FIRRTLOp<"connect", [FConnectLike]> { let hasCanonicalizer = true; } -def StrictConnectOp : FIRRTLOp<"strictconnect", [SameTypeOperands, FConnectLike]> { +def SameAnonTypeOperands: PredOpTrait< + "operands must be structurally equivalent", + CPred<"areAnonymousTypesEquivalent(getOperand(0).getType(), getOperand(1).getType())">>; +def StrictConnectOp : FIRRTLOp<"strictconnect", [FConnectLike, + SameAnonTypeOperands]> { let summary = "Connect two signals"; let description = [{ Connect two values with strict constraints: ``` firrtl.strictconnect %dest, %src : t1 + firrtl.strictconnect %dest, %src : t1, !firrtl.alias ``` }]; - let arguments = (ins SizedOrForeignType:$dest, - SizedOrForeignType:$src); + let arguments = (ins StrictConnectableType:$dest, + StrictConnectableType:$src); let results = (outs); let hasCanonicalizeMethod = true; let hasVerifier = 1; - - let assemblyFormat = - "$dest `,` $src attr-dict `:` qualified(type($dest))"; + let assemblyFormat = [{$dest `,` $src attr-dict `:` + custom(type($dest), type($src))}]; } -def RefConnectOp : FIRRTLOp<"refconnect", [SameTypeOperands, FConnectLike]> { - let summary = "Connect two references"; - let description = [{ - Connect two references with strict constraints: +def RefDefineOp : FIRRTLOp<"ref.define", [SameTypeOperands, FConnectLike]> { + let summary = "FIRRTL Define References"; + let description = [{ + Define a target reference to the source reference: ``` - firrtl.refconnect %dest, %src : ref + firrtl.ref.define %dest, %src : ref ``` - }]; + Used to statically route reference from source to destination + through the design, one module at a time. - let arguments = (ins SizedRefType:$dest, - SizedRefType:$src); - let results = (outs); + Similar to "connect" but cannot have multiple define's to same + destination and the define is never conditional even if under + a "when". + + Source and destination must resolve statically. + }]; + let arguments = (ins RefType:$dest, RefType:$src); let hasVerifier = 1; let assemblyFormat = "$dest `,` $src attr-dict `:` qualified(type($dest))"; + + let extraClassDeclaration = [{ + static ConnectBehaviorKind getConnectBehaviorKind() { + return ConnectBehaviorKind::StaticSingleConnect; + } + }]; } + def PrintFOp : FIRRTLOp<"printf"> { let summary = "Formatted Print Statement"; @@ -163,8 +181,7 @@ def CoverOp : VerifOp<"cover"> { let summary = "Cover Verification Statement"; } -def WhenOp : FIRRTLOp<"when", [SingleBlock, NoTerminator, NoRegionArguments, - RecursiveMemoryEffects, RecursivelySpeculatable]> { +def WhenOp : FIRRTLOp<"when", [SingleBlock, NoTerminator, NoRegionArguments]> { let summary = "When Statement"; let description = [{ The "firrtl.when" operation represents a conditional. Connections within @@ -177,6 +194,7 @@ def WhenOp : FIRRTLOp<"when", [SingleBlock, NoTerminator, NoRegionArguments, let regions = (region SizedRegion<1>:$thenRegion, AnyRegion:$elseRegion); let skipDefaultBuilders = 1; + let hasCanonicalizeMethod = 1; let builders = [ OpBuilder<(ins "Value":$condition, "bool":$withElseRegion, CArg<"std::function", "{}">:$thenCtor, @@ -216,6 +234,72 @@ def WhenOp : FIRRTLOp<"when", [SingleBlock, NoTerminator, NoRegionArguments, }]; } +def MatchOp : FIRRTLOp<"match", [SingleBlock, NoTerminator, + RecursiveMemoryEffects, RecursivelySpeculatable]> { + let summary = "Match Statement"; + let description = [{ + The "firrtl.match" operation represents a pattern matching statement on a + enumeration. This operation does not return a value and cannot be used as an + expression. Last connect semantics work similarly to a when statement. + + Example: + ```mlir + firrtl.match %in : !firrtl.enum, None: uint<0>> { + case Some(%arg0) { + !firrtl.strictconnect %w, %arg0 : !firrtl.uint<1> + } + case None(%arg0) { + !firrt.strictconnect %w, %c1 : !firrtl.uint<1> + } + } + ``` + }]; + let arguments = (ins FEnumType:$input, I32ArrayAttr:$tags); + let results = (outs); + let regions = (region VariadicRegion>:$regions); + let hasVerifier = 1; + let hasCustomAssemblyFormat = 1; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "::mlir::Value":$input, + "::mlir::ArrayAttr":$tags, + "::llvm::MutableArrayRef>":$regions)> + ]; + + let extraClassDeclaration = [{ + IntegerAttr getFieldIndexAttr(size_t caseIndex) { + return cast(getTags()[caseIndex]); + } + + uint32_t getFieldIndex(size_t caseIndex) { + return getFieldIndexAttr(caseIndex).getUInt(); + } + }]; +} + +def PropAssignOp : FIRRTLOp<"propassign", + [FConnectLike, SameTypeOperands, ParentOneOf<["FModuleOp", "ClassOp"]>]> { + let summary = "Assign to a sink property value."; + let description = [{ + Assign an output property value. The types must match exactly. + + Example: + ```mlir + firrtl.propassign %dest, %src : !firrtl.string + ``` + }]; + let arguments = (ins PropertyType:$dest, PropertyType:$src); + let results = (outs); + let hasVerifier = 1; + let assemblyFormat = "$dest `,` $src attr-dict `:` qualified(type($dest))"; + let extraClassDeclaration = [{ + static ConnectBehaviorKind getConnectBehaviorKind() { + return ConnectBehaviorKind::StaticSingleConnect; + } + }]; +} + def ForceOp : FIRRTLOp<"force", [SameTypeOperands]> { let summary = "Force procedural statement"; let description = "Maps to the corresponding `sv.force` operation."; @@ -225,15 +309,107 @@ def ForceOp : FIRRTLOp<"force", [SameTypeOperands]> { "$dest `,` $src attr-dict `:` qualified(type($dest)) `,` qualified(type($src))"; } -def ProbeOp : FIRRTLOp<"probe"> { - let summary = "FIRRTL Value Probe"; +//===- Reference force/release --------------------------------------------===// + +class ForceRefTypeConstraint + : TypesMatchWith<"reference type of " # ref # " should be RWProbe of " # base, + base, ref, + "RefType::get(firrtl::type_cast($_self).getAllConstDroppedType(), true)">; + +def RefForceOp : FIRRTLOp<"ref.force",[ForceRefTypeConstraint<"dest", "src">]> { + let summary = "FIRRTL force statement"; + let description = "Force a RWProbe to the specified value using the specified clock and predicate."; + let arguments = (ins ClockType:$clock, UInt1Type:$predicate, RWProbe:$dest, FIRRTLBaseType:$src); + let assemblyFormat = + "$clock `,` $predicate `,` $dest `,` $src attr-dict `:` type($clock) `,` type($predicate) `,` qualified(type($src))"; + let hasCanonicalizer = 1; +} +def RefForceInitialOp : FIRRTLOp<"ref.force_initial",[ForceRefTypeConstraint<"dest", "src">]> { + let summary = "FIRRTL force_initial statement"; + let description = "Force a RWProbe to the specified value continuously."; + let arguments = (ins UInt1Type:$predicate, RWProbe:$dest, FIRRTLBaseType:$src); + let assemblyFormat = + "$predicate `,` $dest `,` $src attr-dict `:` type($predicate) `,` qualified(type($src))"; + let hasCanonicalizer = 1; +} +def RefReleaseOp : FIRRTLOp<"ref.release"> { + let summary = "FIRRTL release statement"; + let description = "Release the target RWProbe using the specified clock and predicate."; + let arguments = (ins ClockType:$clock, UInt1Type:$predicate, RWProbe:$dest); + let assemblyFormat = + "$clock `,` $predicate `,` $dest attr-dict `:` type($clock) `,` type($predicate) `,` qualified(type($dest))"; + let hasCanonicalizer = 1; +} +def RefReleaseInitialOp : FIRRTLOp<"ref.release_initial"> { + let summary = "FIRRTL release_initial statement"; + let description = "Release the target RWProbe."; + let arguments = (ins UInt1Type:$predicate, RWProbe:$dest); + let assemblyFormat = "$predicate `,` $dest attr-dict `:` type($predicate) `,` qualified(type($dest))"; + let hasCanonicalizer = 1; +} + +def XMRRefOp : FIRRTLOp<"xmr.ref"> { + let summary = "FIRRTL XMR operation, targetable by ref ops."; + let arguments = (ins FlatSymbolRefAttr:$ref, DefaultValuedAttr:$verbatimSuffix); + let results = (outs RefType:$dest); + let assemblyFormat = "$ref (`,` $verbatimSuffix^)? attr-dict `:` qualified(type($dest))"; +} + +def XMRDerefOp : FIRRTLOp<"xmr.deref"> { + let summary = "FIRRTL XMR operation, reading an XMR target."; + let arguments = (ins FlatSymbolRefAttr:$ref, DefaultValuedAttr:$verbatimSuffix); + let results = (outs PassiveType:$dest); + let assemblyFormat = "$ref (`,` $verbatimSuffix^)? attr-dict `:` qualified(type($dest))"; +} + +//===----------------------------------------------------------------------===// +// Verif Dialect Intrinsics +//===----------------------------------------------------------------------===// + +class VerifIntrinsicOp traits = []> : + FIRRTLOp<"int.verif." # mnemonic, traits> { + let summary = "FIRRTL variant of `verif." # mnemonic # "`"; + let description = "See `verif." # mnemonic # "` op in the Verif dialect."; + let arguments = (ins UInt1Type:$property, OptionalAttr:$label); + let assemblyFormat = [{ + operands attr-dict `:` type(operands) + }]; +} + +def VerifAssertIntrinsicOp : VerifIntrinsicOp<"assert">; +def VerifAssumeIntrinsicOp : VerifIntrinsicOp<"assume">; +def VerifCoverIntrinsicOp : VerifIntrinsicOp<"cover">; + +//===----------------------------------------------------------------------===// +// Group Op +//===----------------------------------------------------------------------===// + +def GroupOp : FIRRTLOp< + "group", + [SingleBlock, NoTerminator, NoRegionArguments, + ParentOneOf<["firrtl::FModuleOp", "firrtl::GroupOp"]>, + DeclareOpInterfaceMethods] +> { + let summary = "A definition of an optional group"; let description = [{ - Captures values without binding to any accidental name. + The `firrtl.group` operation defines optional code that is conditionally + part of a `firrtl.module`. This is typically used to store verification or + debugging code that is lowered to a SystemVerilog `bind` instantiation. An + optional group can read from (capture) values defined in parent group + definitions or in the parent module, but may not write to hardware declared + outside the group. + + A `firrtl.group` must refer to an existing optional group declaration + (`firrtl.declgroup`) via a symbol reference. A nested `firrtl.group` refers + to a nested group declaration via a nested symbol reference. }]; - let arguments = (ins SymbolNameAttr:$inner_sym, Variadic:$captured); + let arguments = (ins SymbolRefAttr:$groupName); let results = (outs); - - let assemblyFormat = "$inner_sym attr-dict ( `,` $captured^ `:` qualified(type($captured)))?"; + let regions = (region SizedRegion<1>:$region); + let assemblyFormat = [{ + $groupName $region attr-dict + }]; + let hasVerifier = 1; } #endif // CIRCT_DIALECT_FIRRTL_FIRRTLSTATEMENTS_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td index 3dd1ceeefe1a..e73751e5784a 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td @@ -15,6 +15,7 @@ include "FIRRTLAttributes.td" include "FIRRTLDialect.td" +include "FIRRTLEnums.td" include "FIRRTLOpInterfaces.td" include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Dialect/HW/HWTypes.td" @@ -64,23 +65,60 @@ class FIRRTLModuleLike traits = []> : FIRRTLOp, DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, OpAsmOpInterface, InnerSymbolTable]> { /// Additional class definitions inside the module op. code extraModuleClassDefinition = [{}]; + code extraModuleClassDeclaration = [{}]; + + let extraClassDeclaration = extraModuleClassDeclaration # [{ + }]; let extraClassDefinition = extraModuleClassDefinition # [{ + size_t $cppClass::getNumPorts() { return getPortTypesAttr().size(); } - circt::hw::InnerSymAttr $cppClass::getPortSymbolAttr(size_t portIndex) { - auto syms = getPortSymbols(); - if (syms.empty() || - syms[portIndex].template cast().empty()) - return hw::InnerSymAttr(); - return syms[portIndex].template cast(); + size_t $cppClass::getNumInputPorts() { + size_t count = 0; + for (unsigned i = 0, e = getNumPorts(); i < e; ++i) + if (getPortDirection(i) == Direction::In) + ++count; + return count; + } + + size_t $cppClass::getNumOutputPorts() { + size_t count = 0; + for (unsigned i = 0, e = getNumPorts(); i < e; ++i) + if (getPortDirection(i) == Direction::Out) + ++count; + return count; + } + + size_t $cppClass::getPortIdForInputId(size_t idx) { + for (unsigned i = 0, e = getNumPorts(); i < e; ++i) + if (getPortDirection(i) == Direction::In) { + if (!idx) + return i; + --idx; + } + assert(0 && "Out of bounds input port id"); + return ~0ULL; + } + + size_t $cppClass::getPortIdForOutputId(size_t idx) { + for (unsigned i = 0, e = getNumPorts(); i < e; ++i) + if (getPortDirection(i) == Direction::Out) { + if (!idx) + return i; + --idx; + } + assert(0 && "Out of bounds input port id"); + return ~0ULL; } }]; @@ -95,7 +133,9 @@ def FModuleOp : FIRRTLModuleLike<"module", [SingleBlock, NoTerminator]> { the module. }]; let arguments = - (ins DefaultValuedAttr:$annotations); + (ins ConventionAttr:$convention, + ArrayRefAttr:$portLocations, + DefaultValuedAttr:$annotations); let results = (outs); let regions = (region SizedRegion<1>:$body); @@ -104,11 +144,12 @@ def FModuleOp : FIRRTLModuleLike<"module", [SingleBlock, NoTerminator]> { let hasCustomAssemblyFormat = 1; let hasVerifier = 1; let builders = [ - OpBuilder<(ins "StringAttr":$name, "ArrayRef":$ports, - CArg<"ArrayAttr","ArrayAttr()">:$annotations)> + OpBuilder<(ins "StringAttr":$name, "ConventionAttr":$convention, + "ArrayRef":$ports, + CArg<"ArrayAttr", "ArrayAttr()">:$annotations)>, ]; - let extraClassDeclaration = [{ + let extraModuleClassDeclaration = [{ Block *getBodyBlock() { return &getBody().front(); } using iterator = Block::iterator; @@ -148,10 +189,12 @@ def FExtModuleOp : FIRRTLModuleLike<"extmodule"> { }]; let arguments = (ins OptionalAttr:$defname, + ConventionAttr:$convention, + ArrayRefAttr:$portLocations, ParamDeclArrayAttr:$parameters, DefaultValuedAttr:$annotations, - DefaultValuedAttr:$internalPaths + OptionalAttr:$internalPaths ); let results = (outs); let regions = (region AnyRegion:$body); @@ -159,6 +202,7 @@ def FExtModuleOp : FIRRTLModuleLike<"extmodule"> { let skipDefaultBuilders = 1; let builders = [ OpBuilder<(ins "StringAttr":$name, + "ConventionAttr":$convention, "ArrayRef":$ports, CArg<"StringRef", "StringRef()">:$defnamAttr, CArg<"ArrayAttr", "ArrayAttr()">:$annotations, @@ -166,7 +210,7 @@ def FExtModuleOp : FIRRTLModuleLike<"extmodule"> { CArg<"ArrayAttr", "ArrayAttr()">:$internalPaths)> ]; - let extraClassDeclaration = [{ + let extraModuleClassDeclaration = [{ void getAsmBlockArgumentNames(mlir::Region ®ion, mlir::OpAsmSetValueNameFn setNameFn); }]; @@ -182,10 +226,11 @@ def FIntModuleOp : FIRRTLModuleLike<"intmodule"> { }]; let arguments = (ins OptionalAttr:$intrinsic, + ArrayRefAttr:$portLocations, ParamDeclArrayAttr:$parameters, DefaultValuedAttr:$annotations, - DefaultValuedAttr:$internalPaths + OptionalAttr:$internalPaths ); let results = (outs); let regions = (region AnyRegion:$body); @@ -200,7 +245,7 @@ def FIntModuleOp : FIRRTLModuleLike<"intmodule"> { CArg<"ArrayAttr", "ArrayAttr()">:$internalPaths)> ]; - let extraClassDeclaration = [{ + let extraModuleClassDeclaration = [{ void getAsmBlockArgumentNames(mlir::Region ®ion, mlir::OpAsmSetValueNameFn setNameFn); }]; @@ -223,7 +268,9 @@ def FMemModuleOp : FIRRTLModuleLike<"memmodule"> { (ins UI32Attr:$numReadPorts, UI32Attr:$numWritePorts, UI32Attr:$numReadWritePorts, UI32Attr:$dataWidth, UI32Attr:$maskBits, UI32Attr:$readLatency, UI32Attr:$writeLatency, UI64Attr:$depth, - ArrayAttr:$extraPorts, AnnotationArrayAttr:$annotations); + ArrayAttr:$extraPorts, + ArrayRefAttr:$portLocations, + AnnotationArrayAttr:$annotations); let results = (outs); let regions = (region AnyRegion:$body); @@ -237,7 +284,7 @@ def FMemModuleOp : FIRRTLModuleLike<"memmodule"> { CArg<"ArrayAttr", "ArrayAttr()">:$annotations)> ]; - let extraClassDeclaration = [{ + let extraModuleClassDeclaration = [{ /// Return true if this memory has a mask. bool isMasked() { return getMaskBits() > 1; } void getAsmBlockArgumentNames(mlir::Region ®ion, @@ -248,4 +295,124 @@ def FMemModuleOp : FIRRTLModuleLike<"memmodule"> { let hasCustomAssemblyFormat = 1; } +def ClassOp : FIRRTLModuleLike<"class", [ + SingleBlock, NoTerminator, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { + let summary = "FIRRTL Class"; + let description = [{ + The "firrtl.class" operation defines a class of property-only objects, + including a given name, a list of ports, and a body that represents the + connections within the class. + + A class may only have property ports, and its body may only be ops that act + on properties, such as propassign ops. + }]; + let arguments = (ins SymbolNameAttr:$sym_name, APIntAttr:$portDirections, + ArrayRefAttr:$portNames, ArrayRefAttr:$portTypes, + ArrayRefAttr:$portSyms, ArrayRefAttr:$portLocations); + let results = (outs); + let regions = (region SizedRegion<1>:$body); + + let skipDefaultBuilders = 1; + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + let builders = [ + OpBuilder<(ins + "StringAttr":$name, + "ArrayRef":$ports)>]; + + let extraModuleClassDeclaration = [{ + Block *getBodyBlock() { return &getBody().front(); } + + using iterator = Block::iterator; + iterator begin() { return getBodyBlock()->begin(); } + iterator end() { return getBodyBlock()->end(); } + + Block::BlockArgListType getArguments() { + return getBodyBlock()->getArguments(); + } + + // Return the block argument for the port with the specified index. + BlockArgument getArgument(size_t portNumber); + + OpBuilder getBodyBuilder() { + assert(!getBody().empty() && "Unexpected empty 'body' region."); + Block &bodyBlock = getBody().front(); + return OpBuilder::atBlockEnd(&bodyBlock); + } + + void getAsmBlockArgumentNames(mlir::Region ®ion, + mlir::OpAsmSetValueNameFn setNameFn); + ArrayAttr getParameters(); + }]; +} + +def ExtClassOp : FIRRTLModuleLike<"extclass", [ + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { + let summary = "FIRRTL external class"; + let description = [{ + The "firrtl.extclass" operation represents a reference to an external + firrtl class, and includes a given name, as well as a list of ports. + Just as usual firrtl.class definitions, An ext.class may only have property + ports. + + example: + ```mlir + firrtl.extclass @MyImportedClass(in in_str: !firrtl.string, out out_str: !firrtl.string) + ``` + }]; + let arguments = (ins SymbolNameAttr:$sym_name, APIntAttr:$portDirections, + ArrayRefAttr:$portNames, ArrayRefAttr:$portTypes, + ArrayRefAttr:$portSyms, ArrayRefAttr:$portLocations); + let results = (outs); + let regions = (region AnyRegion:$body); + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins + "StringAttr":$name, + "ArrayRef":$ports + )> + ]; + let extraModuleClassDeclaration = [{ + void getAsmBlockArgumentNames(mlir::Region ®ion, + mlir::OpAsmSetValueNameFn setNameFn); + + ArrayAttr getParameters(); + }]; + let hasCustomAssemblyFormat = 1; +} + +def GroupDeclOp : FIRRTLOp< + "declgroup", + [IsolatedFromAbove, Symbol, SymbolTable, SingleBlock, NoTerminator, + ParentOneOf<["firrtl::CircuitOp", "firrtl::GroupDeclOp"]>] +> { + let summary = "A declaration of an optional group"; + let description = [{ + The `firrtl.declgroup` operation declares an optional group and a lowering + convetion for that group. Optional groups are a feature of FIRRTL that add + verification or debugging code to an existing module at runtime. + + A `firrtl.declgroup` operation only declares the group and any groups nested + under the current declaration. Functionality is added to modules using the + `firrtl.group` operation. + }]; + let arguments = (ins SymbolNameAttr:$sym_name, GroupConventionAttr:$convention); + let results = (outs); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ + $sym_name $convention attr-dict-with-keyword $body + }]; +} + #endif // CIRCT_DIALECT_FIRRTL_FIRRTLSTRUCTURE_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.h b/include/circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.h new file mode 100644 index 000000000000..f0cb64cec72f --- /dev/null +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.h @@ -0,0 +1,18 @@ +//===- FIRRTLTypeInterfaces.h - Declare FIRRTL type interfaces --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares type interfaces for the FIRRTL dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_FIRRTL_FIRRTLTYPEINTERFACES_H +#define CIRCT_DIALECT_FIRRTL_FIRRTLTYPEINTERFACES_H + +#include "circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.h.inc" + +#endif // CIRCT_DIALECT_FIRRTL_FIRRTLTYPEINTERFACES_H diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.td b/include/circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.td new file mode 100644 index 000000000000..7ccaf0d0a1d0 --- /dev/null +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.td @@ -0,0 +1,22 @@ +//===- FIRRTLTypeInterfaces.td - FIRRTL Type Interfaces ----*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This describes the type interfaces of the FIRRTL dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_FIRRTL_FIRRTLTYPEINTERFACES_TD +#define CIRCT_DIALECT_FIRRTL_FIRRTLTYPEINTERFACES_TD + +include "mlir/IR/OpBase.td" + +def WidthQualifiedTypeTrait : NativeTypeTrait<"WidthQualifiedTypeTrait"> { + let cppNamespace = "::circt::firrtl"; +} + +#endif // CIRCT_DIALECT_FIRRTL_FIRRTLTYPEINTERFACES_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypes.h b/include/circt/Dialect/FIRRTL/FIRRTLTypes.h index 2f98dd35a2f4..eb55d4b0b281 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLTypes.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypes.h @@ -13,21 +13,33 @@ #ifndef CIRCT_DIALECT_FIRRTL_TYPES_H #define CIRCT_DIALECT_FIRRTL_TYPES_H +#include "circt/Dialect/FIRRTL/FIRRTLAttributes.h" #include "circt/Dialect/FIRRTL/FIRRTLDialect.h" +#include "circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.h" #include "circt/Dialect/HW/HWTypeInterfaces.h" #include "circt/Support/LLVM.h" #include "mlir/IR/OpDefinition.h" #include "mlir/IR/Types.h" +#include "llvm/ADT/TypeSwitch.h" namespace circt { namespace firrtl { namespace detail { +struct FIRRTLBaseTypeStorage; +struct WidthTypeStorage; struct BundleTypeStorage; -struct VectorTypeStorage; +struct FVectorTypeStorage; +struct FEnumTypeStorage; struct CMemoryTypeStorage; struct RefTypeStorage; +struct BaseTypeAliasStorage; +struct OpenBundleTypeStorage; +struct OpenVectorTypeStorage; +struct ClassTypeStorage; } // namespace detail. +class AnyRefType; +class ClassType; class ClockType; class ResetType; class AsyncResetType; @@ -35,24 +47,37 @@ class SIntType; class UIntType; class AnalogType; class BundleType; +class OpenBundleType; +class OpenVectorType; class FVectorType; +class FEnumType; class RefType; +class PropertyType; +class StringType; +class FIntegerType; +class ListType; +class MapType; +class PathType; +class BoolType; +class DoubleType; +class BaseTypeAliasType; /// A collection of bits indicating the recursive properties of a type. struct RecursiveTypeProperties { /// Whether the type only contains passive elements. bool isPassive : 1; + /// Whether the type contains a reference type. + bool containsReference : 1; /// Whether the type contains an analog type. bool containsAnalog : 1; + /// Whether the type contains a const type. + bool containsConst : 1; + /// Whether the type contains a type alias. + bool containsTypeAlias : 1; /// Whether the type has any uninferred bit widths. bool hasUninferredWidth : 1; - - /// The number of bits required to represent a type's recursive properties. - static constexpr unsigned numBits = 3; - /// Unpack `RecursiveTypeProperties` from a bunch of bits. - static RecursiveTypeProperties fromFlags(unsigned bits); - /// Pack `RecursiveTypeProperties` as a bunch of bits. - unsigned toFlags() const; + /// Whether the type has any uninferred reset. + bool hasUninferredReset : 1; }; // This is a common base class for all FIRRTL types. @@ -63,38 +88,83 @@ class FIRRTLType : public Type { return llvm::isa(type.getDialect()); } -protected: - using Type::Type; -}; - -// Common base class for all base FIRRTL types. -class FIRRTLBaseType : public FIRRTLType { -public: - /// Return true if this is a "passive" type - one that contains no "flip" - /// types recursively within itself. - bool isPassive() { return getRecursiveTypeProperties().isPassive; } + /// Return the recursive properties of the type, containing the `isPassive`, + /// `containsAnalog`, and `hasUninferredWidth` bits, among others. + RecursiveTypeProperties getRecursiveTypeProperties() const; - /// Returns true if this is a "passive" that which is not analog. - bool isRegisterType() { return isPassive() && !containsAnalog(); } + //===--------------------------------------------------------------------===// + // Convenience methods for accessing recursive type properties + //===--------------------------------------------------------------------===// - /// Return true if this is a 'ground' type, aka a non-aggregate type. - bool isGround(); + /// Returns true if this is or contains a 'const' type. + bool containsConst() { return getRecursiveTypeProperties().containsConst; } /// Return true if this is or contains an Analog type. bool containsAnalog() { return getRecursiveTypeProperties().containsAnalog; } + /// Return true if this is or contains a Reference type. + bool containsReference() { + return getRecursiveTypeProperties().containsReference; + } + + /// Return true if this is an anonymous type (no type alias). + bool containsTypeAlias() { + return getRecursiveTypeProperties().containsTypeAlias; + } + /// Return true if this type contains an uninferred bit width. bool hasUninferredWidth() { return getRecursiveTypeProperties().hasUninferredWidth; } - /// Return the recursive properties of the type, containing the `isPassive`, - /// `containsAnalog`, and `hasUninferredWidth` bits. - RecursiveTypeProperties getRecursiveTypeProperties(); + /// Return true if this type contains an uninferred bit reset. + bool hasUninferredReset() { + return getRecursiveTypeProperties().hasUninferredReset; + } + + //===--------------------------------------------------------------------===// + // Type classifications + //===--------------------------------------------------------------------===// + + /// Return true if this is a 'ground' type, aka a non-aggregate type. + bool isGround(); + + /// Returns true if this is a 'const' type that can only hold compile-time + /// constant values + bool isConst(); + +protected: + using Type::Type; +}; + +// Common base class for all base FIRRTL types. +class FIRRTLBaseType + : public FIRRTLType::TypeBase { +public: + using Base::Base; + + /// Returns true if this is a 'const' type that can only hold compile-time + /// constant values + bool isConst(); + + /// Return true if this is a "passive" type - one that contains no "flip" + /// types recursively within itself. + bool isPassive() const { return getRecursiveTypeProperties().isPassive; } /// Return this type with any flip types recursively removed from itself. FIRRTLBaseType getPassiveType(); + /// Return this type with any type alias types recursively removed from + /// itself. + FIRRTLBaseType getAnonymousType(); + + /// Return a 'const' or non-'const' version of this type. + FIRRTLBaseType getConstType(bool isConst); + + /// Return this type with a 'const' modifiers dropped + FIRRTLBaseType getAllConstDroppedType(); + /// Return this type with all ground types replaced with UInt<1>. This is /// used for `mem` operations. FIRRTLBaseType getMaskType(); @@ -112,55 +182,54 @@ class FIRRTLBaseType : public FIRRTLType { /// Support method to enable LLVM-style type casting. static bool classof(Type type) { - return llvm::isa(type.getDialect()) && !type.isa(); + return llvm::isa(type.getDialect()) && + !llvm::isa( + type); + } + + /// Returns true if this is a non-const "passive" that which is not analog. + bool isRegisterType() { + return isPassive() && !containsAnalog() && !containsConst(); } /// Return true if this is a valid "reset" type. bool isResetType(); +}; - /// Get the maximum field ID of this type. For integers and other ground - /// types, there are no subfields and the maximum field ID is 0. For bundle - /// types and vector types, each field is assigned a field ID in a depth-first - /// walk order. This function is used to calculate field IDs when this type is - /// nested under another type. - uint64_t getMaxFieldID(); - - /// Get the sub-type of a type for a field ID, and the subfield's ID. Strip - /// off a single layer of this type and return the sub-type and a field ID - /// targeting the same field, but rebased on the sub-type. - std::pair getSubTypeByFieldID(uint64_t fieldID); - - /// Return the final type targeted by this field ID by recursively walking all - /// nested aggregate types. This is the identity function for ground types. - FIRRTLBaseType getFinalTypeByFieldID(uint64_t fieldID); - - /// Returns the effective field id when treating the index field as the - /// root of the type. Essentially maps a fieldID to a fieldID after a - /// subfield op. Returns the new id and whether the id is in the given - /// child. - std::pair rootChildFieldID(uint64_t fieldID, uint64_t index); - - /// Get the number of ground (non-aggregate) fields in the type. A field - /// which is a bundle or vector is not counted, but the recursive ground - /// fields of are. - uint64_t getGroundFields() const; +/// Returns true if this is a 'const' type whose value is guaranteed to be +/// unchanging at circuit execution time +bool isConst(Type type); -protected: - using FIRRTLType::FIRRTLType; -}; +/// Returns true if the type is or contains a 'const' type whose value is +/// guaranteed to be unchanging at circuit execution time +bool containsConst(Type type); /// Returns whether the two types are equivalent. This implements the exact /// definition of type equivalence in the FIRRTL spec. If the types being /// compared have any outer flips that encode FIRRTL module directions (input or /// output), these should be stripped before using this method. -bool areTypesEquivalent(FIRRTLType destType, FIRRTLType srcType); +bool areTypesEquivalent(FIRRTLType destType, FIRRTLType srcType, + bool destOuterTypeIsConst = false, + bool srcOuterTypeIsConst = false, + bool requireSameWidths = false); /// Returns true if two types are weakly equivalent. See the FIRRTL spec, /// Section 4.6, for a full definition of this. Roughly, the oriented types /// (the types with any flips pushed to the leaves) must match. This allows for /// types with flips in different positions to be equivalent. bool areTypesWeaklyEquivalent(FIRRTLType destType, FIRRTLType srcType, - bool destFlip = false, bool srcFlip = false); + bool destFlip = false, bool srcFlip = false, + bool destOuterTypeIsConst = false, + bool srcOuterTypeIsConst = false); + +/// Returns whether the srcType can be const-casted to the destType. +bool areTypesConstCastable(FIRRTLType destType, FIRRTLType srcType, + bool srcOuterTypeIsConst = false); + +/// Return true if destination ref type can be cast from source ref type, +/// per FIRRTL spec rules they must be identical or destination has +/// more general versions of the corresponding type in the source. +bool areTypesRefCastable(Type dstType, Type srcType); /// Returns true if the destination is at least as wide as a source. The source /// and destination types must be equivalent non-analog types. The types are @@ -171,9 +240,18 @@ bool areTypesWeaklyEquivalent(FIRRTLType destType, FIRRTLType srcType, /// hold their counterparts. bool isTypeLarger(FIRRTLBaseType dstType, FIRRTLBaseType srcType); -mlir::Type getVectorElementType(mlir::Type array); +/// Return true if anonymous types of given arguments are equivalent by pointer +/// comparison. +bool areAnonymousTypesEquivalent(FIRRTLBaseType lhs, FIRRTLBaseType rhs); +bool areAnonymousTypesEquivalent(mlir::Type lhs, mlir::Type rhs); + mlir::Type getPassiveType(mlir::Type anyBaseFIRRTLType); +/// Returns true if the given type has some flipped (aka unaligned) dataflow. +/// This will be true if the port contains either bi-directional signals or +/// analog types. Non-HW types (e.g., ref types) are never considered InOut. +bool isTypeInOut(mlir::Type type); + //===----------------------------------------------------------------------===// // Width Qualified Ground Types //===----------------------------------------------------------------------===// @@ -190,16 +268,16 @@ class WidthQualifiedTypeTrait public: /// Return an optional containing the width, if the width is known (or empty /// if width is unknown). - std::optional getWidth() { - auto width = static_cast(this)->getWidthOrSentinel(); + std::optional getWidth() const { + auto width = static_cast(this)->getWidthOrSentinel(); if (width < 0) return std::nullopt; return width; } /// Return true if this integer type has a known width. - bool hasWidth() { - return 0 <= static_cast(this)->getWidthOrSentinel(); + bool hasWidth() const { + return 0 <= static_cast(this)->getWidthOrSentinel(); } }; @@ -215,21 +293,78 @@ class IntType : public FIRRTLBaseType, public WidthQualifiedTypeTrait { public: using FIRRTLBaseType::FIRRTLBaseType; - /// Return an SIntType or UIntType with the specified signedness and width. + /// Return an SIntType or UIntType with the specified signedness, width, and + /// constness. static IntType get(MLIRContext *context, bool isSigned, - int32_t widthOrSentinel = -1); + int32_t widthOrSentinel = -1, bool isConst = false); bool isSigned() { return isa(); } bool isUnsigned() { return isa(); } /// Return the width of this type, or -1 if it has none specified. - int32_t getWidthOrSentinel(); + int32_t getWidthOrSentinel() const; + + /// Return a 'const' or non-'const' version of this type. + IntType getConstType(bool isConst); + + static bool classof(Type type) { return llvm::isa(type); } +}; + +//===----------------------------------------------------------------------===// +// PropertyTypes +//===----------------------------------------------------------------------===// +class PropertyType : public FIRRTLType { +public: + /// Support method to enable LLVM-style type casting. static bool classof(Type type) { - return type.isa() || type.isa(); + return llvm::isa(type); } + +protected: + using FIRRTLType::FIRRTLType; +}; + +//===----------------------------------------------------------------------===// +// ClassElement +//===----------------------------------------------------------------------===// + +struct ClassElement { + ClassElement(StringAttr name, Type type, Direction direction) + : name(name), type(type), direction(direction) {} + + StringAttr name; + Type type; + Direction direction; + + StringRef getName() const { return name.getValue(); } + + /// Return true if this is a simple output-only element. If you want the + /// direction of the port, use the \p direction field directly. + bool isInput() const { return direction == Direction::In && !isInOut(); } + + /// Return true if this is a simple input-only element. If you want the + /// direction of the port, use the \p direction field directly. + bool isOutput() const { return direction == Direction::Out && !isInOut(); } + + /// Return true if this is an inout port. This will be true if the port + /// contains either bi-directional signals or analog types. + /// Non-HW types (e.g., ref types) are never considered InOut. + bool isInOut() const { return isTypeInOut(type); } + + bool operator==(const ClassElement &rhs) const { + return name == rhs.name && type == rhs.type; + } + + bool operator!=(const ClassElement &rhs) const { return !(*this == rhs); } }; +// NOLINTNEXTLINE(readability-identifier-naming) +inline llvm::hash_code hash_value(const ClassElement &element) { + return llvm::hash_combine(element.name, element.type, element.direction); +} + //===----------------------------------------------------------------------===// // Type helpers //===----------------------------------------------------------------------===// @@ -246,12 +381,14 @@ std::optional getBitWidth(FIRRTLBaseType type, // Parse a FIRRTL type without a leading `!firrtl.` dialect tag. ParseResult parseNestedType(FIRRTLType &result, AsmParser &parser); ParseResult parseNestedBaseType(FIRRTLBaseType &result, AsmParser &parser); +ParseResult parseNestedPropertyType(PropertyType &result, AsmParser &parser); // Print a FIRRTL type without a leading `!firrtl.` dialect tag. void printNestedType(Type type, AsmPrinter &os); using FIRRTLValue = mlir::TypedValue; using FIRRTLBaseValue = mlir::TypedValue; +using FIRRTLPropertyValue = mlir::TypedValue; } // namespace firrtl } // namespace circt @@ -278,25 +415,208 @@ struct DenseMapInfo { static bool isEqual(FIRRTLType LHS, FIRRTLType RHS) { return LHS == RHS; } }; -template <> -struct DenseMapInfo { - using FIRRTLBaseType = circt::firrtl::FIRRTLBaseType; - static FIRRTLBaseType getEmptyKey() { - auto pointer = llvm::DenseMapInfo::getEmptyKey(); - return FIRRTLBaseType(static_cast(pointer)); +} // namespace llvm + +namespace circt { +namespace firrtl { +//===--------------------------------------------------------------------===// +// Utility for type aliases +//===--------------------------------------------------------------------===// + +/// A struct to check if there is a type derived from FIRRTLBaseType. +/// `ContainAliasableTypes::value` returns true if `BaseTy` is derived +/// from `FIRRTLBaseType` and not `FIRRTLBaseType` itself, or is not FIRRTL type +/// to cover type interfaces. +template +class ContainAliasableTypes { +public: + static constexpr bool value = ContainAliasableTypes::value || + ContainAliasableTypes::value; +}; + +template +class ContainAliasableTypes { + static constexpr bool isFIRRTLBaseType = + std::is_base_of::value && + !std::is_same_v; + static constexpr bool isFIRRTLType = + std::is_base_of::value; + +public: + static constexpr bool value = isFIRRTLBaseType || !isFIRRTLType; +}; + +template +bool type_isa(Type type) { // NOLINT(readability-identifier-naming) + // First check if the type is the requested type. + if (isa(type)) + return true; + + // If the requested type is a subtype of FIRRTLBaseType, then check if it is a + // type alias wrapping the requested type. + if constexpr (ContainAliasableTypes::value) { + if (auto alias = dyn_cast(type)) + return type_isa(alias.getInnerType()); } - static FIRRTLBaseType getTombstoneKey() { - auto pointer = llvm::DenseMapInfo::getTombstoneKey(); - return FIRRTLBaseType(static_cast(pointer)); + + return false; +} + +// type_isa for a nullable argument. +template +bool type_isa_and_nonnull(Type type) { // NOLINT(readability-identifier-naming) + if (!type) + return false; + return type_isa(type); +} + +template +BaseTy type_cast(Type type) { // NOLINT(readability-identifier-naming) + assert(type_isa(type) && "type must convert to requested type"); + + // If the type is the requested type, return it. + if (isa(type)) + return cast(type); + + // Otherwise, it must be a type alias wrapping the requested type. + if constexpr (ContainAliasableTypes::value) { + if (auto alias = dyn_cast(type)) + return type_cast(alias.getInnerType()); + } + + // Otherwise, it should fail. `cast` should cause a better assertion failure, + // so just use it. + return cast(type); +} + +template +BaseTy type_dyn_cast(Type type) { // NOLINT(readability-identifier-naming) + if (type_isa(type)) + return type_cast(type); + return {}; +} + +template +BaseTy +type_dyn_cast_or_null(Type type) { // NOLINT(readability-identifier-naming) + if (type_isa_and_nonnull(type)) + return type_cast(type); + return {}; +} + +//===--------------------------------------------------------------------===// +// Type alias aware TypeSwitch. +//===--------------------------------------------------------------------===// + +/// This class implements the same functionality as TypeSwitch except that +/// it uses firrtl::type_dyn_cast for dynamic cast. llvm::TypeSwitch is not +/// customizable so this class currently duplicates the code. +template +class FIRRTLTypeSwitch + : public llvm::detail::TypeSwitchBase, T> { +public: + using BaseT = llvm::detail::TypeSwitchBase, T>; + using BaseT::BaseT; + using BaseT::Case; + FIRRTLTypeSwitch(FIRRTLTypeSwitch &&other) = default; + + /// Add a case on the given type. + template + FIRRTLTypeSwitch & + Case(CallableT &&caseFn) { // NOLINT(readability-identifier-naming) + if (result) + return *this; + + // Check to see if CaseT applies to 'value'. Use `type_dyn_cast` here. + if (auto caseValue = circt::firrtl::type_dyn_cast(this->value)) + result.emplace(caseFn(caseValue)); + return *this; + } + + /// As a default, invoke the given callable within the root value. + template + [[nodiscard]] ResultT + Default(CallableT &&defaultFn) { // NOLINT(readability-identifier-naming) + if (result) + return std::move(*result); + return defaultFn(this->value); } - static unsigned getHashValue(FIRRTLBaseType val) { - return mlir::hash_value(val); + + /// As a default, return the given value. + [[nodiscard]] ResultT + Default(ResultT defaultResult) { // NOLINT(readability-identifier-naming) + if (result) + return std::move(*result); + return defaultResult; } - static bool isEqual(FIRRTLBaseType LHS, FIRRTLBaseType RHS) { - return LHS == RHS; + + [[nodiscard]] operator ResultT() { + assert(result && "Fell off the end of a type-switch"); + return std::move(*result); } + +private: + /// The pointer to the result of this switch statement, once known, + /// null before that. + std::optional result; }; -} // namespace llvm +/// Specialization of FIRRTLTypeSwitch for void returning callables. +template +class FIRRTLTypeSwitch + : public llvm::detail::TypeSwitchBase, T> { +public: + using BaseT = llvm::detail::TypeSwitchBase, T>; + using BaseT::BaseT; + using BaseT::Case; + FIRRTLTypeSwitch(FIRRTLTypeSwitch &&other) = default; + + /// Add a case on the given type. + template + FIRRTLTypeSwitch & + Case(CallableT &&caseFn) { // NOLINT(readability-identifier-naming) + if (foundMatch) + return *this; + + // Check to see if any of the types apply to 'value'. + if (auto caseValue = circt::firrtl::type_dyn_cast(this->value)) { + caseFn(caseValue); + foundMatch = true; + } + return *this; + } + + /// As a default, invoke the given callable within the root value. + template + void Default(CallableT &&defaultFn) { // NOLINT(readability-identifier-naming) + if (!foundMatch) + defaultFn(this->value); + } + +private: + /// A flag detailing if we have already found a match. + bool foundMatch = false; +}; + +template +class BaseTypeAliasOr + : public ::mlir::Type::TypeBase, + firrtl::FIRRTLBaseType, + detail::FIRRTLBaseTypeStorage> { + +public: + using mlir::Type::TypeBase, firrtl::FIRRTLBaseType, + detail::FIRRTLBaseTypeStorage>::Base::Base; + // Support LLVM isa/cast/dyn_cast to BaseTy. + static bool classof(Type other) { return type_isa(other); } + + // Support C++ implicit conversions to BaseTy. + operator BaseTy() const { return circt::firrtl::type_cast(*this); } + + BaseTy get() const { return circt::firrtl::type_cast(*this); } +}; + +} // namespace firrtl +} // namespace circt #endif // CIRCT_DIALECT_FIRRTL_TYPES_H diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypes.td b/include/circt/Dialect/FIRRTL/FIRRTLTypes.td index d381d74e19b2..d2794f2c9833 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLTypes.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypes.td @@ -20,12 +20,21 @@ include "FIRRTLDialect.td" //===----------------------------------------------------------------------===// class FIRRTLDialectType - : DialectType { + : DialectType { let description = desc; } -def FIRRTLType : FIRRTLDialectType()">, - "FIRRTLType", "::circt::firrtl::FIRRTLType", [{ +// Helper class to define firrtl types +class FIRRTLDialectTypeHelper + : FIRRTLDialectType($_self)">, summary, + "::circt::firrtl::" # typeName, desc>; + +// Helper class to define firrtl types which allows alias types to be used as well. +class FIRRTLDialectAliasTypeHelper + : FIRRTLDialectType($_self)">, summary, + "::circt::firrtl::BaseTypeAliasOr<::circt::firrtl::" # typeName #">", desc>; + +def FIRRTLType : FIRRTLDialectTypeHelper<"FIRRTLType", "FIRRTLType", [{ Any FIRRTL dialect type, represented by FIRRTLType. }]>; @@ -41,121 +50,183 @@ def FIRRTLBaseType : FIRRTLDialectType< All base types are FIRRTLType's, and inherit from FIRRTLBaseType. }]>; -def ForeignType : FIRRTLDialectType()">, +def ForeignType : FIRRTLDialectType($_self)">, "foreign type", "::mlir::Type">; -def ClockType : FIRRTLDialectType()">, - "clock", "::circt::firrtl::ClockType">; +def ClockType : FIRRTLDialectAliasTypeHelper<"ClockType", "clock">; + +def NonConstClockType : + FIRRTLDialectAliasTypeHelper<"ClockType", "clock">, + BuildableType<"::circt::firrtl::ClockType::get($_builder.getContext())">; + +def IntType : FIRRTLDialectAliasTypeHelper<"IntType", "sint or uint type">; + +def SIntType : FIRRTLDialectAliasTypeHelper<"SIntType", "sint type">; -def IntType : FIRRTLDialectType()">, - "sint or uint type", "::circt::firrtl::IntType">; +def UIntType : FIRRTLDialectAliasTypeHelper<"UIntType", "uint type">; -def SIntType : FIRRTLDialectType()">, - "sint type", "::circt::firrtl::SIntType">; +def AnalogType : FIRRTLDialectAliasTypeHelper<"AnalogType", "analog type">; -def UIntType : FIRRTLDialectType()">, - "uint type", "::circt::firrtl::UIntType">; +def BundleType : FIRRTLDialectAliasTypeHelper<"BundleType", "bundle type">; -def AnalogType : FIRRTLDialectType()">, - "analog type", "::circt::firrtl::AnalogType">; +def OpenBundleType : FIRRTLDialectTypeHelper<"OpenBundleType", "open bundle type">; -def BundleType : FIRRTLDialectType()">, - "BundleType", "::circt::firrtl::BundleType">; +def FVectorType : FIRRTLDialectAliasTypeHelper<"FVectorType", "vector type">; -def FVectorType : FIRRTLDialectType()">, - "FVectorType", "::circt::firrtl::FVectorType">; +def OpenVectorType : FIRRTLDialectTypeHelper<"OpenVectorType", "open vector type">; + +def FEnumType : FIRRTLDialectAliasTypeHelper<"FEnumType", "enum type">; def AggregateType : FIRRTLDialectType< - Or<[ - CPred<"$_self.isa()">, - CPred<"$_self.isa()"> - ]>, + CPred<"type_isa($_self)">, "a aggregate type", "::circt::firrtl::FIRRTLBaseType">; -def ConnectableType : FIRRTLDialectType< - Or<[ - CPred<"$_self.isa()">, - CPred<"$_self.isa()">, - ]>, - "a connectable type (base or ref type)", "::circt::firrtl::FIRRTLType", [{ - Any type that is valid for use in connect statements. - Currently this is any base type or ref type. - }]>; +def PassiveType : FIRRTLDialectType< + CPred<"type_isa($_self) && cast($_self).isPassive()">, + "a passive base type (contain no flips)", "::circt::firrtl::FIRRTLBaseType">; -def SizedBaseOrRefType : FIRRTLDialectType< - Or<[ - CPred<"$_self.isa() && " - "!$_self.cast().hasUninferredWidth()">, - CPred<"$_self.isa() && " - "!$_self.cast().getType().hasUninferredWidth()">, - ]>, - "a sized base or ref type (contains no uninferred widths)", "::circt::firrtl::FIRRTLType">; - -def SizedType : FIRRTLDialectType() && " - "!$_self.cast().hasUninferredWidth()">, +def SizedType : FIRRTLDialectType($_self) && " + "!type_cast($_self).hasUninferredWidth()">, "a sized type (contains no uninferred widths)", "::circt::firrtl::FIRRTLType">; def SizedOrForeignType : AnyTypeOf<[SizedType, ForeignType]>; -def SizedBaseOrRefTypeOrForeignType : AnyTypeOf<[SizedBaseOrRefType, ForeignType]>; +def SizedPassiveType : FIRRTLDialectType, + "a sized passive base type (contains no uninferred widths, or flips)", "::circt::firrtl::FIRRTLType">; +def SizedPassiveOrForeignType : AnyTypeOf<[SizedPassiveType, ForeignType]>; -def UInt1Type : FIRRTLDialectType< - CPred<"$_self.isa() && " - "($_self.cast().getWidth() == 1 ||" - " $_self.cast().getWidth() == std::nullopt)">, - "UInt<1> or UInt", "::circt::firrtl::UIntType">; +def AsyncResetType : FIRRTLDialectAliasTypeHelper<"AsyncResetType", "async reset type">; -def UInt32Type : FIRRTLDialectType< - CPred<"$_self.isa() && " - "$_self.cast().getWidth() == 32">, - "UInt<32>", "::circt::firrtl::UIntType">; +def ResetType : FIRRTLDialectAliasTypeHelper<"ResetType", "reset type">; -def AsyncResetType : FIRRTLDialectType< - CPred<"$_self.isa()">, - "AsyncReset", "::circt::firrtl::AsyncResetType">; -def ResetType : FIRRTLDialectType< - CPred<"$_self.isa()">, - "Reset", "::circt::firrtl::ResetType">; +def RefType : FIRRTLDialectTypeHelper<"RefType", "reference type">; -def PassiveType : FIRRTLDialectType< - CPred<"$_self.isa() && $_self.cast().isPassive()">, - "a passive base type (contain no flips)", "::circt::firrtl::FIRRTLBaseType">; +def RWProbe : FIRRTLDialectType< + CPred<"type_isa($_self) && type_cast($_self).getForceable()">, + "rwprobe type", "::circt::firrtl::RefType">; + +def RWProbeConcreteReset : FIRRTLDialectType< + CPred<[{type_isa($_self) && + type_cast($_self).getForceable() && + !type_cast($_self).hasUninferredReset()}]>, + "rwprobe type (with concrete resets only)", "::circt::firrtl::RefType">; + +def ConnectableType : AnyTypeOf<[FIRRTLBaseType, ForeignType]>; +def StrictConnectableType : AnyTypeOf<[SizedPassiveType, ForeignType]>; + +//===----------------------------------------------------------------------===// +// Sized and Unsized Integers +//===----------------------------------------------------------------------===// + +def UnsizedUIntType : + FIRRTLDialectType< + CPred<"type_isa($_self) && " + "type_cast($_self).getWidth() == std::nullopt">, + "uint with uninferred width", "::circt::firrtl::UIntType">; + +class SizedUIntType : FIRRTLDialectType< + CPred<"type_isa($_self) && " + "type_cast($_self).getWidth() == " # width>, + width # "-bit uint", "::circt::firrtl::UIntType">; -def RefType : FIRRTLDialectType< - CPred<"$_self.isa()">, - "reference type", "::circt::firrtl::RefType">; +class NonConstSizedUIntType : + SizedUIntType, + BuildableType< + "::circt::firrtl::UIntType::get($_builder.getContext(), " # width # ")">; -def SizedRefType : FIRRTLDialectType< - CPred<"$_self.isa() && " - "!$_self.cast().getType().hasUninferredWidth()">, - "sized reference type", "::circt::firrtl::RefType">; +def UInt1Type : SizedUIntType<1>; +def UInt2Type : SizedUIntType<2>; +def UInt32Type : SizedUIntType<32>; +def NonConstUInt1Type : NonConstSizedUIntType<1>; + +def UInt1OrUnsizedType : AnyTypeOf<[UInt1Type, UnsizedUIntType]>; +def UInt2OrUnsizedType : AnyTypeOf<[UInt2Type, UnsizedUIntType]>; //===----------------------------------------------------------------------===// // FIRRTL Types Predicates //===----------------------------------------------------------------------===// def OneBitType : FIRRTLDialectType< - CPred<"($_self.isa() && $_self.cast().getWidth() == 1) || " - "($_self.isa() && $_self.cast().getWidth() == 1)">, + CPred<"(type_isa($_self) && type_cast($_self).getWidth() == 1) || " + "(type_isa($_self) && type_cast($_self).getWidth() == 1)">, "UInt<1>, SInt<1>, or Analog<1>", "::circt::firrtl::FIRRTLBaseType">; def AnyResetType : FIRRTLDialectType< - CPred<"$_self.isa() && $_self.cast().isResetType()">, + CPred<"type_isa($_self) && type_cast($_self).isResetType()">, "Reset", "::circt::firrtl::FIRRTLBaseType">; def AnyRegisterType : FIRRTLDialectType< - CPred<"$_self.isa() && " - "$_self.cast().isRegisterType()">, - "a passive base type that does not contain analog", + CPred<"type_isa($_self) && " + "type_cast($_self).isRegisterType()">, + "a passive non-'const' base type that does not contain analog", "::circt::firrtl::FIRRTLBaseType">; def UIntSIntClockType : AnyTypeOf<[SIntType, UIntType, ClockType], "sint, uint, or clock", "::circt::firrtl::FIRRTLBaseType">; +def 1DVecUIntType : FIRRTLDialectType< + CPred<"type_isa($_self) && type_isa(type_cast($_self).getElementType())">, + "1d vector with UInt element type", "::circt::firrtl::FIRRTLBaseType">; + +def 1DVecIntType : FIRRTLDialectType< + CPred<"type_isa($_self) && type_isa(type_cast($_self).getElementType())">, + "1d vector with Int element type", "::circt::firrtl::FIRRTLBaseType">; + def OneBitCastableType : AnyTypeOf< [OneBitType, AnyResetType, AsyncResetType, ClockType], "1-bit uint/sint/analog, reset, asyncreset, or clock", "::circt::firrtl::FIRRTLBaseType">; +//===----------------------------------------------------------------------===// +// Constraints on RefOps +//===----------------------------------------------------------------------===// + +class RefTypeConstraint + : TypesMatchWith<"reference base type should match", + ref, base, + "type_cast($_self).getType()">; + +class RefResultTypeConstraint + : TypesMatchWith<"reference base type should match", + base, ref, + "RefType::get(type_cast($_self).getPassiveType())">; + +class CompatibleRefTypes + : PredOpTrait<"reference " # dst # " must be compatible with reference " # src # + ": recursively same or uninferred of same and can only demote rwprobe to probe", + CPred<"circt::firrtl::areTypesRefCastable($" # dst # ".getType(), $" # src # ".getType())">>; + +//===----------------------------------------------------------------------===// +// Property Types +//===----------------------------------------------------------------------===// + +def PropertyType : FIRRTLDialectTypeHelper< + "PropertyType", "property type", [{ + A FIRRTL property type, such as a string. + }]>; + +def ClassType : FIRRTLDialectTypeHelper<"ClassType", "class type">; + +def AnyRefType : FIRRTLDialectTypeHelper<"AnyRefType", "any reference type">, + BuildableType<"::circt::firrtl::AnyRefType::get($_builder.getContext())">; + +def StringType : FIRRTLDialectTypeHelper<"StringType", "string type">, + BuildableType<"::circt::firrtl::StringType::get($_builder.getContext())">; + +def FIntegerType : FIRRTLDialectTypeHelper<"FIntegerType", "integer type">, + BuildableType<"::circt::firrtl::FIntegerType::get($_builder.getContext())">; + +def ListType : FIRRTLDialectTypeHelper<"ListType", "list type">; +def MapType : FIRRTLDialectTypeHelper<"MapType", "map type">; + +def BoolType : FIRRTLDialectTypeHelper<"BoolType", "boolean type">, + BuildableType<"::circt::firrtl::BoolType::get($_builder.getContext())">; + +def DoubleType : FIRRTLDialectTypeHelper<"DoubleType", "double type">, + BuildableType<"::circt::firrtl::DoubleType::get($_builder.getContext())">; + +def PathType : FIRRTLDialectTypeHelper<"PathType", "path type">, + BuildableType<"::circt::firrtl::PathType::get($_builder.getContext())">; + #endif // CIRCT_DIALECT_FIRRTL_FIRRTLTYPES_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td b/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td index 61895c2573b2..9e6261d10f3a 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td @@ -14,20 +14,22 @@ #define CIRCT_DIALECT_FIRRTL_FIRRTLTYPESIMPL_TD include "FIRRTLDialect.td" +include "circt/Dialect/FIRRTL/FIRRTLTypeInterfaces.td" include "circt/Dialect/HW/HWTypeInterfaces.td" // Base class for other typedefs. Provides dialact-specific defaults. class FIRRTLImplType traits = [], string baseCppClass = "::circt::firrtl::FIRRTLBaseType"> - : TypeDef {} - -//===----------------------------------------------------------------------===// -// Type Traits -//===----------------------------------------------------------------------===// + : TypeDef { + // Storage classes must be defined in C++ and + // inherit from FIRRTLBaseTypeStorage + let genStorageClass = false; -def WidthQualifiedTypeTrait : NativeTypeTrait<"WidthQualifiedTypeTrait"> { - let cppNamespace = "::circt::firrtl"; + // MLIR generates awkward accessor "getIsConst" for the "isConst" parameter, + // which is common on FIRRTLBaseType anyway, so we generate the other + // accessors manually + let genAccessors = false; } //===----------------------------------------------------------------------===// @@ -35,123 +37,181 @@ def WidthQualifiedTypeTrait : NativeTypeTrait<"WidthQualifiedTypeTrait"> { //===----------------------------------------------------------------------===// def SIntImpl : FIRRTLImplType<"SInt", - [WidthQualifiedTypeTrait, FieldIDTypeInterface], + [WidthQualifiedTypeTrait], "::circt::firrtl::IntType"> { let summary = "A signed integer type, whose width may not be known."; - let parameters = (ins "int32_t":$widthOrSentinel); + let parameters = (ins "int32_t":$widthOrSentinel, "bool":$isConst); + let storageClass = "WidthTypeStorage"; let builders = [ - TypeBuilder<(ins "std::optional":$width)>, + TypeBuilder<(ins "std::optional":$width, CArg<"bool", "false">:$isConst)>, TypeBuilder<(ins)>, ]; let genVerifyDecl = true; let extraClassDeclaration = [{ using WidthQualifiedTypeTrait::getWidth; using WidthQualifiedTypeTrait::hasWidth; + int32_t getWidthOrSentinel() const; + SIntType getConstType(bool isConst); }]; } def UIntImpl : FIRRTLImplType<"UInt", - [WidthQualifiedTypeTrait, FieldIDTypeInterface], + [WidthQualifiedTypeTrait], "::circt::firrtl::IntType"> { let summary = "An unsigned integer type, whose width may not be known."; - let parameters = (ins "int32_t":$widthOrSentinel); + let parameters = (ins "int32_t":$widthOrSentinel, "bool":$isConst); + let storageClass = "WidthTypeStorage"; let builders = [ - TypeBuilder<(ins "std::optional":$width)>, + TypeBuilder<(ins "std::optional":$width, CArg<"bool", "false">:$isConst)>, TypeBuilder<(ins)>, ]; let genVerifyDecl = true; let extraClassDeclaration = [{ using WidthQualifiedTypeTrait::getWidth; using WidthQualifiedTypeTrait::hasWidth; + int32_t getWidthOrSentinel() const; + UIntType getConstType(bool isConst); }]; } -def ClockTypeImpl : FIRRTLImplType<"Clock", [FieldIDTypeInterface]> { +def ClockTypeImpl : FIRRTLImplType<"Clock"> { let summary = "Clock signal"; + let parameters = (ins "bool":$isConst); + let storageClass = "FIRRTLBaseTypeStorage"; + let builders = [ + TypeBuilder<(ins), [{ + return $_get($_ctxt, false); + }]> + ]; + let extraClassDeclaration = [{ + ClockType getConstType(bool isConst); + }]; } -def ResetTypeImpl : FIRRTLImplType<"Reset", [FieldIDTypeInterface]> { +def ResetTypeImpl : FIRRTLImplType<"Reset"> { let summary = "Reset Signal"; + let parameters = (ins "bool":$isConst); + let storageClass = "FIRRTLBaseTypeStorage"; + let builders = [ + TypeBuilder<(ins), [{ + return $_get($_ctxt, false); + }]> + ]; + let extraClassDeclaration = [{ + ResetType getConstType(bool isConst); + }]; } -def AsyncResetTypeImpl : FIRRTLImplType<"AsyncReset", [FieldIDTypeInterface]> { +def AsyncResetTypeImpl : FIRRTLImplType<"AsyncReset"> { let summary = "AsyncReset signal"; + let parameters = (ins "bool":$isConst); + let storageClass = "FIRRTLBaseTypeStorage"; + let builders = [ + TypeBuilder<(ins), [{ + return $_get($_ctxt, false); + }]> + ]; + let extraClassDeclaration = [{ + AsyncResetType getConstType(bool isConst); + }]; } def AnalogTypeImpl : FIRRTLImplType<"Analog", - [WidthQualifiedTypeTrait, FieldIDTypeInterface]> { + [WidthQualifiedTypeTrait]> { let summary = "Analog signal"; - let parameters = (ins "int32_t":$widthOrSentinel); + let parameters = (ins "int32_t":$widthOrSentinel, "bool":$isConst); + let storageClass = "WidthTypeStorage"; let builders = [ - TypeBuilder<(ins "std::optional":$width)>, + TypeBuilder<(ins "std::optional":$width, CArg<"bool", "false">:$isConst)>, TypeBuilder<(ins)>, ]; + let extraClassDeclaration = [{ + int32_t getWidthOrSentinel() const; + AnalogType getConstType(bool isConst); + }]; let genVerifyDecl = true; } -def FVectorTypeImpl : FIRRTLImplType<"FVector", [FieldIDTypeInterface]> { +class BaseVectorTypeImpl traits = [], string BaseType = ElementType> + : FIRRTLImplType], BaseType> { let summary = "a fixed size collection of elements, like an array."; - let genStorageClass = false; let parameters = (ins - TypeParameter<"::circt::firrtl::FIRRTLBaseType", "Type of vector elements">:$elementType, - "size_t":$numElements + TypeParameter:$elementType, + "size_t":$numElements, + "bool":$isConst ); + let storageClass = name # "TypeStorage"; let skipDefaultBuilders = true; let builders = [ TypeBuilderWithInferredContext<(ins - "FIRRTLBaseType":$elementType, - "size_t":$numElements) + ElementType:$elementType, + "size_t":$numElements, + CArg<"bool", "false">:$isConst) > ]; + + // Additional class declarations to emit. + code firrtlExtraClassDeclaration = ""; + let extraClassDeclaration = [{ + using ElementType = }] # ElementType # [{; + + ElementType getElementType() const; + size_t getNumElements() const; + /// Return the recursive properties of the type. - RecursiveTypeProperties getRecursiveTypeProperties(); + RecursiveTypeProperties getRecursiveTypeProperties() const; + + /// Const support. + bool isConst(); + ElementType getElementTypePreservingConst(); + /// Return a 'const' or non-'const' version of this type. + }] # name # [{Type getConstType(bool isConst); + + }] # firrtlExtraClassDeclaration; +} + +def FVectorImpl : BaseVectorTypeImpl<"FVector","::circt::firrtl::FIRRTLBaseType"> { + let firrtlExtraClassDeclaration = [{ /// Return this type with any flip types recursively removed from itself. FIRRTLBaseType getPassiveType(); - /// Get an integer ID for the field. Field IDs start at 1, and are assigned - /// to each field in a vector in a recursive depth-first walk of all - /// elements. A field ID of 0 is used to reference the vector itself. - uint64_t getFieldID(uint64_t index); - - /// Find the element index corresponding to the desired fieldID. If the - /// fieldID corresponds to a field in nested under an element, it will - /// return the index of the parent element. - uint64_t getIndexForFieldID(uint64_t fieldID); - - /// Find the index of the element that contains the given fieldID. - /// As well, rebase the fieldID to the element. - std::pair getIndexAndSubfieldID(uint64_t fieldID); - - /// Strip off a single layer of this type and return the sub-type and a - /// field ID targeting the same field, but rebased on the sub-type. - std::pair getSubTypeByFieldID(uint64_t fieldID); - - /// Get the maximum field ID in this vector. This is helpful for - /// constructing field IDs when this VectorType is nested in another - /// aggregate type. - uint64_t getMaxFieldID(); - - /// Returns the effective field id when treating the index field as the root - /// of the type. Essentially maps a fieldID to a fieldID after a subfield - /// op. Returns the new id and whether the id is in the given child. - std::pair rootChildFieldID(uint64_t fieldID, uint64_t index); + /// Return this type with a 'const' modifiers dropped + FVectorType getAllConstDroppedType(); + + /// Return this type with any type alias types recursively removed from itself. + FIRRTLBaseType getAnonymousType(); }]; } -def BundleImpl : FIRRTLImplType<"Bundle", [FieldIDTypeInterface]> { +def OpenVectorImpl : BaseVectorTypeImpl<"OpenVector","::circt::firrtl::FIRRTLType"> { + let genVerifyDecl = 1; +} + +class BaseBundleTypeImpl traits = [], string BaseType = ElementType> + : FIRRTLImplType], BaseType> { let summary = "an aggregate of named elements. This is effectively a struct."; - let genStorageClass = false; - let parameters = (ins "ArrayRef":$elements); + let parameters = (ins "ArrayRef":$elements, "bool":$isConst); + let storageClass = name # "TypeStorage"; + let skipDefaultBuilders = true; + let builders = [ + TypeBuilder<(ins "ArrayRef":$elements, CArg<"bool", "false">:$isConst)> + ]; + + // Additional class declarations to emit. + code firrtlExtraClassDeclaration = ""; + let extraClassDeclaration = [{ + using ElementType = }] # ElementType # [{; + /// Each element of a bundle, which is a name and type. struct BundleElement { StringAttr name; bool isFlip; - FIRRTLBaseType type; + ElementType type; - BundleElement(StringAttr name, bool isFlip, FIRRTLBaseType type) + BundleElement(StringAttr name, bool isFlip, ElementType type) : name(name), isFlip(isFlip), type(type) {} bool operator==(const BundleElement &rhs) const { @@ -161,18 +221,21 @@ def BundleImpl : FIRRTLImplType<"Bundle", [FieldIDTypeInterface]> { return !operator==(rhs); } - friend llvm::hash_code hash_value(const BundleType::BundleElement &arg) { - return mlir::hash_value(arg.name) ^ mlir::hash_value(arg.type); + friend llvm::hash_code hash_value(const BundleElement &arg) { + return llvm::hash_combine(arg.name, arg.isFlip, arg.type); } }; - size_t getNumElements() { return getElements().size(); } + ArrayRef getElements() const; + + size_t getNumElements() const { return getElements().size(); } /// Look up an element's index by name. This returns None on failure. std::optional getElementIndex(StringAttr name); std::optional getElementIndex(StringRef name); /// Look up an element's name by index. This asserts if index is invalid. + StringAttr getElementNameAttr(size_t index); StringRef getElementName(size_t index); /// Look up an element by name. This returns None on failure. @@ -183,77 +246,296 @@ def BundleImpl : FIRRTLImplType<"Bundle", [FieldIDTypeInterface]> { BundleElement getElement(size_t index); /// Look up an element type by name. - FIRRTLBaseType getElementType(StringAttr name); - FIRRTLBaseType getElementType(StringRef name); + ElementType getElementType(StringAttr name); + ElementType getElementType(StringRef name); /// Look up an element type by index. - FIRRTLBaseType getElementType(size_t index); + ElementType getElementType(size_t index) const; /// Return the recursive properties of the type. - RecursiveTypeProperties getRecursiveTypeProperties(); + RecursiveTypeProperties getRecursiveTypeProperties() const; + + using iterator = ArrayRef::iterator; + iterator begin() const { return getElements().begin(); } + iterator end() const { return getElements().end(); } + /// Const support. + bool isConst(); + ElementType getElementTypePreservingConst(size_t index); + + /// Return a 'const' or non-'const' version of this type. + }] # name # [{Type getConstType(bool isConst); + + }] # firrtlExtraClassDeclaration; +} + +def BundleImpl : BaseBundleTypeImpl<"Bundle","::circt::firrtl::FIRRTLBaseType"> { + let firrtlExtraClassDeclaration = [{ /// Return this type with any flip types recursively removed from itself. FIRRTLBaseType getPassiveType(); - /// Get an integer ID for the field. Field IDs start at 1, and are assigned - /// to each field in a bundle in a recursive pre-order walk of all fields, - /// visiting all nested bundle fields. A field ID of 0 is used to reference - /// the bundle itself. The ID can be used to uniquely identify any specific - /// field in this bundle. - uint64_t getFieldID(uint64_t index); - - /// Find the element index corresponding to the desired fieldID. If the - /// fieldID corresponds to a field in a nested bundle, it will return the - /// index of the parent field. - uint64_t getIndexForFieldID(uint64_t fieldID); - - /// Find the index of the element that contains the given fieldID. - /// As well, rebase the fieldID to the element. - std::pair getIndexAndSubfieldID(uint64_t fieldID); - - /// Strip off a single layer of this type and return the sub-type and a - /// field ID targeting the same field, but rebased on the sub-type. - std::pair getSubTypeByFieldID(uint64_t fieldID); - - /// Get the maximum field ID in this bundle. This is helpful for - /// constructing field IDs when this BundleType is nested in another - /// aggregate type. - uint64_t getMaxFieldID(); - - /// Returns the effective field id when treating the index field as the root - /// of the type. Essentially maps a fieldID to a fieldID after a subfield - /// op. Returns the new id and whether the id is in the given child. - std::pair rootChildFieldID(uint64_t fieldID, - uint64_t index); + /// Return this type with a 'const' modifiers dropped + BundleType getAllConstDroppedType(); - using iterator = ArrayRef::iterator; + /// Return this type with any type alias types recursively removed from itself. + FIRRTLBaseType getAnonymousType(); + }]; +} + +def OpenBundleImpl : BaseBundleTypeImpl<"OpenBundle","::circt::firrtl::FIRRTLType"> { + let genVerifyDecl = 1; +} + +def FEnumImpl : FIRRTLImplType<"FEnum", [DeclareTypeInterfaceMethods]> { + let summary = "a sum type of named elements."; + let parameters = (ins "ArrayRef":$elements, "bool":$isConst); + let storageClass = "FEnumTypeStorage"; + let genVerifyDecl = true; + let skipDefaultBuilders = true; + let builders = [ + TypeBuilder<(ins "ArrayRef":$elements, CArg<"bool", "false">:$isConst)> + ]; + let extraClassDeclaration = [{ + /// Each element of an enum, which is a name and type. + struct EnumElement { + StringAttr name; + FIRRTLBaseType type; + + EnumElement(StringAttr name, FIRRTLBaseType type) + : name(name), type(type) {} + + bool operator==(const EnumElement &rhs) const { + return name == rhs.name && type == rhs.type; + } + bool operator!=(const EnumElement &rhs) const { + return !operator==(rhs); + } + + friend llvm::hash_code hash_value(const FEnumType::EnumElement &arg) { + return mlir::hash_value(arg.name) ^ mlir::hash_value(arg.type); + } + }; + + ArrayRef getElements() const; + + size_t getNumElements() const { return getElements().size(); } + + FEnumType getConstType(bool isConst); + + /// Return this type with a 'const' modifiers dropped + FEnumType getAllConstDroppedType(); + + /// Return this type with any type alias types recursively removed from itself. + FIRRTLBaseType getAnonymousType(); + + /// Look up an element's index by name. This returns None on failure. + std::optional getElementIndex(StringAttr name); + std::optional getElementIndex(StringRef name); + + /// Look up an element's name by index. This asserts if index is invalid. + StringAttr getElementNameAttr(size_t index); + StringRef getElementName(size_t index); + + /// Look up an element by name. This returns None on failure. + std::optional getElement(StringAttr name); + std::optional getElement(StringRef name); + + /// Look up an element by index. This asserts if index is invalid. + EnumElement getElement(size_t index); + + /// Look up an element type by name. + FIRRTLBaseType getElementType(StringAttr name); + FIRRTLBaseType getElementType(StringRef name); + + /// Look up an element type by index. + FIRRTLBaseType getElementType(size_t index) const; + FIRRTLBaseType getElementTypePreservingConst(size_t index); + + /// Return the recursive properties of the type. + RecursiveTypeProperties getRecursiveTypeProperties() const; + + using iterator = ArrayRef::iterator; iterator begin() const { return getElements().begin(); } iterator end() const { return getElements().end(); } }]; } -def RefImpl : FIRRTLImplType<"Ref", [], "::circt::firrtl::FIRRTLType"> { - let summary = [{ - A reference type, such as `firrtl.ref>`. +def RefImpl : FIRRTLImplType<"Ref", + [], + "::circt::firrtl::FIRRTLType"> { + let summary = "A reference to a signal elsewhere."; + let description = [{ + A reference type, such as `firrtl.probe>` or `firrtl.rwprobe>`. Used for remote reads and writes of the wrapped base type. - Parameterized over the referenced base type, - which must be passive and for now must also be ground. + Parameterized over the referenced base type, with flips removed. Not a base type. Values of this type are used to capture dataflow paths, and do not represent a circuit element or entity. + + Generally read-only (probe), optionally forceable (rwprobe). }]; let parameters = (ins TypeParameter<"::circt::firrtl::FIRRTLBaseType", - "Type of reference target">:$type); + "Type of reference target">:$type, + "bool":$forceable); + let genAccessors = true; + let genStorageClass = true; let genVerifyDecl = true; let skipDefaultBuilders = true; let builders = [ - TypeBuilderWithInferredContext<(ins "::circt::firrtl::FIRRTLBaseType":$type)> + TypeBuilderWithInferredContext<(ins "::circt::firrtl::FIRRTLBaseType":$type, + CArg<"bool", "false">:$forceable)> + ]; + + let extraClassDeclaration = [{ + /// Return the recursive properties of the type. + RecursiveTypeProperties getRecursiveTypeProperties() const; + }]; +} + +def BaseTypeAliasImpl : FIRRTLImplType<"BaseTypeAlias", [DeclareTypeInterfaceMethods], + "::circt::firrtl::FIRRTLBaseType"> { + let summary = "type alias for firrtl base types"; + let parameters = + (ins "StringAttr":$name, TypeParameter<"::circt::firrtl::FIRRTLBaseType", + "An inner type">:$innerType); + let storageClass = "BaseTypeAliasStorage"; + let genAccessors = true; + let skipDefaultBuilders = true; + let extraClassDeclaration = [{ + // FIRRTLBaseType utils. + FIRRTLBaseType getAnonymousType(); + FIRRTLBaseType getPassiveType(); + FIRRTLBaseType getConstType(bool isConst); + FIRRTLBaseType getAllConstDroppedType(); + + /// Return the recursive properties of the type. + RecursiveTypeProperties getRecursiveTypeProperties() const; + + // If a given `newInnerType` is identical to innerType, return `*this` + // because we can reuse the type alias. Otherwise return `newInnerType`. + FIRRTLBaseType getModifiedType(FIRRTLBaseType newInnerType); + }]; + + let builders = [ + TypeBuilderWithInferredContext<(ins "::mlir::StringAttr":$name, + "::circt::firrtl::FIRRTLBaseType":$innerType)> + ]; +} + +//===----------------------------------------------------------------------===// +// Non-Hardware Type Definitions +//===----------------------------------------------------------------------===// + +class PropImplType traits = [], + string baseCppClass = "::circt::firrtl::FIRRTLType"> + : FIRRTLImplType; + +def ClassImpl : PropImplType<"Class", [ + DeclareTypeInterfaceMethods]> { + let summary = [{ + An instance of a class. + + Example: + ```mlir + !firrtl.class<@Module(in p0: !firrtl.uint<8>, out p1: !firrtl.uint<8>)> + ``` + }]; + let parameters = (ins + "FlatSymbolRefAttr":$name, + ArrayRefParameter<"ClassElement">:$elements + ); + let genStorageClass = false; + let genAccessors = false; + let builders = [ + TypeBuilderWithInferredContext<(ins + "FlatSymbolRefAttr":$name, + "ArrayRef":$elements + )> ]; + let extraClassDeclaration = [{ + /// Helper to parse this type as a module interface. + static ParseResult parseInterface( + AsmParser &parser, ClassType &result); + + StringRef getName() const; + FlatSymbolRefAttr getNameAttr() const; + + ArrayRef getElements() const; + size_t getNumElements() const { return getElements().size(); } + const ClassElement &getElement(IntegerAttr index) const; + const ClassElement &getElement(size_t index) const; + std::optional getElementIndex(StringRef fieldName) const; + + /// Helper to print this type as a module interface. + /// Example: + /// ``` + /// @MyClass(in %port: !firrtl.uint<8>, out o: !firrtl.uint<8>) + /// ``` + void printInterface(AsmPrinter &p) const; + }]; +} + +def AnyRefImpl : PropImplType<"AnyRef"> { + let summary = "A reference to an instance of any class."; + let description = [{ + A reference of type AnyRef can be used in ports, property assignments, and + any other Property "plumbing" ops. But it is opaque, and references to + objects of AnyRef type cannot be "dereferenced". There is no information + about the referred to object's fields, so subfield access, etc. is illegal. + }]; + let genStorageClass = true; +} + +def StringImpl : PropImplType<"String"> { + let summary = "An unlimited length string type. Not representable in hardware."; + let parameters = (ins); + let genStorageClass = true; +} + +def IntegerImpl : PropImplType<"FInteger"> { + let summary = "An unlimited length signed integer type. Not representable in hardware."; + let parameters = (ins); + let genStorageClass = true; +} + +def ListImpl : PropImplType<"List"> { + let summary = "A typed property list of any length. Not representable in hardware."; + let parameters = (ins TypeParameter<"circt::firrtl::PropertyType", "element type">:$elementType); + let genStorageClass = true; + let genAccessors = true; +} + +def MapImpl : PropImplType<"Map"> { + let summary = "A typed map of properties. Not representable in hardware."; + let parameters = (ins TypeParameter<"circt::firrtl::PropertyType", "key type">:$keyType, + TypeParameter<"circt::firrtl::PropertyType", "value type">:$valueType); + let genStorageClass = true; + let genAccessors = true; +} + +def PathImpl : PropImplType<"Path"> { + let summary = "A path to a hardware entity. Not representable in hardware."; + let parameters = (ins); + let genStorageClass = true; + let genAccessors = true; +} + +def BoolImpl : PropImplType<"Bool"> { + let summary = "A boolean property. Not representable in hardware."; + let parameters = (ins); + let genStorageClass = true; +} + +def DoubleImpl : PropImplType<"Double"> { + let summary = "A double property. Not representable in hardware."; + let parameters = (ins); + let genStorageClass = true; } #endif // CIRCT_DIALECT_FIRRTL_FIRRTLTYPESIMPL_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLUtils.h b/include/circt/Dialect/FIRRTL/FIRRTLUtils.h index cbd3251861ce..50cb3aa09167 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLUtils.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLUtils.h @@ -14,11 +14,14 @@ #define CIRCT_DIALECT_FIRRTL_FIRRTLUTILS_H #include "circt/Dialect/FIRRTL/FIRRTLOps.h" -#include "circt/Dialect/FIRRTL/Namespace.h" #include "mlir/IR/BuiltinOps.h" #include "llvm/Support/Parallel.h" namespace circt { +namespace hw { +struct InnerSymbolNamespace; +} // namespace hw + namespace firrtl { /// Emit a connect between two values. void emitConnect(OpBuilder &builder, Location loc, Value lhs, Value rhs); @@ -33,6 +36,9 @@ IntegerAttr getIntZerosAttr(Type type); /// Utility for generating a constant all ones attribute. IntegerAttr getIntOnesAttr(Type type); +/// Return the single assignment to a Property value. +PropAssignOp getPropertyAssignment(FIRRTLPropertyValue value); + /// Return the module-scoped driver of a value only looking through one connect. Value getDriverFromConnect(Value val); @@ -78,8 +84,12 @@ bool walkDrivers(FIRRTLBaseValue value, bool lookThroughWires, /// Get the FieldRef from a value. This will travel backwards to through the /// IR, following Subfield and Subindex to find the op which declares the -/// location. -FieldRef getFieldRefFromValue(Value value); +/// location. Optionally look through recognized cast operations, which +/// likely will result in source having slightly different type. +FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts = false); + +/// Get the delta indexing from a value, as a FieldRef. +FieldRef getDeltaRef(Value value, bool lookThroughCasts = false); /// Get a string identifier representing the FieldRef. Return this string and a /// boolean indicating if a valid "root" for the identifier was found. If @@ -100,44 +110,121 @@ Value getValueByFieldID(ImplicitLocOpBuilder builder, Value value, /// Walk leaf ground types in the `firrtlType` and apply the function `fn`. /// The first argument of `fn` is field ID, and the second argument is a -/// leaf ground type. -void walkGroundTypes(FIRRTLType firrtlType, - llvm::function_ref fn); +/// leaf ground type, and the third argument indicates if the element was +/// flipped in a bundle. +void walkGroundTypes( + FIRRTLType firrtlType, + llvm::function_ref fn); + //===----------------------------------------------------------------------===// // Inner symbol and InnerRef helpers. //===----------------------------------------------------------------------===// -/// Returns an operation's `inner_sym`, adding one if necessary. -StringAttr -getOrAddInnerSym(Operation *op, StringRef nameHint, FModuleOp mod, - std::function getNamespace); +/// Return the inner sym target for the specified value and fieldID. +/// If root is a blockargument, this must be FModuleLike. +hw::InnerSymTarget getTargetFor(FieldRef ref); -/// Obtain an inner reference to an operation, possibly adding an `inner_sym` -/// to that operation. -hw::InnerRefAttr -getInnerRefTo(Operation *op, StringRef nameHint, - std::function getNamespace); +/// Ensure that the the InnerSymAttr has a symbol on the field specified. +/// Returns the updated InnerSymAttr as well as the name of the symbol attached +/// to the specified field. +std::pair +getOrAddInnerSym(MLIRContext *context, hw::InnerSymAttr attr, uint64_t fieldID, + llvm::function_ref getNamespace); -/// Returns a port's `inner_sym`, adding one if necessary. +/// Returns an inner symbol identifier for the specified target (op or port), +/// adding one if necessary. StringAttr -getOrAddInnerSym(FModuleLike mod, size_t portIdx, StringRef nameHint, - std::function getNamespace); +getOrAddInnerSym(const hw::InnerSymTarget &target, + llvm::function_ref getNamespace); + +using GetNamespaceCallback = + llvm::function_ref; + +/// Returns an inner symbol identifier for the specified target (op or port), +/// adding one if necessary. +StringAttr getOrAddInnerSym(const hw::InnerSymTarget &target, + GetNamespaceCallback getNamespace); + +/// Obtain an inner reference to the target (operation or port), +/// adding an inner symbol as necessary. +hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, + GetNamespaceCallback getNamespace); + +/// Returns an inner symbol identifier for the specified operation, adding one +/// if necessary. +static inline StringAttr getOrAddInnerSym(Operation *op, + GetNamespaceCallback getNamespace) { + return getOrAddInnerSym(hw::InnerSymTarget(op), getNamespace); +} +/// Returns an inner symbol identifier for the specified operation's field +/// adding one if necessary. +static inline StringAttr getOrAddInnerSym(Operation *op, uint64_t fieldID, + GetNamespaceCallback getNamespace) { + return getOrAddInnerSym(hw::InnerSymTarget(op, fieldID), getNamespace); +} -/// Obtain an inner reference to a port, possibly adding an `inner_sym` -/// to the port. -hw::InnerRefAttr -getInnerRefTo(FModuleLike mod, size_t portIdx, StringRef nameHint, - std::function getNamespace); +/// Obtain an inner reference to an operation, possibly adding an inner symbol. +static inline hw::InnerRefAttr +getInnerRefTo(Operation *op, GetNamespaceCallback getNamespace) { + return getInnerRefTo(hw::InnerSymTarget(op), getNamespace); +} + +/// Obtain an inner reference to an operation's field, possibly adding an inner +/// symbol. +static inline hw::InnerRefAttr +getInnerRefTo(Operation *op, uint64_t fieldID, + GetNamespaceCallback getNamespace) { + return getInnerRefTo(hw::InnerSymTarget(op, fieldID), getNamespace); +} + +/// Returns an inner symbol identifier for the specified port, adding one if +/// necessary. +static inline StringAttr getOrAddInnerSym(FModuleLike mod, size_t portIdx, + GetNamespaceCallback getNamespace) { + return getOrAddInnerSym(hw::InnerSymTarget(portIdx, mod), getNamespace); +} + +/// Returns an inner symbol identifier for the specified port's field, adding +/// one if necessary. +static inline StringAttr getOrAddInnerSym(FModuleLike mod, size_t portIdx, + uint64_t fieldID, + GetNamespaceCallback getNamespace) { + return getOrAddInnerSym(hw::InnerSymTarget(portIdx, mod, fieldID), + getNamespace); +} + +/// Obtain an inner reference to a port, possibly adding an inner symbol. +static inline hw::InnerRefAttr +getInnerRefTo(FModuleLike mod, size_t portIdx, + GetNamespaceCallback getNamespace) { + return getInnerRefTo(hw::InnerSymTarget(portIdx, mod), getNamespace); +} + +/// Obtain an inner reference to a port's field, possibly adding an inner +/// symbol. +static inline hw::InnerRefAttr +getInnerRefTo(FModuleLike mod, size_t portIdx, uint64_t fieldID, + GetNamespaceCallback getNamespace) { + return getInnerRefTo(hw::InnerSymTarget(portIdx, mod, fieldID), getNamespace); +} //===----------------------------------------------------------------------===// // Type utilities //===----------------------------------------------------------------------===// -/// If reftype, return wrapped base type. Otherwise (if base), return as-is. -inline FIRRTLBaseType getBaseType(FIRRTLType type) { - return TypeSwitch(type) +/// If it is a base type, return it as is. If reftype, return wrapped base type. +/// Otherwise, return null. +inline FIRRTLBaseType getBaseType(Type type) { + return TypeSwitch(type) .Case([](auto base) { return base; }) - .Case([](auto ref) { return ref.getType(); }); + .Case([](auto ref) { return ref.getType(); }) + .Default([](Type type) { return nullptr; }); +} + +/// Get base type if isa<> the requested type, else null. +template +inline T getBaseOfType(Type type) { + return dyn_cast_or_null(getBaseType(type)); } /// Return a FIRRTLType with its base type component mutated by the given @@ -146,13 +233,35 @@ inline FIRRTLType mapBaseType(FIRRTLType type, function_ref fn) { return TypeSwitch(type) .Case([&](auto base) { return fn(base); }) - .Case([&](auto ref) { return RefType::get(fn(ref.getType())); }); + .Case([&](auto ref) { + return RefType::get(fn(ref.getType()), ref.getForceable()); + }); +} + +/// Return a FIRRTLType with its base type component mutated by the given +/// function. Return null when the function returns null. +/// (i.e., ref -> ref if f(T) != null else null, and T -> f(T)). +inline FIRRTLType +mapBaseTypeNullable(FIRRTLType type, + function_ref fn) { + return TypeSwitch(type) + .Case([&](auto base) { return fn(base); }) + .Case([&](auto ref) -> FIRRTLType { + auto result = fn(ref.getType()); + if (!result) + return {}; + return RefType::get(result, ref.getForceable()); + }); } /// Given a type, return the corresponding lowered type for the HW dialect. /// Non-FIRRTL types are simply passed through. This returns a null type if it -/// cannot be lowered. -Type lowerType(Type type); +/// cannot be lowered. The optional function is required to specify how to lower +/// AliasTypes. +Type lowerType( + Type type, std::optional loc = {}, + llvm::function_ref + getTypeDeclFn = {}); //===----------------------------------------------------------------------===// // Parser-related utilities diff --git a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h index 01867511ef8f..11627e51f691 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h @@ -31,11 +31,14 @@ class ExprVisitor { // Basic Expressions .template Case< ConstantOp, SpecialConstantOp, AggregateConstantOp, InvalidValueOp, - SubfieldOp, SubindexOp, SubaccessOp, BundleCreateOp, VectorCreateOp, - MultibitMuxOp, + SubfieldOp, SubindexOp, SubaccessOp, IsTagOp, SubtagOp, + BundleCreateOp, VectorCreateOp, FEnumCreateOp, MultibitMuxOp, + TagExtractOp, OpenSubfieldOp, OpenSubindexOp, ObjectSubfieldOp, // Arithmetic and Logical Binary Primitives. AddPrimOp, SubPrimOp, MulPrimOp, DivPrimOp, RemPrimOp, AndPrimOp, OrPrimOp, XorPrimOp, + // Elementwise operations, + ElementwiseOrPrimOp, ElementwiseAndPrimOp, ElementwiseXorPrimOp, // Comparisons. LEQPrimOp, LTPrimOp, GEQPrimOp, GTPrimOp, EQPrimOp, NEQPrimOp, // Misc Binary Primitives. @@ -45,14 +48,25 @@ class ExprVisitor { CvtPrimOp, NegPrimOp, NotPrimOp, AndRPrimOp, OrRPrimOp, XorRPrimOp, // Intrinsic Expressions. IsXIntrinsicOp, PlusArgsValueIntrinsicOp, PlusArgsTestIntrinsicOp, - SizeOfIntrinsicOp, + SizeOfIntrinsicOp, ClockGateIntrinsicOp, LTLAndIntrinsicOp, + LTLOrIntrinsicOp, LTLDelayIntrinsicOp, LTLConcatIntrinsicOp, + LTLNotIntrinsicOp, LTLImplicationIntrinsicOp, + LTLEventuallyIntrinsicOp, LTLClockIntrinsicOp, + LTLDisableIntrinsicOp, Mux2CellIntrinsicOp, Mux4CellIntrinsicOp, + HasBeenResetIntrinsicOp, // Miscellaneous. BitsPrimOp, HeadPrimOp, MuxPrimOp, PadPrimOp, ShlPrimOp, ShrPrimOp, TailPrimOp, VerbatimExprOp, HWStructCastOp, BitCastOp, RefSendOp, - RefResolveOp, RefSubOp, mlir::UnrealizedConversionCastOp>( - [&](auto expr) -> ResultType { - return thisCast->visitExpr(expr, args...); - }) + RefResolveOp, RefSubOp, RWProbeOp, XMRRefOp, XMRDerefOp, + // Casts to deal with weird stuff + UninferredResetCastOp, ConstCastOp, RefCastOp, + mlir::UnrealizedConversionCastOp, + // Property expressions. + StringConstantOp, FIntegerConstantOp, BoolConstantOp, + DoubleConstantOp, ListCreateOp, MapCreateOp, UnresolvedPathOp, + PathOp>([&](auto expr) -> ResultType { + return thisCast->visitExpr(expr, args...); + }) .Default([&](auto expr) -> ResultType { return thisCast->visitInvalidExpr(op, args...); }); @@ -94,10 +108,17 @@ class ExprVisitor { HANDLE(AggregateConstantOp, Unhandled); HANDLE(BundleCreateOp, Unhandled); HANDLE(VectorCreateOp, Unhandled); + HANDLE(FEnumCreateOp, Unhandled); HANDLE(SubfieldOp, Unhandled); HANDLE(SubindexOp, Unhandled); HANDLE(SubaccessOp, Unhandled); + HANDLE(IsTagOp, Unhandled); + HANDLE(SubtagOp, Unhandled); + HANDLE(TagExtractOp, Unhandled); HANDLE(MultibitMuxOp, Unhandled); + HANDLE(OpenSubfieldOp, Unhandled); + HANDLE(OpenSubindexOp, Unhandled); + HANDLE(ObjectSubfieldOp, Unhandled); // Arithmetic and Logical Binary Primitives. HANDLE(AddPrimOp, Binary); @@ -135,11 +156,28 @@ class ExprVisitor { HANDLE(OrRPrimOp, Unary); HANDLE(XorRPrimOp, Unary); + HANDLE(ElementwiseOrPrimOp, Unhandled); + HANDLE(ElementwiseAndPrimOp, Unhandled); + HANDLE(ElementwiseXorPrimOp, Unhandled); + // Intrinsic Expr. HANDLE(IsXIntrinsicOp, Unhandled); HANDLE(PlusArgsValueIntrinsicOp, Unhandled); HANDLE(PlusArgsTestIntrinsicOp, Unhandled); HANDLE(SizeOfIntrinsicOp, Unhandled); + HANDLE(ClockGateIntrinsicOp, Unhandled); + HANDLE(LTLAndIntrinsicOp, Unhandled); + HANDLE(LTLOrIntrinsicOp, Unhandled); + HANDLE(LTLDelayIntrinsicOp, Unhandled); + HANDLE(LTLConcatIntrinsicOp, Unhandled); + HANDLE(LTLNotIntrinsicOp, Unhandled); + HANDLE(LTLImplicationIntrinsicOp, Unhandled); + HANDLE(LTLEventuallyIntrinsicOp, Unhandled); + HANDLE(LTLClockIntrinsicOp, Unhandled); + HANDLE(LTLDisableIntrinsicOp, Unhandled); + HANDLE(Mux4CellIntrinsicOp, Unhandled); + HANDLE(Mux2CellIntrinsicOp, Unhandled); + HANDLE(HasBeenResetIntrinsicOp, Unhandled); // Miscellaneous. HANDLE(BitsPrimOp, Unhandled); @@ -154,11 +192,27 @@ class ExprVisitor { HANDLE(RefSendOp, Unhandled); HANDLE(RefResolveOp, Unhandled); HANDLE(RefSubOp, Unhandled); + HANDLE(RWProbeOp, Unhandled); + HANDLE(XMRRefOp, Unhandled); + HANDLE(XMRDerefOp, Unhandled); // Conversions. HANDLE(HWStructCastOp, Unhandled); + HANDLE(UninferredResetCastOp, Unhandled); + HANDLE(ConstCastOp, Unhandled); HANDLE(mlir::UnrealizedConversionCastOp, Unhandled); HANDLE(BitCastOp, Unhandled); + HANDLE(RefCastOp, Unhandled); + + // Property expressions. + HANDLE(StringConstantOp, Unhandled); + HANDLE(FIntegerConstantOp, Unhandled); + HANDLE(BoolConstantOp, Unhandled); + HANDLE(DoubleConstantOp, Unhandled); + HANDLE(ListCreateOp, Unhandled); + HANDLE(MapCreateOp, Unhandled); + HANDLE(PathOp, Unhandled); + HANDLE(UnresolvedPathOp, Unhandled); #undef HANDLE }; @@ -170,9 +224,12 @@ class StmtVisitor { ResultType dispatchStmtVisitor(Operation *op, ExtraArgs... args) { auto *thisCast = static_cast(this); return TypeSwitch(op) - .template Case( + AssumeOp, CoverOp, PropAssignOp, RefForceOp, + RefForceInitialOp, RefReleaseOp, RefReleaseInitialOp, + VerifAssertIntrinsicOp, VerifAssumeIntrinsicOp, + VerifCoverIntrinsicOp, GroupOp>( [&](auto opNode) -> ResultType { return thisCast->visitStmt(opNode, args...); }) @@ -201,7 +258,7 @@ class StmtVisitor { HANDLE(AttachOp); HANDLE(ConnectOp); HANDLE(StrictConnectOp); - HANDLE(RefConnectOp); + HANDLE(RefDefineOp); HANDLE(ForceOp); HANDLE(PrintFOp); HANDLE(SkipOp); @@ -210,7 +267,15 @@ class StmtVisitor { HANDLE(AssertOp); HANDLE(AssumeOp); HANDLE(CoverOp); - HANDLE(ProbeOp); + HANDLE(PropAssignOp); + HANDLE(RefForceOp); + HANDLE(RefForceInitialOp); + HANDLE(RefReleaseOp); + HANDLE(RefReleaseInitialOp); + HANDLE(VerifAssertIntrinsicOp); + HANDLE(VerifAssumeIntrinsicOp); + HANDLE(VerifCoverIntrinsicOp); + HANDLE(GroupOp); #undef HANDLE }; @@ -223,8 +288,8 @@ class DeclVisitor { ResultType dispatchDeclVisitor(Operation *op, ExtraArgs... args) { auto *thisCast = static_cast(this); return TypeSwitch(op) - .template Case([&](auto opNode) -> ResultType { + .template Case([&](auto opNode) -> ResultType { return thisCast->visitDecl(opNode, args...); }) .Default([&](auto expr) -> ResultType { @@ -250,6 +315,7 @@ class DeclVisitor { } HANDLE(InstanceOp); + HANDLE(ObjectOp); HANDLE(MemOp); HANDLE(NodeOp); HANDLE(RegOp); diff --git a/include/circt/Dialect/FIRRTL/FieldRefCache.h b/include/circt/Dialect/FIRRTL/FieldRefCache.h new file mode 100644 index 000000000000..0674da056e20 --- /dev/null +++ b/include/circt/Dialect/FIRRTL/FieldRefCache.h @@ -0,0 +1,61 @@ +//===- FieldRefCache.h - FieldRef cache -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares FieldRefCache, a caching getFieldRefFromValue that +// caching FieldRef's for each queried value and all indexing operations +// visited during the walk. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_FIRRTL_FIELDREFCACHE_H +#define CIRCT_DIALECT_FIRRTL_FIELDREFCACHE_H + +#include "circt/Support/FieldRef.h" +#include "circt/Support/LLVM.h" + +namespace circt { +namespace firrtl { + +/// Caching version of getFieldRefFromValue. Computes the requested FieldRef +/// and for all operations visited along the way. Tracks some stats in debug. +class FieldRefCache { + using Key = llvm::PointerIntPair; + DenseMap refs; +#ifndef NDEBUG + size_t computed = 0; + size_t hits = 0; + size_t queries = 0; +#endif + +public: + /// Caching version of getFieldRefFromValue. + FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts = false); + + /// Drop all cached entries. + void clear() { refs.clear(); } + +#ifndef NDEBUG + void printStats(llvm::raw_ostream &os) const; + void addToTotals(size_t &totalHits, size_t &totalComputed, + size_t &totalQueries) const; + void verifyImpl() const; +#endif // NDEBUG + + /// Verify cached fieldRefs against firrtl::getFieldRefFromValue. + /// No-op in release builds. + void verify() const { +#ifndef NDEBUG + verifyImpl(); +#endif // NDEBUG + } +}; + +} // namespace firrtl +} // namespace circt + +#endif // CIRCT_DIALECT_FIRRTL_FIELDREFCACHE_H diff --git a/include/circt/Dialect/FIRRTL/NLATable.h b/include/circt/Dialect/FIRRTL/NLATable.h index 60b0a0779389..7fff7537a480 100644 --- a/include/circt/Dialect/FIRRTL/NLATable.h +++ b/include/circt/Dialect/FIRRTL/NLATable.h @@ -122,7 +122,7 @@ class NLATable { /// Record a new FModuleLike operation. This updates the Module name to Module /// operation map. - void addModule(FModuleLike mod) { symToOp[mod.moduleNameAttr()] = mod; } + void addModule(FModuleLike mod) { symToOp[mod.getModuleNameAttr()] = mod; } /// Stop tracking a module. Remove the module from two internal records, /// 1. Module name to Module op map. @@ -165,7 +165,7 @@ class NLATable { /// Remove the NLA from the Module. This updates the module name to NLA /// tracking. void removeNLAfromModule(hw::HierPathOp nla, StringAttr mod) { - llvm::erase_value(nodeMap[mod], nla); + llvm::erase(nodeMap[mod], nla); } /// Remove all the nlas in the set `nlas` from the module. This updates the diff --git a/include/circt/Dialect/FIRRTL/Namespace.h b/include/circt/Dialect/FIRRTL/Namespace.h index 145b13f6ddb8..5a38b2f12d1b 100644 --- a/include/circt/Dialect/FIRRTL/Namespace.h +++ b/include/circt/Dialect/FIRRTL/Namespace.h @@ -36,53 +36,6 @@ struct CircuitNamespace : public Namespace { } }; -/// The namespace of a `FModuleLike` operation, generally inhabited by its ports -/// and declarations. -struct ModuleNamespace : public Namespace { - ModuleNamespace() {} - ModuleNamespace(FModuleLike module) : module(module) { add(module); } - - /// Populate the namespace from a module-like operation. This namespace will - /// be composed of the `inner_sym`s of the module's ports and declarations. - void add(FModuleLike module) { - addPorts(module); - addBody(module); - } - - /// Populate the namespace with the ports of a module-like operation. - void addPorts(FModuleLike module) { - for (auto portSymbol : module.getPortSymbolsAttr()) - if (portSymbol) - static_cast(portSymbol.cast().walkSymbols( - [&](StringAttr sName) { - nextIndex.insert({sName.getValue(), 0}); - return success(); - })); - } - - void addPorts(ArrayRef ports) { - for (auto port : ports) - if (port.sym) - static_cast(port.sym.cast().walkSymbols( - [&](StringAttr symName) { - nextIndex.insert({symName.getValue(), 0}); - return success(); - })); - } - - /// Populate the namespace with the body of a module-like operation. - void addBody(FModuleLike module) { - module.walk([&](Operation *op) { - auto attr = getInnerSymName(op); - if (attr) - nextIndex.insert({attr.getValue(), 0}); - }); - } - - /// The module associated with this namespace. - FModuleLike module; -}; - } // namespace firrtl } // namespace circt diff --git a/include/circt/Dialect/FIRRTL/Passes.h b/include/circt/Dialect/FIRRTL/Passes.h index 58634dbad29f..d67aaebcb593 100644 --- a/include/circt/Dialect/FIRRTL/Passes.h +++ b/include/circt/Dialect/FIRRTL/Passes.h @@ -25,11 +25,15 @@ class Pass; namespace circt { namespace firrtl { +std::unique_ptr createResolvePathsPass(); + std::unique_ptr createLowerFIRRTLAnnotationsPass(bool ignoreUnhandledAnnotations = false, bool ignoreClasslessAnnotations = false, bool noRefTypePorts = false); +std::unique_ptr createLowerOpenAggsPass(); + /// Configure which aggregate values will be preserved by the LowerTypes pass. namespace PreserveAggregate { enum PreserveMode { @@ -50,8 +54,7 @@ enum PreserveMode { std::unique_ptr createLowerFIRRTLTypesPass( PreserveAggregate::PreserveMode mode = PreserveAggregate::None, - PreserveAggregate::PreserveMode memoryMode = PreserveAggregate::None, - bool preservePublicTypes = true); + PreserveAggregate::PreserveMode memoryMode = PreserveAggregate::None); std::unique_ptr createLowerBundleVectorTypesPass(); @@ -70,7 +73,6 @@ std::unique_ptr createInferReadWritePass(); std::unique_ptr createCreateSiFiveMetadataPass(bool replSeqMem = false, - mlir::StringRef replSeqMemCircuit = "", mlir::StringRef replSeqMemFile = ""); std::unique_ptr createWireDFTPass(); @@ -84,6 +86,8 @@ std::unique_ptr createDedupPass(); std::unique_ptr createEmitOMIRPass(mlir::StringRef outputFilename = ""); +std::unique_ptr createLowerMatchesPass(); + std::unique_ptr createExpandWhensPass(); std::unique_ptr createFlattenMemoryPass(); @@ -94,6 +98,11 @@ std::unique_ptr createInferResetsPass(); std::unique_ptr createLowerMemoryPass(); +std::unique_ptr +createHoistPassthroughPass(bool hoistHWDrivers = true); + +std::unique_ptr createProbeDCEPass(); + std::unique_ptr createMemToRegOfVecPass(bool replSeqMem = false, bool ignoreReadEnable = false); @@ -108,10 +117,18 @@ std::unique_ptr createPrintNLATablePass(); std::unique_ptr createBlackBoxReaderPass(std::optional inputPrefix = {}); -std::unique_ptr -createGrandCentralPass(bool instantiateCompanionOnly = false); +enum class CompanionMode { + // Lower companions to SystemVerilog binds. + Bind, + // Lower companions to explicit instances. Used when assertions or other + // debugging constructs from the companion are to be included in the design. + Instantiate, + // Drop companion modules, eliminating them from the design. + Drop, +}; -std::unique_ptr createCheckCombCyclesPass(); +std::unique_ptr +createGrandCentralPass(CompanionMode companionMode = CompanionMode::Bind); std::unique_ptr createCheckCombLoopsPass(); @@ -120,13 +137,20 @@ std::unique_ptr createSFCCompatPass(); std::unique_ptr createMergeConnectionsPass(bool enableAggressiveMerging = false); +std::unique_ptr createVectorizationPass(); + std::unique_ptr createInjectDUTHierarchyPass(); +std::unique_ptr createDropConstPass(); + /// Configure which values will be explicitly preserved by the DropNames pass. namespace PreserveValues { enum PreserveMode { + /// Strip all names. No name on declaration is preserved. + Strip, /// Don't explicitly preserve any named values. Every named operation could - /// be optimized away by the compiler. + /// be optimized away by the compiler. Unlike `Strip` names could be preserved + /// until the end. None, // Explicitly preserved values with meaningful names. If a name begins with // an "_" it is not considered meaningful. @@ -155,6 +179,18 @@ createResolveTracesPass(mlir::StringRef outputAnnotationFilename = ""); std::unique_ptr createInnerSymbolDCEPass(); +std::unique_ptr createFinalizeIRPass(); + +std::unique_ptr createLowerClassesPass(); + +std::unique_ptr createLowerGroupsPass(); + +std::unique_ptr createGroupSinkPass(); + +std::unique_ptr createMaterializeDebugInfoPass(); + +std::unique_ptr createLintingPass(); + /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION #include "circt/Dialect/FIRRTL/Passes.h.inc" diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index 9b79e5539080..c3ed2fb3360d 100644 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -15,6 +15,17 @@ include "mlir/Pass/PassBase.td" +def ResolvePaths : Pass<"firrtl-resolve-paths", "firrtl::CircuitOp"> { + let summary = "Lowers UnresolvedPathOps to PathOps"; + let description = [{ + FIRRTL path operations are initially create as unresolved path operations, + which encode their target as a string. This pass parses and resolves those + target strings to actual path operations. Path operations refer to their + targets using annotations with a unique identifier. + }]; + let constructor = "circt::firrtl::createResolvePathsPass()"; +} + def LowerFIRRTLAnnotations : Pass<"firrtl-lower-annotations", "firrtl::CircuitOp"> { let summary = "Lower FIRRTL annotations to usable entities"; let description = [{ @@ -60,9 +71,6 @@ def LowerFIRRTLTypes : Pass<"firrtl-lower-types", "firrtl::CircuitOp"> { let options = [ Option<"flattenAggregateMemData", "flatten-mem", "bool", "false", "Concat all elements of the aggregate data into a single element.">, - Option<"preservePublicTypes", "preserve-public-types", "bool", - "true", "Force to lower ports of toplevel and external modules even" - "when aggregate preservation mode.">, Option<"preserveAggregate", "preserve-aggregate", "PreserveAggregate::PreserveMode", "PreserveAggregate::None", "Specify aggregate preservation mode", @@ -100,8 +108,8 @@ def IMConstProp : Pass<"firrtl-imconstprop", "firrtl::CircuitOp"> { def RegisterOptimizer : Pass<"firrtl-register-optimizer", "firrtl::FModuleOp"> { let summary = "Optimizer Registers"; let description = [{ - This pass applies classic FIRRTL register optimizations. These - optimizations are isolated to this pass as they can change the visible + This pass applies classic FIRRTL register optimizations. These + optimizations are isolated to this pass as they can change the visible behavior of the register, especially before reset. }]; let constructor = "circt::firrtl::createRegisterOptimizerPass()"; @@ -120,7 +128,7 @@ def RemoveUnusedPorts : Pass<"firrtl-remove-unused-ports", "firrtl::CircuitOp"> ]; } -def IMDeadCodeElim : Pass<"firrtl-imdeadcodeelim", "firrtl::CircuitOp"> { +def IMDeadCodeElim : Pass<"firrtl-imdeadcodeelim", "mlir::ModuleOp"> { let summary = "Intermodule dead code elimination"; let description = [{ This pass performs inter-module liveness analysis and deletes dead code @@ -172,7 +180,7 @@ def AddSeqMemPorts : Pass<"firrtl-add-seqmem-ports", "firrtl::CircuitOp"> { ]; } -def CreateSiFiveMetadata : Pass<"firrtl-emit-metadata", "firrtl::CircuitOp"> { +def CreateSiFiveMetadata : Pass<"firrtl-emit-metadata", "mlir::ModuleOp"> { let summary = "Emit metadata of the FIRRTL modules"; let description = [{ This pass handles the emission of several different kinds of metadata. @@ -180,12 +188,10 @@ def CreateSiFiveMetadata : Pass<"firrtl-emit-metadata", "firrtl::CircuitOp"> { let constructor = "circt::firrtl::createCreateSiFiveMetadataPass()"; let options = [Option<"replSeqMem", "repl-seq-mem", "bool", "false", "Lower the seq mem for macro replacement and emit relevant metadata">, - Option<"replSeqMemCircuit", "repl-seq-mem-circuit", "std::string", "", - "Circuit root for seq mem metadata">, Option<"replSeqMemFile", "repl-seq-mem-file", "std::string", "", "File to which emit seq meme metadata"> ]; - let dependentDialects = ["hw::HWDialect"]; + let dependentDialects = ["hw::HWDialect", "om::OMDialect"]; } def FlattenMemory : Pass<"firrtl-flatten-memory", "firrtl::FModuleOp"> { @@ -260,7 +266,16 @@ def EmitOMIR : Pass<"firrtl-emit-omir", "firrtl::CircuitOp"> { let dependentDialects = ["sv::SVDialect", "hw::HWDialect"]; } -def ExpandWhens : Pass<"firrtl-expand-whens", "firrtl::FModuleOp"> { +def LowerMatches : Pass<"firrtl-lower-matches", "firrtl::FModuleOp"> { + let summary = "Remove all matchs conditional blocks"; + let description = [{ + Lowers FIRRTL match statements in to when statements, which can later be + lowered with ExpandWhens. + }]; + let constructor = "circt::firrtl::createLowerMatchesPass()"; +} + +def ExpandWhens : InterfacePass<"firrtl-expand-whens", "firrtl::FModuleLike"> { let summary = "Remove all when conditional blocks."; let description = [{ This pass will: @@ -448,8 +463,17 @@ def GrandCentral : Pass<"firrtl-grand-central", "CircuitOp"> { let constructor = "circt::firrtl::createGrandCentralPass()"; let dependentDialects = ["circt::sv::SVDialect", "circt::hw::HWDialect"]; let options = [ - Option<"instantiateCompanionOnly", "instantiate-companion-only", "bool", "false", - "Instantiate the companion without a bind and drop the interface"> + Option<"companionMode", "companion-mode", "CompanionMode", + "CompanionMode::Bind", + "specify the handling of companion modules", [{ + ::llvm::cl::values( + clEnumValN(CompanionMode::Bind, "bind", + "Lower companion instances to SystemVerilog binds"), + clEnumValN(CompanionMode::Instantiate, "instantiate", + "Instantiate companions in the design"), + clEnumValN(CompanionMode::Drop, "drop", + "Remove companions from the design")) + }]> ]; let statistics = [ Statistic<"numViews", "num-views-created", @@ -463,18 +487,6 @@ def GrandCentral : Pass<"firrtl-grand-central", "CircuitOp"> { ]; } -def CheckCombCycles : Pass<"firrtl-check-comb-cycles", "firrtl::CircuitOp"> { - let summary = "Check combinational cycles and emit errors"; - let description = [{ - This pass checks combinational cycles in the IR and emit errors. - }]; - let options = [ - Option<"printSimpleCycle", "print-simple-cycle", "bool", "true", - "Print a simple cycle instead of printing all operations in SCC"> - ]; - let constructor = "circt::firrtl::createCheckCombCyclesPass()"; -} - def CheckCombLoops : Pass<"firrtl-check-comb-loops", "firrtl::CircuitOp"> { let summary = "Check combinational cycles and emit errors"; let description = [{ @@ -497,6 +509,11 @@ def MergeConnections : Pass<"merge-connections", "firrtl::FModuleOp"> { ]; } +def Vectorization : Pass<"vectorization", "firrtl::FModuleOp"> { + let summary = "Transform firrtl primitive operations into vector operations"; + let constructor = "circt::firrtl::createVectorizationPass()"; +} + def InferReadWrite : Pass<"firrtl-infer-rw", "firrtl::FModuleOp"> { let summary = "Infer the read-write memory port"; let description = [{ @@ -582,8 +599,10 @@ def DropName : Pass<"firrtl-drop-names", "firrtl::FModuleOp"> { "PreserveValues::None", "specify the values which can be optimized away", [{ ::llvm::cl::values( + clEnumValN(PreserveValues::Strip, "strip", + "Strip all names. No name is preserved"), clEnumValN(PreserveValues::None, "none", - "Preserve no values"), + "Preserve no named values. Names could be preserved by best-effort unlike `strip`"), clEnumValN(PreserveValues::Named, "named", "Preserve values with meaningful names"), clEnumValN(PreserveValues::All, "all", @@ -591,6 +610,8 @@ def DropName : Pass<"firrtl-drop-names", "firrtl::FModuleOp"> { }]> ]; let statistics = [ + Statistic<"numNamesDropped", "num-names-dropped", + "Number of names dropped">, Statistic<"numNamesConverted", "num-names-converted", "Number of interesting names made droppable">, ]; @@ -629,6 +650,15 @@ def LowerIntrinsics : Pass<"firrtl-lower-intrinsics", "firrtl::CircuitOp"> { let constructor = "circt::firrtl::createLowerIntrinsicsPass()"; } +def LowerOpenAggs : Pass<"firrtl-lower-open-aggs", "firrtl::CircuitOp"> { + let summary = "Lower 'Open' aggregates by splitting out non-hardware elements"; + let description = [{ + This pass lowers aggregates of the more open varieties into their equivalents + using only hardware types, by pulling out non-hardware to other locations. + }]; + let constructor = "circt::firrtl::createLowerOpenAggsPass()"; +} + def ResolveTraces : Pass<"firrtl-resolve-traces", "firrtl::CircuitOp"> { let summary = "Write out TraceAnnotations to an output annotation file"; let description = [{ @@ -671,4 +701,120 @@ def VBToBV : Pass<"firrtl-vb-to-bv", "firrtl::CircuitOp"> { let constructor = "circt::firrtl::createVBToBVPass()"; } +def DropConst : Pass<"firrtl-drop-const", "firrtl::CircuitOp"> { + let summary = "Drop 'const' modifier from types"; + let description = [{ + This pass drops the 'const' modifier from all types and removes all + const-cast ops. + + This simplifies downstream passes and folds so that they do not need to + take 'const' into account. + }]; + let constructor = "circt::firrtl::createDropConstPass()"; +} + +def FinalizeIR : Pass<"firrtl-finalize-ir", "mlir::ModuleOp"> { + let summary = "Perform final IR mutations after ExportVerilog"; + let description = [{ + This pass finalizes the IR after it has been exported with ExportVerilog, + and before firtool emits the final IR. This includes mutations like dropping + verbatim ops that represent sideband files and are not required in the IR. + }]; + let constructor = "circt::firrtl::createFinalizeIRPass()"; + let dependentDialects = ["hw::HWDialect", "sv::SVDialect"]; +} + +def LowerClasses : Pass<"firrtl-lower-classes", "firrtl::CircuitOp"> { + let summary = "Lower FIRRTL classes and objects to OM classes and objects"; + let description = [{ + This pass walks all FIRRTL classes and creates OM classes. It also lowers + FIRRTL objects to OM objects. OM classes are created with parameters + corresponding to FIRRTL input properties, and OM class fields corresponding + to FIRRTL output properties. FIRRTL operations are converted to OM + operations. + }]; + let constructor = "circt::firrtl::createLowerClassesPass()"; + let dependentDialects = ["om::OMDialect"]; +} + +def LowerGroups : Pass<"firrtl-lower-groups", "firrtl::CircuitOp"> { + let summary = "Lower optional groups to instances"; + let description = [{ + This pass converts FIRRTL optional groups to new modules and instantiations. + Referenced SSA values captured by a group are converted to ports of the + created module. + }]; + let constructor = "circt::firrtl::createLowerGroupsPass()"; + let dependentDialects = ["sv::SVDialect"]; +} + +def GroupSink : Pass<"firrtl-group-sink", "firrtl::FModuleOp"> { + let summary = "Sink operations into FIRRTL optional groups"; + let description = [{ + + }]; + let constructor = "::circt::firrtl::createGroupSinkPass()"; + let statistics = [ + Statistic<"numSunk", "num-sunk", "Number of operations sunk">, + ]; +} + +def HoistPassthrough : Pass<"firrtl-hoist-passthrough", "firrtl::CircuitOp"> { + let summary = "Hoist passthrough logic"; + let description = [{ + This pass hoists simple passthrough connections up. + }]; + let constructor = "circt::firrtl::createHoistPassthroughPass()"; + let options = [ + Option<"hoistHWDrivers", "hoist-hw", "bool", "true", + "Hoist HW drivers."> + ]; + let statistics = [ + Statistic<"numUTurnsHoisted", "num-u-turns", + "Number of u-turns hoisted">, + Statistic<"numRefDrivers", "num-ref-drivers", + "Number of ref-type drivers processed">, + Statistic<"numHWDrivers", "num-hw-drivers", + "Number of hw-type drivers processed"> + ]; +} + +def ProbeDCE : Pass<"firrtl-probe-dce", "firrtl::CircuitOp"> { + let summary = "Delete dead probe ports and operations."; + let description = [{ + Simple pass to delete dead probe ports and operations. + Diagnoses illegal or unsupported input probe uses. + Post-condition: no input probe ports. + }]; + let constructor = "circt::firrtl::createProbeDCEPass()"; + let statistics = [ + Statistic<"numErasedPorts", "num-erased-ports", "Number of ports erased">, + Statistic<"numErasedOps", "num-erased-ops", "Number of operations erased"> + ]; +} + +def MaterializeDebugInfo : + Pass<"firrtl-materialize-debug-info", "firrtl::FModuleOp"> { + let summary = "Generate debug ops to track FIRRTL values"; + let description = [{ + This pass creates debug ops to track FIRRTL-level ports, nodes, wires, + registers, and instances throughout the pipeline. The `DebugInfo` analysis + can then be used at a later point in the pipeline to obtain a source + language view into the lowered IR. + }]; + let constructor = "circt::firrtl::createMaterializeDebugInfoPass()"; + let dependentDialects = ["debug::DebugDialect"]; +} + +def Lint : + Pass<"firrtl-lint", "firrtl::FModuleOp"> { + let summary = "An analysis pass to detect static simulation failures."; + let description = [{ + This pass detects operations that will trivially fail any simulation. + Currently it detects assertions whose predicate condition can be statically + inferred to be false. The pass emits error on such failing ops. + }]; + let constructor = "circt::firrtl::createLintingPass()"; +} + #endif // CIRCT_DIALECT_FIRRTL_PASSES_TD diff --git a/include/circt/Dialect/FSM/FSM.td b/include/circt/Dialect/FSM/FSM.td index f288fa4bbf78..24bb72c9c8c6 100644 --- a/include/circt/Dialect/FSM/FSM.td +++ b/include/circt/Dialect/FSM/FSM.td @@ -11,7 +11,7 @@ include "mlir/IR/AttrTypeBase.td" include "mlir/IR/OpBase.td" -include "mlir/IR/FunctionInterfaces.td" +include "mlir/Interfaces/FunctionInterfaces.td" def FSMDialect : Dialect { let name = "fsm"; @@ -24,6 +24,13 @@ def FSMDialect : Dialect { }]; let useDefaultTypePrinterParser = 1; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + + let dependentDialects = [ + "circt::seq::SeqDialect" + ]; } // Base class for the types in this dialect. diff --git a/include/circt/Dialect/FSM/FSMOps.h b/include/circt/Dialect/FSM/FSMOps.h index 538be17eeb7e..839e3956c0e6 100644 --- a/include/circt/Dialect/FSM/FSMOps.h +++ b/include/circt/Dialect/FSM/FSMOps.h @@ -12,12 +12,13 @@ #include "circt/Dialect/FSM/FSMDialect.h" #include "circt/Dialect/FSM/FSMTypes.h" #include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/Seq/SeqTypes.h" #include "circt/Support/LLVM.h" #include "mlir/IR/Builders.h" -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/SymbolTable.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" #define GET_OP_CLASSES #include "circt/Dialect/FSM/FSM.h.inc" diff --git a/include/circt/Dialect/FSM/FSMOps.td b/include/circt/Dialect/FSM/FSMOps.td index 858e77b416bb..db4966a7efe4 100644 --- a/include/circt/Dialect/FSM/FSMOps.td +++ b/include/circt/Dialect/FSM/FSMOps.td @@ -12,14 +12,15 @@ include "mlir/IR/OpAsmInterface.td" include "mlir/IR/SymbolInterfaces.td" include "mlir/Interfaces/ControlFlowInterfaces.td" -include "circt/Dialect/HW/HWOpInterfaces.td" +include "circt/Dialect/Seq/SeqTypes.td" +include "circt/Support/InstanceGraphInterface.td" def HasCustomSSAName : DeclareOpInterfaceMethods; def MachineOp : FSMOp<"machine", [ FunctionOpInterface, Symbol, SymbolTable, NoTerminator, - DeclareOpInterfaceMethods]> { + DeclareOpInterfaceMethods]> { let summary = "Define a finite-state machine"; let description = [{ `fsm.machine` represents a finite-state machine, including a machine name, @@ -61,6 +62,13 @@ def MachineOp : FSMOp<"machine", [ /// Returns the result types of this function. ArrayRef getResultTypes() { return getFunctionType().getResults(); } + /// Returns the region on the current operation that is callable. This may + /// return null in the case of an external callable object, e.g. an external + /// function. + ::mlir::Region *getCallableRegion() { + return &getBody(); + } + /// Verify the type attribute of this function. Returns failure and emits /// an error if the attribute is invalid. LogicalResult verifyType() { @@ -76,21 +84,11 @@ def MachineOp : FSMOp<"machine", [ /// Return the name of the i'th result of this machine. StringAttr getResName(size_t i); - }]; - let extraClassDefinition = [{ - size_t $cppClass::getNumPorts() { - auto machineType = getFunctionType(); - return machineType.getNumInputs() + machineType.getNumResults(); - } - - circt::hw::InnerSymAttr $cppClass::getPortSymbolAttr(size_t portIndex) { - for (NamedAttribute argAttr : - mlir::function_interface_impl::getArgAttrs(*this, portIndex)) - if (auto sym = argAttr.getValue().dyn_cast()) - return sym; - return circt::hw::InnerSymAttr(); - } + //===------------------------------------------------------------------===// + // PortList Methods + //===------------------------------------------------------------------===// + SmallVector<::circt::hw::PortInfo> getPortList(); }]; let skipDefaultBuilders = 1; @@ -113,6 +111,11 @@ def InstanceOp : FSMOp<"instance", [Symbol, HasCustomSSAName]> { let extraClassDeclaration = [{ /// Lookup the machine for the symbol. This returns null on invalid IR. MachineOp getMachineOp(); + + //===------------------------------------------------------------------===// + // PortList Methods + //===------------------------------------------------------------------===// + SmallVector<::circt::hw::PortInfo> getPortList(); }]; let hasVerifier = 1; @@ -143,7 +146,7 @@ def TriggerOp : FSMOp<"trigger", []> { def HWInstanceOp : FSMOp<"hw_instance", [ Symbol, - DeclareOpInterfaceMethods + DeclareOpInterfaceMethods ]> { let summary = "Create a hardware-style instance of a state machine"; let description = [{ @@ -154,7 +157,7 @@ def HWInstanceOp : FSMOp<"hw_instance", [ }]; let arguments = (ins StrAttr:$sym_name, FlatSymbolRefAttr:$machine, - Variadic:$inputs, I1:$clock, I1:$reset); + Variadic:$inputs, ClockType:$clock, I1:$reset); let results = (outs Variadic:$outputs); let assemblyFormat = [{ @@ -167,20 +170,14 @@ def HWInstanceOp : FSMOp<"hw_instance", [ MachineOp getMachineOp(); /// Module name is the same as the machine name. - StringRef getModuleName() { - return getMachine(); - } - FlatSymbolRefAttr getModuleNameAttr() { - return getMachineAttr(); - } + StringRef getModuleName(); - // HWInstanceLike interface - StringRef getInstanceName() { - return getSymName(); - } - StringAttr getInstanceNameAttr() { - return getSymNameAttr(); - } + FlatSymbolRefAttr getModuleNameAttr(); + + //===------------------------------------------------------------------===// + // PortList Methods + //===------------------------------------------------------------------===// + SmallVector<::circt::hw::PortInfo> getPortList(); }]; let hasVerifier = 1; diff --git a/include/circt/Dialect/HW/ConversionPatterns.h b/include/circt/Dialect/HW/ConversionPatterns.h new file mode 100644 index 000000000000..e9c4d52b86d5 --- /dev/null +++ b/include/circt/Dialect/HW/ConversionPatterns.h @@ -0,0 +1,58 @@ +//===- ConversionPatterns.h - Common Conversion patterns --------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_CONVERSIONPATTERNS_H +#define CIRCT_SUPPORT_CONVERSIONPATTERNS_H + +#include "circt/Support/LLVM.h" + +#include "mlir/Transforms/DialectConversion.h" + +namespace circt { + +// Performs type conversion on the given operation. +LogicalResult doTypeConversion(Operation *op, ValueRange operands, + ConversionPatternRewriter &rewriter, + const TypeConverter *typeConverter); + +/// Generic pattern which replaces an operation by one of the same operation +/// name, but with converted attributes, operands, and result types to eliminate +/// illegal types. Uses generic builders based on OperationState to make sure +/// that this pattern can apply to _any_ operation. +/// +/// Useful when a conversion can be entirely defined by a TypeConverter. +struct TypeConversionPattern : public mlir::ConversionPattern { +public: + TypeConversionPattern(TypeConverter &converter, MLIRContext *context) + : ConversionPattern(converter, MatchAnyOpTypeTag(), 1, context) {} + using ConversionPattern::ConversionPattern; + + LogicalResult + matchAndRewrite(Operation *op, ArrayRef operands, + ConversionPatternRewriter &rewriter) const override { + return doTypeConversion(op, operands, rewriter, getTypeConverter()); + } +}; + +// Specialization of the above which targets a specific operation. +template +struct TypeOpConversionPattern : public mlir::OpConversionPattern { + using mlir::OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename mlir::OpConversionPattern::OpAdaptor; + + LogicalResult + matchAndRewrite(OpTy op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + return doTypeConversion(op.getOperation(), adaptor.getOperands(), rewriter, + this->getTypeConverter()); + } +}; + +} // namespace circt + +#endif // CIRCT_SUPPORT_CONVERSIONPATTERNS_H diff --git a/include/circt/Dialect/HW/HWAggregates.td b/include/circt/Dialect/HW/HWAggregates.td index 507608fce77d..27f5a3ab6b12 100644 --- a/include/circt/Dialect/HW/HWAggregates.td +++ b/include/circt/Dialect/HW/HWAggregates.td @@ -205,18 +205,32 @@ def StructExtractOp : HWOp<"struct_extract", ``` }]; - let arguments = (ins StructType:$input, StrAttr:$field); + let arguments = (ins StructType:$input, I32Attr:$fieldIndex); let results = (outs HWNonInOutType:$result); let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; let builders = [ OpBuilder<(ins "Value":$input, "StructType::FieldInfo":$field)>, - OpBuilder<(ins "Value":$input, "StringAttr":$field)>, - OpBuilder<(ins "Value":$input, "StringRef":$field), [{ - build(odsBuilder, odsState, input, odsBuilder.getStringAttr(field)); + OpBuilder<(ins "Value":$input, "StringAttr":$fieldName)>, + OpBuilder<(ins "Value":$input, "StringRef":$fieldName), [{ + build($_builder, $_state, input, $_builder.getStringAttr(fieldName)); }]> ]; + let extraClassDeclaration = [{ + /// Return the name attribute of the accessed field. + StringAttr getFieldNameAttr() { + StructType type = getInput().getType(); + return type.getElements()[getFieldIndex()].name; + } + + /// Return the name of the accessed field. + StringRef getFieldName() { + return getFieldNameAttr().getValue(); + } + }]; + let hasFolder = 1; let hasCanonicalizeMethod = 1; } @@ -232,10 +246,32 @@ def StructInjectOp : HWOp<"struct_inject", [Pure, ``` }]; - let arguments = (ins StructType:$input, StrAttr:$field, + let arguments = (ins StructType:$input, I32Attr:$fieldIndex, HWNonInOutType:$newValue); let results = (outs StructType:$result); let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + + let builders = [ + OpBuilder<(ins "Value":$input, "StringAttr":$fieldName, "Value":$newValue)>, + OpBuilder<(ins "Value":$input, "StringRef":$fieldName, "Value":$newValue), [{ + build($_builder, $_state, input, $_builder.getStringAttr(fieldName), newValue); + }]> + ]; + + let extraClassDeclaration = [{ + /// Return the name attribute of the accessed field. + StringAttr getFieldNameAttr() { + StructType type = getInput().getType(); + return type.getElements()[getFieldIndex()].name; + } + + /// Return the name of the accessed field. + StringRef getFieldName() { + return getFieldNameAttr().getValue(); + } + }]; + let hasFolder = 1; let hasCanonicalizeMethod = 1; } @@ -277,12 +313,35 @@ def UnionCreateOp : HWOp<"union_create", [Pure]> { ``` }]; - let arguments = (ins StrAttr:$field, HWNonInOutType:$input); + let arguments = (ins I32Attr:$fieldIndex, HWNonInOutType:$input); let results = (outs UnionType:$result); let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + + let builders = [ + OpBuilder<(ins "Type":$unionType, "StringAttr":$fieldName, "Value":$input)>, + OpBuilder<(ins "Type":$unionType, "StringRef":$fieldName, "Value":$input), [{ + build($_builder, $_state, unionType, $_builder.getStringAttr(fieldName), input); + }]> + ]; + + let extraClassDeclaration = [{ + /// Return the name attribute of the accessed field. + StringAttr getFieldNameAttr() { + UnionType type = getType(); + return type.getElements()[getFieldIndex()].name; + } + + /// Return the name of the accessed field. + StringRef getFieldName() { + return getFieldNameAttr().getValue(); + } + }]; } -def UnionExtractOp : HWOp<"union_extract", [Pure]> { +def UnionExtractOp : HWOp<"union_extract", [Pure, + DeclareOpInterfaceMethods, + ]> { let summary = "Get a union member."; let description = [{ Get the value of a union, interpreting it as the type of the specified @@ -296,9 +355,29 @@ def UnionExtractOp : HWOp<"union_extract", [Pure]> { ``` }]; - let arguments = (ins UnionType:$input, StrAttr:$field); + let arguments = (ins UnionType:$input, I32Attr:$fieldIndex); let results = (outs HWNonInOutType:$result); let hasCustomAssemblyFormat = 1; + + let builders = [ + OpBuilder<(ins "Value":$input, "StringAttr":$fieldName)>, + OpBuilder<(ins "Value":$input, "StringRef":$fieldName), [{ + build($_builder, $_state, input, $_builder.getStringAttr(fieldName)); + }]> + ]; + + let extraClassDeclaration = [{ + /// Return the name attribute of the accessed field. + StringAttr getFieldNameAttr() { + UnionType type = getInput().getType(); + return type.getElements()[getFieldIndex()].name; + } + + /// Return the name of the accessed field. + StringRef getFieldName() { + return getFieldNameAttr().getValue(); + } + }]; } #endif // CIRCT_DIALECT_HW_HWAGGREGATES_TD diff --git a/include/circt/Dialect/HW/HWAttributes.h b/include/circt/Dialect/HW/HWAttributes.h index 76d583a0def2..61dbca2cc061 100644 --- a/include/circt/Dialect/HW/HWAttributes.h +++ b/include/circt/Dialect/HW/HWAttributes.h @@ -19,25 +19,18 @@ class PEOAttr; class EnumType; enum class PEO : uint32_t; -// Eventually move this to an op trait -struct InnerName { - static llvm::StringRef getInnerNameAttrName() { return "inner_sym"; } -}; - -// Forward declaration. -class GlobalRefOp; - /// Returns a resolved version of 'type' wherein any parameter reference /// has been evaluated based on the set of provided 'parameters'. mlir::FailureOr evaluateParametricType(mlir::Location loc, mlir::ArrayAttr parameters, - mlir::Type type); + mlir::Type type, + bool emitErrors = true); /// Evaluates a parametric attribute (param.decl.ref/param.expr) based on a set /// of provided parameter values. -mlir::FailureOr +mlir::FailureOr evaluateParametricAttr(mlir::Location loc, mlir::ArrayAttr parameters, - mlir::Attribute paramAttr); + mlir::Attribute paramAttr, bool emitErrors = true); /// Returns true if any part of t is parametric. bool isParametricType(mlir::Type t); diff --git a/include/circt/Dialect/HW/HWAttributes.td b/include/circt/Dialect/HW/HWAttributes.td index e10d8c76182d..7bb1faf9a087 100644 --- a/include/circt/Dialect/HW/HWAttributes.td +++ b/include/circt/Dialect/HW/HWAttributes.td @@ -164,7 +164,14 @@ def ParamDeclAttr : AttrDef { "return $_get(context, name, value.getType(), value);">, AttrBuilderWithInferredContext<(ins "::mlir::StringRef":$name, "::mlir::TypedAttr":$value), - "return get(StringAttr::get(value.getContext(), name), value);"> + "return get(StringAttr::get(value.getContext(), name), value);">, + AttrBuilderWithInferredContext<(ins "::mlir::StringRef":$name, + "::mlir::Type":$type, + "::mlir::TypedAttr":$value), + [{return get(value.getContext(), + StringAttr::get(value.getContext(), name), + type, + value);}]> ]; let extraClassDeclaration = [{ @@ -208,11 +215,18 @@ def ParamVerbatimAttr : AttrDef:$type); let mnemonic = "param.verbatim"; - let hasCustomAssemblyFormat = 1; + let builders = [ + AttrBuilderWithInferredContext<(ins "::mlir::StringAttr":$value), [{ + return get(value, NoneType::get(value.getContext())); + }]>, + AttrBuilderWithInferredContext< + (ins "::mlir::StringAttr":$value, "::mlir::Type":$type), [{ + return get(value.getContext(), value, type); + }]>, + ]; } - /// Parameter Expression Opcodes. let cppNamespace = "circt::hw" in { @@ -299,12 +313,4 @@ def EnumFieldAttr : AttrDef { }]; } - -let cppNamespace = "circt::hw" in { -def WUW_Undefined : I32EnumAttrCase<"Undefined", 0>; -def WUW_PortOrder : I32EnumAttrCase<"PortOrder", 1>; -def WUWAttr : I32EnumAttr<"WUW", "Write Under Write Behavior", - [WUW_Undefined, WUW_PortOrder]>; -} - #endif // CIRCT_DIALECT_HW_HWATTRIBUTES_TD diff --git a/include/circt/Dialect/HW/HWAttributesNaming.td b/include/circt/Dialect/HW/HWAttributesNaming.td index e54de58c94b9..7a6acf7868e7 100644 --- a/include/circt/Dialect/HW/HWAttributesNaming.td +++ b/include/circt/Dialect/HW/HWAttributesNaming.td @@ -46,25 +46,4 @@ def InnerRefAttr : AttrDef { }]; } -def GlobalRefAttr : AttrDef { - let summary = "Refer to a non-local symbol"; - let description = [{ - This works like a symbol reference, but to a global symbol with a possible - unique instance path. - }]; - let mnemonic = "globalNameRef"; - let parameters = (ins "::mlir::FlatSymbolRefAttr":$glblSym); - let builders = [ - AttrBuilderWithInferredContext<(ins "::circt::hw::GlobalRefOp":$ref),[{ - return get(ref.getContext(), SymbolRefAttr::get(ref)); - }]>, - ]; - - let assemblyFormat = "`<` $glblSym `>`"; - - let extraClassDeclaration = [{ - static constexpr char DialectAttrName[] = "circt.globalRef"; - }]; -} - #endif // CIRCT_DIALECT_HW_HWATTRIBUTESNAMING diff --git a/include/circt/Dialect/HW/HWDialect.td b/include/circt/Dialect/HW/HWDialect.td index 37f724fb03e8..4d9a12965fea 100644 --- a/include/circt/Dialect/HW/HWDialect.td +++ b/include/circt/Dialect/HW/HWDialect.td @@ -28,6 +28,9 @@ def HWDialect : Dialect { let hasConstantMaterializer = 1; let useDefaultTypePrinterParser = 1; + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + let extraClassDeclaration = [{ /// Register all HW types. void registerTypes(); diff --git a/include/circt/Dialect/HW/HWInstanceGraph.h b/include/circt/Dialect/HW/HWInstanceGraph.h index a23310783b53..50100322154b 100644 --- a/include/circt/Dialect/HW/HWInstanceGraph.h +++ b/include/circt/Dialect/HW/HWInstanceGraph.h @@ -13,28 +13,30 @@ #ifndef CIRCT_DIALECT_HW_HWINSTANCEGRAPH_H #define CIRCT_DIALECT_HW_HWINSTANCEGRAPH_H -#include "circt/Dialect/HW/InstanceGraphBase.h" +#include "circt/Dialect/HW/HWOpInterfaces.h" +#include "circt/Support/InstanceGraph.h" namespace circt { namespace hw { /// HW-specific instance graph with a virtual entry node linking to /// all publicly visible modules. -class InstanceGraph : public InstanceGraphBase { +class InstanceGraph : public igraph::InstanceGraph { public: InstanceGraph(Operation *operation); /// Return the entry node linking to all public modules. - InstanceGraphNode *getTopLevelNode() override { return &entry; } + igraph::InstanceGraphNode *getTopLevelNode() override { return &entry; } /// Adds a module, updating links to entry. - InstanceGraphNode *addModule(HWModuleLike module) override; + igraph::InstanceGraphNode *addHWModule(HWModuleLike module); /// Erases a module, updating links to entry. - void erase(InstanceGraphNode *node) override; + void erase(igraph::InstanceGraphNode *node) override; private: - InstanceGraphNode entry; + using igraph::InstanceGraph::addModule; + igraph::InstanceGraphNode entry; }; } // namespace hw @@ -43,12 +45,12 @@ class InstanceGraph : public InstanceGraphBase { // Specialisation for the HW instance graph. template <> struct llvm::GraphTraits - : public llvm::GraphTraits {}; + : public llvm::GraphTraits {}; template <> struct llvm::DOTGraphTraits - : public llvm::DOTGraphTraits { - using llvm::DOTGraphTraits::DOTGraphTraits; + : public llvm::DOTGraphTraits { + using llvm::DOTGraphTraits::DOTGraphTraits; }; #endif // CIRCT_DIALECT_HW_HWINSTANCEGRAPH_H diff --git a/include/circt/Dialect/HW/HWMiscOps.td b/include/circt/Dialect/HW/HWMiscOps.td index 21baee088e9b..bd534a5b7550 100644 --- a/include/circt/Dialect/HW/HWMiscOps.td +++ b/include/circt/Dialect/HW/HWMiscOps.td @@ -15,6 +15,7 @@ include "circt/Dialect/HW/HWAttributes.td" include "circt/Dialect/HW/HWDialect.td" +include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Dialect/HW/HWTypes.td" include "mlir/IR/OpAsmInterface.td" include "mlir/Interfaces/InferTypeOpInterface.td" @@ -56,7 +57,8 @@ def ConstantOp def WireOp : HWOp<"wire", [ SameOperandsAndResultType, - DeclareOpInterfaceMethods]> { + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { let summary = "Assign a name or symbol to an SSA edge"; let description = [{ An `hw.wire` is used to assign a human-readable name or a symbol for remote @@ -88,22 +90,21 @@ def WireOp : HWOp<"wire", [ let arguments = (ins AnyType:$input, OptionalAttr:$name, - OptionalAttr:$inner_sym); + OptionalAttr:$inner_sym); let results = (outs AnyType:$result); let hasFolder = true; let hasCanonicalizeMethod = 1; - let skipDefaultBuilders = 1; let builders = [ OpBuilder<(ins "mlir::Value":$input, CArg<"const StringAttrOrRef &", "{}">:$name, - CArg<"const StringAttrOrRef &", "{}">:$inner_sym), [{ + CArg<"hw::InnerSymAttr", "{}">:$innerSym), [{ auto *context = odsBuilder.getContext(); odsState.addOperands(input); if (auto attr = name.get(context)) odsState.addAttribute(getNameAttrName(odsState.name), attr); - if (auto attr = inner_sym.get(context)) - odsState.addAttribute(getInnerSymAttrName(odsState.name), attr); + if (innerSym) + odsState.addAttribute(getInnerSymAttrName(odsState.name), innerSym); odsState.addTypes(input.getType()); }]> ]; @@ -163,10 +164,29 @@ def EnumConstantOp : HWOp<"enum.constant", [Pure, ConstantLike, let results = (outs EnumType:$result); let hasCustomAssemblyFormat = 1; let hasFolder = true; - + let hasVerifier = true; let builders = [ OpBuilder<(ins "hw::EnumFieldAttr":$field)>, ]; } +def EnumCmpOp : HWOp<"enum.cmp", [Pure]> { + let summary = "Compare two values of an enumeration"; + let description = [{ + This operation compares two values with the same canonical enumeration + type, returning 0 if they are different, and 1 if they are the same. + + Example: + ```mlir + %enumcmp = hw.enum.cmp %A, %B : !hw.enum, !hw.enum + ``` + }]; + let arguments = (ins EnumType:$lhs, EnumType:$rhs); + let results = (outs I1:$result); + let hasVerifier = true; + let assemblyFormat = [{ + $lhs `,` $rhs attr-dict `:` qualified(type($lhs)) `,` qualified(type($rhs)) + }]; +} + #endif // CIRCT_DIALECT_HW_HWMISCOPS_TD diff --git a/include/circt/Dialect/HW/HWModuleGraph.h b/include/circt/Dialect/HW/HWModuleGraph.h index be300574ab82..b976e4258be3 100644 --- a/include/circt/Dialect/HW/HWModuleGraph.h +++ b/include/circt/Dialect/HW/HWModuleGraph.h @@ -14,8 +14,8 @@ #define CIRCT_DIALECT_HW_HWMODULEGRAPH_H #include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWInstanceGraph.h" #include "circt/Dialect/HW/HWOps.h" -#include "circt/Dialect/HW/InstanceGraphBase.h" #include "circt/Dialect/Seq/SeqOps.h" #include "circt/Support/LLVM.h" #include "llvm/ADT/GraphTraits.h" @@ -109,8 +109,12 @@ struct llvm::DOTGraphTraits } llvm_unreachable("unhandled ICmp predicate"); }) - .Case( - [&](auto op) { return op.getName().str(); }) + .Case([&](auto op) { return op.getName().str(); }) + .Case([&](auto op) -> std::string { + if (auto name = op.getName()) + return name->str(); + return "reg"; + }) .Case([&](auto op) { llvm::SmallString<64> valueString; op.getValue().toString(valueString, 10, false); @@ -150,14 +154,15 @@ struct llvm::DOTGraphTraits auto &os = g.getOStream(); os << "subgraph cluster_entry_args {\n"; os << "label=\"Input arguments\";\n"; + circt::hw::ModulePortInfo iports(mod.getPortList()); for (auto [info, arg] : - llvm::zip(mod.getPorts().inputs, mod.getArguments())) { + llvm::zip(iports.getInputs(), mod.getBodyBlock()->getArguments())) { g.emitSimpleNode(reinterpret_cast(&arg), "", info.getName().str()); } os << "}\n"; for (auto [info, arg] : - llvm::zip(mod.getPorts().inputs, mod.getArguments())) { + llvm::zip(iports.getInputs(), mod.getBodyBlock()->getArguments())) { for (auto *user : arg.getUsers()) { g.emitEdge(reinterpret_cast(&arg), 0, user, -1, ""); } @@ -183,7 +188,7 @@ struct llvm::DOTGraphTraits os << " style=bold"; return os.str(); - }; + } }; #endif // CIRCT_DIALECT_HW_HWMODULEGRAPH_H diff --git a/include/circt/Dialect/HW/HWOpInterfaces.h b/include/circt/Dialect/HW/HWOpInterfaces.h index ee4b54998118..e083929b55f5 100644 --- a/include/circt/Dialect/HW/HWOpInterfaces.h +++ b/include/circt/Dialect/HW/HWOpInterfaces.h @@ -13,7 +13,10 @@ #ifndef CIRCT_DIALECT_HW_HWOPINTERFACES_H #define CIRCT_DIALECT_HW_HWOPINTERFACES_H +#include "circt/Dialect/HW/HWTypes.h" #include "circt/Dialect/HW/InnerSymbolTable.h" +#include "circt/Dialect/HW/InstanceImplementation.h" +#include "circt/Support/InstanceGraphInterface.h" #include "circt/Support/LLVM.h" #include "mlir/IR/OpDefinition.h" #include "mlir/IR/SymbolTable.h" @@ -21,59 +24,216 @@ namespace circt { namespace hw { -/// A module port direction. -enum class PortDirection { - INPUT = 1, - OUTPUT = 2, - INOUT = 3, -}; +void populateHWModuleLikeTypeConversionPattern(StringRef moduleLikeOpName, + RewritePatternSet &patterns, + TypeConverter &converter); /// This holds the name, type, direction of a module's ports -struct PortInfo { - StringAttr name; - PortDirection direction; - Type type; - +struct PortInfo : public ModulePort { /// This is the argument index or the result index depending on the direction. /// "0" for an output means the first output, "0" for a in/inout means the /// first argument. size_t argNum = ~0U; /// The optional symbol for this port. - InnerSymAttr sym = {}; + DictionaryAttr attrs = {}; LocationAttr loc = {}; StringRef getName() const { return name.getValue(); } - bool isInput() const { return direction == PortDirection::INPUT; } - bool isOutput() const { return direction == PortDirection::OUTPUT; } - bool isInOut() const { return direction == PortDirection::INOUT; } + bool isInput() const { return dir == ModulePort::Direction::Input; } + bool isOutput() const { return dir == ModulePort::Direction::Output; } + bool isInOut() const { return dir == ModulePort::Direction::InOut; } /// Return a unique numeric identifier for this port. ssize_t getId() const { return isOutput() ? argNum : (-1 - argNum); }; + + // Inspect or mutate attributes + InnerSymAttr getSym() const; + void setSym(InnerSymAttr sym, MLIRContext *ctx); }; +raw_ostream &operator<<(raw_ostream &printer, PortInfo port); + /// This holds a decoded list of input/inout and output ports for a module or /// instance. struct ModulePortInfo { - explicit ModulePortInfo(ArrayRef inputs, ArrayRef outputs) - : inputs(inputs.begin(), inputs.end()), - outputs(outputs.begin(), outputs.end()) {} - - explicit ModulePortInfo(ArrayRef mergedPorts) { - inputs.reserve(mergedPorts.size()); - outputs.reserve(mergedPorts.size()); - for (auto port : mergedPorts) { - if (port.isOutput()) - outputs.push_back(port); - else - inputs.push_back(port); + explicit ModulePortInfo(ArrayRef inputs, + ArrayRef outputs) { + ports.insert(ports.end(), inputs.begin(), inputs.end()); + ports.insert(ports.end(), outputs.begin(), outputs.end()); + sanitizeInOut(); + } + + explicit ModulePortInfo(ArrayRef mergedPorts) + : ports(mergedPorts.begin(), mergedPorts.end()) { + sanitizeInOut(); + } + + using iterator = SmallVector::iterator; + using const_iterator = SmallVector::const_iterator; + + iterator begin() { return ports.begin(); } + iterator end() { return ports.end(); } + const_iterator begin() const { return ports.begin(); } + const_iterator end() const { return ports.end(); } + + using PortDirectionRange = llvm::iterator_range< + llvm::filter_iterator>>; + + using ConstPortDirectionRange = llvm::iterator_range>>; + + PortDirectionRange getPortsOfDirection(bool input) { + std::function predicateFn; + if (input) { + predicateFn = [](const PortInfo &port) -> bool { + return port.dir == ModulePort::Direction::Input || + port.dir == ModulePort::Direction::InOut; + }; + } else { + predicateFn = [](const PortInfo &port) -> bool { + return port.dir == ModulePort::Direction::Output; + }; + } + return llvm::make_filter_range(ports, predicateFn); + } + + ConstPortDirectionRange getPortsOfDirection(bool input) const { + std::function predicateFn; + if (input) { + predicateFn = [](const PortInfo &port) -> bool { + return port.dir == ModulePort::Direction::Input || + port.dir == ModulePort::Direction::InOut; + }; + } else { + predicateFn = [](const PortInfo &port) -> bool { + return port.dir == ModulePort::Direction::Output; + }; } + return llvm::make_filter_range(ports, predicateFn); } - /// This contains a list of the input and inout ports. - SmallVector inputs; - /// This is a list of the output ports. - SmallVector outputs; + PortDirectionRange getInputs() { return getPortsOfDirection(true); } + + PortDirectionRange getOutputs() { return getPortsOfDirection(false); } + + ConstPortDirectionRange getInputs() const { + return getPortsOfDirection(true); + } + + ConstPortDirectionRange getOutputs() const { + return getPortsOfDirection(false); + } + + size_t size() const { return ports.size(); } + size_t sizeInputs() const { + auto r = getInputs(); + return std::distance(r.begin(), r.end()); + } + size_t sizeOutputs() const { + auto r = getOutputs(); + return std::distance(r.begin(), r.end()); + } + + size_t portNumForInput(size_t idx) const { + size_t port = 0; + while (idx || ports[port].isOutput()) { + if (!ports[port].isOutput()) + --idx; + ++port; + } + return port; + } + + size_t portNumForOutput(size_t idx) const { + size_t port = 0; + while (idx || !ports[port].isOutput()) { + if (ports[port].isOutput()) + --idx; + ++port; + } + return port; + } + + PortInfo &at(size_t idx) { return ports[idx]; } + PortInfo &atInput(size_t idx) { return ports[portNumForInput(idx)]; } + PortInfo &atOutput(size_t idx) { return ports[portNumForOutput(idx)]; } + + const PortInfo &at(size_t idx) const { return ports[idx]; } + const PortInfo &atInput(size_t idx) const { + return ports[portNumForInput(idx)]; + } + const PortInfo &atOutput(size_t idx) const { + return ports[portNumForOutput(idx)]; + } + + void eraseInput(size_t idx) { + assert(idx < sizeInputs()); + ports.erase(ports.begin() + portNumForInput(idx)); + } + +private: + // convert input inout -> inout type + void sanitizeInOut() { + for (auto &p : ports) + if (auto inout = dyn_cast(p.type)) { + p.type = inout.getElementType(); + p.dir = ModulePort::Direction::InOut; + } + } + + /// This contains a list of all ports. Input first. + SmallVector ports; +}; + +// This provides capability for looking up port indices based on port names. +struct ModulePortLookupInfo { + FailureOr + lookupPortIndex(const llvm::DenseMap &portMap, + StringAttr name) const { + auto it = portMap.find(name); + if (it == portMap.end()) + return failure(); + return it->second; + } + +public: + explicit ModulePortLookupInfo(MLIRContext *ctx, + const ModulePortInfo &portInfo) + : ctx(ctx) { + for (auto &in : portInfo.getInputs()) + inputPortMap[in.name] = in.argNum; + + for (auto &out : portInfo.getOutputs()) + outputPortMap[out.name] = out.argNum; + } + + explicit ModulePortLookupInfo(MLIRContext *ctx, + const SmallVector &portInfo) + : ModulePortLookupInfo(ctx, ModulePortInfo(portInfo)) {} + + // Return the index of the input port with the specified name. + FailureOr getInputPortIndex(StringAttr name) const { + return lookupPortIndex(inputPortMap, name); + } + + // Return the index of the output port with the specified name. + FailureOr getOutputPortIndex(StringAttr name) const { + return lookupPortIndex(outputPortMap, name); + } + + FailureOr getInputPortIndex(StringRef name) const { + return getInputPortIndex(StringAttr::get(ctx, name)); + } + + FailureOr getOutputPortIndex(StringRef name) const { + return getOutputPortIndex(StringAttr::get(ctx, name)); + } + +private: + llvm::DenseMap inputPortMap; + llvm::DenseMap outputPortMap; + MLIRContext *ctx; }; class InnerSymbolOpInterface; @@ -84,6 +244,17 @@ namespace detail { LogicalResult verifyInnerRefNamespace(Operation *op); } // namespace detail +/// Classify operations that are InnerRefNamespace-like, +/// until structure is in place to do this via Traits. +/// Useful for getParentOfType<>, or scheduling passes. +/// Prefer putting the trait on operations here or downstream. +struct InnerRefNamespaceLike { + /// Return if this operation is explicitly an IRN or appears compatible. + static bool classof(mlir::Operation *op); + /// Return if this operation is explicitly an IRN or appears compatible. + static bool classof(const mlir::RegisteredOperationName *opInfo); +}; + } // namespace hw } // namespace circt @@ -123,7 +294,7 @@ class InnerSymbolTable : public TraitBase { // InnerSymbolTable's must be directly nested within an InnerRefNamespace. auto *parent = op->getParentOp(); - if (!parent || !parent->hasTrait()) + if (!parent || !isa(parent)) return op->emitError( "InnerSymbolTable must have InnerRefNamespace parent"); diff --git a/include/circt/Dialect/HW/HWOpInterfaces.td b/include/circt/Dialect/HW/HWOpInterfaces.td index f8449022c8ec..e3bf0e382924 100644 --- a/include/circt/Dialect/HW/HWOpInterfaces.td +++ b/include/circt/Dialect/HW/HWOpInterfaces.td @@ -13,37 +13,310 @@ #ifndef CIRCT_DIALECT_HW_HWOPINTERFACES #define CIRCT_DIALECT_HW_HWOPINTERFACES +include "mlir/IR/SymbolInterfaces.td" include "mlir/IR/OpBase.td" +include "circt/Support/InstanceGraphInterface.td" -def HWModuleLike : OpInterface<"HWModuleLike"> { +def PortList : OpInterface<"PortList", []> { + let cppNamespace = "circt::hw"; + let description = "Operations which produce a unified port list representation"; + let methods = [ + InterfaceMethod<"Get port list", + "SmallVector<::circt::hw::PortInfo>", "getPortList", (ins)>, + + InterfaceMethod<"Get port list", + "::circt::hw::PortInfo", "getPort", (ins "size_t":$idx)>, + + InterfaceMethod<"Get the port a specific input", + "size_t", "getPortIdForInputId", (ins "size_t":$idx)>, + + InterfaceMethod<"Get the port a specific output", + "size_t", "getPortIdForOutputId", (ins "size_t":$idx)>, + + InterfaceMethod<"Get the number of ports", + "size_t", "getNumPorts", (ins)>, + InterfaceMethod<"Get the number of input ports", + "size_t", "getNumInputPorts", (ins)>, + InterfaceMethod<"Get the number of output ports", + "size_t", "getNumOutputPorts", (ins)>, + ]; +} + +def HWModuleLike : OpInterface<"HWModuleLike", [ + Symbol, PortList, InstanceGraphModuleOpInterface]> { let cppNamespace = "circt::hw"; let description = "Provide common module information."; let methods = [ - InterfaceMethod<"Check whether the module is publicly visible", - "bool", "isPublic", (ins), - /*methodBody=*/[{ - return ::mlir::SymbolTable::getSymbolVisibility($_op) == - ::mlir::SymbolTable::Visibility::Public; - }]>, + InterfaceMethod<"Get the module type", + "::circt::hw::ModuleType", "getHWModuleType", (ins)>, - InterfaceMethod<"Get the module name", - "::llvm::StringRef", "moduleName", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ return $_op.getName(); }]>, + InterfaceMethod<"Get the port Attributes", + "SmallVector", "getAllPortAttrs", (ins)>, - InterfaceMethod<"Get the module name", - "::mlir::StringAttr", "moduleNameAttr", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ return $_op.getNameAttr(); }]>, + InterfaceMethod<"Set the port Attributes", + "void", "setAllPortAttrs", (ins "ArrayRef":$attrs)>, - InterfaceMethod<"Get the number of ports", - "size_t", "getNumPorts">, + InterfaceMethod<"Remove the port Attributes", + "void", "removeAllPortAttrs", (ins)>, + + InterfaceMethod<"Get the port Locations", + "SmallVector", "getAllPortLocs", (ins)>, + + InterfaceMethod<"Set the port Locations", + "void", "setAllPortLocs", (ins "ArrayRef":$locs)>, + + InterfaceMethod<"Set the module type (and port names)", + "void", "setHWModuleType", (ins "::circt::hw::ModuleType":$type)>, + + InterfaceMethod<"Set the port names", + "void", "setAllPortNames", (ins "ArrayRef":$names)>, - InterfaceMethod<"Get a port symbol attribute", - "::circt::hw::InnerSymAttr", "getPortSymbolAttr", (ins "size_t":$portIndex)> ]; + let extraSharedClassDeclaration = [{ + /// Attribute name for port symbols. + static StringRef getPortSymbolAttrName() { + return "hw.exportPort"; + } + + /// Return the region containing the body of this function. + Region &getModuleBody() { return $_op->getRegion(0); } + Block *getBodyBlock() { + Region &body = getModuleBody(); + if (body.empty()) + return nullptr; + return &body.front(); + } + + /// Returns the entry block argument at the given index. + BlockArgument getArgumentForInput(unsigned idx) { + return $_op.getModuleBody().getArgument(idx); + } + + /// Returns the entry block argument for the given port. May be null. + BlockArgument getArgumentForPort(unsigned idx) { + return $_op.getModuleBody().getArgument($_op.getHWModuleType().getInputIdForPortId(idx)); + } + + SmallVector getPortTypes() { + return $_op.getHWModuleType().getPortTypes(); + } + + SmallVector getInputTypes() { + return $_op.getHWModuleType().getInputTypes(); + } + + SmallVector getOutputTypes() { + return $_op.getHWModuleType().getOutputTypes(); + } + + /// Return the set of names on input and inout ports + SmallVector getInputNamesStr() { + return $_op.getHWModuleType().getInputNamesStr(); + } + + /// Return the set of names on output ports + SmallVector getOutputNamesStr() { + return $_op.getHWModuleType().getOutputNamesStr(); + } + + /// Return the set of names on input and inout ports + SmallVector getInputNames() { + return $_op.getHWModuleType().getInputNames(); + } + + /// Return the set of names on output ports + SmallVector getOutputNames() { + return $_op.getHWModuleType().getOutputNames(); + } + + void setInputNames(ArrayRef names) { + SmallVector newNames(names.begin(), names.end()); + auto resNames = $_op.getOutputNames(); + newNames.append(resNames.begin(), resNames.end()); + $_op.setAllPortNames(newNames); + } + + void setOutputNames(ArrayRef names) { + SmallVector newNames = $_op.getInputNames(); + newNames.append(names.begin(), names.end()); + $_op.setAllPortNames(newNames); + } + + // Get the name for the specified input or inout port + StringRef getPortName(size_t idx) { + return $_op.getHWModuleType().getPortName(idx); + } + + // Get the name for the specified input or inout port + StringRef getInputName(size_t idx) { + return $_op.getHWModuleType().getInputName(idx); + } + + // Get the name for the specified output port + StringRef getOutputName(size_t idx) { + return $_op.getHWModuleType().getOutputName(idx); + } + + StringAttr getInputNameAttr(size_t idx) { + return $_op.getHWModuleType().getInputNameAttr(idx); + } + + StringAttr getOutputNameAttr(size_t idx) { + return $_op.getHWModuleType().getOutputNameAttr(idx); + } + + Attribute getPortAttrs(size_t idx) { + return $_op.getAllPortAttrs()[idx]; + } + + SmallVector getAllInputAttrs() { + auto attrs = $_op.getAllPortAttrs(); + auto num = $_op.getNumInputPorts(); + SmallVector retval(num); + auto modType = $_op.getHWModuleType(); + for (unsigned x = 0; x < num; ++x) + retval[x] = attrs[modType.getPortIdForInputId(x)]; + return retval; + } + + SmallVector getAllOutputAttrs() { + auto attrs = $_op.getAllPortAttrs(); + auto num = $_op.getNumOutputPorts(); + SmallVector retval(num); + auto modType = $_op.getHWModuleType(); + for (unsigned x = 0; x < num; ++x) + retval[x] = attrs[modType.getPortIdForOutputId(x)]; + return retval; + } + + void setAllInputAttrs(ArrayRef attrs) { + SmallVector retval(attrs.begin(), attrs.end()); + auto resAttrs = $_op.getAllOutputAttrs(); + retval.append(resAttrs.begin(), resAttrs.end()); + $_op.setAllPortAttrs(retval); + } + + void setAllOutputAttrs(ArrayRef attrs) { + SmallVector retval = $_op.getAllInputAttrs(); + retval.append(attrs.begin(), attrs.end()); + $_op.setAllPortAttrs(retval); + } + + Attribute getInputAttrs(size_t idx) { + return $_op.getAllPortAttrs()[$_op.getPortIdForInputId(idx)]; + } + + Attribute getOutputAttrs(size_t idx) { + return $_op.getAllPortAttrs()[$_op.getPortIdForOutputId(idx)]; + } + + void setPortAttrs(size_t idx, DictionaryAttr attr) { + auto attrs = $_op.getAllPortAttrs(); + attrs[idx] = attr; + $_op.setAllPortAttrs(attrs); + } + + void setPortAttr(size_t idx, StringAttr name, Attribute value) { + auto attrs = $_op.getAllPortAttrs(); + NamedAttrList pattr(cast(attrs[idx])); + Attribute oldValue; + if (!value) + oldValue = pattr.erase(name); + else + oldValue = pattr.set(name, value); + if (oldValue != value) { + attrs[idx] = pattr.getDictionary($_op.getContext()); + $_op.setAllPortAttrs(attrs); + } + } + + void setPortAttrs(StringAttr attrName, ArrayRef newAttrs) { + auto attrs = $_op.getAllPortAttrs(); + auto ctxt = $_op.getContext(); + assert(newAttrs.size() == attrs.size()); + for (size_t idx = 0, e = attrs.size(); idx != e; ++idx) { + NamedAttrList pattr(cast_or_null(attrs[idx])); + auto newAttr = newAttrs[idx]; + if (newAttr) + pattr.set(attrName, newAttr); + else + pattr.erase(attrName); + attrs[idx] = pattr.getDictionary(ctxt); + } + $_op.setAllPortAttrs(attrs); + } + + Location getPortLoc(size_t idx) { + return $_op.getAllPortLocs()[idx]; + } + + void setPortLoc(size_t idx, Location loc) { + auto locs = $_op.getAllPortLocs(); + locs[idx] = loc; + return $_op.setAllPortLocs(locs); + } + + Location getInputLoc(size_t idx) { + return $_op.getAllPortLocs()[$_op.getPortIdForInputId(idx)]; + } + + Location getOutputLoc(size_t idx) { + return $_op.getAllPortLocs()[$_op.getPortIdForOutputId(idx)]; + } + + SmallVector getInputLocs() { + auto locs = $_op.getAllPortLocs(); + SmallVector retval; + for (unsigned x = 0, e = $_op.getNumInputPorts(); x < e; ++x) + retval.push_back(locs[$_op.getPortIdForInputId(x)]); + return retval; + } + + ArrayAttr getInputLocsAttr() { + auto locs = $_op.getAllPortLocs(); + SmallVector retval; + for (unsigned x = 0, e = $_op.getNumInputPorts(); x < e; ++x) + retval.push_back(locs[$_op.getPortIdForInputId(x)]); + return ArrayAttr::get($_op->getContext(), retval); + } + + void setInputLocs(ArrayRef inAttrs) { + assert(inAttrs.size() == $_op.getNumInputPorts()); + auto outAttrs = getOutputLocs(); + SmallVector attrs; + attrs.append(inAttrs.begin(), inAttrs.end()); + attrs.append(outAttrs.begin(), outAttrs.end()); + $_op.setAllPortLocs(attrs); + } + + SmallVector getOutputLocs() { + auto locs = $_op.getAllPortLocs(); + SmallVector retval; + for (unsigned x = 0, e = $_op.getNumOutputPorts(); x < e; ++x) + retval.push_back(locs[$_op.getPortIdForOutputId(x)]); + return retval; + } + + ArrayAttr getOutputLocsAttr() { + auto locs = $_op.getAllPortLocs(); + SmallVector retval; + for (unsigned x = 0, e = $_op.getNumOutputPorts(); x < e; ++x) + retval.push_back(locs[$_op.getPortIdForOutputId(x)]); + return ArrayAttr::get($_op->getContext(), retval); + } + + void setOutputLocs(ArrayRef outAttrs) { + assert(outAttrs.size() == $_op.getNumOutputPorts()); + auto inAttrs = getInputLocs(); + SmallVector attrs; + attrs.append(inAttrs.begin(), inAttrs.end()); + attrs.append(outAttrs.begin(), outAttrs.end()); + $_op.setAllPortLocs(attrs); + } + }]; + let verify = [{ static_assert( ConcreteOp::template hasTrait<::mlir::SymbolOpInterface::Trait>(), @@ -52,59 +325,19 @@ def HWModuleLike : OpInterface<"HWModuleLike"> { }]; } -def HWMutableModuleLike : OpInterface<"HWMutableModuleLike"> { +def HWMutableModuleLike : OpInterface<"HWMutableModuleLike", [HWModuleLike]> { let cppNamespace = "circt::hw"; let description = "Provide methods to mutate a module."; let methods = [ - InterfaceMethod<"Return the number of inputs to this module", - "unsigned", "getNumInputs", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ - return $_op->template getAttrOfType("argNames").size(); - }]>, - - InterfaceMethod<"Return the number of outputs from this module", - "unsigned", "getNumOutputs", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ - return $_op->template getAttrOfType("resultNames").size(); - }]>, - - InterfaceMethod<"Return the names of the inputs to this module", - "mlir::ArrayAttr", "getArgNames", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ - return $_op->template getAttrOfType("argNames"); - }]>, - - InterfaceMethod<"Return the names of the outputs this module", - "mlir::ArrayAttr", "getResultNames", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ - return $_op->template getAttrOfType("resultNames"); - }]>, - - InterfaceMethod<"Return the locations of the inputs to this module", - "mlir::ArrayAttr", "getArgLocs", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ - return $_op->template getAttrOfType("argLocs"); - }]>, - - InterfaceMethod<"Return the locations of the outputs of this module", - "mlir::ArrayAttr", "getResultLocs", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ - return $_op->template getAttrOfType("resultLocs"); - }]>, - InterfaceMethod< - "Decode information about the input and output ports on this module.", - "::circt::hw::ModulePortInfo", "getPorts", (ins), + InterfaceMethod<"Get a handle to a utility class which provides by-name lookup of port indices. The returned object does _not_ update if the module is mutated.", + "::circt::hw::ModulePortLookupInfo", "getPortLookupInfo", (ins), /*methodBody=*/[{}], /*defaultImplementation=*/[{ - return getModulePortInfo($_op); + return hw::ModulePortLookupInfo( + $_op->getContext(), + $_op.getPortList()); }]>, /// Insert and remove input and output ports of this module. Does not modify @@ -153,35 +386,20 @@ def HWMutableModuleLike : OpInterface<"HWMutableModuleLike"> { } -def HWInstanceLike : OpInterface<"HWInstanceLike"> { +def HWInstanceLike : OpInterface<"HWInstanceLike", [ + PortList, InstanceGraphInstanceOpInterface]> { let cppNamespace = "circt::hw"; let description = "Provide common module information."; let methods = [ - InterfaceMethod<"Get the name of the instance", - "::llvm::StringRef", "instanceName", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ return $_op.getInstanceName(); }]>, - - InterfaceMethod<"Get the name of the instance", - "::mlir::StringAttr", "instanceNameAttr", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ return $_op.getInstanceNameAttr(); }]>, - - InterfaceMethod<"Get the name of the instantiated module", - "::llvm::StringRef", "referencedModuleName", (ins), + InterfaceMethod<"Get the referenced module via a HW symbol cache.", + "::mlir::Operation *", "getReferencedModuleCached", (ins + "const ::circt::hw::HWSymbolCache *":$cache), /*methodBody=*/[{}], - /*defaultImplementation=*/[{ return $_op.getModuleName(); }]>, - - InterfaceMethod<"Get the name of the instantiated module", - "::mlir::StringAttr", "referencedModuleNameAttr", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ return $_op.getModuleNameAttr().getAttr(); }]>, - - InterfaceMethod<"Get the referenced module", - "::mlir::Operation *", "getReferencedModule", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{}]>, + /*defaultImplementation=*/[{ + return hw::instance_like_impl::getReferencedModule( + cache, $_op, $_op.getModuleNameAttr()); + }]>, ]; } @@ -271,10 +489,26 @@ def InnerSymbol : OpInterface<"InnerSymbolOpInterface"> { circt::hw::InnerSymbolTable::getInnerSymbolAttrName()); }] >, + // Ask an operation if per-field symbols are allowed. + // Defaults to indicating they're allowed iff there's a defined target result, + // but let operations answer this differently if for some reason that makes sense. + StaticInterfaceMethod<"Returns whether per-field symbols are supported for this operation type.", + "bool", "supportsPerFieldSymbols", (ins), [{}], /*defaultImplementation=*/[{ + return ConcreteOp::getTargetResultIndex().has_value(); + }]>, + StaticInterfaceMethod<"Returns the index of the result the innner symbol targets, if applicable. Per-field symbols are resolved into this.", + "std::optional", "getTargetResultIndex">, + InterfaceMethod<"Returns the result the innner symbol targets, if applicable. Per-field symbols are resolved into this.", + "OpResult", "getTargetResult", (ins), [{}], /*defaultImplementation=*/[{ + auto idx = ConcreteOp::getTargetResultIndex(); + if (!idx) + return {}; + return $_op->getResult(*idx); + }]>, ]; let verify = [{ - return verifyInnerSymAttr(op); + return verifyInnerSymAttr(cast(op)); }]; } diff --git a/include/circt/Dialect/HW/HWOps.h b/include/circt/Dialect/HW/HWOps.h index 06578c14ee81..bb15c9c8a1b7 100644 --- a/include/circt/Dialect/HW/HWOps.h +++ b/include/circt/Dialect/HW/HWOps.h @@ -18,12 +18,12 @@ #include "circt/Dialect/HW/HWTypes.h" #include "circt/Support/BuilderUtils.h" #include "mlir/IR/BuiltinOps.h" -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/RegionKindInterface.h" #include "mlir/IR/SymbolTable.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" #include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "llvm/ADT/StringExtras.h" @@ -34,25 +34,10 @@ namespace hw { class EnumFieldAttr; /// Flip a port direction. -PortDirection flip(PortDirection direction); +ModulePort::Direction flip(ModulePort::Direction direction); /// TODO: Move all these functions to a hw::ModuleLike interface. -/// Return an encapsulated set of information about input and output ports of -/// the specified module or instance. -ModulePortInfo getModulePortInfo(Operation *op); - -/// Return an encapsulated set of information about input and output ports of -/// the specified module or instance. The input ports always come before the -/// output ports in the list. -SmallVector getAllModulePortInfos(Operation *op); - -/// Return the PortInfo for the specified input or inout port. -PortInfo getModuleInOrInoutPort(Operation *op, size_t idx); - -/// Return the PortInfo for the specified output port. -PortInfo getModuleOutputPort(Operation *op, size_t idx); - /// Insert and remove ports of a module. The insertion and removal indices must /// be in ascending order. The indices refer to the port positions before any /// insertion or removal occurs. Ports inserted at the same index will appear in @@ -67,29 +52,12 @@ void modifyModulePorts(Operation *op, // Helpers for working with modules. -/// Return true if this is an hw.module, external module, generated module etc. -bool isAnyModule(Operation *module); - /// Return true if isAnyModule or instance. bool isAnyModuleOrInstance(Operation *module); /// Return the signature for the specified module as a function type. FunctionType getModuleType(Operation *module); -/// Return the number of inputs for the specified module/instance. -inline unsigned getModuleNumInputs(Operation *moduleOrInstance) { - assert(isAnyModuleOrInstance(moduleOrInstance) && - "must be called on instance or module"); - return moduleOrInstance->getAttrOfType("argNames").size(); -} - -/// Return the number of outputs for the specified module/instance. -inline unsigned getModuleNumOutputs(Operation *moduleOrInstance) { - assert(isAnyModuleOrInstance(moduleOrInstance) && - "must be called on instance or module"); - return moduleOrInstance->getAttrOfType("resultNames").size(); -} - /// Returns the verilog module name attribute or symbol name of any module-like /// operations. StringAttr getVerilogModuleNameAttr(Operation *module); @@ -97,35 +65,10 @@ inline StringRef getVerilogModuleName(Operation *module) { return getVerilogModuleNameAttr(module).getValue(); } -/// Return the port name for the specified argument or result. These can only -/// return a null StringAttr when the IR is invalid. -StringAttr getModuleArgumentNameAttr(Operation *module, size_t argNo); -StringAttr getModuleResultNameAttr(Operation *module, size_t resultNo); - -static inline StringRef getModuleArgumentName(Operation *module, size_t argNo) { - auto attr = getModuleArgumentNameAttr(module, argNo); - return attr ? attr.getValue() : StringRef(); -} -static inline StringRef getModuleResultName(Operation *module, - size_t resultNo) { - auto attr = getModuleResultNameAttr(module, resultNo); - return attr ? attr.getValue() : StringRef(); -} - -/// Return the port location for the specified argument or result. These can -/// only return a null LocationAttr when the IR is invalid. -LocationAttr getModuleArgumentLocAttr(Operation *module, size_t argNo); -LocationAttr getModuleResultLocAttr(Operation *module, size_t resultNo); - // Index width should be exactly clog2 (size of array), or either 0 or 1 if the // array is a singleton. bool isValidIndexBitWidth(Value index, Value array); -void setModuleArgumentNames(Operation *module, ArrayRef names); -void setModuleResultNames(Operation *module, ArrayRef names); -void setModuleArgumentLocs(Operation *module, ArrayRef locs); -void setModuleResultLocs(Operation *module, ArrayRef locs); - /// Return true if the specified operation is a combinational logic op. bool isCombinational(Operation *op); @@ -154,14 +97,6 @@ LogicalResult checkParameterInContext( &instanceError, bool disallowParamRefs = false); -/// Return the symbol (if exists, else null) on the corresponding input port -/// argument. -InnerSymAttr getArgSym(Operation *op, unsigned i); - -/// Return the symbol (if any, else null) on the corresponding output port -/// argument. -InnerSymAttr getResultSym(Operation *op, unsigned i); - // Check whether an integer value is an offset from a base. bool isOffset(Value base, Value index, uint64_t offset); @@ -182,7 +117,7 @@ class HWModulePortAccessor { void setOutput(unsigned i, Value v); void setOutput(StringRef name, Value v); - const ModulePortInfo &getModulePortInfo() const { return info; } + const ModulePortInfo &getPortList() const { return info; } const llvm::SmallVector &getOutputOperands() const { return outputOperands; } diff --git a/include/circt/Dialect/HW/HWPasses.h b/include/circt/Dialect/HW/HWPasses.h index 0a0fa002792a..93d4ea6a6954 100644 --- a/include/circt/Dialect/HW/HWPasses.h +++ b/include/circt/Dialect/HW/HWPasses.h @@ -25,6 +25,7 @@ std::unique_ptr createPrintInstanceGraphPass(); std::unique_ptr createHWSpecializePass(); std::unique_ptr createPrintHWModuleGraphPass(); std::unique_ptr createFlattenIOPass(); +std::unique_ptr createVerifyInnerRefNamespacePass(); /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION diff --git a/include/circt/Dialect/HW/HWReductions.h b/include/circt/Dialect/HW/HWReductions.h new file mode 100644 index 000000000000..3b9f301a5f8b --- /dev/null +++ b/include/circt/Dialect/HW/HWReductions.h @@ -0,0 +1,29 @@ +//===- HWReductions.h - HW reduction interface declaration ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_HW_HWREDUCTIONS_H +#define CIRCT_DIALECT_HW_HWREDUCTIONS_H + +#include "circt/Reduce/Reduction.h" + +namespace circt { +namespace hw { + +/// A dialect interface to provide reduction patterns to a reducer tool. +struct HWReducePatternDialectInterface : public ReducePatternDialectInterface { + using ReducePatternDialectInterface::ReducePatternDialectInterface; + void populateReducePatterns(circt::ReducePatternSet &patterns) const override; +}; + +/// Register the HW Reduction pattern dialect interface to the given registry. +void registerReducePatternDialectInterface(mlir::DialectRegistry ®istry); + +} // namespace hw +} // namespace circt + +#endif // CIRCT_DIALECT_HW_HWREDUCTIONS_H diff --git a/include/circt/Dialect/HW/HWStructure.td b/include/circt/Dialect/HW/HWStructure.td index d273d8a048c5..9ce9ced92df8 100644 --- a/include/circt/Dialect/HW/HWStructure.td +++ b/include/circt/Dialect/HW/HWStructure.td @@ -17,7 +17,7 @@ include "circt/Dialect/HW/HWAttributes.td" include "circt/Dialect/HW/HWDialect.td" include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Dialect/HW/HWTypes.td" -include "mlir/IR/FunctionInterfaces.td" +include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/IR/OpAsmInterface.td" include "mlir/IR/OpBase.td" include "mlir/IR/RegionKindInterface.td" @@ -29,9 +29,10 @@ include "mlir/Interfaces/SideEffectInterfaces.td" /// the module-like operations. class HWModuleOpBase traits = []> : HWOp, DeclareOpInterfaceMethods, DeclareOpInterfaceMethods, - FunctionOpInterface, Symbol, + Symbol, InnerSymbolTable, OpAsmOpInterface, HasParent<"mlir::ModuleOp">]> { /// Additional class declarations inside the module op. code extraModuleClassDeclaration = ?; @@ -49,23 +50,62 @@ class HWModuleOpBase traits = []> : ArrayRef eraseInputs, ArrayRef eraseOutputs ); + + void setPortSymbolAttr(size_t portIndex, ::circt::hw::InnerSymAttr sym); + + StringAttr getArgName(size_t index) { + return getHWModuleType().getInputNameAttr(index); + } + }]; /// Additional class definitions inside the module op. code extraModuleClassDefinition = [{}]; let extraClassDefinition = extraModuleClassDefinition # [{ + + ModuleType $cppClass::getHWModuleType() { + return getModuleType(); + } + + void $cppClass::setPortSymbolAttr(size_t portIndex, ::circt::hw::InnerSymAttr sym) { + auto portSymAttr = StringAttr::get(getContext(), getPortSymbolAttrName()); + setPortAttr(portIndex, portSymAttr, sym); + } + size_t $cppClass::getNumPorts() { - return getAllPorts().size(); + auto modty = getHWModuleType(); + return modty.getNumPorts(); + } + + size_t $cppClass::getNumInputPorts() { + auto modty = getHWModuleType(); + return modty.getNumInputs(); + } + + size_t $cppClass::getNumOutputPorts() { + auto modty = getHWModuleType(); + return modty.getNumOutputs(); + } + + size_t $cppClass::getPortIdForInputId(size_t idx) { + auto modty = getHWModuleType(); + return modty.getPortIdForInputId(idx); + } + + size_t $cppClass::getPortIdForOutputId(size_t idx) { + auto modty = getHWModuleType(); + return modty.getPortIdForOutputId(idx); + } + + SmallVector<::circt::hw::PortInfo> $cppClass::getPortList() { + return ::getPortList(*this); } - ::circt::hw::InnerSymAttr $cppClass::getPortSymbolAttr(size_t portIndex) { - for (::mlir::NamedAttribute argAttr : - ::mlir::function_interface_impl::getArgAttrs(*this, portIndex)) - if (auto sym = argAttr.getValue().dyn_cast<::circt::hw::InnerSymAttr>()) - return sym; - return ::circt::hw::InnerSymAttr(); + ::circt::hw::PortInfo $cppClass::getPort(size_t idx) { + return ::getPort(*this, idx); } + }]; } @@ -79,15 +119,12 @@ def HWModuleOp : HWModuleOpBase<"module", name, a list of ports, a list of parameters, and a body that represents the connections within the module. }]; - let arguments = (ins TypeAttrOf:$function_type, - OptionalAttr:$arg_attrs, - OptionalAttr:$res_attrs, - StrArrayAttr:$argNames, - StrArrayAttr:$resultNames, - LocationArrayAttr:$argLocs, - LocationArrayAttr:$resultLocs, + let arguments = (ins SymbolNameAttr:$sym_name, + TypeAttrOf:$module_type, + OptionalAttr:$per_port_attrs, + OptionalAttr:$port_locs, ParamDeclArrayAttr:$parameters, - StrAttr:$comment); + OptionalAttr:$comment); let results = (outs); let regions = (region SizedRegion<1>:$body); @@ -110,45 +147,20 @@ def HWModuleOp : HWModuleOpBase<"module", ]; let extraModuleClassDeclaration = [{ - using mlir::detail::FunctionOpInterfaceTrait::front; - using mlir::detail::FunctionOpInterfaceTrait::getFunctionBody; // Implement RegionKindInterface. static RegionKind getRegionKind(unsigned index) { return RegionKind::Graph;} - /// Decode information about the input and output ports on this module. - ModulePortInfo getPorts() { - return getModulePortInfo(*this); - } - - /// Return all the module ports merged into one list. - SmallVector getAllPorts() { - return getAllModulePortInfos(*this); - } - - /// Returns the number of in or inout ports. - size_t getNumInOrInoutPorts() { return getArgumentTypes().size(); } - - /// Return the PortInfo for the specified input or inout port. - PortInfo getInOrInoutPort(size_t i) { - return getModuleInOrInoutPort(*this, i); - } - - /// Return the PortInfo for the specified output port. - PortInfo getOutputPort(size_t i) { - return getModuleOutputPort(*this, i); - } - /// Append an input with a given name and type to the port list. /// If the name is not unique, a unique name is created and returned. std::pair appendInput(const Twine &name, Type ty) { - return insertInput(getNumInOrInoutPorts(), name, ty); + return insertInput(getNumInputPorts(), name, ty); } std::pair appendInput(StringAttr name, Type ty) { - return insertInput(getNumInOrInoutPorts(), name.getValue(), ty); + return insertInput(getNumInputPorts(), name.getValue(), ty); } /// Prepend an input with a given name and type to the port list. @@ -177,12 +189,12 @@ def HWModuleOp : HWModuleOpBase<"module", /// Append an output with a given name and type to the port list. /// If the name is not unique, a unique name is created. void appendOutput(StringAttr name, Value value) { - return insertOutputs(getResultTypes().size(), {{name, value}}); + return insertOutputs(getNumOutputPorts(), {{name, value}}); } void appendOutput(const Twine &name, Value value) { ::mlir::StringAttr nameAttr = ::mlir::StringAttr::get(getContext(), name); - return insertOutputs(getResultTypes().size(), {{nameAttr, value}}); + return insertOutputs(getNumOutputPorts(), {{nameAttr, value}}); } /// Prepend an output with a given name and type to the port list. @@ -202,8 +214,6 @@ def HWModuleOp : HWModuleOpBase<"module", void insertOutputs(unsigned index, ArrayRef> outputs); - Block *getBodyBlock() { return &getFunctionBody().front(); } - // Get the module's symbolic name as StringAttr. StringAttr getNameAttr() { return (*this)->getAttrOfType( @@ -217,22 +227,6 @@ def HWModuleOp : HWModuleOpBase<"module", void getAsmBlockArgumentNames(mlir::Region ®ion, mlir::OpAsmSetValueNameFn setNameFn); - /// Returns the argument types of this function. - ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } - - /// Returns the result types of this function. - ArrayRef getResultTypes() { return getFunctionType().getResults(); } - - /// Verify the type attribute of this function. Returns failure and emits - /// an error if the attribute is invalid. - LogicalResult verifyType() { - auto type = getFunctionTypeAttr().getValue(); - if (!type.isa()) - return emitOpError("requires '" + getFunctionTypeAttrName().getValue() + - "' attribute of function type"); - return success(); - } - /// Verifies the body of the function. LogicalResult verifyBody(); }]; @@ -252,10 +246,10 @@ def HWModuleExternOp : HWModuleOpBase<"module.extern"> { have proper parameterization in the hw.dialect. We need a way to represent parameterized types instead of just concrete types. }]; - let arguments = (ins TypeAttrOf:$function_type, - OptionalAttr:$arg_attrs, - OptionalAttr:$res_attrs, - StrArrayAttr:$argNames, StrArrayAttr:$resultNames, + let arguments = (ins SymbolNameAttr:$sym_name, + TypeAttrOf:$module_type, + OptionalAttr:$per_port_attrs, + OptionalAttr:$port_locs, ParamDeclArrayAttr:$parameters, OptionalAttr:$verilogName); let results = (outs); @@ -274,20 +268,6 @@ def HWModuleExternOp : HWModuleOpBase<"module.extern"> { ]; let extraModuleClassDeclaration = [{ - /// Return all the module ports merged into one list. - SmallVector getAllPorts() { - return getAllModulePortInfos(*this); - } - - /// Return the PortInfo for the specified input or inout port. - PortInfo getInOrInoutPort(size_t i) { - return getModuleInOrInoutPort(*this, i); - } - - /// Return the PortInfo for the specified output port. - PortInfo getOutputPort(size_t i) { - return getModuleOutputPort(*this, i); - } /// Return the name to use for the Verilog module that we're referencing /// here. This is typically the symbol, but can be overridden with the @@ -315,21 +295,6 @@ def HWModuleExternOp : HWModuleOpBase<"module.extern"> { void getAsmBlockArgumentNames(mlir::Region ®ion, mlir::OpAsmSetValueNameFn setNameFn); - /// Returns the argument types of this function. - ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } - - /// Returns the result types of this function. - ArrayRef getResultTypes() { return getFunctionType().getResults(); } - - /// Verify the type attribute of this function. Returns failure and emits - /// an error if the attribute is invalid. - LogicalResult verifyType() { - auto type = getFunctionTypeAttr().getValue(); - if (!type.isa()) - return emitOpError("requires '" + getFunctionTypeAttrName().getValue() + - "' attribute of function type"); - return success(); - } }]; let hasCustomAssemblyFormat = 1; @@ -371,11 +336,11 @@ def HWModuleGeneratedOp : HWModuleOpBase<"module.generated", [ The 'verilogName' attribute (when present) specifies the spelling of the module name in Verilog we can use. See hw.module for an explanation. }]; - let arguments = (ins FlatSymbolRefAttr:$generatorKind, - TypeAttrOf:$function_type, - OptionalAttr:$arg_attrs, - OptionalAttr:$res_attrs, - StrArrayAttr:$argNames, StrArrayAttr:$resultNames, + let arguments = (ins SymbolNameAttr:$sym_name, + FlatSymbolRefAttr:$generatorKind, + TypeAttrOf:$module_type, + OptionalAttr:$per_port_attrs, + OptionalAttr:$port_locs, ParamDeclArrayAttr:$parameters, OptionalAttr:$verilogName); let results = (outs); @@ -396,16 +361,6 @@ def HWModuleGeneratedOp : HWModuleOpBase<"module.generated", [ ]; let extraModuleClassDeclaration = [{ - /// Decode information about the input and output ports on this module. - ModulePortInfo getPorts() { - return getModulePortInfo(*this); - } - - /// Return all the module ports merged into one list. - SmallVector getAllPorts() { - return getAllModulePortInfos(*this); - } - /// Return the name to use for the Verilog module that we're referencing /// here. This is typically the symbol, but can be overridden with the /// verilogName attribute. @@ -425,23 +380,6 @@ def HWModuleGeneratedOp : HWModuleOpBase<"module.generated", [ void getAsmBlockArgumentNames(mlir::Region ®ion, mlir::OpAsmSetValueNameFn setNameFn); - /// Returns the argument types of this function. - ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } - - /// Returns the result types of this function. - ArrayRef getResultTypes() { return getFunctionType().getResults(); } - - /// Verify the type attribute of this function. Returns failure and emits - /// an error if the attribute is invalid. - LogicalResult verifyType() { - auto type = getFunctionTypeAttr().getValue(); - if (!type.isa()) - return emitOpError("requires '" + getFunctionTypeAttrName().getValue() + - "' attribute of function type"); - return success(); - } - - TypeAttr getTypeAttr(); }]; let hasCustomAssemblyFormat = 1; @@ -450,7 +388,9 @@ def HWModuleGeneratedOp : HWModuleOpBase<"module.generated", [ def InstanceOp : HWOp<"instance", [ DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, DeclareOpInterfaceMethods]> { let summary = "Create an instance of a module"; let description = [{ @@ -467,7 +407,7 @@ def InstanceOp : HWOp<"instance", [ Variadic:$inputs, StrArrayAttr:$argNames, StrArrayAttr:$resultNames, ParamDeclArrayAttr:$parameters, - OptionalAttr:$inner_sym); + OptionalAttr:$inner_sym); let results = (outs Variadic:$results); let builders = [ @@ -475,14 +415,14 @@ def InstanceOp : HWOp<"instance", [ OpBuilder<(ins "Operation*":$module, "StringAttr":$name, "ArrayRef":$inputs, CArg<"ArrayAttr", "{}">:$parameters, - CArg<"StringAttr", "{}">:$sym_name)>, + CArg<"InnerSymAttr", "{}">:$innerSym)>, /// Create a instance that refers to a known module. OpBuilder<(ins "Operation*":$module, "StringRef":$name, "ArrayRef":$inputs, CArg<"ArrayAttr", "{}">:$parameters, - CArg<"StringAttr", "{}">:$sym_name), [{ + CArg<"InnerSymAttr", "{}">:$innerSym), [{ build($_builder, $_state, module, $_builder.getStringAttr(name), inputs, - parameters, sym_name); + parameters, innerSym); }]>, ]; @@ -502,35 +442,24 @@ def InstanceOp : HWOp<"instance", [ void setResultName(size_t i, StringAttr name); /// Change the names of all the input ports. - void setArgumentNames(ArrayAttr names) { + void setInputNames(ArrayAttr names) { setArgNamesAttr(names); } /// Change the names of all the result ports. - void setResultNames(ArrayAttr names) { + void setOutputNames(ArrayAttr names) { setResultNamesAttr(names); } /// Lookup the module or extmodule for the symbol. This returns null on /// invalid IR. - Operation *getReferencedModule(const HWSymbolCache *cache); - - /// Get the instances's name. - StringAttr getName() { - return getInstanceNameAttr(); - } + Operation *getReferencedModuleSlow(); - /// Set the instance's name. - void setName(StringAttr name) { - setInstanceNameAttr(name); - } - - //===------------------------------------------------------------------===// - // SymbolOpInterface Methods - //===------------------------------------------------------------------===// + /// Return the values for the port in port order. + /// Note: The module ports may not be input, output ordered. This computes + /// the port index to instance result/input Value mapping. + void getValues(SmallVectorImpl &values, const ModulePortInfo &mpi); - /// An InstanceOp may optionally define a symbol. - bool isOptionalSymbol() { return true; } }]; let hasCustomAssemblyFormat = 1; @@ -556,41 +485,6 @@ def OutputOp : HWOp<"output", [Terminator, HasParent<"HWModuleOp">, let hasVerifier = 1; } -def GlobalRefOp : HWOp<"globalRef", [ - DeclareOpInterfaceMethods, - IsolatedFromAbove, Symbol]> { - let summary = "A global reference to uniquely identify each" - "instance of an operation"; - let description = [{ - This works like a symbol reference to an operation by specifying the - instance path to uniquely identify it globally. - It can be used to attach per instance metadata (non-local attributes). - This also lets components of the path point to a common entity. - }]; - - let arguments = (ins SymbolNameAttr:$sym_name, NameRefArrayAttr:$namepath); - - let assemblyFormat = [{ $sym_name $namepath attr-dict }]; -} - -def ProbeOp : HWOp<"probe", []> { - let summary = "Probe values for use in remote references"; - let description = [{ - Captures values without binding to any accidental name. This allows - capturing names holding values of interest while allowing the name to - resolved only at emission time. - }]; - - let arguments = (ins SymbolNameAttr:$inner_sym, - Variadic:$captured); - let results = (outs); - - let assemblyFormat = [{ - $inner_sym attr-dict (`,` $captured^ `:` qualified(type($captured)))? - }]; - -} - def HierPathOp : HWOp<"hierpath", [IsolatedFromAbove, Symbol, DeclareOpInterfaceMethods]> { @@ -666,4 +560,53 @@ def HierPathOp : HWOp<"hierpath", }]; } +// Edge behavior for trigger blocks. Currently these map 1:1 to SV event +// control kinds. + +/// AtPosEdge triggers on a rise from 0 to 1/X/Z, or X/Z to 1. +def AtPosEdge: I32EnumAttrCase<"AtPosEdge", 0, "posedge">; +/// AtNegEdge triggers on a drop from 1 to 0/X/Z, or X/Z to 0. +def AtNegEdge: I32EnumAttrCase<"AtNegEdge", 1, "negedge">; +/// AtEdge(v) is syntactic sugar for "AtPosEdge(v) or AtNegEdge(v)". +def AtEdge : I32EnumAttrCase<"AtEdge", 2, "edge">; + +def EventControlAttr : I32EnumAttr<"EventControl", "edge control trigger", + [AtPosEdge, AtNegEdge, AtEdge]> { + let cppNamespace = "circt::hw"; +} + +def TriggeredOp : HWOp<"triggered", [ + IsolatedFromAbove, SingleBlock, NoTerminator]> { + let summary = "A procedural region with a trigger condition"; + let description = [{ + A procedural region that can be triggered by an event. The trigger + condition is a 1-bit value that is activated based on some event control + attribute. + The operation is `IsolatedFromAbove`, and thus requires values passed into + the trigger region to be explicitly passed in through the `inputs` list. + }]; + + let regions = (region SizedRegion<1>:$body); + let arguments = (ins EventControlAttr:$event, I1:$trigger, Variadic:$inputs); + let results = (outs); + + let assemblyFormat = [{ + $event $trigger `(` $inputs `)` `:` type($inputs) $body attr-dict + }]; + + let extraClassDeclaration = [{ + Block *getBodyBlock() { return &getBody().front(); } + + // Return the input arguments inside the trigger region. + ArrayRef getInnerInputs() { + return getBodyBlock()->getArguments(); + } + }]; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "EventControlAttr":$event, "Value":$trigger, "ValueRange":$inputs)> + ]; +} + #endif // CIRCT_DIALECT_HW_HWSTRUCTURE_TD diff --git a/include/circt/Dialect/HW/HWTypeInterfaces.h b/include/circt/Dialect/HW/HWTypeInterfaces.h index ed496c4ef5e4..4701ae698ea5 100644 --- a/include/circt/Dialect/HW/HWTypeInterfaces.h +++ b/include/circt/Dialect/HW/HWTypeInterfaces.h @@ -16,6 +16,29 @@ #include "circt/Support/LLVM.h" #include "mlir/IR/Types.h" +namespace circt { +namespace hw { +namespace FieldIdImpl { +uint64_t getMaxFieldID(Type); + +std::pair<::mlir::Type, uint64_t> getSubTypeByFieldID(Type, uint64_t fieldID); + +::mlir::Type getFinalTypeByFieldID(Type type, uint64_t fieldID); + +std::pair projectToChildFieldID(Type, uint64_t fieldID, + uint64_t index); + +std::pair getIndexAndSubfieldID(Type type, + uint64_t fieldID); + +uint64_t getFieldID(Type type, uint64_t index); + +uint64_t getIndexForFieldID(Type type, uint64_t fieldID); + +} // namespace FieldIdImpl +} // namespace hw +} // namespace circt + #include "circt/Dialect/HW/HWTypeInterfaces.h.inc" #endif // CIRCT_DIALECT_HW_HWTYPEINTERFACES_H diff --git a/include/circt/Dialect/HW/HWTypeInterfaces.td b/include/circt/Dialect/HW/HWTypeInterfaces.td index ea1c184652c5..7e8ca9b4b10e 100644 --- a/include/circt/Dialect/HW/HWTypeInterfaces.td +++ b/include/circt/Dialect/HW/HWTypeInterfaces.td @@ -19,11 +19,62 @@ def FieldIDTypeInterface : TypeInterface<"FieldIDTypeInterface"> { let cppNamespace = "circt::hw"; let description = [{ Common methods for types which can be indexed by a FieldID. + FieldID is a depth-first numbering of the elements of a type. For example: + ``` + struct a /* 0 */ { + int b; /* 1 */ + struct c /* 2 */ { + int d; /* 3 */ + } + } + + int e; /* 0 */ + ``` }]; let methods = [ InterfaceMethod<"Get the maximum field ID for this type", - "uint64_t", "getMaxFieldID"> + "uint64_t", "getMaxFieldID">, + + InterfaceMethod<[{ + Get the sub-type of a type for a field ID, and the subfield's ID. Strip + off a single layer of this type and return the sub-type and a field ID + targeting the same field, but rebased on the sub-type. + + The resultant type *may* not be a FieldIDTypeInterface if the resulting + fieldID is zero. This means that leaf types may be ground without + implementing an interface. An empty aggregate will also appear as a + zero. + }], "std::pair<::mlir::Type, uint64_t>", + "getSubTypeByFieldID", (ins "uint64_t":$fieldID)>, + + InterfaceMethod<[{ + Returns the effective field id when treating the index field as the + root of the type. Essentially maps a fieldID to a fieldID after a + subfield op. Returns the new id and whether the id is in the given + child. + }], "std::pair", "projectToChildFieldID", + (ins "uint64_t":$fieldID, "uint64_t":$index)>, + + InterfaceMethod<[{ + Returns the index (e.g. struct or vector element) for a given FieldID. + This returns the containing index in the case that the fieldID points to a + child field of a field. + }], "uint64_t", "getIndexForFieldID", (ins "uint64_t":$fieldID)>, + + InterfaceMethod<[{ + Return the fieldID of a given index (e.g. struct or vector element). + Field IDs start at 1, and are assigned + to each field in a recursive depth-first walk of all + elements. A field ID of 0 is used to reference the type itself. + }], "uint64_t", "getFieldID", (ins "uint64_t":$fieldID)>, + + InterfaceMethod<[{ + Find the index of the element that contains the given fieldID. + As well, rebase the fieldID to the element. + }], "std::pair", "getIndexAndSubfieldID", + (ins "uint64_t":$fieldID)>, + ]; } diff --git a/include/circt/Dialect/HW/HWTypes.h b/include/circt/Dialect/HW/HWTypes.h index 281ce82a5a6f..72d0e786e2e6 100644 --- a/include/circt/Dialect/HW/HWTypes.h +++ b/include/circt/Dialect/HW/HWTypes.h @@ -15,6 +15,7 @@ #define CIRCT_DIALECT_HW_TYPES_H #include "circt/Dialect/HW/HWDialect.h" +#include "circt/Dialect/HW/HWTypeInterfaces.h" #include "circt/Support/LLVM.h" #include "mlir/IR/BuiltinTypes.h" @@ -22,15 +23,38 @@ namespace circt { namespace hw { + +struct ModulePort { + enum Direction { Input, Output, InOut }; + mlir::StringAttr name; + mlir::Type type; + Direction dir; +}; + class HWSymbolCache; class ParamDeclAttr; class TypedeclOp; +class ModuleType; + namespace detail { -/// Struct defining a field. Used in structs and unions. + +ModuleType fnToMod(Operation *op, ArrayRef inputNames, + ArrayRef outputNames); +ModuleType fnToMod(FunctionType fn, ArrayRef inputNames, + ArrayRef outputNames); + +/// Struct defining a field. Used in structs. struct FieldInfo { mlir::StringAttr name; mlir::Type type; }; + +/// Struct defining a field with an offset. Used in unions. +struct OffsetFieldInfo { + StringAttr name; + Type type; + size_t offset; +}; } // namespace detail } // namespace hw } // namespace circt @@ -77,7 +101,7 @@ bool type_isa(Type type) { // Then check if it is a type alias wrapping the requested type. if (auto alias = type.dyn_cast()) - return alias.getInnerType().isa(); + return type_isa(alias.getInnerType()); return false; } @@ -99,7 +123,7 @@ BaseTy type_cast(Type type) { return type.cast(); // Otherwise, it must be a type alias wrapping the requested type. - return type.cast().getInnerType().cast(); + return type_cast(type.cast().getInnerType()); } template diff --git a/include/circt/Dialect/HW/HWTypes.td b/include/circt/Dialect/HW/HWTypes.td index 3f22ae3b4da7..3d13624f4698 100644 --- a/include/circt/Dialect/HW/HWTypes.td +++ b/include/circt/Dialect/HW/HWTypes.td @@ -72,18 +72,17 @@ def HWAggregateType : DialectType}]>; -//===----------------------------------------------------------------------===// -// Type Definitions -//===----------------------------------------------------------------------===// - -/// Points to a name within a module. -def HWInnerRefAttr : Attr< - CPred<"$_self.isa<::circt::hw::InnerRefAttr>()">, - "name reference attribute"> { - let returnType = "::circt::hw::InnerRefAttr"; - let storageType = "::circt::hw::InnerRefAttr"; - let convertFromStorage = "$_self"; -} +// A handle to refer to circt::hw::ModuleType in ODS. +def ModuleType : DialectType($_self)">, + "a module", "::circt::hw::ModuleType">; + +// A handle to refer to circt::hw::StringType in ODS. +def HWStringType : + DialectType($_self)">, + "a HW string", "::circt::hw::StringType">, + BuildableType<"::circt::hw::StringType::get($_builder.getContext())">; /// A flat symbol reference or a reference to a name within a module. def NameRefAttr : Attr< @@ -103,7 +102,7 @@ def NameRefArrayAttr : TypedArrayAttrBase { let mnemonic = "innerSymProps"; let parameters = (ins - "mlir::StringAttr":$name, + "::mlir::StringAttr":$name, DefaultValuedParameter<"uint64_t", "0">:$fieldID, DefaultValuedParameter<"::mlir::StringAttr", "public">:$sym_visibility ); @@ -116,6 +115,7 @@ def InnerSymProperties : AttrDef { let hasCustomAssemblyFormat = 1; // The assembly format is as follows: // "`<` `@` $name `,` $fieldID `,` $sym_visibility `>`"; + let genVerifyDecl = 1; } @@ -144,7 +144,7 @@ def InnerSymAttr : AttrDef { ]; let extraClassDeclaration = [{ /// Get the inner sym name for fieldID, if it exists. - mlir::StringAttr getSymIfExists(unsigned fieldID) const; + mlir::StringAttr getSymIfExists(uint64_t fieldID) const; /// Get the inner sym name for fieldID=0, if it exists. mlir::StringAttr getSymName() const { return getSymIfExists(0); } @@ -156,7 +156,7 @@ def InnerSymAttr : AttrDef { bool empty() const { return getProps().empty(); } /// Return an InnerSymAttr with the inner symbol for the specified fieldID removed. - InnerSymAttr erase(unsigned fieldID) const; + InnerSymAttr erase(uint64_t fieldID) const; using iterator = mlir::ArrayRef::iterator; /// Iterator begin for all the InnerSymProperties. diff --git a/include/circt/Dialect/HW/HWTypesImpl.td b/include/circt/Dialect/HW/HWTypesImpl.td index 368f455a9447..9319a4847a77 100644 --- a/include/circt/Dialect/HW/HWTypesImpl.td +++ b/include/circt/Dialect/HW/HWTypesImpl.td @@ -14,10 +14,12 @@ #define CIRCT_DIALECT_HW_HWTYPESIMPL_TD include "circt/Dialect/HW/HWDialect.td" +include "circt/Dialect/HW/HWTypeInterfaces.td" include "mlir/IR/AttrTypeBase.td" // Base class for other typedefs. Provides dialact-specific defaults. -class HWType : TypeDef { } +class HWType traits = []> + : TypeDef { } //===----------------------------------------------------------------------===// // Type declarations @@ -47,7 +49,7 @@ def IntTypeImpl : HWType<"Int"> { } // A simple fixed size array. Declares the hw::ArrayType in C++. -def ArrayTypeImpl : HWType<"Array"> { +def ArrayTypeImpl : HWType<"Array", [DeclareTypeInterfaceMethods]> { let summary = "fixed-sized array"; let description = [{ Fixed sized HW arrays are roughly similar to C arrays. On the wire (vs. @@ -69,12 +71,12 @@ def ArrayTypeImpl : HWType<"Array"> { auto sizeAttr = ::mlir::IntegerAttr::get(intType, size); return ArrayType::get(ctx, elementType, sizeAttr); } - size_t getSize() const; + size_t getNumElements() const; }]; } // An 'unpacked' array of fixed size. -def UnpackedArrayType : HWType<"UnpackedArray"> { +def UnpackedArrayType : HWType<"UnpackedArray", [DeclareTypeInterfaceMethods]> { let summary = "SystemVerilog 'unpacked' fixed-sized array"; let description = [{ Unpacked arrays are a more flexible array representation than packed arrays, @@ -94,7 +96,7 @@ def UnpackedArrayType : HWType<"UnpackedArray"> { auto sizeAttr = ::mlir::IntegerAttr::get(intType, size); return UnpackedArrayType::get(ctx, elementType, sizeAttr); } - size_t getSize() const; + size_t getNumElements() const; }]; } @@ -120,7 +122,7 @@ def InOutTypeImpl : HWType<"InOut"> { } // A packed struct. Declares the hw::StructType in C++. -def StructTypeImpl : HWType<"Struct"> { +def StructTypeImpl : HWType<"Struct", [DeclareTypeInterfaceMethods]> { let summary = "HW struct type"; let description = [{ Represents a structure of name, value pairs. @@ -129,6 +131,7 @@ def StructTypeImpl : HWType<"Struct"> { let mnemonic = "struct"; let hasCustomAssemblyFormat = 1; + let genVerifyDecl = 1; let parameters = ( ins ArrayRefParameter< @@ -139,8 +142,8 @@ def StructTypeImpl : HWType<"Struct"> { using FieldInfo = ::circt::hw::detail::FieldInfo; mlir::Type getFieldType(mlir::StringRef fieldName); void getInnerTypes(mlir::SmallVectorImpl&); - std::optional getFieldIndex(mlir::StringRef fieldName); - std::optional getFieldIndex(mlir::StringAttr fieldName); + std::optional getFieldIndex(mlir::StringRef fieldName); + std::optional getFieldIndex(mlir::StringAttr fieldName); }]; } @@ -161,6 +164,9 @@ def EnumTypeImpl : HWType<"Enum"> { /// Returns true if the requested field is part of this enum bool contains(mlir::StringRef field); + /// Returns the number of bits used by the enum + size_t getBitWidth(); + /// Returns the index of the requested field, or a nullopt if the field is // not part of this enum. std::optional indexOf(mlir::StringRef field); @@ -179,10 +185,16 @@ def UnionTypeImpl : HWType<"Union"> { let mnemonic = "union"; let hasCustomAssemblyFormat = 1; + let genVerifyDecl = 1; let extraClassDeclaration = [{ - using FieldInfo = ::circt::hw::detail::FieldInfo; - mlir::Type getFieldType(mlir::StringRef fieldName); + using FieldInfo = ::circt::hw::detail::OffsetFieldInfo; + + FieldInfo getFieldInfo(::mlir::StringRef fieldName); + + ::mlir::Type getFieldType(::mlir::StringRef fieldName); + std::optional getFieldIndex(mlir::StringRef fieldName); + std::optional getFieldIndex(mlir::StringAttr fieldName); }]; } @@ -222,4 +234,56 @@ def TypeAliasType : HWType<"TypeAlias"> { }]; } +def ModuleTypeImpl : HWType<"Module"> { + let summary = "Module Type"; + let description = [{ + Module types have ports. + }]; + let parameters = (ins ArrayRefParameter<"::circt::hw::ModulePort", "port list">:$ports); + let hasCustomAssemblyFormat = 1; + let genVerifyDecl = 1; + let mnemonic = "modty"; + + let extraClassDeclaration = [{ + // Many of these are transitional and will be removed when modules and instances + // have moved over to this type. + // *Input* are input indexed + // *Output* are output indexed + // *Port* are absolutely indexed + size_t getNumPorts(); + size_t getNumInputs(); + size_t getNumOutputs(); + SmallVector getPortTypes(); + SmallVector getInputTypes(); + SmallVector getOutputTypes(); + Type getPortType(size_t); + Type getInputType(size_t); + Type getOutputType(size_t); + SmallVector getInputNamesStr(); + SmallVector getOutputNamesStr(); + SmallVector getInputNames(); + SmallVector getOutputNames(); + StringAttr getPortNameAttr(size_t); + StringRef getPortName(size_t); + StringAttr getInputNameAttr(size_t); + StringRef getInputName(size_t); + StringAttr getOutputNameAttr(size_t); + StringRef getOutputName(size_t); + FunctionType getFuncType(); + bool isOutput(size_t); + size_t getInputIdForPortId(size_t); + size_t getOutputIdForPortId(size_t); + size_t getPortIdForInputId(size_t); + size_t getPortIdForOutputId(size_t); + FailureOr resolveParametricTypes( + ArrayAttr parameters, LocationAttr loc, bool emitErrors); + }]; +} + +def HWStringTypeImpl : HWType<"String"> { + let summary = "String type"; + let description = "Defines a string type for the hw-centric dialects"; + let mnemonic = "string"; +} + #endif // CIRCT_DIALECT_HW_HWTYPESIMPL_TD diff --git a/include/circt/Dialect/HW/HWVisitors.h b/include/circt/Dialect/HW/HWVisitors.h index 27df98b98e32..531ee140e3c7 100644 --- a/include/circt/Dialect/HW/HWVisitors.h +++ b/include/circt/Dialect/HW/HWVisitors.h @@ -32,10 +32,12 @@ class TypeOpVisitor { ArraySliceOp, ArrayCreateOp, ArrayConcatOp, ArrayGetOp, // Struct operations StructCreateOp, StructExtractOp, StructInjectOp, + // Union operations + UnionCreateOp, UnionExtractOp, // Cast operation BitcastOp, ParamValueOp, // Enum operations - EnumConstantOp>([&](auto expr) -> ResultType { + EnumConstantOp, EnumCmpOp>([&](auto expr) -> ResultType { return thisCast->visitTypeOp(expr, args...); }) .Default([&](auto expr) -> ResultType { @@ -68,10 +70,13 @@ class TypeOpVisitor { HANDLE(StructCreateOp, Unhandled); HANDLE(StructExtractOp, Unhandled); HANDLE(StructInjectOp, Unhandled); + HANDLE(UnionCreateOp, Unhandled); + HANDLE(UnionExtractOp, Unhandled); HANDLE(ArraySliceOp, Unhandled); HANDLE(ArrayGetOp, Unhandled); HANDLE(ArrayCreateOp, Unhandled); HANDLE(ArrayConcatOp, Unhandled); + HANDLE(EnumCmpOp, Unhandled); HANDLE(EnumConstantOp, Unhandled); #undef HANDLE }; @@ -84,7 +89,7 @@ class StmtVisitor { ResultType dispatchStmtVisitor(Operation *op, ExtraArgs... args) { auto *thisCast = static_cast(this); return TypeSwitch(op) - .template Case( + .template Case( [&](auto expr) -> ResultType { return thisCast->visitStmt(expr, args...); }) @@ -122,7 +127,6 @@ class StmtVisitor { } // Basic nodes. - HANDLE(ProbeOp, Unhandled); HANDLE(OutputOp, Unhandled); HANDLE(InstanceOp, Unhandled); HANDLE(TypeScopeOp, Unhandled); diff --git a/include/circt/Dialect/HW/InnerSymbolNamespace.h b/include/circt/Dialect/HW/InnerSymbolNamespace.h new file mode 100644 index 000000000000..4acb868ca494 --- /dev/null +++ b/include/circt/Dialect/HW/InnerSymbolNamespace.h @@ -0,0 +1,52 @@ +//===- InnerSymbolNamespace.h - Inner Symbol Table Namespace ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares the InnerSymbolNamespace, which tracks the names +// used by inner symbols within an InnerSymbolTable. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_HW_INNERSYMBOLNAMESPACE_H +#define CIRCT_DIALECT_HW_INNERSYMBOLNAMESPACE_H + +#include "circt/Dialect/HW/InnerSymbolTable.h" +#include "circt/Support/Namespace.h" + +namespace circt { +namespace hw { + +struct InnerSymbolNamespace : Namespace { + InnerSymbolNamespace() = default; + InnerSymbolNamespace(Operation *module) { add(module); } + + /// Populate the namespace from a module-like operation. This namespace will + /// be composed of the `inner_sym`s of the module's ports and declarations. + void add(Operation *module) { + hw::InnerSymbolTable::walkSymbols( + module, [&](StringAttr name, const InnerSymTarget &target) { + nextIndex.insert({name.getValue(), 0}); + }); + } +}; + +struct InnerSymbolNamespaceCollection { + + InnerSymbolNamespace &get(Operation *op) { + return collection.try_emplace(op, op).first->second; + } + + InnerSymbolNamespace &operator[](Operation *op) { return get(op); } + +private: + DenseMap collection; +}; + +} // namespace hw +} // namespace circt + +#endif // CIRCT_DIALECT_HW_INNERSYMBOLNAMESPACE_H diff --git a/include/circt/Dialect/HW/InnerSymbolTable.h b/include/circt/Dialect/HW/InnerSymbolTable.h index 2120737d607e..15ea4ecda348 100644 --- a/include/circt/Dialect/HW/InnerSymbolTable.h +++ b/include/circt/Dialect/HW/InnerSymbolTable.h @@ -14,9 +14,9 @@ #ifndef CIRCT_DIALECT_HW_INNERSYMBOLTABLE_H #define CIRCT_DIALECT_HW_INNERSYMBOLTABLE_H -#include "circt/Dialect/FIRRTL/FIRRTLAttributes.h" -#include "circt/Dialect/FIRRTL/FIRRTLTypes.h" #include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/SymbolTable.h" #include "llvm/ADT/StringRef.h" @@ -27,15 +27,17 @@ namespace hw { class InnerSymTarget { public: /// Default constructor, invalid. - /* implicit */ InnerSymTarget() { assert(!*this); } + InnerSymTarget() { assert(!*this); } - /// Target an operation, and optionally a field (=0 means the op itself). - explicit InnerSymTarget(Operation *op, size_t fieldID = 0) + /// Target an operation. + explicit InnerSymTarget(Operation *op) : InnerSymTarget(op, 0) {} + + /// Target an operation and a field (=0 means the op itself). + InnerSymTarget(Operation *op, size_t fieldID) : op(op), portIdx(invalidPort), fieldID(fieldID) {} /// Target a port, and optionally a field (=0 means the port itself). - /// Operation should be an FModuleLike. - explicit InnerSymTarget(size_t portIdx, Operation *op, size_t fieldID = 0) + InnerSymTarget(size_t portIdx, Operation *op, size_t fieldID = 0) : op(op), portIdx(portIdx), fieldID(fieldID) {} InnerSymTarget(const InnerSymTarget &) = default; @@ -250,10 +252,10 @@ OS &operator<<(OS &os, const InnerSymTarget &target) { os << "field " << target.getField() << " of "; if (target.isPort()) - os << ""; + os << "port " << target.getPort() << " on @" + << SymbolTable::getSymbolName(target.getOp()).getValue() << ""; else - os << ""; + os << "op " << *target.getOp() << ""; return os; } diff --git a/include/circt/Dialect/HW/InstanceImplementation.h b/include/circt/Dialect/HW/InstanceImplementation.h index 0174a12b7c1f..9aa24d6d3b45 100644 --- a/include/circt/Dialect/HW/InstanceImplementation.h +++ b/include/circt/Dialect/HW/InstanceImplementation.h @@ -30,17 +30,12 @@ namespace instance_like_impl { using EmitErrorFn = std::function)>; -/// Convenience function to query the input and result names of a HW module (as -/// determined by 'hw::isAnyModule'). -std::pair getHWModuleArgAndResultNames(Operation *module); - /// Return a pointer to the referenced module operation. Operation *getReferencedModule(const HWSymbolCache *cache, Operation *instanceOp, mlir::FlatSymbolRefAttr moduleName); -/// Verify that the instance refers to a valid HW module as determined by the -/// 'hw::isAnyModule' function. +/// Verify that the instance refers to a valid HW module. LogicalResult verifyReferencedModule(Operation *instanceOp, SymbolTableCollection &symbolTable, mlir::FlatSymbolRefAttr moduleName, diff --git a/include/circt/Dialect/HW/ModuleImplementation.h b/include/circt/Dialect/HW/ModuleImplementation.h index 2672312381c9..bfe8e0182db3 100644 --- a/include/circt/Dialect/HW/ModuleImplementation.h +++ b/include/circt/Dialect/HW/ModuleImplementation.h @@ -15,17 +15,19 @@ #ifndef CIRCT_DIALECT_HW_MODULEIMPLEMENTATION_H #define CIRCT_DIALECT_HW_MODULEIMPLEMENTATION_H +#include "circt/Dialect/HW/HWTypes.h" #include "circt/Support/LLVM.h" - #include "mlir/IR/DialectImplementation.h" namespace circt { namespace hw { namespace module_like_impl { -/// Get the portname from an SSA value string, if said value name is not a -/// number. -StringAttr getPortNameAttr(MLIRContext *context, StringRef name); + +struct PortParse : OpAsmParser::Argument { + ModulePort::Direction direction; + std::string rawName; +}; /// This is a variant of mlir::parseFunctionSignature that allows names on /// result arguments. @@ -42,6 +44,12 @@ void printModuleSignature(OpAsmPrinter &p, Operation *op, ArrayRef argTypes, bool isVariadic, ArrayRef resultTypes, bool &needArgNamesAttr); +/// New Style parsing +ParseResult parseModuleSignature(OpAsmParser &parser, + SmallVectorImpl &args, + TypeAttr &modType); +void printModuleSignatureNew(OpAsmPrinter &p, Operation *op); + } // namespace module_like_impl } // namespace hw } // namespace circt diff --git a/include/circt/Dialect/HW/Namespace.h b/include/circt/Dialect/HW/Namespace.h deleted file mode 100644 index f9ff57c5da59..000000000000 --- a/include/circt/Dialect/HW/Namespace.h +++ /dev/null @@ -1,44 +0,0 @@ -//===- Namespace.h - A symbol table for HW ops ------------------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file implements HW namespace which are symbol tables for HW operations -// that automatically resolve name collisions. -// -//===----------------------------------------------------------------------===// - -#ifndef CIRCT_DIALECT_HW_NAMESPACE_H -#define CIRCT_DIALECT_HW_NAMESPACE_H - -#include "circt/Dialect/HW/HWOps.h" -#include "circt/Support/Namespace.h" - -namespace circt { -namespace hw { - -struct ModuleNamespace : Namespace { - ModuleNamespace() = default; - ModuleNamespace(hw::HWModuleOp module) { add(module); } - - /// Populate the namespace from a module-like operation. This namespace will - /// be composed of the `inner_sym`s of the module's ports and declarations. - void add(hw::HWModuleOp module) { - for (auto port : module.getAllPorts()) - if (port.sym && !port.sym.empty()) - nextIndex.insert({port.sym.getSymName().getValue(), 0}); - module.walk([&](Operation *op) { - auto attr = op->getAttrOfType("inner_sym"); - if (attr) - nextIndex.insert({attr.getValue(), 0}); - }); - } -}; - -} // namespace hw -} // namespace circt - -#endif // CIRCT_DIALECT_HW_NAMESPACE_H diff --git a/include/circt/Dialect/HW/Passes.td b/include/circt/Dialect/HW/Passes.td index 95c633f529d3..6507a1e6b43c 100644 --- a/include/circt/Dialect/HW/Passes.td +++ b/include/circt/Dialect/HW/Passes.td @@ -53,4 +53,9 @@ def HWSpecialize : Pass<"hw-specialize", "mlir::ModuleOp"> { }]; } +def VerifyInnerRefNamespace : Pass<"hw-verify-irn"> { + let summary = "Verify InnerRefNamespaceLike operations, if not self-verifying."; + let constructor = "circt::hw::createVerifyInnerRefNamespacePass()"; +} + #endif // CIRCT_DIALECT_HW_PASSES_TD diff --git a/include/circt/Dialect/HW/PortConverter.h b/include/circt/Dialect/HW/PortConverter.h new file mode 100644 index 000000000000..77b0e9197959 --- /dev/null +++ b/include/circt/Dialect/HW/PortConverter.h @@ -0,0 +1,182 @@ +//===- PortConverter.h - Module I/O rewriting utility -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// The PortConverter is a utility class for rewriting arguments of a +// HWMutableModuleLike operation. +// It is intended to be a generic utility that can facilitate replacement of +// a given module in- or output to an arbitrary set of new inputs and outputs +// (i.e. 1 port -> N in, M out ports). Typical usecases is where an in (or +// output) of a module represents some higher-level abstraction that will be +// implemented by a set of lower-level in- and outputs ports + supporting +// operations within a module. It also attempts to do so in an optimal way, by +// e.g. being able to collect multiple port modifications of a module, and +// perform them all at once. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_HW_PORTCONVERTER_H +#define CIRCT_DIALECT_HW_PORTCONVERTER_H + +#include "circt/Dialect/HW/HWInstanceGraph.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Support/BackedgeBuilder.h" +#include "circt/Support/LLVM.h" + +namespace circt { +namespace hw { + +class PortConversionBuilder; +class PortConversion; + +class PortConverterImpl { +public: + /// Run port conversion. + LogicalResult run(); + Block *getBody() const { return body; } + hw::HWMutableModuleLike getModule() const { return mod; } + + /// These two methods take care of allocating new ports in the correct place + /// based on the position of 'origPort'. The new port is based on the original + /// name and suffix. The specification for the new port is given by `newPort` + /// and is recorded internally. Any changes to 'newPort' after calling this + /// will not be reflected in the modules new port list. Will also add the new + /// input to the block arguments of the body of the module. + Value createNewInput(hw::PortInfo origPort, const Twine &suffix, Type type, + hw::PortInfo &newPort); + /// Same as above. 'output' is the value fed into the new port and is required + /// if 'body' is non-null. Important note: cannot be a backedge which gets + /// replaced since this isn't attached to an op until later in the pass. + void createNewOutput(hw::PortInfo origPort, const Twine &suffix, Type type, + Value output, hw::PortInfo &newPort); + +protected: + PortConverterImpl(igraph::InstanceGraphNode *moduleNode); + + std::unique_ptr ssb; + +private: + /// Updates an instance of the module. This is called after the module has + /// been updated. It will update the instance to match the new port + void updateInstance(hw::InstanceOp); + + // If the module has a block and it wants to be modified, this'll be + // non-null. + Block *body = nullptr; + + igraph::InstanceGraphNode *moduleNode; + hw::HWMutableModuleLike mod; + OpBuilder b; + + // Keep around a reference to the specific port conversion classes to + // facilitate updating the instance ops. Indexed by the original port + // location. + SmallVector> loweredInputs; + SmallVector> loweredOutputs; + + // Tracking information to modify the module. Populated by the + // 'createNew(Input|Output)' methods. Will be cleared once port changes have + // materialized. Default length is 0 to save memory in case we'll be keeping + // this around for later use. + SmallVector, 0> newInputs; + SmallVector, 0> newOutputs; + + // Maintain a handle to the terminator of the body, if any. This will get + // continuously updated during port conversion whenever a new output is added + // to the module. + Operation *terminator = nullptr; +}; + +/// Base class for the port conversion of a particular port. Abstracts the +/// details of a particular port conversion from the port layout. Subclasses +/// keep around port mapping information to use when updating instances. +class PortConversion { +public: + PortConversion(PortConverterImpl &converter, hw::PortInfo origPort) + : converter(converter), body(converter.getBody()), origPort(origPort) {} + virtual ~PortConversion() = default; + + // An optional initialization step that can be overridden by subclasses. + // This allows subclasses to perform a failable post-construction + // initialization step. + virtual LogicalResult init() { return success(); } + + // Lower the specified port into a wire-level signaling protocol. The two + // virtual methods 'build*Signals' should be overridden by subclasses. They + // should use the 'create*' methods in 'PortConverter' to create the + // necessary ports. + void lowerPort() { + if (origPort.dir == hw::ModulePort::Direction::Output) + buildOutputSignals(); + else + buildInputSignals(); + } + + /// Update an instance port to the new port information. + virtual void mapInputSignals(OpBuilder &b, Operation *inst, Value instValue, + SmallVectorImpl &newOperands, + ArrayRef newResults) = 0; + virtual void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue, + SmallVectorImpl &newOperands, + ArrayRef newResults) = 0; + + MLIRContext *getContext() { return getModule()->getContext(); } + bool isUntouched() const { return isUntouchedFlag; } + +protected: + // Build the input and output signals for the port. This pertains to modifying + // the module itself. + virtual void buildInputSignals() = 0; + virtual void buildOutputSignals() = 0; + + PortConverterImpl &converter; + Block *body; + hw::PortInfo origPort; + + hw::HWMutableModuleLike getModule() { return converter.getModule(); } + + // We don't need full LLVM-style RTTI support for PortConversion (would + // require some mechanism of registering user-provided PortConversion-derived + // classes), we only need to dynamically tell whether any given PortConversion + // is the UntouchedPortConversion. + bool isUntouchedFlag = false; +}; // namespace hw + +// A PortConversionBuilder will, given an input type, build the appropriate +// port conversion for that type. +class PortConversionBuilder { +public: + PortConversionBuilder(PortConverterImpl &converter) : converter(converter) {} + virtual ~PortConversionBuilder() = default; + + // Builds the appropriate port conversion for the port. Users should + // override this method with their own llvm::TypeSwitch-based dispatch code, + // and by default call this method when no port conversion applies. + virtual FailureOr> build(hw::PortInfo port); + + PortConverterImpl &converter; +}; + +// A PortConverter wraps a single HWMutableModuleLike operation, and is +// initialized from an instance graph node. The port converter is templated +// on a PortConversionBuilder, which is used to build the appropriate +// port conversion for each port type. +template +class PortConverter : public PortConverterImpl { +public: + template + PortConverter(hw::InstanceGraph &graph, hw::HWMutableModuleLike mod, + Args &&...args) + : PortConverterImpl(graph.lookup(cast(*mod))) { + ssb = std::make_unique(*this, args...); + } +}; + +} // namespace hw +} // namespace circt + +#endif // CIRCT_DIALECT_HW_PORTCONVERTER_H diff --git a/include/circt/Dialect/HWArith/HWArithDialect.td b/include/circt/Dialect/HWArith/HWArithDialect.td index 111e64525ef5..00f13e9d9741 100644 --- a/include/circt/Dialect/HWArith/HWArithDialect.td +++ b/include/circt/Dialect/HWArith/HWArithDialect.td @@ -17,6 +17,9 @@ def HWArithDialect : Dialect { let name = "hwarith"; let cppNamespace = "::circt::hwarith"; + // This will be the default after next LLVM bump. + let usePropertiesForAttributes = 1; + let summary = "Types and operations for the HWArith dialect"; let description = [{ This dialect defines the `HWArith` dialect, modeling bit-width aware diff --git a/include/circt/Dialect/HWArith/HWArithOps.h b/include/circt/Dialect/HWArith/HWArithOps.h index 166f35cae141..29924126a2b5 100644 --- a/include/circt/Dialect/HWArith/HWArithOps.h +++ b/include/circt/Dialect/HWArith/HWArithOps.h @@ -15,6 +15,7 @@ #include "circt/Dialect/HWArith/HWArithDialect.h" #include "circt/Support/LLVM.h" +#include "mlir/Bytecode/BytecodeOpInterface.h" #include "mlir/IR/OpImplementation.h" #include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" diff --git a/include/circt/Dialect/HWArith/HWArithOps.td b/include/circt/Dialect/HWArith/HWArithOps.td index 12cb2c0435eb..779d4ef35a7f 100644 --- a/include/circt/Dialect/HWArith/HWArithOps.td +++ b/include/circt/Dialect/HWArith/HWArithOps.td @@ -58,6 +58,7 @@ class BinOp traits = []> : std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results); }]; diff --git a/include/circt/Dialect/Handshake/Handshake.td b/include/circt/Dialect/Handshake/Handshake.td index d5966d973bf1..cf5784273ec9 100644 --- a/include/circt/Dialect/Handshake/Handshake.td +++ b/include/circt/Dialect/Handshake/Handshake.td @@ -17,7 +17,7 @@ include "mlir/IR/OpBase.td" include "mlir/IR/PatternBase.td" include "mlir/IR/SymbolInterfaces.td" include "mlir/IR/RegionKindInterface.td" -include "mlir/IR/FunctionInterfaces.td" +include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/Interfaces/CallInterfaces.td" include "mlir/Interfaces/SideEffectInterfaces.td" include "circt/Dialect/Handshake/HandshakeInterfaces.td" @@ -34,14 +34,13 @@ def Handshake_Dialect : Dialect { }]; let useDefaultAttributePrinterParser = 1; -} -// Base class for Handshake dialect ops. -class Handshake_Op traits = []> - : Op, - DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods]> { + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + + let dependentDialects = [ + "circt::seq::SeqDialect" + ]; } include "circt/Dialect/Handshake/HandshakeOps.td" diff --git a/include/circt/Dialect/Handshake/HandshakeInterfaces.h b/include/circt/Dialect/Handshake/HandshakeInterfaces.h new file mode 100644 index 000000000000..f6343c12bb23 --- /dev/null +++ b/include/circt/Dialect/Handshake/HandshakeInterfaces.h @@ -0,0 +1,55 @@ +//===- HandshakeInterfaces.h - Handshake op interfaces ----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the interfaces of the handshake dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_HANDSHAKE_HANDSHAKEINTERFACES_H +#define CIRCT_DIALECT_HANDSHAKE_HANDSHAKEINTERFACES_H + +#include "circt/Dialect/Handshake/HandshakeDialect.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/OpDefinition.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/Operation.h" +#include "mlir/Interfaces/CallInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" + +namespace circt { +namespace handshake { + +struct MemLoadInterface { + unsigned index; + mlir::Value addressIn; + mlir::Value dataOut; + mlir::Value doneOut; +}; + +struct MemStoreInterface { + unsigned index; + mlir::Value addressIn; + mlir::Value dataIn; + mlir::Value doneOut; +}; + +/// Default implementation for checking whether an operation is a control +/// operation. This function cannot be defined within ControlInterface +/// because its implementation attempts to cast the operation to an +/// SOSTInterface, which may not be declared at the point where the default +/// trait's method is defined. Therefore, the default implementation of +/// ControlInterface's isControl method simply calls this function. +bool isControlOpImpl(Operation *op); +} // end namespace handshake +} // end namespace circt + +#include "circt/Dialect/Handshake/HandshakeInterfaces.h.inc" + +#endif // CIRCT_DIALECT_HANDSHAKE_HANDSHAKEINTERFACES_H diff --git a/include/circt/Dialect/Handshake/HandshakeInterfaces.td b/include/circt/Dialect/Handshake/HandshakeInterfaces.td index 31c2cd843166..20f390f19cc4 100644 --- a/include/circt/Dialect/Handshake/HandshakeInterfaces.td +++ b/include/circt/Dialect/Handshake/HandshakeInterfaces.td @@ -15,7 +15,18 @@ include "mlir/IR/OpBase.td" +def FineGrainedDataflowRegionOpInterface : OpInterface<"FineGrainedDataflowRegionOpInterface"> { + let description = [{ + An interface for describing operations that define fine-grained dataflow regions. + The interface doesn't provide any methods, but is instead used to ensure + that users of handshake ops explicitly acknowledge that a given region has + fine-grained dataflow semantics. + }]; + let cppNamespace = "::circt::handshake"; +} + def SOSTInterface : OpInterface<"SOSTInterface"> { + let cppNamespace = "::circt::handshake"; let description = [{ Sized Operation with Single Type (SOST). @@ -30,9 +41,9 @@ def SOSTInterface : OpInterface<"SOSTInterface"> { The default implementation of this method simply returns the type of the first operation operand. }], - "Type", "getDataType", (ins), "", + "mlir::Type", "getDataType", (ins), "", [{ - auto concreteOp = cast($_op); + auto concreteOp = mlir::cast($_op); return concreteOp->getOperands().front().getType(); }] >, @@ -43,7 +54,7 @@ def SOSTInterface : OpInterface<"SOSTInterface"> { }], "unsigned", "getSize", (ins), "", [{ - auto concreteOp = cast($_op); + auto concreteOp = mlir::cast($_op); return concreteOp->getNumOperands(); }] >, @@ -55,10 +66,10 @@ def SOSTInterface : OpInterface<"SOSTInterface"> { }], "bool", "sostIsControl", (ins), "", [{ - auto concreteOp = cast($_op); + auto concreteOp = mlir::cast($_op); // The operation is a control operation if its single data type is a // NoneType. - return concreteOp.getDataType().template isa(); + return concreteOp.getDataType().template isa(); }] >, InterfaceMethod<[{ @@ -71,7 +82,7 @@ def SOSTInterface : OpInterface<"SOSTInterface"> { "mlir::OpAsmPrinter &": $printer, "bool": $explicitSize ), "", [{ - auto concreteOp = cast($_op); + auto concreteOp = mlir::cast($_op); if (explicitSize) { printer << " [" << concreteOp.getSize() << "]"; @@ -84,7 +95,7 @@ def SOSTInterface : OpInterface<"SOSTInterface"> { ]; let verify = [{ - auto concreteOp = cast($_op); + auto concreteOp = mlir::cast($_op); // SOST operation's size must be at least one if (concreteOp.getSize() < 1) { @@ -100,11 +111,12 @@ def SOSTInterface : OpInterface<"SOSTInterface"> { return concreteOp.emitOpError("SOST operation reports having data type ") << dataType << ", but one operand has type " << operand.getType(); - return success(); + return mlir::success(); }]; } def MergeLikeOpInterface : OpInterface<"MergeLikeOpInterface"> { + let cppNamespace = "::circt::handshake"; let description = [{ Some handshake operations can have predecessors in other blocks. This is primarily useful for verification purposes during @@ -115,30 +127,31 @@ def MergeLikeOpInterface : OpInterface<"MergeLikeOpInterface"> { InterfaceMethod<[{ Returns an operand range over the data signals being merged. }], - "OperandRange", "getDataOperands", (ins) + "mlir::OperandRange", "getDataOperands", (ins) >, ]; let verify = [{ - auto concreteOp = cast($_op); + auto concreteOp = mlir::cast($_op); auto operands = concreteOp.getDataOperands(); if (!operands.size()) return concreteOp.emitOpError("must have at least one data operand"); - Type resultType = $_op->getResult(0).getType(); + mlir::Type resultType = $_op->getResult(0).getType(); for (auto operand : operands) if (operand.getType() != resultType) return concreteOp.emitOpError("operand has type ") << operand.getType() << ", but result has type " << resultType; - return success(); + return mlir::success(); }]; } def GeneralOpInterface : OpInterface<"GeneralOpInterface"> { + let cppNamespace = "::circt::handshake"; let description = [{"Simulate the Execution of ops. The op takes a set of input values and " "returns the corresponding outputs assuming the precondition to " @@ -154,6 +167,7 @@ def GeneralOpInterface : OpInterface<"GeneralOpInterface"> { } def ExecutableOpInterface : OpInterface<"ExecutableOpInterface"> { + let cppNamespace = "::circt::handshake"; let description = [{"Simulate the Execution of ops"}]; let methods = [ @@ -181,6 +195,7 @@ def ExecutableOpInterface : OpInterface<"ExecutableOpInterface"> { } def MemoryOpInterface : OpInterface<"MemoryOpInterface"> { + let cppNamespace = "::circt::handshake"; let description = [{"Allocate the memory to the meory map in the simulation. "}]; @@ -195,6 +210,7 @@ def MemoryOpInterface : OpInterface<"MemoryOpInterface"> { } def NamedIOInterface : OpInterface<"NamedIOInterface"> { + let cppNamespace = "::circt::handshake"; let description = [{"Provides detailed names for the operands and results of an operation."}]; @@ -219,6 +235,7 @@ def NamedIOInterface : OpInterface<"NamedIOInterface"> { } def ControlInterface : OpInterface<"ControlInterface"> { + let cppNamespace = "::circt::handshake"; let description = [{"Provides information on whether this operation is a control operation."}]; diff --git a/include/circt/Dialect/Handshake/HandshakeOps.h b/include/circt/Dialect/Handshake/HandshakeOps.h index 57bd26418306..9bac1faa54bf 100644 --- a/include/circt/Dialect/Handshake/HandshakeOps.h +++ b/include/circt/Dialect/Handshake/HandshakeOps.h @@ -14,13 +14,13 @@ #define CIRCT_HANDSHAKEOPS_OPS_H_ #include "circt/Dialect/Handshake/HandshakeDialect.h" +#include "circt/Dialect/Handshake/HandshakeInterfaces.h" #include "circt/Support/LLVM.h" #include "mlir/IR/Attributes.h" #include "mlir/IR/Builders.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Dialect.h" -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/IR/OpDefinition.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/Operation.h" @@ -28,45 +28,37 @@ #include "mlir/IR/TypeSupport.h" #include "mlir/IR/Types.h" #include "mlir/Interfaces/CallInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" #include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Pass/Pass.h" #include "llvm/ADT/Any.h" -namespace circt { -namespace handshake { - -struct MemLoadInterface { - unsigned index; - mlir::Value addressIn; - mlir::Value dataOut; - mlir::Value doneOut; -}; - -struct MemStoreInterface { - unsigned index; - mlir::Value addressIn; - mlir::Value dataIn; - mlir::Value doneOut; -}; - -/// Default implementation for checking whether an operation is a control -/// operation. This function cannot be defined within ControlInterface -/// because its implementation attempts to cast the operation to an -/// SOSTInterface, which may not be declared at the point where the default -/// trait's method is defined. Therefore, the default implementation of -/// ControlInterface's isControl method simply calls this function. -bool isControlOpImpl(Operation *op); - -#include "circt/Dialect/Handshake/HandshakeInterfaces.h.inc" - -} // end namespace handshake -} // end namespace circt - namespace mlir { namespace OpTrait { template class HasClock : public TraitBase {}; + +template +class HasParentInterface { +public: + template + class Impl : public TraitBase::Impl> { + public: + static LogicalResult verifyTrait(Operation *op) { + if (llvm::isa_and_nonnull(op->getParentOp())) + return success(); + + // @mortbopet: What a horrible error message - however, there's no way to + // report the interface name without going in and adjusting the tablegen + // backend to also emit string literal names for interfaces. + return op->emitOpError() << "expects parent op to be of the interface " + "parent type required by the given op type"; + } + }; +}; + } // namespace OpTrait } // namespace mlir diff --git a/include/circt/Dialect/Handshake/HandshakeOps.td b/include/circt/Dialect/Handshake/HandshakeOps.td index 7fc7fcc404fe..5e280e8dbffe 100644 --- a/include/circt/Dialect/Handshake/HandshakeOps.td +++ b/include/circt/Dialect/Handshake/HandshakeOps.td @@ -16,6 +16,19 @@ include "mlir/IR/BuiltinTypes.td" include "mlir/IR/BuiltinAttributeInterfaces.td" include "mlir/Interfaces/InferTypeOpInterface.td" +// @mortbopet: some kind of support for interfaces as parent ops is currently +// being tracked here: https://github.com/llvm/llvm-project/pull/66196 +class HasParentInterface + : ParamNativeOpTrait<"HasParentInterface", interface>, StructuralOpTrait {} + +// Base class for Handshake dialect ops. +class Handshake_Op traits = []> + : Op, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { +} + // This is almost exactly like a standard FuncOp, except that it has some // extra verification conditions. In particular, each Value must // only have a single use. Also, it defines a Dominance-Free Scope @@ -25,7 +38,8 @@ def FuncOp : Op { let summary = "Handshake dialect function."; let description = [{ @@ -57,6 +71,11 @@ def FuncOp : Op getArgumentTypes() { return getFunctionType().getInputs(); } @@ -101,6 +120,11 @@ def FuncOp : Op ]> { let summary = "module instantiate operation"; @@ -173,10 +198,20 @@ def InstanceOp : Handshake_Op<"instance", [ return (*this)->getAttrOfType("module"); } + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + (*this)->setAttr(getModuleAttrName(), callee.get()); + } + /// Get the control operand of this instance op Value getControl() { return getOperands().back(); } + + MutableOperandRange getArgOperandsMutable() { + return getOpOperandsMutable(); + } + }]; let assemblyFormat = [{ @@ -390,7 +425,6 @@ def MuxOp : Handshake_Op<"mux", [ def ControlMergeOp : Handshake_Op<"control_merge", [ Pure, MergeLikeOpInterface, HasClock, SOSTInterface, - DeclareOpInterfaceMethods, DeclareOpInterfaceMethods, DeclareOpInterfaceMethods ]> { @@ -400,19 +434,29 @@ def ControlMergeOp : Handshake_Op<"control_merge", [ (nondeterministic) control merge. Any input is propagated to the first output and the index of the propagated input is sent to the second output. The number of inputs corresponds to the number of - predecessor blocks. ControlMerge is a control-only - component(i.e., has no data but only bidirectional handshake). + predecessor blocks. Example: ``` - %0, %idx = control_merge %a, %b, %c : i32 + %0, %idx = control_merge %a, %b, %c {attributes} : i32, index ``` }]; let arguments = (ins Variadic : $dataOperands); - let results = (outs AnyType : $result, Index : $index); - let hasCanonicalizer = 1; + let results = (outs AnyType : $result, AnyType : $index); + + let builders = [OpBuilder< + (ins "ValueRange":$operands), [{ + assert(!operands.empty() && "cmerge needs at least one operand"); + $_state.addOperands(operands); + // By default, the index result has an Index type + $_state.addTypes(ArrayRef{operands[0].getType(), + $_builder.getIndexType()}); + }]>]; + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + let hasCanonicalizer = 1; } def BranchOp : Handshake_Op<"br", [ @@ -472,39 +516,6 @@ def ConditionalBranchOp : Handshake_Op<"cond_br", [ }]; } -def SelectOp : Handshake_Op<"select", [ - Pure, - DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods, - TypesMatchWith<"data operand type matches true branch result type", - "trueOperand", "falseOperand", "$_self">, - TypesMatchWith<"data operand type matches false branch result type", - "falseOperand", "result", "$_self"> -]> { - let summary = "Select operation"; - let description = [{ - The select operation will select between two inputs based on an input - conditional. The select operation differs from a mux in that - 1. All operands must be valid before the operation can transact - 2. All operands will be transacted at simultaneously - - The 'select' operation is intended to handle 'std.select' and other - ternary-like operators, which considers strictly dataflow. The 'mux' operator - considers control+dataflow between blocks. - - Example: - ```mlir - %res = select %cond, %true, %false : i32 - ``` - }]; - - let arguments = (ins I1 : $condOperand, - AnyType : $trueOperand, AnyType : $falseOperand); - let results = (outs AnyType : $result); - let hasCustomAssemblyFormat = 1; -} - def SinkOp : Handshake_Op<"sink", [ SOSTInterface, DeclareOpInterfaceMethods ]> { diff --git a/include/circt/Dialect/Handshake/HandshakePasses.h b/include/circt/Dialect/Handshake/HandshakePasses.h index 51f3e5446e49..99355e084bbf 100644 --- a/include/circt/Dialect/Handshake/HandshakePasses.h +++ b/include/circt/Dialect/Handshake/HandshakePasses.h @@ -19,6 +19,10 @@ #include #include +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/Dialect/SCF/IR/SCF.h" + namespace circt { namespace handshake { class FuncOp; diff --git a/include/circt/Dialect/Handshake/HandshakePasses.td b/include/circt/Dialect/Handshake/HandshakePasses.td index 7bbc23c41047..80fa87390c5f 100644 --- a/include/circt/Dialect/Handshake/HandshakePasses.td +++ b/include/circt/Dialect/Handshake/HandshakePasses.td @@ -117,7 +117,7 @@ def HandshakeLegalizeMemrefs : Pass<"handshake-legalize-memrefs", "mlir::func::F let summary = "Memref legalization and lowering pass."; let description = [{ Lowers various memref operations to a state suitable for passing to the - StandardToHandshake lowering. + CFToHandshake lowering. }]; let constructor = "circt::handshake::createHandshakeLegalizeMemrefsPass()"; let dependentDialects = ["mlir::scf::SCFDialect"]; diff --git a/include/circt/Dialect/Handshake/Visitor.h b/include/circt/Dialect/Handshake/Visitor.h index 3630685921bf..a834bf9e273f 100644 --- a/include/circt/Dialect/Handshake/Visitor.h +++ b/include/circt/Dialect/Handshake/Visitor.h @@ -33,11 +33,10 @@ class HandshakeVisitor { // Handshake nodes. BranchOp, BufferOp, ConditionalBranchOp, ConstantOp, ControlMergeOp, ForkOp, FuncOp, InstanceOp, JoinOp, LazyForkOp, LoadOp, MemoryOp, - ExternalMemoryOp, MergeOp, MuxOp, ReturnOp, SinkOp, - handshake::SelectOp, SourceOp, StoreOp, SyncOp, PackOp, UnpackOp>( - [&](auto opNode) -> ResultType { - return thisCast->visitHandshake(opNode, args...); - }) + ExternalMemoryOp, MergeOp, MuxOp, ReturnOp, SinkOp, SourceOp, + StoreOp, SyncOp, PackOp, UnpackOp>([&](auto opNode) -> ResultType { + return thisCast->visitHandshake(opNode, args...); + }) .Default([&](auto opNode) -> ResultType { return thisCast->visitInvalidOp(op, args...); }); @@ -73,7 +72,6 @@ class HandshakeVisitor { HANDLE(LazyForkOp); HANDLE(LoadOp); HANDLE(MemoryOp); - HANDLE(handshake::SelectOp); HANDLE(ExternalMemoryOp); HANDLE(MergeOp); HANDLE(MuxOp); @@ -106,9 +104,10 @@ class StdExprVisitor { arith::CmpIOp, arith::AddIOp, arith::SubIOp, arith::MulIOp, arith::DivSIOp, arith::RemSIOp, arith::DivUIOp, arith::RemUIOp, arith::XOrIOp, arith::AndIOp, arith::OrIOp, arith::ShLIOp, - arith::ShRSIOp, arith::ShRUIOp>([&](auto opNode) -> ResultType { - return thisCast->visitStdExpr(opNode, args...); - }) + arith::ShRSIOp, arith::ShRUIOp, arith::SelectOp>( + [&](auto opNode) -> ResultType { + return thisCast->visitStdExpr(opNode, args...); + }) .Default([&](auto opNode) -> ResultType { return thisCast->visitInvalidOp(op, args...); }); diff --git a/include/circt/Dialect/Ibis/CMakeLists.txt b/include/circt/Dialect/Ibis/CMakeLists.txt new file mode 100644 index 000000000000..cfce0427c66c --- /dev/null +++ b/include/circt/Dialect/Ibis/CMakeLists.txt @@ -0,0 +1,21 @@ +add_circt_dialect(Ibis ibis) +add_circt_dialect_doc(Ibis ibis) + +set(LLVM_TARGET_DEFINITIONS IbisPasses.td) +mlir_tablegen(IbisPasses.h.inc -gen-pass-decls) +add_public_tablegen_target(CIRCTIbisTransformsIncGen) +add_circt_doc(IbisPasses IbisPasses -gen-pass-doc) + +set(LLVM_TARGET_DEFINITIONS IbisInterfaces.td) +mlir_tablegen(IbisInterfaces.h.inc -gen-op-interface-decls) +mlir_tablegen(IbisInterfaces.cpp.inc -gen-op-interface-defs) +add_public_tablegen_target(MLIRIbisInterfacIbisncGen) +add_dependencies(circt-headers MLIRIbisInterfacIbisncGen) + +set(LLVM_TARGET_DEFINITIONS Ibis.td) +mlir_tablegen(IbisEnums.h.inc -gen-enum-decls) +mlir_tablegen(IbisEnums.cpp.inc -gen-enum-defs) +mlir_tablegen(IbisAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect=ibis) +mlir_tablegen(IbisAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect=ibis) +add_public_tablegen_target(MLIRIbisEnumsIncGen) +add_dependencies(circt-headers MLIRIbisEnumsIncGen) diff --git a/include/circt/Dialect/Ibis/Ibis.td b/include/circt/Dialect/Ibis/Ibis.td new file mode 100644 index 000000000000..89890e9eede1 --- /dev/null +++ b/include/circt/Dialect/Ibis/Ibis.td @@ -0,0 +1,20 @@ +//===- Ibis.td - Definition of Ibis dialect -------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_TD +#define CIRCT_DIALECT_IBIS_TD + +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/OpBase.td" +include "mlir/Interfaces/FunctionInterfaces.td" + +include "circt/Dialect/Ibis/IbisDialect.td" +include "circt/Dialect/Ibis/IbisTypes.td" +include "circt/Dialect/Ibis/IbisOps.td" + +#endif // CIRCT_DIALECT_IBIS_TD diff --git a/include/circt/Dialect/Ibis/IbisDialect.h b/include/circt/Dialect/Ibis/IbisDialect.h new file mode 100644 index 000000000000..36af99bcbc16 --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisDialect.h @@ -0,0 +1,23 @@ +//===- IbisDialect.h - Definition of Ibis dialect ----------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_IBISDIALECT_H +#define CIRCT_DIALECT_IBIS_IBISDIALECT_H + +#include "mlir/IR/Dialect.h" + +// Pull in the dialect definition. +#include "circt/Dialect/Ibis/IbisDialect.h.inc" + +// Pull in all enum type definitions and utility function declarations. +#include "circt/Dialect/Ibis/IbisEnums.h.inc" + +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/Ibis/IbisAttributes.h.inc" + +#endif // CIRCT_DIALECT_IBIS_IBISDIALECT_H diff --git a/include/circt/Dialect/Ibis/IbisDialect.td b/include/circt/Dialect/Ibis/IbisDialect.td new file mode 100644 index 000000000000..03d3d019b4a6 --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisDialect.td @@ -0,0 +1,38 @@ +//===- IbisDialect.td - Ibis dialect definition ----------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_DIALECT_TD +#define CIRCT_DIALECT_IBIS_DIALECT_TD + +include "mlir/IR/OpBase.td" + +def IbisDialect : Dialect { + let name = "ibis"; + let cppNamespace = "::circt::ibis"; + + let summary = "Types and operations for Ibis dialect"; + let description = [{ + The `ibis` dialect is intended to support porting and eventual open sourcing + of an internal hardware development language. + }]; + let useDefaultTypePrinterParser = 1; + let useDefaultAttributePrinterParser = 1; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + + let extraClassDeclaration = [{ + void registerTypes(); + void registerAttributes(); + }]; + + // Needed for ibis.pipeline_header + let dependentDialects = ["seq::SeqDialect", "hw::HWDialect"]; +} + +#endif // CIRCT_DIALECT_IBIS_DIALECT_TD diff --git a/include/circt/Dialect/Ibis/IbisInterfaces.td b/include/circt/Dialect/Ibis/IbisInterfaces.td new file mode 100644 index 000000000000..4b072299e85d --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisInterfaces.td @@ -0,0 +1,171 @@ +//===- IbisInterfaces.td - Ibis Interfaces ---------------*- tablegen -*---===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This describes the interfaces in the Ibis dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_INTERFACES_TD +#define CIRCT_DIALECT_IBIS_INTERFACES_TD + +include "mlir/IR/OpBase.td" +include "mlir/IR/SymbolInterfaces.td" + +def PortOpInterface : OpInterface<"PortOpInterface"> { + let cppNamespace = "circt::ibis"; + let description = + "An interface for operations which describe ports."; + + let methods = [ + InterfaceMethod< + "Returns the data type of the port.", + "mlir::Type", "getPortType">, + InterfaceMethod< + "Returns the name of the port", + "mlir::StringAttr", "getPortName">, + InterfaceMethod< + "Returns the `!ibis.portref` value defined by the op", + "mlir::TypedValue", "getPort" + > + ]; +} + +// TODO: @mortbopet: should inherit from InnerSymTable once we have a way to +// support nested symbol tables. +def ScopeOpInterface : OpInterface<"ScopeOpInterface", [Symbol]> { + let cppNamespace = "circt::ibis"; + let description = [{ + An interface for operations which define Ibis scopes, that can be referenced + by an `ibis.this` operation. + }]; + + let verify = "return detail::verifyScopeOpInterface($_op);"; + + let methods = [ + InterfaceMethod< + "Returns the body of the scope", + "mlir::Block*", "getBodyBlock", + (ins), [{ + return $_op.getBodyBlock(); + }] + >, + InterfaceMethod< + "Returns `%this` of the scope", + "mlir::TypedValue", "getThis", + (ins), [{ + return *detail::getThisFromScope($_op); + }] + >, + InterfaceMethod< + "Returns the `ThisOp` of this scope", + "Operation*", "getThisOp", + (ins), [{ + return cast($_op.getOperation()).getThis().getDefiningOp(); + }] + >, + InterfaceMethod< + "Returns the symbol name of the scope", + "mlir::StringAttr", "getScopeName", + (ins), [{ + return $_op.getNameAttr(); + }] + >, + InterfaceMethod< + "Lookup an inner symbol in the scope", + "Operation*", "lookupInnerSym", + (ins "llvm::StringRef":$symName), + "", + [{ + // TODO: @mortbopet: fix once we have a way to do nested symbol + // tables, and by extension, do nested symbol table lookup inside this + // scope. + // Until then, brute-force scan the inner symbol ops for a match. + for (auto op : $_op.getBodyBlock()->template getOps()) { + if (*op.getInnerName() == symName) + return op; + } + return nullptr; + }] + >, + InterfaceMethod< + "Lookup a port in the scope", + "ibis::PortOpInterface", "lookupPort", + (ins "llvm::StringRef":$portName), + [{}], + [{ + return dyn_cast_or_null(this->lookupInnerSym(portName)); + }] + > + ]; +} + +def BlockOpInterface : OpInterface<"BlockOpInterface"> { + let cppNamespace = "circt::ibis"; + let description = [{ + An interface for Ibis block-like operations. + }]; + + let methods = [ + InterfaceMethod< + "Returns the result types of this block from the point of view of inside the block", + "llvm::SmallVector", "getInternalResultTypes", + (ins)> + ]; +} + +def MethodLikeOpInterface : OpInterface<"MethodLikeOpInterface"> { + let cppNamespace = "circt::ibis"; + let description = [{ + An interface for Ibis operations that act like callable methods. + This partially implements similar functionality to the FunctionLike interface + which cannot be used for Ibis Methods due to Ibis methods defining InnerSym's + whereas the FunctionLike interface is built on the assumption of the function + defining a Symbol (i.e. inherits from the Symbol interface). + }]; + + let methods = [ + InterfaceMethod<[{ + Returns the type of the method. + }], + "::mlir::FunctionType", "getFunctionType">, + InterfaceMethod<[{ + Returns the name of the method. + }], + "::mlir::StringAttr", "getMethodName"> + ]; + + let extraClassDeclaration = [{ + //===------------------------------------------------------------------===// + // FunctionOpInterface Methods + //===------------------------------------------------------------------===// + + /// Returns the argument types of this function. + ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } + + /// Returns the result types of this function. + ArrayRef getResultTypes() { return getFunctionType().getResults(); } + }]; + + let extraSharedClassDeclaration = [{ + /// Returns the entry block of the function. + Block* getBodyBlock() { + auto& region = getFunctionBody(); + assert(!region.empty() && "expected non-empty function body"); + return ®ion.front(); + } + + using BlockArgListType = Region::BlockArgListType; + BlockArgListType getArguments() { return getFunctionBody().getArguments(); } + Region &getFunctionBody() { + assert($_op->getNumRegions() == 1 && "expected one region"); + return $_op->getRegion(0); + } + }]; +} + +#endif // CIRCT_DIALECT_IBIS_INTERFACES_TD diff --git a/include/circt/Dialect/Ibis/IbisOps.h b/include/circt/Dialect/Ibis/IbisOps.h new file mode 100644 index 000000000000..09042776aaa5 --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisOps.h @@ -0,0 +1,56 @@ +//===- IbisOps.h - Definition of Ibis dialect ops ----------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_IBISOPS_H +#define CIRCT_DIALECT_IBIS_IBISOPS_H + +#include "circt/Dialect/DC/DCTypes.h" +#include "circt/Dialect/HW/HWOpInterfaces.h" +#include "circt/Dialect/Handshake/HandshakeInterfaces.h" +#include "circt/Dialect/Ibis/IbisDialect.h" +#include "circt/Dialect/Ibis/IbisTypes.h" +#include "circt/Dialect/Seq/SeqTypes.h" +#include "circt/Support/InstanceGraphInterface.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/RegionKindInterface.h" +#include "mlir/IR/SymbolTable.h" +#include "mlir/Interfaces/CallInterfaces.h" +#include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" +namespace circt { +namespace ibis { +class ContainerOp; +class ThisOp; + +// Symbol name for the ibis operator library to be used during scheduling. +static constexpr const char *kIbisOperatorLibName = "ibis_operator_library"; + +namespace detail { +// Verify that `op` conforms to the ScopeOpInterface. +LogicalResult verifyScopeOpInterface(Operation *op); + +// Returns the %this value of an ibis scope-defining operation. Implemented +// here to hide the dependence on `ibis.this`, which is not defined before the +// interface definition. +mlir::FailureOr> getThisFromScope(Operation *op); + +} // namespace detail +} // namespace ibis +} // namespace circt + +#include "circt/Dialect/Ibis/IbisInterfaces.h.inc" + +#define GET_OP_CLASSES +#include "circt/Dialect/Ibis/Ibis.h.inc" + +#endif // CIRCT_DIALECT_IBIS_IBISOPS_H diff --git a/include/circt/Dialect/Ibis/IbisOps.td b/include/circt/Dialect/Ibis/IbisOps.td new file mode 100644 index 000000000000..ee5ba144d30c --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisOps.td @@ -0,0 +1,922 @@ +//===- IbisOps.td - Definition of Ibis dialect operations -----------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_IBISOPS_TD +#define CIRCT_DIALECT_IBIS_IBISOPS_TD + +include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/RegionKindInterface.td" +include "mlir/IR/SymbolInterfaces.td" +include "mlir/Interfaces/CallInterfaces.td" +include "mlir/Interfaces/ControlFlowInterfaces.td" +include "mlir/Interfaces/SideEffectInterfaces.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/IR/BuiltinAttributeInterfaces.td" + +include "circt/Dialect/Handshake/HandshakeInterfaces.td" +include "circt/Dialect/HW/HWOpInterfaces.td" +include "circt/Dialect/Ibis/IbisInterfaces.td" +include "circt/Dialect/Ibis/IbisTypes.td" +include "circt/Dialect/Handshake/HandshakeInterfaces.td" +include "circt/Dialect/Seq/SeqTypes.td" +include "circt/Support/InstanceGraphInterface.td" +include "circt/Dialect/HW/HWTypes.td" + +class IbisOp traits = []> : + Op; + +def HasCustomSSAName : + DeclareOpInterfaceMethods; + +def ClassOp : IbisOp<"class", [ + Symbol, + IsolatedFromAbove, RegionKindInterface, + InnerSymbolTable, + SingleBlock, + NoTerminator, ScopeOpInterface, + InstanceGraphModuleOpInterface, + HasParent<"mlir::ModuleOp">]> { + + let summary = "Ibis class"; + let description = [{ + Ibis has the notion of a class which can contain methods and member + variables. + + In the low-level Ibis representation, the ClassOp becomes a container for + `ibis.port`s, `ibis.container`s, and contain logic for member variables. + }]; + + let arguments = (ins SymbolNameAttr:$sym_name); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ + $sym_name attr-dict-with-keyword $body + }]; + + let extraClassDeclaration = [{ + // Implement RegionKindInterface. + static RegionKind getRegionKind(unsigned index) { return RegionKind::Graph; } + + Block* getBodyBlock() { return &getBody().front(); } + + StringAttr getModuleNameAttr() { + return getSymNameAttr(); + } + + llvm::StringRef getModuleName() { + return getSymName(); + } + }]; +} + +class InstanceOpBase : IbisOp, + DeclareOpInterfaceMethods, + InstanceGraphInstanceOpInterface, + HasCustomSSAName +]> { + + let arguments = (ins InnerSymAttr:$inner_sym, FlatSymbolRefAttr:$targetName); + let results = (outs ScopeRefType:$scopeRef); + let assemblyFormat = [{ + $inner_sym `,` $targetName attr-dict + custom(type($scopeRef), ref($targetName)) + }]; + + let builders = [ + OpBuilder<(ins "hw::InnerSymAttr":$instanceName, "StringAttr":$targetName), [{ + build($_builder, $_state, $_builder.getType(targetName), + instanceName, targetName); + }]> + ]; + + code extraInstanceClassDeclaration = ?; + + let extraClassDeclaration = extraInstanceClassDeclaration # [{ + llvm::StringRef getInstanceName() { + return getInnerSym().getSymName().strref(); + } + + mlir::StringAttr getInstanceNameAttr() { + return getInnerSymAttr().getSymName(); + } + + llvm::StringRef getModuleName() { + return getTargetName(); + } + + mlir::FlatSymbolRefAttr getModuleNameAttr() { + return getTargetNameAttr(); + } + }]; + + code extraInstanceClassDefinition = ?; + let extraClassDefinition = extraInstanceClassDefinition # [{ + // For hw:InnerSymbol - the symbol targets the operation and not any result. + std::optional $cppClass::getTargetResultIndex() { + return std::nullopt; + } + }]; +} + +def InstanceOp : InstanceOpBase<"instance"> { + let summary = "Ibis class instance"; + let description = [{ + Instantiates an Ibis class. + }]; + + let extraInstanceClassDeclaration = [{ + // Return the class this instance is instantiating. + ClassOp getClass(SymbolTable *symbolTable = nullptr); + + Operation* getReferencedModuleSlow(); + Operation* getReferencedModule(SymbolTable&); + }]; + + let extraInstanceClassDefinition = [{ + Operation* InstanceOp::getReferencedModuleSlow() { + return getClass(); + } + + Operation* InstanceOp::getReferencedModule(SymbolTable& symbolTable) { + return getClass(&symbolTable); + } + }]; +} + + +class MethodOpBase traits = []> : + IbisOp, + // FunctionOpInterface @mortbopet: This is very much a function-like + // operation, but FunctionOpInterface inherits from Symbol which is + // incompatible with the fact that ibis methods define inner symbols. + MethodLikeOpInterface, + HasParent<"ClassOp">, + ])> { + + let arguments = (ins InnerSymAttr:$inner_sym, + TypeAttrOf:$function_type, + ArrayAttr:$argNames, + OptionalAttr:$arg_attrs, + OptionalAttr:$res_attrs); + let hasCustomAssemblyFormat = 1; + + code extraMethodClassDeclaration = ""; + let extraClassDeclaration = extraMethodClassDeclaration # [{ + StringAttr getMethodName() { + return getInnerSymAttr().getSymName(); + } + }]; + + let extraClassDefinition = [{ + // For hw:InnerSymbol - the symbol targets the operation and not any result. + std::optional $cppClass::getTargetResultIndex() { + return std::nullopt; + } + }]; +} + +def MethodOp : MethodOpBase<"method", [ + AutomaticAllocationScope, + DeclareOpInterfaceMethods + ]> { + + let summary = "Ibis method"; + let description = [{ + Ibis methods are a lot like software functions: a list of named arguments + and unnamed return values with imperative control flow. + }]; + + let regions = (region AnyRegion:$body); +} + +def DataflowMethodOp : MethodOpBase<"method.df", [ + SingleBlockImplicitTerminator<"ibis::ReturnOp">, + RegionKindInterface, + FineGrainedDataflowRegionOpInterface + ]> { + + let summary = "Ibis dataflow method"; + let description = [{ + Ibis dataflow methods share the same interface as an `ibis.method` but + without imperative CFG-based control flow. Instead, this method implements a + graph region, and control flow is expected to be defined by dataflow operations. + }]; + let regions = (region SizedRegion<1>:$body); + + let extraMethodClassDeclaration = [{ + // Implement RegionKindInterface. + static RegionKind getRegionKind(unsigned index) { return RegionKind::Graph; } + }]; +} + +def ReturnOp : IbisOp<"return", [ + Pure, ReturnLike, Terminator, + ParentOneOf<["MethodOp", "DataflowMethodOp"]>]> { + let summary = "Ibis method terminator"; + + let arguments = (ins Variadic:$retValues); + let assemblyFormat = "($retValues^)? attr-dict (`:` type($retValues)^)?"; + let hasVerifier = 1; + + let builders = [ + OpBuilder<(ins)>, + ]; +} + +class BlockLikeOp traits = []> : IbisOp, + AutomaticAllocationScope, + BlockOpInterface])> { + let arguments = (ins + Variadic:$inputs, + OptionalAttr]>>:$maxThreads + ); + let results = (outs Variadic:$outputs); + let regions = (region SizedRegion<1>:$body); + let hasCustomAssemblyFormat = 1; + + code extraBlockClassDeclaration = ?; + let extraClassDeclaration = extraBlockClassDeclaration # [{ + // Return the body of this block. + Block* getBodyBlock() { return &getBody().front(); } + }]; + let hasVerifier = 1; + let skipDefaultBuilders = 1; +} + +class HighLevelBlockLikeOp traits = []> : BlockLikeOp { + let builders = [ + OpBuilder<(ins "TypeRange":$outputs, "ValueRange":$inputs, + CArg<"IntegerAttr", "{}">:$maxThreads), [{ + $_state.addOperands(inputs); + if (maxThreads) + $_state.addAttribute(getMaxThreadsAttrName($_state.name), maxThreads); + auto* region = $_state.addRegion(); + $_state.addTypes(outputs); + ensureTerminator(*region, $_builder, $_state.location); + llvm::SmallVector argLocs; + for(auto input : inputs) + argLocs.push_back(input.getLoc()); + region->front().addArguments(inputs.getTypes(), argLocs); + }]> + ]; + + let extraBlockClassDeclaration = [{ + llvm::SmallVector getInternalResultTypes() { + llvm::SmallVector outTypes; + llvm::copy(getResultTypes(), std::back_inserter(outTypes)); + return outTypes; + } + }]; +} + +def StaticBlockOp : HighLevelBlockLikeOp<"sblock"> { + let summary = "Ibis block"; + let description = [{ + The `ibis.sblock` operation defines a block wherein a group of operations + are expected to be statically scheduleable. + The operation is not isolated from above to facilitate ease of construction. + However, once a program has been constructed and lowered to a sufficient + level, the user may run `--ibis-argify-blocks` to effectively isolate the + block from above, by converting SSA values referenced through dominanes into + arguments of the block + + The block may contain additional attributes to specify constraints on + the block further down the compilation pipeline. + }]; +} + +def IsolatedStaticBlockOp : HighLevelBlockLikeOp<"sblock.isolated", [ + IsolatedFromAbove +]> { + let summary = "Ibis isolated block"; + let description = [{ + The `ibis.sblock.isolated` operation is like an `ibis.sblock` operation + but with an IsolatedFromAbove condition, meaning that all arguments and + results are passed through the block as arguments and results. + }]; +} + +def DCBlockOp : BlockLikeOp<"sblock.dc", [ + IsolatedFromAbove +]> { + let summary = "DC-interfaced Ibis block"; + let description = [{ + The `ibis.sblock.dc` operation is like an `ibis.sblock` operation with + a few differences, being: + 1. The operation is DC-interfaced, meaning that all arguments and results + are dc-value typed. + 2. The operation is IsolatedFromAbove. + }]; + + let builders = [ + OpBuilder<(ins "TypeRange":$outputs, "ValueRange":$inputs, + CArg<"IntegerAttr", "{}">:$maxThreads)> + ]; + + let extraBlockClassDeclaration = [{ + // The internal result types of a DC block op is the DC-type stripped + // outer result values. + llvm::SmallVector getInternalResultTypes() { + llvm::SmallVector innerRes; + for(Type outerRes : getResultTypes()) + innerRes.push_back(outerRes.cast().getInnerType()); + return innerRes; + } + }]; +} + +def InlineStaticBlockBeginOp : IbisOp<"sblock.inline.begin", [ + HasParent<"MethodOp"> +]> { + let summary = "Ibis inline static block begin marker"; + let description = [{ + The `ibis.sblock.inline.begin` operation is a marker that indicates the + begin of an inline static block. + The operation is used to maintain `ibis.sblocks` while in the Ibis inline + phase (to facilitate e.g. mem2reg). + + The operation: + 1. denotes the begin of the sblock + 2. carries whatever attributes that the source `ibis.sblock` carried. + 3. is considered side-effectfull. + }]; + + let assemblyFormat = "attr-dict"; + let extraClassDeclaration = [{ + /// Return the `InlineStaticBlockEndOp` that this operation is referencing. + InlineStaticBlockEndOp getEndOp(); + }]; +} + +def InlineStaticBlockEndOp : IbisOp<"sblock.inline.end", [ + HasParent<"MethodOp"> +]> { + let summary = "Ibis inline static block end marker"; + let description = [{ + The `ibis.sblock.inline.end` operation is a marker that indicates the + end of an inline static block. + The operation is used to maintain `ibis.sblocks` while in the Ibis inline + phase (to facilitate e.g. mem2reg). + }]; + + let assemblyFormat = "attr-dict"; + + let extraClassDeclaration = [{ + /// Return the `InlineStaticBlockBeginOp` that this operation is referencing. + InlineStaticBlockBeginOp getBeginOp(); + }]; +} + +def BlockReturnOp : IbisOp<"sblock.return", [ + Pure, ReturnLike, Terminator, + ParentOneOf<["StaticBlockOp", "IsolatedStaticBlockOp", "DCBlockOp"]>]> { + let summary = "Ibis static block terminator"; + + let arguments = (ins Variadic:$retValues); + let assemblyFormat = "($retValues^)? attr-dict (`:` type($retValues)^)?"; + let hasVerifier = 1; + + let builders = [ + OpBuilder<(ins), "/*no-op*/"> + ]; +} + +def MemRefTypeAttr : TypeAttrBase<"MemRefType", "any memref type">; +def VarOp : IbisOp<"var", [ + DeclareOpInterfaceMethods +]> { + let summary = "Ibis variable definition"; + let description = [{ + Defines an Ibis class member variable. The variable is typed with a + `memref.memref` type, and may define either a singleton or uni-dimensional + array of values. + `ibis.var` defines a symbol within the encompassing class scope which can + be dereferenced through a `!ibis.scoperef` value of the parent class. + }]; + + let arguments = (ins InnerSymAttr:$inner_sym, MemRefTypeAttr:$type); + let assemblyFormat = "$inner_sym `:` $type attr-dict"; + + let extraClassDefinition = [{ + // For hw:InnerSymbol - the symbol targets the operation and not any result. + std::optional $cppClass::getTargetResultIndex() { + return std::nullopt; + } + }]; +} + +def GetVarOp : IbisOp<"get_var", [ + DeclareOpInterfaceMethods, + HasCustomSSAName +]> { + let summary = "Dereferences an ibis member variable through a scoperef"; + let arguments = (ins ScopeRefType:$instance, FlatSymbolRefAttr:$varName); + let results = (outs AnyMemRef:$var); + let assemblyFormat = [{ + $instance `,` $varName attr-dict `:` qualified(type($instance)) `->` qualified(type($var)) + }]; + + let extraClassDeclaration = [{ + // Return the `VarOp` that this operation is referencing. + FailureOr getTarget(SymbolTable* symbolTable = nullptr); + }]; + + let extraClassDefinition = [{ + void $cppClass::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + setNameFn(getResult(), getVarName()); + } + }]; +} + +def CallOp : IbisOp<"call", [CallOpInterface]> { + let summary = "Ibis method call"; + let description = [{ + Dispatch a call to an Ibis method. + }]; + + let arguments = (ins SymbolRefAttr:$callee, Variadic:$operands); + let results = (outs Variadic); + + let extraClassDeclaration = [{ + MutableOperandRange getArgOperandsMutable() { + return getOperandsMutable(); + } + + /// Get the argument operands to the called method. + operand_range getArgOperands() { + return {arg_operand_begin(), arg_operand_end()}; + } + + operand_iterator arg_operand_begin() { return operand_begin(); } + operand_iterator arg_operand_end() { return operand_end(); } + + /// Return the callee of this operation. + CallInterfaceCallable getCallableForCallee() { + return (*this)->getAttrOfType("callee"); + } + + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + (*this)->setAttr(getCalleeAttrName(), callee.get()); + } + }]; + + let assemblyFormat = [{ + $callee `(` $operands `)` attr-dict `:` functional-type($operands, results) + }]; +} + +def PathDirection : I32EnumAttr<"PathDirection", "path direction", [ + I32EnumAttrCase<"Parent", 0, "parent">, + I32EnumAttrCase<"Child", 1, "child">]> { + let cppNamespace = "::circt::ibis"; +} + +def PathStepAttr : AttrDef { + let description = "Used to describe a single step in a path"; + let parameters = (ins + "PathDirection":$direction, + AttributeSelfTypeParameter<"">:$type, + OptionalParameter<"mlir::FlatSymbolRefAttr">:$child + ); + let mnemonic = "step"; + // Would like to either have no prefix before child, or `:`, but both are + // currently invalid due to + // https://github.com/llvm/llvm-project/issues/64724 + let assemblyFormat = "`<` $direction (`,` $child^)? `:` $type `>`"; + let genVerifyDecl = 1; +} + +def PathStepArrayAttr : + TypedArrayAttrBase; + +def PathOp : IbisOp<"path", [ + Pure, InferTypeOpInterface, HasCustomSSAName + ]> { + let summary = "Ibis path"; + let description = [{ + The `ibis.path` operation describes an instance hierarchy path relative to + the current scope. The path is specified by a list of either parent or + child identifiers (navigating up or down the hierarchy, respectively). + + Scopes along the path are optionally typed, however, An `ibis.path` must + lways terminate in a fully typed specifier, i.e. never an `!ibis.scoperef<>`. + + The operation returns a single `!ibis.scoperef`-typed value representing + the scope at the end of the path. + }]; + + let arguments = (ins + PathStepArrayAttr:$path + ); + + let results = (outs ScopeRefType:$instance); + let assemblyFormat = [{ + $path attr-dict + }]; + + let extraClassDeclaration = [{ + /// Infer the return types of this operation. + static LogicalResult inferReturnTypes(MLIRContext *context, + std::optional loc, + ValueRange operands, + DictionaryAttr attrs, + mlir::OpaqueProperties properties, + mlir::RegionRange regions, + SmallVectorImpl &results); + + auto getPathAsRange() { + return getPath().getAsRange(); + } + }]; + let hasVerifier = 1; + let hasCanonicalizeMethod = 1; +} + +def ThisOp : IbisOp<"this", [ + DeclareOpInterfaceMethods, + HasCustomSSAName +]> { + let summary = "Return a handle to the current scope `!ibis.scoperef`"; + let arguments = (ins FlatSymbolRefAttr:$scopeName); + let results = (outs ScopeRefType:$thisRef); + + let assemblyFormat = [{ + $scopeName attr-dict custom(type($thisRef), ref($scopeName)) + }]; + + let builders = [ + OpBuilder<(ins "StringAttr":$name), [{ + build($_builder, $_state, $_builder.getType(name), name); + }]> + ]; +} + +// ===---------------------------------------------------------------------===// +// Low-level Ibis operations +// ===---------------------------------------------------------------------===// + +def ContainerOp : IbisOp<"container", [ + Symbol, + // TODO: @mortbopet: ContainerOp should be an innersymbol table (or equivalent) + // whenever we figure out how to support nested symbol tables... + // InnerSymbolTable + SingleBlock, + NoTerminator, NoRegionArguments, + ScopeOpInterface, IsolatedFromAbove, + InstanceGraphModuleOpInterface, + RegionKindInterface +]> { + let summary = "Ibis container"; + let description = [{ + An ibis container describes a collection of logic nested within an Ibis class. + }]; + + let arguments = (ins SymbolNameAttr:$sym_name); + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + $sym_name attr-dict-with-keyword $body + }]; + + let extraClassDeclaration = [{ + // Implement RegionKindInterface. + static RegionKind getRegionKind(unsigned index) { return RegionKind::Graph; } + + Block* getBodyBlock() { return &getBody().front(); } + + StringAttr getModuleNameAttr() { + return getSymNameAttr(); + } + + llvm::StringRef getModuleName() { + return getSymName(); + } + }]; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "StringAttr":$name), [{ + auto region = $_state.addRegion(); + region->push_back(new Block()); + $_state.addAttribute(getSymNameAttrName($_state.name), name); + }]> + ]; +} + +def ContainerInstanceOp : InstanceOpBase<"container.instance"> { + let summary = "Ibis container instance"; + let description = [{ + Instantiates an Ibis container. + }]; + + let extraInstanceClassDeclaration = [{ + // Return the container this instance is instantiating. + ContainerOp getContainer(SymbolTable* symbolTable = nullptr); + + Operation* getReferencedModuleSlow(); + Operation* getReferencedModule(SymbolTable&); + }]; + + let extraInstanceClassDefinition = [{ + Operation* ContainerInstanceOp::getReferencedModuleSlow() { + return getContainer(); + } + + Operation* ContainerInstanceOp::getReferencedModule(SymbolTable& symbolTable) { + return getContainer(&symbolTable); + } + }]; +} + +def GetPortOp : IbisOp<"get_port", [ + Pure, + DeclareOpInterfaceMethods, + HasCustomSSAName +]> { + let summary = "Ibis get port"; + let description = [{ + Given an Ibis class reference, returns a port of said class. The port + is specified by the symbol name of the port in the referenced class. + + Importantly, the user must specify how they intend to use the op, by + specifying the direction of the portref type that this op is generated with. + If the request port is to be read from, the type must be `!ibis.portref` + and if the port is to be written to, the type must be `!ibis.portref`. + This is to ensure that the usage is reflected in the get_port type which in + turn is used by the tunneling passes to create the proper ports through the + hierarchy. + + This implies that the portref direction of the get_port op is independent of + the actual direction of the target port, and only the inner portref type + must match. + }]; + + let arguments = (ins + ScopeRefType:$instance, + FlatSymbolRefAttr:$portSymbol); + let results = (outs PortRefType:$port); + let assemblyFormat = [{ + $instance `,` $portSymbol `:` qualified(type($instance)) `->` + qualified(type($port)) attr-dict + }]; + + let builders = [ + OpBuilder<(ins "Value":$instance, "StringAttr":$portName, "Type":$innerPortType, + "ibis::Direction":$direction), [{ + build($_builder, $_state, $_builder.getType( + innerPortType, direction), instance, portName + ); + }]> + ]; + + let hasCanonicalizeMethod = 1; + let extraClassDeclaration = [{ + // Returns the direction requested by this get_port op. + ibis::Direction getDirection() { + return getPort().getType().cast().getDirection(); + } + }]; +} + +class PortLikeOp traits = []> : + IbisOp, + InferTypeOpInterface, + HasCustomSSAName, + DeclareOpInterfaceMethods + ])> { + let arguments = (ins InnerSymAttr:$inner_sym, TypeAttrOf:$type); + let results = (outs PortRefType:$port); + let assemblyFormat = [{ + $inner_sym `:` $type attr-dict + }]; + + let extraClassDeclaration = [{ + Type getPortType() { + return getTypeAttr().getValue(); + } + + mlir::StringAttr getPortName() { + return getInnerSymAttr().getSymName(); + } + + static ibis::Direction getPortDirection(); + + /// Infer the return types of this operation. + static LogicalResult inferReturnTypes(MLIRContext *context, + std::optional loc, + ValueRange operands, + DictionaryAttr attrs, + mlir::OpaqueProperties properties, + mlir::RegionRange regions, + SmallVectorImpl &results) { + results.push_back(PortRefType::get(context, attrs.get("type") + .cast().getValue(), getPortDirection())); + return success(); + } + }]; + + let hasVerifier = 1; + + code extraPortClassDefinition = ?; + let extraClassDefinition = extraPortClassDefinition # [{ + LogicalResult $cppClass::verify() { + if(getType().isa()) + return emitOpError("port type cannot be a scope reference"); + + return success(); + } + + void $cppClass::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + setNameFn(getResult(), getPortName()); + } + + // For hw:InnerSymbol - the symbol targets the operation and not any result. + std::optional $cppClass::getTargetResultIndex() { + return std::nullopt; + } + }]; +} + +def InputPortOp : PortLikeOp<"port.input"> { + let summary = "Ibis input port"; + + let extraPortClassDefinition = [{ + ibis::Direction InputPortOp::getPortDirection() { + return ibis::Direction::Input; + } + }]; +} + +def OutputPortOp : PortLikeOp<"port.output"> { + let summary = "Ibis output port"; + let extraPortClassDefinition = [{ + ibis::Direction OutputPortOp::getPortDirection() { + return ibis::Direction::Output; + } + }]; + let hasCanonicalizeMethod = 1; +} + +class WireLikeOp traits = []> : + IbisOp, + HasCustomSSAName, + DeclareOpInterfaceMethods + ])> { + + let extraClassDeclaration = [{ + Type getPortType() { + return getPort().getType(); + } + + mlir::StringAttr getPortName() { + return getInnerSymAttr().getSymName(); + } + + static ibis::Direction getPortDirection(); + }]; + + code extraWireClassDefinition = ?; + let extraClassDefinition = extraWireClassDefinition # [{ + // For hw:InnerSymbol - the symbol targets the operation and not any result. + std::optional $cppClass::getTargetResultIndex() { + return std::nullopt; + } + }]; +} + +class InnerTypeToPortrefTypeConstraint + : TypesMatchWith<"the rhs type dictates the inner type of the rhs portref type", + lhs, rhs, "PortRefType::get($_ctxt, $_self, ibis::Direction::" # dir # ")">; + +class PortRefToInnerTypeConstraint + : TypesMatchWith<"the inner type of the lhs portref type dictates the rhs type", + lhs, rhs, "$_self.cast().getPortType()">; + +def InputWireOp : WireLikeOp<"wire.input", [ + InnerTypeToPortrefTypeConstraint<"output", "port", "Input"> +]> { + let summary = "Ibis input wire"; + let description = [{ + An input wire defines an `ibis.portref` port alongside a value + of type `T` which represents the value to-be-written to the wire. + }]; + + let arguments = (ins InnerSymAttr:$inner_sym); + let results = (outs PortRefType:$port, AnyType:$output); + let assemblyFormat = [{ + $inner_sym `:` qualified(type($output)) attr-dict + }]; + + let extraWireClassDefinition = [{ + ibis::Direction $cppClass::getPortDirection() { + return ibis::Direction::Input; + } + + void $cppClass::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + setNameFn(getPort(), getPortName()); + setNameFn(getOutput(), StringAttr::get(getContext(), + getPortName().strref() + ".out").strref()); + } + }]; + + let builders = [ + OpBuilder<(ins "StringAttr":$name, "Type":$innerPortType), [{ + build($_builder, $_state, + {$_builder.getType( + innerPortType, ibis::Direction::Input), innerPortType}, hw::InnerSymAttr::get(name)); + }]> + ]; + + let hasCanonicalizeMethod = 1; +} + +def OutputWireOp : WireLikeOp<"wire.output", [ + InnerTypeToPortrefTypeConstraint<"input", "port", "Output"> +]> { + let summary = "Ibis output wire"; + let description = [{ + An output wire defines an `ibis.portref` port that can be read. + The operation takes an input value of type `T` which represents the value + on the output portref. + }]; + + let arguments = (ins InnerSymAttr:$inner_sym, AnyType:$input); + let results = (outs PortRefType:$port); + let assemblyFormat = [{ + $inner_sym `,` $input `:` qualified(type($input)) attr-dict + }]; + + let extraWireClassDefinition = [{ + ibis::Direction $cppClass::getPortDirection() { + return ibis::Direction::Output; + } + + void $cppClass::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + setNameFn(getPort(), getPortName()); + } + }]; + + let hasCanonicalizeMethod = 1; +} + +def PortReadOp : IbisOp<"port.read", [ + PortRefToInnerTypeConstraint<"port", "output">, + Pure, + HasCustomSSAName +]> { + let summary = "Ibis port read"; + let description = [{ + Read the value of a port reference. + }]; + + let arguments = (ins PortRefType:$port); + let results = (outs AnyType:$output); + let assemblyFormat = [{ + $port attr-dict `:` qualified(type($port)) + }]; +} + +def PortWriteOp : IbisOp<"port.write", [ + PortRefToInnerTypeConstraint<"port", "value"> +]> { + let summary = "Ibis port write"; + let description = [{ + Write a value to a port reference. + }]; + + let arguments = (ins PortRefType:$port, AnyType:$value); + let assemblyFormat = [{ + $port `,` $value attr-dict `:` qualified(type($port)) + }]; +} + +def PipelineHeaderOp : IbisOp<"pipeline.header", [ + Pure +]> { + let summary = "Ibis pipeline header operation"; + let description = [{ + This operation defines the hardware-like values used to drive a pipeline, + such as clock and reset. + This is an intermediate operation, meaning that it's strictly used to + facilitate progressive lowering of ibis static blocks to scheduled pipelines. + }]; + + let arguments = (ins); + let results = (outs ClockType:$clock, I1:$reset, I1:$go, I1:$stall); + let assemblyFormat = "attr-dict"; +} + +#endif // CIRCT_DIALECT_IBIS_IBISOPS_TD diff --git a/include/circt/Dialect/Ibis/IbisPassPipelines.h b/include/circt/Dialect/Ibis/IbisPassPipelines.h new file mode 100644 index 000000000000..763de7c90073 --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisPassPipelines.h @@ -0,0 +1,29 @@ +//===- IbisPassPipelines.h - Ibis pass pipelines -----------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_IBISPASSPIPELINES_H +#define CIRCT_DIALECT_IBIS_IBISPASSPIPELINES_H + +#include "circt/Dialect/Ibis/IbisPasses.h" +#include "mlir/Pass/PassManager.h" +#include +#include + +namespace circt { +namespace ibis { + +// Loads a pass pipeline to transform low-level Ibis constructs. +void loadIbisLowLevelPassPipeline(mlir::PassManager &pm); + +// Loads a pass pipeline to transform high-level Ibis constructs. +void loadIbisHighLevelPassPipeline(mlir::PassManager &pm); + +} // namespace ibis +} // namespace circt + +#endif // CIRCT_DIALECT_IBIS_IBISPASSPIPELINES_H diff --git a/include/circt/Dialect/Ibis/IbisPasses.h b/include/circt/Dialect/Ibis/IbisPasses.h new file mode 100644 index 000000000000..eac94cd9b37e --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisPasses.h @@ -0,0 +1,50 @@ +//===- Passes.h - Ibis pass entry points -------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_IBISPASSES_H +#define CIRCT_DIALECT_IBIS_IBISPASSES_H + +#include "circt/Dialect/Pipeline/PipelineDialect.h" +#include "circt/Dialect/SSP/SSPDialect.h" +#include "mlir/Dialect/Arith/IR/Arith.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Pass/PassRegistry.h" +#include +#include + +namespace circt { +namespace ibis { + +#define GEN_PASS_DECL_IBISTUNNELING +#include "circt/Dialect/Ibis/IbisPasses.h.inc" + +std::unique_ptr createCallPrepPass(); +std::unique_ptr createContainerizePass(); +std::unique_ptr +createTunnelingPass(const IbisTunnelingOptions & = {}); +std::unique_ptr createPortrefLoweringPass(); +std::unique_ptr createCleanSelfdriversPass(); +std::unique_ptr createContainersToHWPass(); +std::unique_ptr createArgifyBlocksPass(); +std::unique_ptr createReblockPass(); +std::unique_ptr createInlineSBlocksPass(); +std::unique_ptr createConvertCFToHandshakePass(); +std::unique_ptr createPrepareSchedulingPass(); +std::unique_ptr createConvertHandshakeToDCPass(); +std::unique_ptr createConvertMethodsToContainersPass(); +std::unique_ptr createAddOperatorLibraryPass(); + +/// Generate the code for registering passes. +#define GEN_PASS_REGISTRATION +#include "circt/Dialect/Ibis/IbisPasses.h.inc" + +} // namespace ibis +} // namespace circt + +#endif // CIRCT_DIALECT_IBIS_IBISPASSES_H diff --git a/include/circt/Dialect/Ibis/IbisPasses.td b/include/circt/Dialect/Ibis/IbisPasses.td new file mode 100644 index 000000000000..bf82255aa5e8 --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisPasses.td @@ -0,0 +1,224 @@ +//===-- Passes.td - Ibis pass definition file --------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_PASSES_TD +#define CIRCT_DIALECT_IBIS_PASSES_TD + +include "mlir/Pass/PassBase.td" + +def IbisCallPrep : Pass<"ibis-call-prep", "ModuleOp"> { + let summary = "Convert ibis method calls to use `dc.value`"; + + let constructor = "circt::ibis::createCallPrepPass()"; + let dependentDialects = [ + "circt::hw::HWDialect", "circt::dc::DCDialect"]; +} + +def IbisContainerize : Pass<"ibis-containerize", "ModuleOp"> { + let summary = "Ibis containerization pass"; + let description = [{ + Convert Ibis classes to containers, and outlines containers inside classes. + }]; + let constructor = "circt::ibis::createContainerizePass()"; +} + +def IbisTunneling : Pass<"ibis-tunneling", "mlir::ModuleOp"> { + let summary = "Ibis tunneling pass"; + let description = [{ + Tunnels relative `get_port` ops through the module hierarchy, based on + `ibis.path` ops. The result of this pass is that various new in- and output + ports of `!ibis.portref<...>` type are created. + After this pass, `get_port` ops should only exist at the same scope of + container instantiations. + + The user may provide options for `readSuffix` and `writeSuffix`, respectively, + which is to be used to generate the name of the ports that are tunneled + through the hierarchy, with respect to how the port was requested to be accessed. + Suffixes must be provided in cases where a port is tunneled for both read and + write accesses, and the suffixes must be different (in this case, the suffixes + will be appended to the target port name, and thus de-alias the resulting ports). + }]; + let constructor = "circt::ibis::createTunnelingPass()"; + let options = [ + Option<"readSuffix", "read-suffix", "std::string", "\".rd\"", + "Suffix to be used for the port when a port is tunneled for read access">, + Option<"writeSuffix", "write-suffix", "std::string", "\".wr\"", + "Suffix to be used for the port when a port is tunneled for write access"> + ]; +} + +def IbisPortrefLowering : Pass<"ibis-lower-portrefs", "mlir::ModuleOp"> { + let summary = "Ibis portref lowering pass"; + let description = [{ + Lower `ibis.portref>` to T (i.e. portref resolution). + + We do this by analyzing how a portref is used + inside a container, and then creating an in- or output port based on that. + That is: + - write to `portref>` becomes `out T` + i.e this container wants to write to an input of another container, hence + it will produce an output value that will drive that input port. + - read from `portref>` becomes `in T` + i.e. this container wants to read from an output of another container, + hence it will have an input port that will be driven by that output port. + - write to `portref>` becomes `out T` + i.e. a port reference inside the module will be driven by a value from + the outside. + - read from `portref>` becomes `in T` + i.e. a port reference inside the module will be driven by a value from + the outside. + + A benefit of having portref lowering separate from portref tunneling is that + portref lowering can be done on an `ibis.container` granularity, allowing + for a bit of parallelism in the flow. + }]; + let constructor = "circt::ibis::createPortrefLoweringPass()"; +} + +def IbisCleanSelfdrivers : Pass<"ibis-clean-selfdrivers", "mlir::ModuleOp"> { + let summary = "Ibis clean selfdrivers pass"; + let description = [{ + - Removes `ibis.port.input`s which are driven by operations within the same + container. + - Removes reads of instance ports which are also written to within the same + container. + }]; + + let constructor = "circt::ibis::createCleanSelfdriversPass()"; + let dependentDialects = ["circt::hw::HWDialect"]; +} + +def IbisContainersToHW : Pass<"ibis-convert-containers-to-hw", "mlir::ModuleOp"> { + let summary = "Ibis containers to hw conversion pass"; + let description = [{ + Converts `ibis.container` ops to `hw.module` ops. + }]; + let constructor = "circt::ibis::createContainersToHWPass()"; + let dependentDialects = ["circt::hw::HWDialect"]; +} + +def IbisArgifyBlocks : Pass<"ibis-argify-blocks"> { + let summary = "Add arguments to ibis blocks"; + let description = [{ + Analyses `ibis.sblock` operations and converts any SSA value defined outside + the `ibis.sblock` to a block argument. As a result, `ibis.sblock.isolated` + are produced. + }]; + let constructor = "circt::ibis::createArgifyBlocksPass()"; +} + +def IbisReblock : Pass<"ibis-reblock", "ibis::MethodOp"> { + let summary = "Recreates `ibis.sblock` operations from a CFG"; + let description = [{ + Recreates `ibis.sblock` operations from a CFG. Any `ibis.block.attributes` + operations at the parent operation will be added to the resulting blocks. + + The IR is expected to be in maximal SSA form prior to this pass, given that + the pass will only inspect the terminator operation of a block for any + values that are generated within the block. Maximum SSA form thus ensures + that any value defined within the block is never used outside of the block. + + It is expected that `ibis.call` operations have been isolated into + their own basic blocks before this pass is run. This implies that all + operations within a block (except for the terminator operation) can be + statically scheduled with each other. + + e.g. + ```mlir + ^bb_foo(%arg0 : i32, %arg1 : i32): + %res = arith.addi %arg0, %arg1 : i32 + %v = ... + cf.br ^bb_bar(%v : i32) + ``` + + becomes + ```mlir + ^bb_foo(%arg0 : i32, %arg1 : i32): + %v_outer = ibis.sblock(%a0 : i32 = %arg0, %a1 : i32 = %arg1) -> (i32) { + %res = arith.addi %arg0, %arg1 : i32 + %v = ... + ibis.sblock.return %v : i32 + } + cf.br ^bb_bar(%v_outer : i32) + ``` + }]; + let constructor = "circt::ibis::createReblockPass()"; +} + +def IbisInlineSBlocks : Pass<"ibis-inline-sblocks", "ibis::MethodOp"> { + let summary = "Inlines `ibis.sblock` operations as MLIR blocks"; + let description = [{ + Inlines `ibis.sblock` operations, by creating MLIR blocks and `cf` + operations, while adding attributes to the parent operation about + `sblock`-specific attributes. + + The parent attributes are located under the `ibis.blockinfo` identifier as + a dictionary attribute. + Each entry in the dictionary consists of: + - Key: an ID (numerical) string identifying the block. + - Value: a dictionary of attributes. As a minimum this will contain a + `loc`-keyed attribute specifying the location of the block. + }]; + let constructor = "circt::ibis::createInlineSBlocksPass()"; + let dependentDialects = ["mlir::cf::ControlFlowDialect"]; +} + +def IbisConvertCFToHandshake : Pass<"ibis-convert-cf-to-handshake", "ibis::ClassOp"> { + let summary = "Converts an `ibis.method` to `ibis.method.df`"; + let description = [{ + Converts an `ibis.method` from using `cf` operations and MLIR blocks to + an `ibis.method.df` operation, using the `handshake` dialect to represent + control flow through the `handshake` fine grained dataflow operations. + }]; + + let constructor = "circt::ibis::createConvertCFToHandshakePass()"; + let dependentDialects = ["circt::handshake::HandshakeDialect", "mlir::cf::ControlFlowDialect"]; +} + +def IbisConvertHandshakeToDC : Pass<"ibis-convert-handshake-to-dc", "ibis::ClassOp"> { + let summary = "Converts an `ibis.method.df` to use DC"; + let description = [{ + Converts an `ibis.method.df` from using `handshake` operations to + `dc` operations. + }]; + let constructor = "circt::ibis::createConvertHandshakeToDCPass()"; + let dependentDialects = [ + "circt::dc::DCDialect", + "circt::handshake::HandshakeDialect", + "mlir::arith::ArithDialect" + ]; +} + +def IbisPrepareScheduling : Pass<"ibis-prepare-scheduling", "ibis::IsolatedStaticBlockOp"> { + let summary = "Prepare `ibis.sblock` operations for scheduling"; + let description = [{ + Prepares `ibis.sblock` operations for scheduling by: + - creating an `ibis.pipleine.header` operation + - moving operations of an `ibis.sblock` into a `pipeline.unscheduled` + operation, which is connected to the pipeline header. + }]; + let constructor = "circt::ibis::createPrepareSchedulingPass()"; + let dependentDialects = ["circt::pipeline::PipelineDialect"]; +} + +def IbisConvertMethodsToContainers : Pass<"ibis-convert-methods-to-containers", "ibis::ClassOp"> { + let summary = "Converts `ibis.method.df` to `ibis.container`s"; + let constructor = "circt::ibis::createConvertMethodsToContainersPass()"; +} + +def IbisAddOperatorLibrary : Pass<"ibis-add-operator-library", "mlir::ModuleOp"> { + let summary = "Injects the Ibis operator library into the IR"; + let description = [{ + Injects the Ibis operator library into the IR, which contains the + definitions of the Ibis operators. + }]; + let constructor = "circt::ibis::createAddOperatorLibraryPass()"; + let dependentDialects = ["circt::ssp::SSPDialect"]; +} + +#endif // CIRCT_DIALECT_IBIS_PASSES_TD diff --git a/include/circt/Dialect/Ibis/IbisTypes.h b/include/circt/Dialect/Ibis/IbisTypes.h new file mode 100644 index 000000000000..63f6e7af90e8 --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisTypes.h @@ -0,0 +1,27 @@ +//===- IbisTypes.h - Definition of Ibis dialect types ------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_IBISTYPES_H +#define CIRCT_DIALECT_IBIS_IBISTYPES_H + +#include "circt/Dialect/Ibis/IbisDialect.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Types.h" + +namespace circt { +namespace ibis { +// Returns true if the given type is an opaque reference to an ibis class. +bool isOpaqueScopeRefType(mlir::Type type); +} // namespace ibis +} // namespace circt + +#define GET_TYPEDEF_CLASSES +#include "circt/Dialect/Ibis/IbisTypes.h.inc" + +#endif // CIRCT_DIALECT_IBIS_IBISTYPES_H diff --git a/include/circt/Dialect/Ibis/IbisTypes.td b/include/circt/Dialect/Ibis/IbisTypes.td new file mode 100644 index 000000000000..530f883e070e --- /dev/null +++ b/include/circt/Dialect/Ibis/IbisTypes.td @@ -0,0 +1,84 @@ +//===- IbisTypes.td - Definition of Ibis dialect types --------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_IBIS_IBISTYPES_TD +#define CIRCT_DIALECT_IBIS_IBISTYPES_TD + +include "circt/Dialect/Ibis/IbisDialect.td" +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/EnumAttr.td" + +class IbisTypeDef : TypeDef { } + +def ScopeRefType : IbisTypeDef<"ScopeRef"> { + let mnemonic = "scoperef"; + let description = [{ + A reference to an Ibis scope. May be either a reference to a specific + scope (given a `$scopeName` argument) or an opaque reference. + }]; + + let parameters = (ins OptionalParameter<"mlir::FlatSymbolRefAttr">:$scopeRef); + let assemblyFormat = "(`<` $scopeRef^ `>`)?"; + let extraClassDeclaration = [{ + bool isOpaque() { + return !getScopeRef(); + } + }]; + + let builders = [ + // Builds an opaque (unresolved) class reference. + TypeBuilder<(ins), [{ + return $_get($_ctxt, nullptr); + }]>, + TypeBuilder<(ins "StringAttr":$scopeRef), [{ + return $_get($_ctxt, FlatSymbolRefAttr::get(scopeRef)); + }]> + ]; +} + +def AnyScopeRefType : Type< + CPred<"$_self.isa()">, + "must be a !dc.classref type", + "ibis::ScopeRefType">{ +} + +def OpaqueScopeRefType : Type< + CPred<"ibis::isOpaqueScopeRefType($_self)">, + "must be a !dc.classref<> type">, + BuildableType<"$_builder.getType()"> { +} + +def Input : I32EnumAttrCase<"Input", 0, "in">; +def Output : I32EnumAttrCase<"Output", 1, "out">; +def Direction : I32EnumAttr<"Direction", "Ibis port direction", + [Input, Output]> { + let cppNamespace = "::circt::ibis"; +} + +def PortRefType : IbisTypeDef<"PortRef"> { + let mnemonic = "portref"; + let parameters = (ins "TypeAttr":$portTypeAttr, "ibis::Direction":$direction); + let assemblyFormat = "`<` $direction $portTypeAttr `>`"; + let description = [{ + A reference to an Ibis port. + }]; + + let extraClassDeclaration = [{ + Type getPortType() { + return getPortTypeAttr().getValue(); + } + }]; + + let builders = [ + TypeBuilder<(ins "Type":$t, "ibis::Direction":$d), [{ + return $_get($_ctxt, TypeAttr::get(t), d); + }]> + ]; +} + +#endif // CIRCT_DIALECT_IBIS_IBISTYPES_TD diff --git a/include/circt/Dialect/Interop/Interop.td b/include/circt/Dialect/Interop/Interop.td index 82f0ae2dd886..ccf63def224c 100644 --- a/include/circt/Dialect/Interop/Interop.td +++ b/include/circt/Dialect/Interop/Interop.td @@ -32,6 +32,10 @@ def InteropDialect : Dialect { solutions. }]; let cppNamespace = "::circt::interop"; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + } //===----------------------------------------------------------------------===// diff --git a/include/circt/Dialect/LLHD/IR/LLHD.td b/include/circt/Dialect/LLHD/IR/LLHD.td index 86ab3c60e70a..51e3a5b0633d 100644 --- a/include/circt/Dialect/LLHD/IR/LLHD.td +++ b/include/circt/Dialect/LLHD/IR/LLHD.td @@ -20,7 +20,7 @@ include "mlir/Interfaces/SideEffectInterfaces.td" include "mlir/Interfaces/ControlFlowInterfaces.td" include "mlir/Interfaces/CallInterfaces.td" include "mlir/Interfaces/InferTypeOpInterface.td" -include "mlir/IR/FunctionInterfaces.td" +include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/IR/SymbolInterfaces.td" //===----------------------------------------------------------------------===// @@ -39,6 +39,9 @@ def LLHD_Dialect : Dialect { let useDefaultTypePrinterParser = 1; let useDefaultAttributePrinterParser = 1; + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + let extraClassDeclaration = [{ /// Register all LLHD types. void registerTypes(); diff --git a/include/circt/Dialect/LLHD/IR/LLHDOps.h b/include/circt/Dialect/LLHD/IR/LLHDOps.h index cc73a8e447b5..11acbc9c6958 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDOps.h +++ b/include/circt/Dialect/LLHD/IR/LLHDOps.h @@ -19,9 +19,9 @@ #include "circt/Dialect/LLHD/IR/LLHDTypes.h" #include "circt/Support/LLVM.h" #include "mlir/IR/BuiltinTypes.h" -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/Interfaces/CallInterfaces.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" #include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" diff --git a/include/circt/Dialect/LLHD/IR/StructureOps.td b/include/circt/Dialect/LLHD/IR/StructureOps.td index 7c57120bc37d..be4f81942e73 100644 --- a/include/circt/Dialect/LLHD/IR/StructureOps.td +++ b/include/circt/Dialect/LLHD/IR/StructureOps.td @@ -12,7 +12,7 @@ def LLHD_EntityOp : LLHD_Op<"entity", [ Symbol, - FunctionOpInterface, + DeclareOpInterfaceMethods, IsolatedFromAbove, SingleBlock, NoTerminator, @@ -60,12 +60,6 @@ def LLHD_EntityOp : LLHD_Op<"entity", [ let hasVerifier = 1; let extraClassDeclaration = [{ - /// Returns the argument types of this function. - ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } - - /// Returns the result types of this function. - ArrayRef getResultTypes() { return getFunctionType().getResults(); } - /// Verify the type attribute of this function. Returns failure and emits /// an error if the attribute is invalid. LogicalResult verifyType(); @@ -83,7 +77,7 @@ def LLHD_EntityOp : LLHD_Op<"entity", [ def LLHD_ProcOp : LLHD_Op<"proc", [ Symbol, - FunctionOpInterface, + DeclareOpInterfaceMethods, IsolatedFromAbove, DeclareOpInterfaceMethods ]> { @@ -134,12 +128,6 @@ def LLHD_ProcOp : LLHD_Op<"proc", [ let regions = (region AnyRegion: $body); let extraClassDeclaration = [{ - /// Returns the argument types of this function. - ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } - - /// Returns the result types of this function. - ArrayRef getResultTypes() { return getFunctionType().getResults(); } - /// Verify the type attribute of this function. Returns failure and emits /// an error if the attribute is invalid. LogicalResult verifyType(); @@ -197,6 +185,10 @@ def LLHD_InstOp : LLHD_Op<"inst", [ let extraClassDeclaration = [{ FunctionType getCalleeType(); + MutableOperandRange getArgOperandsMutable() { + return getInputsMutable(); + } + /// Get the argument operands to the called function. operand_range getArgOperands() { return {arg_operand_begin(), arg_operand_end()}; @@ -209,6 +201,11 @@ def LLHD_InstOp : LLHD_Op<"inst", [ CallInterfaceCallable getCallableForCallee() { return (*this)->getAttrOfType("callee"); } + + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + (*this)->setAttr(getCalleeAttrName(), callee.get()); + } }]; let hasVerifier = 1; diff --git a/include/circt/Dialect/LTL/CMakeLists.txt b/include/circt/Dialect/LTL/CMakeLists.txt new file mode 100644 index 000000000000..682fb0474604 --- /dev/null +++ b/include/circt/Dialect/LTL/CMakeLists.txt @@ -0,0 +1,13 @@ +add_circt_dialect(LTL ltl) +add_circt_doc(LTLOps Dialects/LTLOps -gen-op-doc) +add_circt_doc(LTLTypes Dialects/LTLTypes -gen-typedef-doc -dialect ltl) + +set(LLVM_TARGET_DEFINITIONS LTL.td) +mlir_tablegen(LTLEnums.h.inc -gen-enum-decls) +mlir_tablegen(LTLEnums.cpp.inc -gen-enum-defs) +add_public_tablegen_target(CIRCTLTLEnumsIncGen) +add_dependencies(circt-headers CIRCTLTLEnumsIncGen) + +mlir_tablegen(LTLFolds.cpp.inc -gen-rewriters) +add_public_tablegen_target(CIRCTLTLFoldsIncGen) +add_dependencies(circt-headers CIRCTLTLFoldsIncGen) diff --git a/include/circt/Dialect/LTL/LTL.td b/include/circt/Dialect/LTL/LTL.td new file mode 100644 index 000000000000..97e3a0885a65 --- /dev/null +++ b/include/circt/Dialect/LTL/LTL.td @@ -0,0 +1,17 @@ +//===- LTL.td - LTL dialect definition ---------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTL_TD +#define CIRCT_DIALECT_LTL_LTL_TD + +include "circt/Dialect/LTL/LTLDialect.td" +include "circt/Dialect/LTL/LTLTypes.td" +include "circt/Dialect/LTL/LTLOps.td" +include "circt/Dialect/LTL/LTLFolds.td" + +#endif // CIRCT_DIALECT_LTL_LTL_TD diff --git a/include/circt/Dialect/LTL/LTLDialect.h b/include/circt/Dialect/LTL/LTLDialect.h new file mode 100644 index 000000000000..c3879737753c --- /dev/null +++ b/include/circt/Dialect/LTL/LTLDialect.h @@ -0,0 +1,19 @@ +//===- LTLDialect.h - LTL dialect definition --------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTLDIALECT_H +#define CIRCT_DIALECT_LTL_LTLDIALECT_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Dialect.h" + +#include "circt/Dialect/LTL/LTLDialect.h.inc" +#include "circt/Dialect/LTL/LTLEnums.h.inc" + +#endif // CIRCT_DIALECT_LTL_LTLDIALECT_H diff --git a/include/circt/Dialect/LTL/LTLDialect.td b/include/circt/Dialect/LTL/LTLDialect.td new file mode 100644 index 000000000000..b1b34b0d109c --- /dev/null +++ b/include/circt/Dialect/LTL/LTLDialect.td @@ -0,0 +1,26 @@ +//===- LTLDialect.td - LTL dialect definition --------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTLDIALECT_TD +#define CIRCT_DIALECT_LTL_LTLDIALECT_TD + +include "mlir/IR/OpBase.td" + +def LTLDialect : Dialect { + let name = "ltl"; + let summary = "Linear temporal logic, sequences, and properties."; + // See `docs/Dialect/LTL.md` for detailed dialect documentation. + let cppNamespace = "circt::ltl"; + let useDefaultTypePrinterParser = 1; + let dependentDialects = ["hw::HWDialect", "comb::CombDialect"]; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; +} + +#endif // CIRCT_DIALECT_LTL_LTLDIALECT_TD diff --git a/include/circt/Dialect/LTL/LTLFolds.td b/include/circt/Dialect/LTL/LTLFolds.td new file mode 100644 index 000000000000..6db7668f1923 --- /dev/null +++ b/include/circt/Dialect/LTL/LTLFolds.td @@ -0,0 +1,65 @@ +//===- LTLFolds.td - LTL dialect declarative rewrites ------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTLFOLDS_TD +#define CIRCT_DIALECT_LTL_LTLFOLDS_TD + +include "circt/Dialect/LTL/LTLOps.td" +include "mlir/IR/PatternBase.td" + +//===----------------------------------------------------------------------===// +// Utilities +//===----------------------------------------------------------------------===// + +def valueHead : NativeCodeCall<"$0[0]">; +def valueTail : NativeCodeCall<"$0.drop_front()">; +def concatValues : NativeCodeCall< + "concatValues(ValueRange{$0}, ValueRange{$1})">; + +//===----------------------------------------------------------------------===// +// DelayOp +//===----------------------------------------------------------------------===// + +// delay(delay(s, I), J) -> delay(s, I+J) +// delay(delay(s, I), J, N) -> delay(s, I+J) +// delay(delay(s, I, N), J) -> delay(s, I+J) +// delay(delay(s, I, N), J, M) -> delay(s, I+J, N+M) +def mergedDelays : NativeCodeCall< + "$_builder.getI64IntegerAttr($0.getInt() + $1.getInt())">; +def mergedLengths : NativeCodeCall<[{ + $0 && $1 ? $_builder.getI64IntegerAttr($0.getInt() + $1.getInt()) + : IntegerAttr{} +}]>; +def NestedDelays : Pat< + (DelayOp (DelayOp $input, $delay1, $length1), $delay2, $length2), + (DelayOp $input, (mergedDelays $delay1, $delay2), + (mergedLengths $length1, $length2))>; + +// delay(concat(s, ...), N, M) -> concat(delay(s, N, M), ...) +def MoveDelayIntoConcat : Pat< + (DelayOp (ConcatOp $inputs), $delay, $length), + (ConcatOp (concatValues (DelayOp (valueHead $inputs), $delay, $length), + (valueTail $inputs)))>; + +//===----------------------------------------------------------------------===// +// ConcatOp +//===----------------------------------------------------------------------===// + +// concat(..., concat(s0, s1), ...) -> concat(..., s0, s1, ...) +def AnyDefiningOpIsConcat : Constraint(); + }) + }]>, + "any value is defined by ConcatOp">; +def flattenConcats : NativeCodeCall<"flattenConcats($0)">; +def FlattenConcats : Pat< + (ConcatOp $inputs), (ConcatOp (flattenConcats $inputs)), + [(AnyDefiningOpIsConcat $inputs)]>; + +#endif // CIRCT_DIALECT_LTL_LTLFOLDS_TD diff --git a/include/circt/Dialect/LTL/LTLOps.h b/include/circt/Dialect/LTL/LTLOps.h new file mode 100644 index 000000000000..a9adecfa58f5 --- /dev/null +++ b/include/circt/Dialect/LTL/LTLOps.h @@ -0,0 +1,20 @@ +//===- LTLOps.h - LTL dialect operations --------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTLOPS_H +#define CIRCT_DIALECT_LTL_LTLOPS_H + +#include "mlir/Interfaces/InferTypeOpInterface.h" + +#include "circt/Dialect/LTL/LTLDialect.h" +#include "circt/Dialect/LTL/LTLTypes.h" + +#define GET_OP_CLASSES +#include "circt/Dialect/LTL/LTL.h.inc" + +#endif // CIRCT_DIALECT_LTL_LTLOPS_H diff --git a/include/circt/Dialect/LTL/LTLOps.td b/include/circt/Dialect/LTL/LTLOps.td new file mode 100644 index 000000000000..4405890ed4c5 --- /dev/null +++ b/include/circt/Dialect/LTL/LTLOps.td @@ -0,0 +1,316 @@ +//===- LTLOps.td - LTL dialect operations ------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTLOPS_TD +#define CIRCT_DIALECT_LTL_LTLOPS_TD + +include "circt/Dialect/LTL/LTLDialect.td" +include "circt/Dialect/LTL/LTLTypes.td" +include "mlir/IR/EnumAttr.td" +include "mlir/IR/PatternBase.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +class LTLOp traits = []> : + Op; + +//===----------------------------------------------------------------------===// +// Generic Operations +//===----------------------------------------------------------------------===// + +class AssocLTLOp traits = []> : + LTLOp]> { + let arguments = (ins Variadic:$inputs); + let results = (outs LTLAnyPropertyType:$result); + let assemblyFormat = [{ + $inputs attr-dict `:` type($inputs) + }]; +} + +def AndOp : AssocLTLOp<"and"> { + let summary = "A conjunction of booleans, sequences, or properties."; + let description = [{ + If any of the `$inputs` is of type `!ltl.property`, the result of the op is + an `!ltl.property`. Otherwise it is an `!ltl.sequence`. + }]; +} + +def OrOp : AssocLTLOp<"or"> { + let summary = "A disjunction of booleans, sequences, or properties."; + let description = [{ + If any of the `$inputs` is of type `!ltl.property`, the result of the op is + an `!ltl.property`. Otherwise it is an `!ltl.sequence`. + }]; +} + +//===----------------------------------------------------------------------===// +// Sequences +//===----------------------------------------------------------------------===// + +def DelayOp : LTLOp<"delay", [Pure]> { + let arguments = (ins + LTLAnySequenceType:$input, + I64Attr:$delay, + OptionalAttr:$length); + let results = (outs LTLSequenceType:$result); + let assemblyFormat = [{ + $input `,` $delay (`,` $length^)? attr-dict `:` type($input) + }]; + let hasFolder = 1; + let hasCanonicalizer = 1; + + let summary = "Delay a sequence by a number of cycles."; + let description = [{ + Delays the `$input` sequence by the number of cycles specified by `$delay`. + The delay must be greater than or equal to zero. The optional `$length` + specifies during how many cycles after the initial delay the sequence can + match. Omitting `$length` indicates an unbounded but finite delay. For + example: + + - `ltl.delay %seq, 2, 0` delays `%seq` by exactly 2 cycles. The resulting + sequence matches if `%seq` matches exactly 2 cycles in the future. + - `ltl.delay %seq, 2, 2` delays `%seq` by 2, 3, or 4 cycles. The resulting + sequence matches if `%seq` matches 2, 3, or 4 cycles in the future. + - `ltl.delay %seq, 2` delays `%seq` by 2 or more cycles. The number of + cycles is unbounded but finite, which means that `%seq` *has* to match at + some point, instead of effectively never occuring by being delayed an + infinite number of cycles. + - `ltl.delay %seq, 0, 0` is equivalent to just `%seq`. + + #### Clocking + + The cycle delay specified on the operation refers to a clocking event. This + event is not directly specified by the delay operation itself. Instead, the + [`ltl.clock`](#ltlclock-circtltlclockop) operation can be used to associate + all delays within a sequence with a clock. + }]; +} + +def ConcatOp : LTLOp<"concat", [Pure]> { + let arguments = (ins Variadic:$inputs); + let results = (outs LTLSequenceType:$result); + let assemblyFormat = [{ + $inputs attr-dict `:` type($inputs) + }]; + let hasFolder = 1; + let hasCanonicalizer = 1; + + let summary = "Concatenate sequences into a longer sequence."; + let description = [{ + Concatenates all of the `$inputs` sequences one after another into one + longer sequence. The sequences are arranged such that the end time of the + previous sequences coincides with the start time of the next sequence. This + means there is no implicit cycle of delay between the concatenated + sequences, which may be counterintuitive. + + If a sequence should follow in the cycle after another sequence finishes, + that cycle of delay needs to be explicit. For example, *"u followed by v in + next cycle"* (`u ##1 v` in SVA) is represented as + `concat(u, delay(v, 1, 0))`: + ``` + %0 = ltl.delay %v, 1, 0 : i1 + ltl.concat %u, %v : !ltl.sequence, !ltl.sequence + ``` + The resulting sequence checks for `u` in the first cycle and `v` in the + second, `[u, v]` in short. + + Without this explicit delay, the previous sequence's end overlaps with the + next sequence's start. For example, consider the two sequences `u = a ##1 b` + and `v = c ##1 d`, which check for `a` and `c` in the first, and `b` and `d` + in the second cycle. When these two sequences are concatenated, + `concat(u, v)`, the end time of the first sequence coincides with the start + time of the second. As a result, the check for `b` at the end of the first + sequence will coincide with the check for `c` at the start of the second + sequence: `concat(u, v) = a ##1 (b && c) ##1 d`. The resulting sequence + checks for `a` in the first cycle, `b` and `c` in the second, and `d` in the + third, `[a, (b && c), d]` in short. + + By making the delay between concatenated sequences explicit, the `concat` + operation behaves nicely in the presence of zero-length sequences. An empty, + zero-length sequence in a concatenation behaves as if the sequence wasn't + present at all. Compare this to SVAs which struggle with empty sequences. + For example, `x ##1 y ##1 z` would become `x ##2 z` if `y` was empty. + Similarly, expressing zero or more repetitions of a sequence, `w ##[*]`, is + challenging in SVA since concatenation always implies a cycle of delay, but + trivial if the delay is made explicit. This is related to the handling of + empty rules in a parser's grammar. + + Note that concatenating two boolean values *a* and *b* is equivalent to + computing the logical AND of them. Booleans are sequences that check if the + boolean is true in the current cycle, which means that the sequence starts + and ends in the same cycle. Since concatenation aligns the sequences such + that end time of *a* and start time of *b* coincide, the resulting sequence + checks if *a* and *b* both are true in the current cycle, which is an AND + operation. + }]; +} + +//===----------------------------------------------------------------------===// +// Properties +//===----------------------------------------------------------------------===// + +def NotOp : LTLOp<"not", [Pure]> { + let arguments = (ins LTLAnyPropertyType:$input); + let results = (outs LTLPropertyType:$result); + let assemblyFormat = [{ + $input attr-dict `:` type($input) + }]; + + let summary = "A negation of a property."; + let description = [{ + Negates the `$input` property. The resulting property evaluates to true if + `$input` evaluates to false, and it evaluates to false if `$input` evaluates + to true. + }]; +} + +def ImplicationOp : LTLOp<"implication", [Pure]> { + let arguments = (ins LTLAnySequenceType:$antecedent, + LTLAnyPropertyType:$consequent); + let results = (outs LTLPropertyType:$result); + let assemblyFormat = [{ + operands attr-dict `:` type(operands) + }]; + + let summary = "Only check a property after a sequence matched."; + let description = [{ + Preconditions the checking of the `$consequent` property on the + `$antecedent` sequence. In a nutshell, if the `$antecedent` sequence matches + at a given point in time, the `$consequent` property is checked starting at + the point in time at which the matched sequence ends. The result property of + the `ltl.implication` holds if the `$consequent` holds. Conversely, if the + `$antecedent` does *not* match at a given point in time, the result property + trivially holds. This is conceptually identical to the implication operator + →, but with additional temporal semantics. + }]; +} + +def EventuallyOp : LTLOp<"eventually", [Pure]> { + let arguments = (ins LTLAnyPropertyType:$input); + let results = (outs LTLPropertyType:$result); + let assemblyFormat = [{ + $input attr-dict `:` type($input) + }]; + + let summary = "Ensure that a property will hold at some time in the future."; + let description = [{ + Checks that the `$input` property will hold at a future time. This operator + is strong: it requires that the `$input` holds after a *finite* number of + cycles. The operator does *not* hold if the `$input` can't hold in the + future. + }]; +} + +//===----------------------------------------------------------------------===// +// Clocking +//===----------------------------------------------------------------------===// + +// Edge behavior enum for always block. See SV Spec 9.4.2. + +/// AtPosEdge triggers on a rise from 0 to 1/X/Z, or X/Z to 1. +def AtPosEdge: I32EnumAttrCase<"Pos", 0, "posedge">; +/// AtNegEdge triggers on a drop from 1 to 0/X/Z, or X/Z to 0. +def AtNegEdge: I32EnumAttrCase<"Neg", 1, "negedge">; +/// AtEdge is syntactic sugar for AtPosEdge or AtNegEdge. +def AtEdge : I32EnumAttrCase<"Both", 2, "edge">; + +def ClockEdgeAttr : I32EnumAttr<"ClockEdge", "clock edge", + [AtPosEdge, AtNegEdge, AtEdge]> { + let cppNamespace = "circt::ltl"; +} + +def ClockOp : LTLOp<"clock", [ + Pure, InferTypeOpInterface, DeclareOpInterfaceMethods +]> { + let arguments = (ins LTLAnyPropertyType:$input, ClockEdgeAttr:$edge, I1:$clock); + let results = (outs LTLSequenceOrPropertyType:$result); + let assemblyFormat = [{ + $input `,` $edge $clock attr-dict `:` type($input) + }]; + + let summary = "Specify the clock for a property or sequence."; + let description = [{ + Specifies the `$edge` on a given `$clock` to be the clock for an `$input` + property or sequence. All cycle delays in the `$input` implicitly refer to a + clock that advances the state to the next cycle. The `ltl.clock` operation + provides that clock. The clock applies to the entire property or sequence + expression tree below `$input`, up to any other nested `ltl.clock` + operations. + + The operation returns a property if the `$input` is a property, and a + sequence otherwise. + }]; +} + +def DisableOp : LTLOp<"disable", [Pure]> { + let arguments = (ins LTLAnyPropertyType:$input, I1:$condition); + let results = (outs LTLPropertyType:$result); + let assemblyFormat = [{ + $input `if` $condition attr-dict `:` type($input) + }]; + let hasFolder = 1; + + let summary = "Disable the evaluation of a property based on a condition."; + let description = [{ + Creates a new property which evaluates the given `$input` property only if + the given disable `$condition` is false throughout the entire evaluation. If + the `$condition` is true at any point in time during the evaluation of + `$input`, the resulting property is disabled. + + The disabling is "infectious". If a property is disabled, it also implicitly + disables all properties that use it. Consider the following example: + ``` + %0 = ltl.disable %prop if %cond + %1 = ltl.or %0, %otherProp + ``` + If the property `%0` is disabled, the derived property `%1` is also + disabled. + + As a result, it is always legal to canonicalize the IR into a form where + there is only one `ltl.disable` operation at the root of a property + expression. + + Note that a property being disabled based on a condition is different from a + property that trivially evaluates to true if the condition does not hold. + The former ensures that a property is only checked when a certain condition + is true, but the number of cases in which the property holds or doesn't hold + remains unchanged. The latter adds additional cases where the property + holds, which can offer a solver unintended ways to make assertions or + coverage proofs derived from the property pass. For example: + + ``` + %p0 = ltl.or %prop, %cond + %p1 = ltl.disable %prop if %cond + ``` + + `$cond` being true would disable the evaluation of `%p0` and would make + `%p1` evaluate to true. These are subtly different. If used in an assertion + during simulation, `$cond` would adequately disable triggering of the + assertion in both cases. However, if used in a formal verification setting + where proofs for `%p0` and `%p1` always holding or never holding are sought, + a solver might use `%cond = true` to trivially make `%p0` hold, which is not + possible for `%p1`. Consider the following more concrete example: + + ``` + %p2 = ltl.or %protocolCorrectProperty, %reset + %p3 = ltl.disable %protocolCorrectProperty if %reset + verif.cover %p2 + verif.cover %p3 + ``` + + The intent is to formally prove coverage for `%protocolCorrectProperty` + while the circuit is in regular operation (i.e., out of reset). A formal + solver would trivially prove coverage for `%p2` by assigning + `%reset = true`, but would have to actually prove coverage for the + underlying `%protocolCorrectProperty` for `%p3`. The latter is almost always + the intended behavior. + }]; +} + +#endif // CIRCT_DIALECT_LTL_LTLOPS_TD diff --git a/include/circt/Dialect/LTL/LTLTypes.h b/include/circt/Dialect/LTL/LTLTypes.h new file mode 100644 index 000000000000..514377b0e597 --- /dev/null +++ b/include/circt/Dialect/LTL/LTLTypes.h @@ -0,0 +1,18 @@ +//===- LTLTypes.h - LTL dialect types ---------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTLTYPES_H +#define CIRCT_DIALECT_LTL_LTLTYPES_H + +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Types.h" + +#define GET_TYPEDEF_CLASSES +#include "circt/Dialect/LTL/LTLTypes.h.inc" + +#endif // CIRCT_DIALECT_LTL_LTLTYPES_H diff --git a/include/circt/Dialect/LTL/LTLTypes.td b/include/circt/Dialect/LTL/LTLTypes.td new file mode 100644 index 000000000000..194849f37c51 --- /dev/null +++ b/include/circt/Dialect/LTL/LTLTypes.td @@ -0,0 +1,49 @@ +//===- LTLTypes.td - LTL dialect types ---------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTLTYPES_TD +#define CIRCT_DIALECT_LTL_LTLTYPES_TD + +include "circt/Dialect/LTL/LTLDialect.td" +include "mlir/IR/AttrTypeBase.td" + +class LTLTypeDef : TypeDef { + let mnemonic = typeMnemonic; +} + +def LTLSequenceType : LTLTypeDef<"Sequence", "sequence"> { + let summary = "LTL sequence type"; + let description = [{ + The `ltl.sequence` type represents a sequence of linear temporal logic, for + example, *"A is true two cycles after B is true"*. + + Note that this type explicitly identifies a *sequence*. However, a boolean + value (`i1`) is also a valid sequence. Operations that accept a sequence as + an operand will use the `AnySequence` constraint, which also accepts `i1`. + }]; +} + +def LTLPropertyType : LTLTypeDef<"Property", "property"> { + let summary = "LTL property type"; + let description = [{ + The `ltl.property` type represents a verifiable property built from linear + temporal logic sequences and quantifiers, for example, *"if you see sequence + A, eventually you will see sequence B"*. + + Note that this type explicitly identifies a *property*. However, a boolean + value (`i1`) or a sequence (`ltl.sequence`) is also a valid property. + Operations that accept a property as an operand will use the `AnyProperty` + constraint, which also accepts `ltl.sequence` and `i1`. + }]; +} + +def LTLAnySequenceType : AnyTypeOf<[I1, LTLSequenceType]>; +def LTLAnyPropertyType : AnyTypeOf<[I1, LTLSequenceType, LTLPropertyType]>; +def LTLSequenceOrPropertyType : AnyTypeOf<[LTLSequenceType, LTLPropertyType]>; + +#endif // CIRCT_DIALECT_LTL_LTLTYPES_TD diff --git a/include/circt/Dialect/LTL/LTLVisitors.h b/include/circt/Dialect/LTL/LTLVisitors.h new file mode 100644 index 000000000000..32df0fcdbba6 --- /dev/null +++ b/include/circt/Dialect/LTL/LTLVisitors.h @@ -0,0 +1,66 @@ +//===- LTLVisitors.h - LTL dialect visitors ---------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LTL_LTLVISITORS_H +#define CIRCT_DIALECT_LTL_LTLVISITORS_H + +#include "circt/Dialect/LTL/LTLOps.h" +#include "llvm/ADT/TypeSwitch.h" + +namespace circt { +namespace ltl { +template +class Visitor { +public: + ResultType dispatchLTLVisitor(Operation *op, ExtraArgs... args) { + auto *thisCast = static_cast(this); + return TypeSwitch(op) + .template Case( + [&](auto op) -> ResultType { + return thisCast->visitLTL(op, args...); + }) + .Default([&](auto) -> ResultType { + return thisCast->visitInvalidLTL(op, args...); + }); + } + + /// This callback is invoked on any non-LTL operations. + ResultType visitInvalidLTL(Operation *op, ExtraArgs... args) { + op->emitOpError("is not an LTL operation"); + abort(); + } + + /// This callback is invoked on any LTL operations that were not handled by + /// their concrete `visitLTL(...)` callback. + ResultType visitUnhandledLTL(Operation *op, ExtraArgs... args) { + return ResultType(); + } + +#define HANDLE(OPTYPE, OPKIND) \ + ResultType visitLTL(OPTYPE op, ExtraArgs... args) { \ + return static_cast(this)->visit##OPKIND##LTL(op, args...); \ + } + + HANDLE(AndOp, Unhandled); + HANDLE(OrOp, Unhandled); + HANDLE(DelayOp, Unhandled); + HANDLE(ConcatOp, Unhandled); + HANDLE(NotOp, Unhandled); + HANDLE(ImplicationOp, Unhandled); + HANDLE(EventuallyOp, Unhandled); + HANDLE(ClockOp, Unhandled); + HANDLE(DisableOp, Unhandled); +#undef HANDLE +}; + +} // namespace ltl +} // namespace circt + +#endif // CIRCT_DIALECT_LTL_LTLVISITORS_H diff --git a/include/circt/Dialect/LoopSchedule/CMakeLists.txt b/include/circt/Dialect/LoopSchedule/CMakeLists.txt new file mode 100644 index 000000000000..7f52ed1a5eaa --- /dev/null +++ b/include/circt/Dialect/LoopSchedule/CMakeLists.txt @@ -0,0 +1,2 @@ +add_circt_dialect(LoopSchedule loopschedule) +add_circt_dialect_doc(LoopSchedule loopschedule) diff --git a/include/circt/Dialect/LoopSchedule/LoopSchedule.td b/include/circt/Dialect/LoopSchedule/LoopSchedule.td new file mode 100644 index 000000000000..e76524bfc916 --- /dev/null +++ b/include/circt/Dialect/LoopSchedule/LoopSchedule.td @@ -0,0 +1,26 @@ +//===- LoopSchedule.td - LoopSchedule Definitions ----------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_LOOP_SCHEDULE_DIALECT_TD +#define CIRCT_LOOP_SCHEDULE_DIALECT_TD + +include "mlir/IR/DialectBase.td" +include "mlir/IR/OpBase.td" + +def LoopSchedule_Dialect : Dialect { + let name = "loopschedule"; + let cppNamespace = "::circt::loopschedule"; + let summary = "Representation of scheduled loops"; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; +} + +include "circt/Dialect/LoopSchedule/LoopScheduleOps.td" + +#endif // CIRCT_LOOP_SCHEDULE_DIALECT_TD diff --git a/include/circt/Dialect/LoopSchedule/LoopScheduleDialect.h b/include/circt/Dialect/LoopSchedule/LoopScheduleDialect.h new file mode 100644 index 000000000000..226baa3b2c86 --- /dev/null +++ b/include/circt/Dialect/LoopSchedule/LoopScheduleDialect.h @@ -0,0 +1,22 @@ +//===- LoopScheduleDialect.h - LoopSchedule Definitions ---------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the LoopSchedule CIRCT dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LOOPSCHEDULE_LOOPSCHEDULEDIALECT_H +#define CIRCT_DIALECT_LOOPSCHEDULE_LOOPSCHEDULEDIALECT_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Dialect.h" + +#include "circt/Dialect/LoopSchedule/LoopScheduleDialect.h.inc" + +#endif // CIRCT_DIALECT_LOOPSCHEDULE_LOOPSCHEDULEDIALECT_H diff --git a/include/circt/Dialect/Pipeline/Pipeline.h b/include/circt/Dialect/LoopSchedule/LoopScheduleOps.h similarity index 50% rename from include/circt/Dialect/Pipeline/Pipeline.h rename to include/circt/Dialect/LoopSchedule/LoopScheduleOps.h index 6af01c3d8f1f..b4203dcfed50 100644 --- a/include/circt/Dialect/Pipeline/Pipeline.h +++ b/include/circt/Dialect/LoopSchedule/LoopScheduleOps.h @@ -1,17 +1,13 @@ -//===- Pipeline.h - Pipeline Definitions ------------------------*- C++ -*-===// +//===- LoopScheduleOps.h - LoopSchdule Op Definitions -----------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// -// This file defines an Pipeline MLIR dialect. -// -//===----------------------------------------------------------------------===// -#ifndef CIRCT_PIPELINE_OPS_H_ -#define CIRCT_PIPELINE_OPS_H_ +#ifndef CIRCT_DIALECT_LOOPSCHEDULE_LOOPSCHEDULEOPS_H +#define CIRCT_DIALECT_LOOPSCHEDULE_LOOPSCHEDULEOPS_H #include "circt/Support/LLVM.h" #include "mlir/IR/Attributes.h" @@ -22,16 +18,10 @@ #include "mlir/IR/OpDefinition.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/Operation.h" -#include "mlir/IR/RegionKindInterface.h" -#include "mlir/IR/TypeSupport.h" -#include "mlir/IR/Types.h" -#include "mlir/Interfaces/InferTypeOpInterface.h" -#include "mlir/Interfaces/SideEffectInterfaces.h" -#include "mlir/Pass/Pass.h" -#include "circt/Dialect/Pipeline/PipelineDialect.h.inc" +#include "circt/Dialect/LoopSchedule/LoopScheduleDialect.h" #define GET_OP_CLASSES -#include "circt/Dialect/Pipeline/Pipeline.h.inc" +#include "circt/Dialect/LoopSchedule/LoopSchedule.h.inc" -#endif // CIRCT_PIPELINE_OPS_H_ +#endif // CIRCT_DIALECT_LOOPSCHEDULE_LOOPSCHEDULEOPS_H diff --git a/include/circt/Dialect/LoopSchedule/LoopScheduleOps.td b/include/circt/Dialect/LoopSchedule/LoopScheduleOps.td new file mode 100644 index 000000000000..7f7b3e881fc8 --- /dev/null +++ b/include/circt/Dialect/LoopSchedule/LoopScheduleOps.td @@ -0,0 +1,206 @@ +//===- LoopScheduleOps.td - LoopSchedule Op Definitions ---*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// LoopSchedule Ops are defined in tablegen. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCIT_LOOP_SCHEDULE_OPS_TD +#define CIRCIT_LOOP_SCHEDULE_OPS_TD + +include "mlir/IR/OpBase.td" +include "mlir/IR/SymbolInterfaces.td" +include "mlir/IR/RegionKindInterface.td" +include "circt/Dialect/LoopSchedule/LoopSchedule.td" + +class LoopScheduleOp traits = []> : + Op; + +def LoopSchedulePipelineOp : LoopScheduleOp<"pipeline", []> { + let summary = "LoopSchedule dialect pipeline-loop."; + let description = [{ + The `loopschedule.pipeline` operation represents a statically scheduled + pipeline stucture that executes while a condition is true. For more details, + see: https://llvm.discourse.group/t/rfc-representing-pipelined-loops/4171. + + A pipeline captures the result of scheduling, and is not generally safe to + transform, besides lowering to hardware dialects. For more discussion about + relaxing this, see: https://github.com/llvm/circt/issues/2204. + + This is the top-level operation representing a high-level pipeline. It is + not isolated from above, but could be if this is helpful. A pipeline + contains two regions: `condition` and `stages`. + + The pipeline may accept an optional `iter_args`, similar to the SCF dialect, + for representing loop-carried values like induction variables or reductions. + When the pipeline starts execution, the registers indicated as `iter_args` + by `pipeline.terminator` should be initialized to the initial + values specified in the `iter_args` section here. The `iter_args` relate to + the initiation interval of the loop. The maximum distance in stages between + where an `iter_arg` is used and where that `iter_arg` is registered must be + less than the loop's initiation interval. For example, with II=1, each + `iter_arg` must be used and registered in the same stage. + + The single-block `condition` region dictates the condition under which the + pipeline should execute. It has a `register` terminator, and the + pipeline initiates new iterations while the registered value is `true : i1`. + It may access SSA values dominating the pipeline, as well as `iter_args`, + which are block arguments. The body of the block may only contain + "combinational" operations, which are currently defined to be simple + arithmetic, comparisons, and selects from the `Standard` dialect. + + The single-block `stages` region wraps `loopschedule.pipeline.stage` + operations. It has a `loopschedule.terminator` terminator, which can + both return results from the pipeline and register `iter_args`. Stages may + access SSA values dominating the pipeline, as well as `iter_args`, which are + block arguments. + }]; + + let arguments = (ins + I64Attr:$II, + OptionalAttr:$tripCount, + Variadic:$iterArgs + ); + + let results = (outs + Variadic:$results + ); + + let regions = (region + SizedRegion<1>:$condition, + SizedRegion<1>:$stages + ); + + let hasCustomAssemblyFormat = 1; + + let hasVerifier = 1; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "mlir::TypeRange":$resultTypes, "mlir::IntegerAttr":$II, + "std::optional": $tripCount, + "mlir::ValueRange":$iterArgs)> + ]; + + let extraClassDeclaration = [{ + Block &getCondBlock() { return getCondition().front(); } + Block &getStagesBlock() { return getStages().front(); } + }]; +} + +def LoopSchedulePipelineStageOp : LoopScheduleOp<"pipeline.stage", + [HasParent<"LoopSchedulePipelineOp">]> { + let summary = "LoopSchedule dialect pipeline stage."; + let description = [{ + This operation has a single-block region which dictates the operations that + may occur concurrently. + + It has a `start` attribute, which indicates the start cycle for this stage. + + It may have an optional `when` predicate, which supports conditional + execution for each stage. This is in addition to the `condition` region that + controls the execution of the whole pipeline. A stage with a `when` + predicate should only execute when the predicate is `true : i1`, and push a + bubble through the pipeline otherwise. + + It has a `register` terminator, which passes the concurrently + computed values forward to the next stage. + + Any stage may access `iter_args`. If a stage accesses an `iter_arg` after + the stage in which it is defined, it is up to lowering passes to preserve + this value until the last stage that needs it. + + Other than `iter_args`, stages may only access SSA values dominating the + pipeline or SSA values computed by any previous stage. This ensures the + stages capture the coarse-grained schedule of the pipeline and how values + feed forward and backward. + }]; + + let arguments = (ins + SI64Attr:$start, + Optional:$when + ); + + let results = (outs + Variadic:$results + ); + + let regions = (region + SizedRegion<1>:$body + ); + + let assemblyFormat = [{ + `start` `=` $start (`when` $when^)? $body (`:` qualified(type($results))^)? attr-dict + }]; + + let hasVerifier = 1; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "mlir::TypeRange":$resultTypes, "mlir::IntegerAttr":$start)> + ]; + + let extraClassDeclaration = [{ + Block &getBodyBlock() { return getBody().front(); } + unsigned getStageNumber(); + }]; +} + +def LoopScheduleRegisterOp : LoopScheduleOp<"register", + [ParentOneOf<["LoopSchedulePipelineOp", "LoopSchedulePipelineStageOp"]>, Terminator]> { + let summary = "LoopSchedule dialect loop register."; + let description = [{ + The `loopschedule.register` terminates a pipeline stage and + "registers" the values specified as operands. These values become the + results of the stage. + }]; + + let arguments = (ins + Variadic:$operands + ); + + let assemblyFormat = [{ + $operands (`:` qualified(type($operands))^)? attr-dict + }]; + + let hasVerifier = 1; +} + +def LoopScheduleTerminatorOp : LoopScheduleOp<"terminator", + [HasParent<"LoopSchedulePipelineOp">, Terminator, AttrSizedOperandSegments]> { + let summary = "LoopSchedule dialect terminator."; + let description = [{ + The `loopschedule.terminator` operation represents the terminator of + a `loopschedule.pipeline`. + + The `results` section accepts a variadic list of values which become the + pipeline’s return values. These must be results of a stage, and their types + must match the pipeline's return types. The results need not be defined in + the final stage, and it is up to lowering passes to preserve these values + until the final stage is complete. + + The `iter_args` section accepts a variadic list of values which become the + next iteration’s `iter_args`. These may be the results of any stage, and + their types must match the pipeline's `iter_args` types. + }]; + + let arguments = (ins + Variadic:$iter_args, + Variadic:$results + ); + + let assemblyFormat = [{ + `iter_args` `(` $iter_args `)` `,` + `results` `(` $results `)` `:` + functional-type($iter_args, $results) attr-dict + }]; + + let hasVerifier = 1; +} + +#endif // CIRCIT_LOOP_SCHEDULE_OPS_TD diff --git a/include/circt/Dialect/MSFT/DeviceDB.h b/include/circt/Dialect/MSFT/DeviceDB.h index e72fdc0459f6..686c2dd52d74 100644 --- a/include/circt/Dialect/MSFT/DeviceDB.h +++ b/include/circt/Dialect/MSFT/DeviceDB.h @@ -146,7 +146,7 @@ class PlacementDB { /// Load the placements from `inst`. Return the number of placements which /// weren't added due to conflicts. size_t addPlacements(DynamicInstanceOp inst); - LogicalResult insertPlacement(DynInstDataOpInterface owner, PhysLocationAttr); + LogicalResult insertPlacement(DynInstDataOpInterface op, PhysLocationAttr); /// Load the database from the IR. Return the number of placements which /// failed to load due to invalid specifications. diff --git a/include/circt/Dialect/MSFT/ExportTcl.h b/include/circt/Dialect/MSFT/ExportTcl.h index 700cbde5621c..c995f8be81ae 100644 --- a/include/circt/Dialect/MSFT/ExportTcl.h +++ b/include/circt/Dialect/MSFT/ExportTcl.h @@ -29,11 +29,11 @@ class MSFTModuleOp; class TclEmitter { public: TclEmitter(mlir::ModuleOp topLevel); - LogicalResult emit(Operation *forMod, StringRef outputFile); + LogicalResult emit(Operation *hwMod, StringRef outputFile); Operation *getDefinition(FlatSymbolRefAttr); - const DenseSet &getRefsUsed() { return refsUsed; } - void usedRef(hw::GlobalRefOp ref) { refsUsed.insert(ref); } + const DenseSet &getRefsUsed() { return refsUsed; } + void usedRef(hw::HierPathOp ref) { refsUsed.insert(ref); } private: mlir::ModuleOp topLevel; @@ -46,7 +46,7 @@ class TclEmitter { DenseMap>> tclOpsForModInstance; - DenseSet refsUsed; + DenseSet refsUsed; LogicalResult populate(); }; diff --git a/include/circt/Dialect/MSFT/MSFT.td b/include/circt/Dialect/MSFT/MSFT.td index 287472eb005b..50ee506f728c 100644 --- a/include/circt/Dialect/MSFT/MSFT.td +++ b/include/circt/Dialect/MSFT/MSFT.td @@ -17,7 +17,7 @@ include "mlir/IR/AttrTypeBase.td" include "mlir/IR/OpBase.td" include "mlir/IR/OpAsmInterface.td" include "mlir/IR/SymbolInterfaces.td" -include "mlir/IR/FunctionInterfaces.td" +include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/IR/RegionKindInterface.td" include "mlir/Interfaces/SideEffectInterfaces.td" include "mlir/Interfaces/ControlFlowInterfaces.td" @@ -50,7 +50,6 @@ class MSFTOp traits = []> : include "circt/Dialect/MSFT/MSFTAttributes.td" include "circt/Dialect/MSFT/MSFTOpInterfaces.td" -include "circt/Dialect/MSFT/MSFTOps.td" include "circt/Dialect/MSFT/MSFTPDOps.td" include "circt/Dialect/MSFT/MSFTConstructs.td" include "circt/Dialect/MSFT/MSFTPasses.td" diff --git a/include/circt/Dialect/MSFT/MSFTAttributes.td b/include/circt/Dialect/MSFT/MSFTAttributes.td index 8e43ea5422f7..64ea64fdd959 100644 --- a/include/circt/Dialect/MSFT/MSFTAttributes.td +++ b/include/circt/Dialect/MSFT/MSFTAttributes.td @@ -73,17 +73,3 @@ def LocationVector : MSFT_Attr<"LocationVector"> { let hasCustomAssemblyFormat = 1; let genVerifyDecl = 1; } - -def AppIDAttr : MSFT_Attr<"AppID"> { - let summary = "An application relevant instance identifier"; - let description = [{ - Identifies an instance which is visible through multiple hierarchy levels. - Indended to make locating an instance easier in the instance hierarchy. - }]; - - let parameters = (ins "StringAttr":$name, "uint64_t":$index); - let mnemonic = "appid"; - let assemblyFormat = [{ - `<` $name `[` $index `]` `>` - }]; -} diff --git a/include/circt/Dialect/MSFT/MSFTConstructs.td b/include/circt/Dialect/MSFT/MSFTConstructs.td index 57071682777d..d06b64113d0e 100644 --- a/include/circt/Dialect/MSFT/MSFTConstructs.td +++ b/include/circt/Dialect/MSFT/MSFTConstructs.td @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// include "circt/Dialect/HW/HWTypes.td" +include "circt/Dialect/Seq/SeqTypes.td" include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/IR/SymbolInterfaces.td" @@ -21,7 +22,7 @@ def MatrixType : DialectType ]> { let summary = "Model of a row/column broadcast systolic array"; // TODO: flesh out description once we've proved this op out. @@ -45,7 +46,7 @@ def PEOutputOp: MSFTOp<"pe.output", [Terminator]> { } def ChannelOp: MSFTOp<"constructs.channel", - [Pure, Symbol, HasParent<"MSFTModuleOp">, + [Pure, Symbol, AllTypesMatch<["input", "output"]>]> { let summary = "A pipeline-able connection"; @@ -59,7 +60,7 @@ def ChannelOp: MSFTOp<"constructs.channel", is always used. }]; - let arguments = (ins AnyType:$input, I1:$clk, StrAttr:$sym_name, + let arguments = (ins AnyType:$input, ClockType:$clk, StrAttr:$sym_name, UI64Attr:$defaultStages); let results = (outs AnyType:$output); @@ -90,7 +91,7 @@ def LinearOp : MSFTOp<"hlc.linear", [ ``` }]; - let arguments = (ins I1:$clock); + let arguments = (ins ClockType:$clock); let results = (outs Variadic:$outs); let regions = (region SizedRegion<1>:$datapath); @@ -101,3 +102,15 @@ def LinearOp : MSFTOp<"hlc.linear", [ let hasVerifier = 1; let assemblyFormat = [{ `clock` $clock attr-dict `:` type($outs) $datapath }]; } + +def OutputOp : MSFTOp<"output", [Terminator, + Pure, ReturnLike]> { + let summary = "termination operation"; + + let arguments = (ins Variadic:$operands); + let builders = [ + OpBuilder<(ins)> + ]; + + let assemblyFormat = "attr-dict ($operands^ `:` qualified(type($operands)))?"; +} diff --git a/include/circt/Dialect/MSFT/MSFTOpInterfaces.h b/include/circt/Dialect/MSFT/MSFTOpInterfaces.h index 15cedfeb524d..329027f6dd4f 100644 --- a/include/circt/Dialect/MSFT/MSFTOpInterfaces.h +++ b/include/circt/Dialect/MSFT/MSFTOpInterfaces.h @@ -14,7 +14,13 @@ namespace circt { namespace msft { -LogicalResult verifyDynInstData(Operation *); +LogicalResult verifyUnaryDynInstDataOp(Operation *); + +/// Returns the top-level module which the given HierPathOp that defines +/// pathSym, refers to. +Operation *getHierPathTopModule(Location loc, + circt::hw::HWSymbolCache &symCache, + FlatSymbolRefAttr pathSym); class InstanceHierarchyOp; } // namespace msft } // namespace circt diff --git a/include/circt/Dialect/MSFT/MSFTOpInterfaces.td b/include/circt/Dialect/MSFT/MSFTOpInterfaces.td index 13790f0c33b3..8175f99f0730 100644 --- a/include/circt/Dialect/MSFT/MSFTOpInterfaces.td +++ b/include/circt/Dialect/MSFT/MSFTOpInterfaces.td @@ -10,21 +10,40 @@ include "mlir/IR/OpBase.td" def DynInstDataOpInterface : OpInterface<"DynInstDataOpInterface"> { let description = [{ - Interface for anything which needs to refer to a GlobalRefOp. + Interface for ops that define dynamic instance data. + }]; + let cppNamespace = "::circt::msft"; + + let methods = [ + InterfaceMethod< + /*desc=*/[{ + Get the top module op to which this op is providing data for. + }], + /*retTy=*/"Operation *", + /*methodName=*/"getTopModule", + /*args=*/(ins "circt::hw::HWSymbolCache &":$symCache) + > + ]; +} + + def UnaryDynInstDataOpInterface : OpInterface<"UnaryDynInstDataOpInterface", + [DynInstDataOpInterface]> { + let description = [{ + Interface for anything which needs to refer to a HierPathOp. }]; let cppNamespace = "::circt::msft"; let verify = [{ - return ::circt::msft::verifyDynInstData($_op); + return ::circt::msft::verifyUnaryDynInstDataOp($_op); }]; let methods = [ InterfaceMethod< /*desc=*/[{ - Set the GlobalRefOp to which this op is referring. + Set the HierPathOp to which this op is referring. }], /*retTy=*/"void", - /*methodName=*/"setGlobalRef", - /*args=*/(ins "::circt::hw::GlobalRefOp":$ref), + /*methodName=*/"setPathOp", + /*args=*/(ins "::circt::hw::HierPathOp":$ref), /*methodBody=*/[{}], /*defaultImplementation=*/[{ $_op.setRefAttr(FlatSymbolRefAttr::get(ref)); @@ -32,42 +51,15 @@ def DynInstDataOpInterface : OpInterface<"DynInstDataOpInterface"> { >, InterfaceMethod< /*desc=*/[{ - Get the symbol of the GlobalRefOp to which this op is referring. + Get the symbol of the HierPathOp to which this op is referring. }], /*retTy=*/"FlatSymbolRefAttr", - /*methodName=*/"getGlobalRefSym", + /*methodName=*/"getPathSym", /*args=*/(ins), /*methodBody=*/[{}], /*defaultImplementation=*/[{ return $_op.getRefAttr(); }] - >, - InterfaceMethod< - /*desc=*/[{ - Get the top module op to which the GlobalRefOp which this op is referring. - }], - /*retTy=*/"Operation *", - /*methodName=*/"getTopModule", - /*args=*/(ins "circt::hw::HWSymbolCache &":$symCache), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ - FlatSymbolRefAttr refSym = $_op.getGlobalRefSym(); - if (!refSym) { - $_op->emitOpError("must run dynamic instance lowering first"); - return nullptr; - } - auto ref = dyn_cast_or_null( - symCache.getDefinition(refSym)); - if (!ref) { - $_op->emitOpError("could not find hw.globalRef ") << refSym; - return nullptr; - } - if (ref.getNamepath().empty()) - return nullptr; - auto modSym = FlatSymbolRefAttr::get( - ref.getNamepath()[0].cast().getModule()); - return symCache.getDefinition(modSym); - }] > ]; } diff --git a/include/circt/Dialect/MSFT/MSFTOps.h b/include/circt/Dialect/MSFT/MSFTOps.h index c4267c87cea2..527b9b29b254 100644 --- a/include/circt/Dialect/MSFT/MSFTOps.h +++ b/include/circt/Dialect/MSFT/MSFTOps.h @@ -17,6 +17,7 @@ #include "circt/Dialect/MSFT/MSFTAttributes.h" #include "circt/Dialect/MSFT/MSFTDialect.h" #include "circt/Dialect/MSFT/MSFTOpInterfaces.h" +#include "circt/Dialect/Seq/SeqTypes.h" #include "circt/Support/LLVM.h" #include "mlir/IR/BuiltinAttributes.h" diff --git a/include/circt/Dialect/MSFT/MSFTOps.td b/include/circt/Dialect/MSFT/MSFTOps.td deleted file mode 100644 index ccba8bc6062c..000000000000 --- a/include/circt/Dialect/MSFT/MSFTOps.td +++ /dev/null @@ -1,264 +0,0 @@ -//===- MSFTOps.td - MSFT operations definitions ------------*- tablegen -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -include "circt/Dialect/HW/HWOpInterfaces.td" - -def InstanceOp : MSFTOp<"instance", [ - Symbol, - DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods - ]> { - let summary = "Instantiate a module"; - - let arguments = (ins SymbolNameAttr:$sym_name, - FlatSymbolRefAttr:$moduleName, - Variadic:$inputs, - OptionalAttr:$parameters, - OptionalAttr:$targetDesignPartition); - let results = (outs Variadic); - - let builders = [ - OpBuilder<(ins "ArrayRef":$resultTypes, "StringAttr":$sym_name, - "FlatSymbolRefAttr":$moduleName, "ArrayRef":$inputs)> - ]; - - let extraClassDeclaration = [{ - // Return the name of the specified result or empty string if it cannot be - // determined. - StringAttr getResultName(size_t i); - - /// Instance name is the same as the symbol name. This may change in the - /// future. - StringRef instanceName() { - return getSymName(); - } - StringAttr instanceNameAttr() { - return getSymNameAttr(); - } - /// Check that the operands and results match the module specified. - LogicalResult verifySignatureMatch(const circt::hw::ModulePortInfo&); - - // Update the results. - InstanceOp getWithNewResults(MSFTModuleOp mod, - ArrayRef newToOldMap); - }]; - - /// sym keyword for optional symbol simplifies parsing - let assemblyFormat = [{ - $sym_name $moduleName `(` $inputs `)` custom($parameters) - attr-dict `:` functional-type($inputs, results) - }]; -} - -def OneOrNoBlocksRegion : Region< - CPred<"::llvm::hasNItemsOrLess($_self, 1)">, - "region with at most 1 block">; - -def AppIDArrayAttr : TypedArrayAttrBase; - -class MSFTModuleOpBase traits = []> : - MSFTOp, DeclareOpInterfaceMethods - ]> { - /// Additional class declarations inside the module op. - code extraModuleClassDeclaration = ?; - - let extraClassDeclaration = extraModuleClassDeclaration # [{ - // Decode information about the input and output ports on this module. - ::circt::hw::ModulePortInfo getPorts(); - - // Get the module's symbolic name as StringAttr. - StringAttr getNameAttr() { - return (*this)->getAttrOfType( - ::mlir::SymbolTable::getSymbolAttrName()); - } - - // Get the module's symbolic name. - StringRef getName() { - return getNameAttr().getValue(); - } - - /// Returns the argument types of this function. - ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } - - /// Returns the result types of this function. - ArrayRef getResultTypes() { return getFunctionType().getResults(); } - - /// Verify the type attribute of this function. Returns failure and emits - /// an error if the attribute is invalid. - LogicalResult verifyType() { - auto type = getFunctionTypeAttr().getValue(); - if (!type.isa()) - return emitOpError( - "requires '" + getFunctionTypeAttrName().getValue() + - "' attribute of function type"); - return success(); - } - - void getAsmBlockArgumentNames(Region ®ion, - OpAsmSetValueNameFn setNameFn) { - if (region.empty()) - return; - Block *block = getBodyBlock(); - ArrayAttr argNames = getArgNamesAttr(); - for (size_t i = 0, e = block->getNumArguments(); i != e; ++i) { - auto name = argNames[i].cast().getValue(); - if (!name.empty()) - setNameFn(block->getArgument(i), name); - } - } - }]; -} - -def MSFTModuleOp : MSFTModuleOpBase<"module", - [RegionKindInterface, HasParent<"mlir::ModuleOp">, - SingleBlockImplicitTerminator<"OutputOp">, - DeclareOpInterfaceMethods]>{ - let summary = "MSFT HW Module"; - let description = [{ - A lot like `hw.module`, but with a few differences: - - Can exist without a body. The body is filled in by a generator post op - creation. - - Provides methods for mutation. - }]; - let arguments = (ins - TypeAttrOf:$function_type, - OptionalAttr:$arg_attrs, - OptionalAttr:$res_attrs, - StrArrayAttr:$argNames, - StrArrayAttr:$resultNames, - LocationArrayAttr:$argLocs, - LocationArrayAttr:$resultLocs, - DictionaryAttr:$parameters, - OptionalAttr:$fileName, - OptionalAttr:$childAppIDBases); - let results = (outs); - let regions = (region OneOrNoBlocksRegion:$body); - - let skipDefaultBuilders = 1; - let builders = [ - OpBuilder<(ins "StringAttr":$name, "hw::ModulePortInfo":$ports, - "ArrayRef":$params)> - ]; - - let extraModuleClassDeclaration = [{ - using mlir::detail::FunctionOpInterfaceTrait::front; - using mlir::detail::FunctionOpInterfaceTrait::getFunctionBody; - - // Implement RegionKindInterface. - static RegionKind getRegionKind(unsigned index) { - return RegionKind::Graph; - } - - // Insert and remove ports. - void modifyPorts( - ArrayRef> insertInputs, - ArrayRef> insertOutputs, - ArrayRef eraseInputs, ArrayRef eraseOutputs); - - // Adds input and output ports. Returns a list of new block arguments for - // the new inputs. - SmallVector addPorts( - ArrayRef> inputs, - ArrayRef> outputs); - - // Remove the ports at the specified indexes. Returns the new to old result - // mapping. - SmallVector - removePorts(llvm::BitVector inputs, llvm::BitVector outputs); - - Block *getBodyBlock() { return &getFunctionBody().front(); } - }]; - - let extraClassDefinition = [{ - size_t $cppClass::getNumPorts() { - auto ports = getPorts(); - return ports.inputs.size() + ports.outputs.size(); - } - - circt::hw::InnerSymAttr $cppClass::getPortSymbolAttr(size_t portIndex) { - for (NamedAttribute argAttr : - mlir::function_interface_impl::getArgAttrs(*this, portIndex)) - if (auto sym = argAttr.getValue().dyn_cast()) - return sym; - return circt::hw::InnerSymAttr(); - } - }]; - - let hasCustomAssemblyFormat = 1; - let hasVerifier = 1; -} - -def MSFTModuleExternOp : MSFTOp<"module.extern", - [Symbol, FunctionOpInterface, HasParent<"mlir::ModuleOp">]> { - let summary = "MSFT external Module"; - let description = [{ - Identical to `hw.module.extern`, and trivially lowers to that. This op - exists so that we can use `msft.instance` to refer to both `msft.module` and - `msft.module.extern`, rather than mixing `hw.instance` with `msft.instance`. - }]; - let arguments = (ins TypeAttrOf:$function_type, - OptionalAttr:$arg_attrs, - OptionalAttr:$res_attrs, - StrArrayAttr:$argNames, StrArrayAttr:$resultNames, - ParamDeclArrayAttr:$parameters, - OptionalAttr:$verilogName); - let regions = (region SizedRegion<0>:$body); - - let skipDefaultBuilders = 1; - let builders = [ - OpBuilder<(ins "StringAttr":$name, "const hw::ModulePortInfo &":$ports, - CArg<"StringRef", "StringRef()">:$verilogName, - CArg<"ArrayAttr", "{}">:$parameters, - CArg<"ArrayRef", "{}">:$attributes)> - ]; - - let hasCustomAssemblyFormat = 1; - let hasVerifier = 1; - - let extraClassDeclaration = [{ - /// Decode information about the input and output ports on this module. - hw::ModulePortInfo getPorts(); - - /// Returns the argument types of this function. - ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } - - /// Returns the result types of this function. - ArrayRef getResultTypes() { return getFunctionType().getResults(); } - }]; -} - -def DesignPartitionOp : MSFTOp<"partition", - [Symbol, HasParent<"msft::MSFTModuleOp">]> { - let summary = "A target \"module\" for moving entities"; - let description = [{ - Sometimes EDA tools require designs to have a module hierarchy which doesn't - match the logical structure a designer would like to have. "Design - partitions" allow the designer to "tag" entities (instances, registers, etc.) - with a target design partition. During lowering, CIRCT will modify the - hierarchy to move the tagged entities into the design partition module. The - target design partition can then be used by subsequent EDA tools. - }]; - - let arguments = (ins SymbolNameAttr:$sym_name, StrAttr:$verilogName); - let assemblyFormat = "$sym_name `,` $verilogName attr-dict"; -} - -def OutputOp : MSFTOp<"output", [Terminator, ParentOneOf<["MSFTModuleOp", "LinearOp"]>, - Pure, ReturnLike]> { - let summary = "termination operation"; - - let arguments = (ins Variadic:$operands); - let builders = [ - OpBuilder<(ins)> - ]; - - let assemblyFormat = "attr-dict ($operands^ `:` qualified(type($operands)))?"; -} diff --git a/include/circt/Dialect/MSFT/MSFTPDOps.td b/include/circt/Dialect/MSFT/MSFTPDOps.td index 8ae28af1a5c3..51e259e806b2 100644 --- a/include/circt/Dialect/MSFT/MSFTPDOps.td +++ b/include/circt/Dialect/MSFT/MSFTPDOps.td @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +include "circt/Dialect/HW/HWAttributesNaming.td" include "circt/Dialect/HW/HWTypes.td" def DeclPhysicalRegionOp : MSFTOp<"physical_region", @@ -20,25 +21,15 @@ def DeclPhysicalRegionOp : MSFTOp<"physical_region", }]; } -def EntityExternOp : MSFTOp<"entity.extern", - [Symbol, HasParent<"mlir::ModuleOp">]> { - let arguments = (ins - SymbolNameAttr:$sym_name, - AnyAttr:$metadata); - let assemblyFormat = [{ - $sym_name $metadata attr-dict - }]; -} - def PDPhysLocationOp : MSFTOp<"pd.location", - [DeclareOpInterfaceMethods]> { + [DeclareOpInterfaceMethods]> { let summary = "Specify a location for an instance"; let description = [{ Used to specify a specific location on an FPGA to place a dynamic instance. Supports specifying the location of a subpath for extern modules and device primitives. Intended to live as a child of `instance.dynamic` initially without the `ref` field. The dynamic instance lowering will fill in `ref` - with the symol of the `hw.globalref` op corresponding to the lowered dynamic + with the symol of the `hw.hierpath` op corresponding to the lowered dynamic instance. }]; let arguments = (ins PhysLocation:$loc, @@ -47,10 +38,16 @@ def PDPhysLocationOp : MSFTOp<"pd.location", let assemblyFormat = [{ ($ref^)? custom($loc) (`path` `:` $subPath^)? attr-dict }]; + + let extraClassDeclaration = [{ + Operation* getTopModule(circt::hw::HWSymbolCache &symCache) { + return getHierPathTopModule(getOperation()->getLoc(), symCache, getPathSym()); + } + }]; } def PDRegPhysLocationOp : MSFTOp<"pd.reg_location", - [DeclareOpInterfaceMethods]> { + [DeclareOpInterfaceMethods]> { let summary = "Specify register locations"; let description = [{ A version of "PDPhysLocationOp" specialized for registers, which have one @@ -61,10 +58,34 @@ def PDRegPhysLocationOp : MSFTOp<"pd.reg_location", let assemblyFormat = [{ (`ref` $ref^)? custom($locs) attr-dict }]; + + let extraClassDeclaration = [{ + Operation* getTopModule(circt::hw::HWSymbolCache &symCache) { + return getHierPathTopModule(getOperation()->getLoc(), symCache, getPathSym()); + } + }]; +} + +def PDMulticycleOp : MSFTOp<"pd.multicycle", + [DeclareOpInterfaceMethods]> { + let summary = "Specify a multicycle constraint"; + let description = [{ + Specifies a multicycle constraint in between two registers. + `source` and `dest` symbols reference `HierPathOp` symbols denoting the + exact registers in the instance hierarchy to which the constraint applies. + }]; + let arguments = (ins + FlatSymbolRefAttr:$source, + FlatSymbolRefAttr:$dest, + ConfinedAttr]>:$cycles + ); + let assemblyFormat = [{ + $cycles $source `->` $dest attr-dict + }]; } def PDPhysRegionOp : MSFTOp<"pd.physregion", - [DeclareOpInterfaceMethods]> { + [DeclareOpInterfaceMethods]> { let summary = "Specify a physical region for an instance"; let description = [{ Annotate a particular entity within an op with the region of the devices @@ -77,6 +98,12 @@ def PDPhysRegionOp : MSFTOp<"pd.physregion", let assemblyFormat = [{ ($ref^)? $physRegionRef (`path` `:` $subPath^)? attr-dict }]; + + let extraClassDeclaration = [{ + Operation* getTopModule(circt::hw::HWSymbolCache &symCache) { + return getHierPathTopModule(getOperation()->getLoc(), symCache, getPathSym()); + } + }]; } def InstanceHierarchyOp : MSFTOp<"instance.hierarchy", @@ -109,17 +136,17 @@ def DynamicInstanceOp : MSFTOp<"instance.dynamic", Represents an instance (as in instance in the instance hierarchy) referred to henceforth as a dynamic instance. Specified with a path through the instance hierarchy (which in the future will be replaced with an AppID). - Lowers to a `hw.globalref` but unlike a global ref, does not require all of - the ops participating in the globalref to contain a back pointer attribute. + Lowers to a `hw.hierpath` but unlike a global ref, does not require all of + the ops participating in the hierpath to contain a back pointer attribute. Allows users to efficiently add placements to a large number of dynamic instances which happen to map to a small number of static instances by - bulk-adding the necessary `hw.globalref` attributes. + bulk-adding the necessary `hw.hierpath` attributes. During the lowering, moves the operations in the body to the top level and - gives them the symbol of the globalref which was created to replace the + gives them the symbol of the hierpath which was created to replace the dynamic instance. }]; - let arguments = (ins HWInnerRefAttr:$instanceRef); + let arguments = (ins InnerRefAttr:$instanceRef); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ @@ -127,12 +154,13 @@ def DynamicInstanceOp : MSFTOp<"instance.dynamic", }]; let extraClassDeclaration = [{ - ::mlir::ArrayAttr globalRefPath(); + ::mlir::ArrayAttr getPath(); }]; } def DynamicInstanceVerbatimAttrOp : MSFTOp< - "instance.verb_attr", [DeclareOpInterfaceMethods]> { + "instance.verb_attr", [ + DeclareOpInterfaceMethods]> { let summary = "Specify an arbitrary attribute attached to a dynamic instance"; let description = [{ Allows a user to specify a custom attribute name and value which is attached @@ -148,4 +176,10 @@ def DynamicInstanceVerbatimAttrOp : MSFTOp< let assemblyFormat = [{ ($ref^)? `name` `:` $name `value` `:` $value (`path` `:` $subPath^)? attr-dict }]; + + let extraClassDeclaration = [{ + Operation* getTopModule(circt::hw::HWSymbolCache &symCache) { + return getHierPathTopModule(getOperation()->getLoc(), symCache, getPathSym()); + } + }]; } diff --git a/include/circt/Dialect/MSFT/MSFTPasses.h b/include/circt/Dialect/MSFT/MSFTPasses.h index bcdceda9fa6e..77e5aa017641 100644 --- a/include/circt/Dialect/MSFT/MSFTPasses.h +++ b/include/circt/Dialect/MSFT/MSFTPasses.h @@ -12,6 +12,7 @@ #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/MSFT/MSFTOps.h" +#include "mlir/Pass/Pass.h" #include "mlir/Pass/PassRegistry.h" namespace mlir { @@ -21,23 +22,17 @@ class Pass; namespace circt { namespace msft { -std::unique_ptr createWireCleanupPass(); -std::unique_ptr createPartitionPass(); -std::unique_ptr createLowerToHWPass(); std::unique_ptr createLowerInstancesPass(); std::unique_ptr createLowerConstructsPass(); std::unique_ptr createExportTclPass(); -std::unique_ptr createDiscoverAppIDsPass(); /// A set of methods which are broadly useful in a number of dialects. struct PassCommon { protected: SymbolCache topLevelSyms; - DenseMap> + DenseMap> moduleInstantiations; - LogicalResult verifyInstances(ModuleOp topMod); - // Find all the modules and use the partial order of the instantiation DAG // to sort them. If we use this order when "bubbling" up operations, we // guarantee one-pass completeness. As a side-effect, populate the module to diff --git a/include/circt/Dialect/MSFT/MSFTPasses.td b/include/circt/Dialect/MSFT/MSFTPasses.td index 77822a558830..54759ed9a387 100644 --- a/include/circt/Dialect/MSFT/MSFTPasses.td +++ b/include/circt/Dialect/MSFT/MSFTPasses.td @@ -8,16 +8,6 @@ include "mlir/Pass/PassBase.td" -def LowerToHW: Pass<"lower-msft-to-hw", "mlir::ModuleOp"> { - let summary = "Lower MSFT ops to hw ops"; - let constructor = "circt::msft::createLowerToHWPass()"; - let dependentDialects = ["circt::sv::SVDialect", "circt::hw::HWDialect"]; - let options = [ - Option<"verilogFile", "verilog-file", "std::string", - "", "File to output Verilog into">, - ]; -} - def ExportTcl: Pass<"msft-export-tcl", "mlir::ModuleOp"> { let summary = "Create tcl ops"; let constructor = "circt::msft::createExportTclPass()"; @@ -31,18 +21,6 @@ def ExportTcl: Pass<"msft-export-tcl", "mlir::ModuleOp"> { ]; } -def Partition: Pass<"msft-partition", "mlir::ModuleOp"> { - let summary = "Move the entities targeted for a design partition"; - let constructor = "circt::msft::createPartitionPass()"; - let dependentDialects = ["circt::hw::HWDialect"]; -} - -def WireCleanup: Pass<"msft-wire-cleanup", "mlir::ModuleOp"> { - let summary = "Cleanup unnecessary ports and wires"; - let constructor = "circt::msft::createWireCleanupPass()"; - let dependentDialects = []; -} - def LowerInstances: Pass<"msft-lower-instances", "mlir::ModuleOp"> { let summary = "Lower dynamic instances"; let constructor = "circt::msft::createLowerInstancesPass()"; @@ -54,8 +32,3 @@ def LowerConstructs: Pass<"msft-lower-constructs", "mlir::ModuleOp"> { let constructor = "circt::msft::createLowerConstructsPass()"; let dependentDialects = ["circt::hw::HWDialect"]; } - -def DiscoverAppIDs: Pass<"msft-discover-appids", "mlir::ModuleOp"> { - let summary = "Discover the appids in a module hierarchy"; - let constructor = "circt::msft::createDiscoverAppIDsPass()"; -} diff --git a/include/circt/Dialect/Moore/MooreDialect.td b/include/circt/Dialect/Moore/MooreDialect.td index 2139a7c111dd..236d547fd3e0 100644 --- a/include/circt/Dialect/Moore/MooreDialect.td +++ b/include/circt/Dialect/Moore/MooreDialect.td @@ -32,6 +32,9 @@ def MooreDialect : Dialect { void printType(Type, DialectAsmPrinter &) const override; }]; let useDefaultTypePrinterParser = 0; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; } #endif // CIRCT_DIALECT_MOORE_MOOREDIALECT diff --git a/include/circt/Dialect/OM/CMakeLists.txt b/include/circt/Dialect/OM/CMakeLists.txt index 8bfadc84bdf2..ce88bbf873a7 100644 --- a/include/circt/Dialect/OM/CMakeLists.txt +++ b/include/circt/Dialect/OM/CMakeLists.txt @@ -8,3 +8,19 @@ add_circt_dialect(OM om) add_circt_dialect_doc(OM om) + +set(LLVM_TARGET_DEFINITIONS OMEnums.td) +mlir_tablegen(OMEnums.h.inc -gen-enum-decls) +mlir_tablegen(OMEnums.cpp.inc -gen-enum-defs) + +set(LLVM_TARGET_DEFINITIONS OM.td) +mlir_tablegen(OMAttributes.h.inc -gen-attrdef-decls) +mlir_tablegen(OMAttributes.cpp.inc -gen-attrdef-defs) +add_public_tablegen_target(MLIROMAttrIncGen) +add_dependencies(circt-headers MLIROMAttrIncGen) +add_circt_interface(OMOpInterfaces) + +set(LLVM_TARGET_DEFINITIONS OMPasses.td) +mlir_tablegen(OMPasses.h.inc -gen-pass-decls) +add_public_tablegen_target(CIRCTOMTransformsIncGen) +add_circt_doc(OMPasses OMPasses -gen-pass-doc) diff --git a/include/circt/Dialect/OM/Evaluator/Evaluator.h b/include/circt/Dialect/OM/Evaluator/Evaluator.h new file mode 100644 index 000000000000..303bca0971b1 --- /dev/null +++ b/include/circt/Dialect/OM/Evaluator/Evaluator.h @@ -0,0 +1,537 @@ +//===- Evaluator.h - Object Model dialect evaluator -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file contains the Object Model dialect declaration. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_EVALUATOR_EVALUATOR_H +#define CIRCT_DIALECT_OM_EVALUATOR_EVALUATOR_H + +#include "circt/Dialect/OM/OMOps.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/SymbolTable.h" +#include "mlir/Support/LogicalResult.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallString.h" + +#include +#include + +namespace circt { +namespace om { + +namespace evaluator { +struct EvaluatorValue; + +/// A value of an object in memory. It is either a composite Object, or a +/// primitive Attribute. Further refinement is expected. +using EvaluatorValuePtr = std::shared_ptr; + +/// The fields of a composite Object, currently represented as a map. Further +/// refinement is expected. +using ObjectFields = SmallDenseMap; + +/// Base class for evaluator runtime values. +/// Enables the shared_from_this functionality so Evaluator Value pointers can +/// be passed through the CAPI and unwrapped back into C++ smart pointers with +/// the appropriate reference count. +struct EvaluatorValue : std::enable_shared_from_this { + // Implement LLVM RTTI. + enum class Kind { Attr, Object, List, Tuple, Map, Reference, BasePath, Path }; + EvaluatorValue(MLIRContext *ctx, Kind kind, Location loc) + : kind(kind), ctx(ctx), loc(loc) {} + Kind getKind() const { return kind; } + MLIRContext *getContext() const { return ctx; } + + // Return true the value is fully evaluated. + bool isFullyEvaluated() const { return fullyEvaluated; } + void markFullyEvaluated() { + assert(!fullyEvaluated && "should not mark twice"); + fullyEvaluated = true; + } + + /// Return the associated MLIR context. + MLIRContext *getContext() { return ctx; } + + // Return a MLIR type which the value represents. + Type getType() const; + + // Finalize the evaluator value. Strip intermidiate reference values. + LogicalResult finalize(); + + // Return the Location associated with the Value. + Location getLoc() const { return loc; } + // Set the Location associated with the Value. + void setLoc(Location l) { loc = l; } + // Set the Location, if it is unknown. + void setLocIfUnknown(Location l) { + if (isa(loc)) + loc = l; + } + +private: + const Kind kind; + MLIRContext *ctx; + Location loc; + bool fullyEvaluated = false; + bool finalized = false; +}; + +/// Values which can be used as pointers to different values. +/// ReferenceValue is replaced with its element and erased at the end of +/// evaluation. +struct ReferenceValue : EvaluatorValue { + ReferenceValue(Type type, Location loc) + : EvaluatorValue(type.getContext(), Kind::Reference, loc), value(nullptr), + type(type) {} + + // Implement LLVM RTTI. + static bool classof(const EvaluatorValue *e) { + return e->getKind() == Kind::Reference; + } + + Type getValueType() const { return type; } + EvaluatorValuePtr getValue() const { return value; } + void setValue(EvaluatorValuePtr newValue) { + value = std::move(newValue); + markFullyEvaluated(); + } + + // Finalize the value. + LogicalResult finalizeImpl(); + + // Return the first non-reference value that is reachable from the reference. + FailureOr getStrippedValue() const { + llvm::SmallPtrSet visited; + auto currentValue = value; + while (auto *v = dyn_cast(currentValue.get())) { + // Detect a cycle. + if (!visited.insert(v).second) + return failure(); + currentValue = v->getValue(); + } + return success(currentValue); + } + +private: + EvaluatorValuePtr value; + Type type; +}; + +/// Values which can be directly representable by MLIR attributes. +struct AttributeValue : EvaluatorValue { + AttributeValue(Attribute attr) + : AttributeValue(attr, mlir::UnknownLoc::get(attr.getContext())) {} + AttributeValue(Attribute attr, Location loc) + : EvaluatorValue(attr.getContext(), Kind::Attr, loc), attr(attr) { + markFullyEvaluated(); + } + Attribute getAttr() const { return attr; } + template + AttrTy getAs() const { + return dyn_cast(attr); + } + static bool classof(const EvaluatorValue *e) { + return e->getKind() == Kind::Attr; + } + + // Finalize the value. + LogicalResult finalizeImpl() { return success(); } + + Type getType() const { return attr.cast().getType(); } + +private: + Attribute attr = {}; +}; + +// This perform finalization to `value`. +static inline LogicalResult finalizeEvaluatorValue(EvaluatorValuePtr &value) { + if (failed(value->finalize())) + return failure(); + if (auto *ref = llvm::dyn_cast(value.get())) { + auto v = ref->getStrippedValue(); + if (failed(v)) + return v; + value = v.value(); + } + return success(); +} + +/// A List which contains variadic length of elements with the same type. +struct ListValue : EvaluatorValue { + ListValue(om::ListType type, SmallVector elements, + Location loc) + : EvaluatorValue(type.getContext(), Kind::List, loc), type(type), + elements(std::move(elements)) { + markFullyEvaluated(); + } + + void setElements(SmallVector newElements) { + elements = std::move(newElements); + markFullyEvaluated(); + } + + // Finalize the value. + LogicalResult finalizeImpl(); + + // Partially evaluated value. + ListValue(om::ListType type, Location loc) + : EvaluatorValue(type.getContext(), Kind::List, loc), type(type) {} + + const auto &getElements() const { return elements; } + + /// Return the type of the value, which is a ListType. + om::ListType getListType() const { return type; } + + /// Implement LLVM RTTI. + static bool classof(const EvaluatorValue *e) { + return e->getKind() == Kind::List; + } + +private: + om::ListType type; + SmallVector elements; +}; + +/// A Map value. +struct MapValue : EvaluatorValue { + MapValue(om::MapType type, DenseMap elements, + Location loc) + : EvaluatorValue(type.getContext(), Kind::Map, loc), type(type), + elements(std::move(elements)) { + markFullyEvaluated(); + } + + // Partially evaluated value. + MapValue(om::MapType type, Location loc) + : EvaluatorValue(type.getContext(), Kind::Map, loc), type(type) {} + + const auto &getElements() const { return elements; } + void setElements(DenseMap newElements) { + elements = std::move(newElements); + markFullyEvaluated(); + } + + // Finalize the evaluator value. + LogicalResult finalizeImpl(); + + /// Return the type of the value, which is a MapType. + om::MapType getMapType() const { return type; } + + /// Return an array of keys in the ascending order. + ArrayAttr getKeys(); + + /// Implement LLVM RTTI. + static bool classof(const EvaluatorValue *e) { + return e->getKind() == Kind::Map; + } + +private: + om::MapType type; + DenseMap elements; +}; + +/// A composite Object, which has a type and fields. +struct ObjectValue : EvaluatorValue { + ObjectValue(om::ClassOp cls, ObjectFields fields, Location loc) + : EvaluatorValue(cls.getContext(), Kind::Object, loc), cls(cls), + fields(std::move(fields)) { + markFullyEvaluated(); + } + + // Partially evaluated value. + ObjectValue(om::ClassOp cls, Location loc) + : EvaluatorValue(cls.getContext(), Kind::Object, loc), cls(cls) {} + + om::ClassOp getClassOp() const { return cls; } + const auto &getFields() const { return fields; } + + void setFields(llvm::SmallDenseMap newFields) { + fields = std::move(newFields); + markFullyEvaluated(); + } + + /// Return the type of the value, which is a ClassType. + om::ClassType getObjectType() const { + auto clsConst = const_cast(cls); + return ClassType::get(clsConst.getContext(), + FlatSymbolRefAttr::get(clsConst.getNameAttr())); + } + + Type getType() const { return getObjectType(); } + + /// Implement LLVM RTTI. + static bool classof(const EvaluatorValue *e) { + return e->getKind() == Kind::Object; + } + + /// Get a field of the Object by name. + FailureOr getField(StringAttr field); + FailureOr getField(StringRef field) { + return getField(StringAttr::get(getContext(), field)); + } + + /// Get all the field names of the Object. + ArrayAttr getFieldNames(); + + // Finalize the evaluator value. + LogicalResult finalizeImpl(); + +private: + om::ClassOp cls; + llvm::SmallDenseMap fields; +}; + +/// Tuple values. +struct TupleValue : EvaluatorValue { + using TupleElements = llvm::SmallVector; + TupleValue(TupleType type, TupleElements tupleElements, Location loc) + : EvaluatorValue(type.getContext(), Kind::Tuple, loc), type(type), + elements(std::move(tupleElements)) { + markFullyEvaluated(); + } + + // Partially evaluated value. + TupleValue(TupleType type, Location loc) + : EvaluatorValue(type.getContext(), Kind::Tuple, loc), type(type) {} + + void setElements(TupleElements newElements) { + elements = std::move(newElements); + markFullyEvaluated(); + } + + LogicalResult finalizeImpl() { + for (auto &&value : elements) + if (failed(finalizeEvaluatorValue(value))) + return failure(); + + return success(); + } + /// Implement LLVM RTTI. + static bool classof(const EvaluatorValue *e) { + return e->getKind() == Kind::Tuple; + } + + /// Return the type of the value, which is a TupleType. + TupleType getTupleType() const { return type; } + + const TupleElements &getElements() const { return elements; } + +private: + TupleType type; + TupleElements elements; +}; + +/// A Basepath value. +struct BasePathValue : EvaluatorValue { + BasePathValue(MLIRContext *context); + + /// Create a path value representing a basepath. + BasePathValue(om::PathAttr path, Location loc); + + om::PathAttr getPath() const; + + /// Set the basepath which this path is relative to. + void setBasepath(const BasePathValue &basepath); + + /// Finalize the evaluator value. + LogicalResult finalizeImpl() { return success(); } + + /// Implement LLVM RTTI. + static bool classof(const EvaluatorValue *e) { + return e->getKind() == Kind::BasePath; + } + +private: + om::PathAttr path; +}; + +/// A Path value. +struct PathValue : EvaluatorValue { + /// Create a path value representing a regular path. + PathValue(om::TargetKindAttr targetKind, om::PathAttr path, StringAttr module, + StringAttr ref, StringAttr field, Location loc); + + static PathValue getEmptyPath(Location loc); + + om::TargetKindAttr getTargetKind() const { return targetKind; } + + om::PathAttr getPath() const { return path; } + + StringAttr getModule() const { return module; } + + StringAttr getRef() const { return ref; } + + StringAttr getField() const { return field; } + + StringAttr getAsString() const; + + void setBasepath(const BasePathValue &basepath); + + // Finalize the evaluator value. + LogicalResult finalizeImpl() { return success(); } + + /// Implement LLVM RTTI. + static bool classof(const EvaluatorValue *e) { + return e->getKind() == Kind::Path; + } + +private: + om::TargetKindAttr targetKind; + om::PathAttr path; + StringAttr module; + StringAttr ref; + StringAttr field; +}; + +} // namespace evaluator + +using Object = evaluator::ObjectValue; +using EvaluatorValuePtr = evaluator::EvaluatorValuePtr; + +SmallVector +getEvaluatorValuesFromAttributes(MLIRContext *context, + ArrayRef attributes); + +/// An Evaluator, which is constructed with an IR module and can instantiate +/// Objects. Further refinement is expected. +struct Evaluator { + /// Construct an Evaluator with an IR module. + Evaluator(ModuleOp mod); + + /// Instantiate an Object with its class name and actual parameters. + FailureOr + instantiate(StringAttr className, ArrayRef actualParams); + + /// Get the Module this Evaluator is built from. + mlir::ModuleOp getModule(); + + FailureOr + getPartiallyEvaluatedValue(Type type, Location loc); + + using ActualParameters = + SmallVectorImpl> *; + + using ObjectKey = std::pair; + +private: + bool isFullyEvaluated(Value value, ActualParameters key) { + return isFullyEvaluated({value, key}); + } + + bool isFullyEvaluated(ObjectKey key) { + auto val = objects.lookup(key); + return val && val->isFullyEvaluated(); + } + + FailureOr + getOrCreateValue(Value value, ActualParameters actualParams, Location loc); + FailureOr + allocateObjectInstance(StringAttr clasName, ActualParameters actualParams); + + /// Evaluate a Value in a Class body according to the small expression grammar + /// described in the rationale document. The actual parameters are the values + /// supplied at the current instantiation of the Class being evaluated. + FailureOr + evaluateValue(Value value, ActualParameters actualParams, Location loc); + + /// Evaluator dispatch functions for the small expression grammar. + FailureOr evaluateParameter(BlockArgument formalParam, + ActualParameters actualParams, + Location loc); + + FailureOr + evaluateConstant(ConstantOp op, ActualParameters actualParams, Location loc); + /// Instantiate an Object with its class name and actual parameters. + FailureOr + evaluateObjectInstance(StringAttr className, ActualParameters actualParams, + Location loc, ObjectKey instanceKey = {}); + FailureOr + evaluateObjectInstance(ObjectOp op, ActualParameters actualParams); + FailureOr + evaluateObjectField(ObjectFieldOp op, ActualParameters actualParams, + Location loc); + FailureOr evaluateListCreate(ListCreateOp op, + ActualParameters actualParams, + Location loc); + FailureOr + evaluateTupleCreate(TupleCreateOp op, ActualParameters actualParams, + Location loc); + FailureOr + evaluateTupleGet(TupleGetOp op, ActualParameters actualParams, Location loc); + FailureOr + evaluateMapCreate(MapCreateOp op, ActualParameters actualParams, + Location loc); + FailureOr + evaluateBasePathCreate(FrozenBasePathCreateOp op, + ActualParameters actualParams, Location loc); + FailureOr + evaluatePathCreate(FrozenPathCreateOp op, ActualParameters actualParams, + Location loc); + FailureOr + evaluateEmptyPath(FrozenEmptyPathOp op, ActualParameters actualParams, + Location loc); + + FailureOr + createParametersFromOperands(ValueRange range, ActualParameters actualParams, + Location loc); + + /// The symbol table for the IR module the Evaluator was constructed with. + /// Used to look up class definitions. + SymbolTable symbolTable; + + /// This uniquely stores vectors that represent parameters. + SmallVector< + std::unique_ptr>>> + actualParametersBuffers; + + /// A worklist that tracks values which needs to be fully evaluated. + std::queue worklist; + + /// Evaluator value storage. Return an evaluator value for the given + /// instantiation context (a pair of Value and parameters). + DenseMap> objects; +}; + +/// Helper to enable printing objects in Diagnostics. +static inline mlir::Diagnostic & +operator<<(mlir::Diagnostic &diag, + const evaluator::EvaluatorValue &evaluatorValue) { + if (auto *attr = llvm::dyn_cast(&evaluatorValue)) + diag << attr->getAttr(); + else if (auto *object = + llvm::dyn_cast(&evaluatorValue)) + diag << "Object(" << object->getType() << ")"; + else if (auto *list = llvm::dyn_cast(&evaluatorValue)) + diag << "List(" << list->getType() << ")"; + else if (auto *map = llvm::dyn_cast(&evaluatorValue)) + diag << "Map(" << map->getType() << ")"; + else if (llvm::isa(&evaluatorValue)) + diag << "BasePath()"; + else if (llvm::isa(&evaluatorValue)) + diag << "Path()"; + else + assert(false && "unhandled evaluator value"); + return diag; +} + +/// Helper to enable printing objects in Diagnostics. +static inline mlir::Diagnostic & +operator<<(mlir::Diagnostic &diag, const EvaluatorValuePtr &evaluatorValue) { + return diag << *evaluatorValue.get(); +} + +} // namespace om +} // namespace circt + +#endif // CIRCT_DIALECT_OM_EVALUATOR_EVALUATOR_H diff --git a/include/circt/Dialect/OM/OM.td b/include/circt/Dialect/OM/OM.td index c2743f5361ac..d6458ed6d51e 100644 --- a/include/circt/Dialect/OM/OM.td +++ b/include/circt/Dialect/OM/OM.td @@ -17,6 +17,10 @@ include "mlir/IR/OpBase.td" include "circt/Dialect/OM/OMDialect.td" +include "circt/Dialect/OM/OMAttributes.td" + +include "circt/Dialect/OM/OMTypes.td" + include "circt/Dialect/OM/OMOps.td" #endif // CIRCT_DIALECT_OM_OM_TD diff --git a/include/circt/Dialect/OM/OMAttributes.h b/include/circt/Dialect/OM/OMAttributes.h new file mode 100644 index 000000000000..916dc27bbf47 --- /dev/null +++ b/include/circt/Dialect/OM/OMAttributes.h @@ -0,0 +1,46 @@ +//===- OMAttributes.h - Object Model attribute declarations ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file contains the Object Model attribute declarations. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMATTRIBUTES_H +#define CIRCT_DIALECT_OM_OMATTRIBUTES_H + +#include "circt/Dialect/OM/OMDialect.h" +#include "mlir/IR/BuiltinAttributes.h" + +namespace circt::om { + +/// A module name, and the name of an instance inside that module. +struct PathElement { + PathElement(mlir::StringAttr module, mlir::StringAttr instance) + : module(module), instance(instance) {} + + bool operator==(const PathElement &rhs) const { + return module == rhs.module && instance == rhs.instance; + } + + // NOLINTNEXTLINE(readability-identifier-naming) + friend llvm::hash_code hash_value(const PathElement &arg) { + return ::llvm::hash_combine(arg.module, arg.instance); + } + + mlir::StringAttr module; + mlir::StringAttr instance; +}; +} // namespace circt::om + +#include "circt/Dialect/HW/HWAttributes.h" +#include "mlir/IR/Attributes.h" + +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/OM/OMAttributes.h.inc" + +#endif // CIRCT_DIALECT_OM_OMATTRIBUTES_H diff --git a/include/circt/Dialect/OM/OMAttributes.td b/include/circt/Dialect/OM/OMAttributes.td new file mode 100644 index 000000000000..5a1312863018 --- /dev/null +++ b/include/circt/Dialect/OM/OMAttributes.td @@ -0,0 +1,137 @@ +//===- OMAttributes.td - Object Model dialect attributes ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This contains the Object Model dialect type definitions. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMATTRIBUTES_TD +#define CIRCT_DIALECT_OM_OMATTRIBUTES_TD + +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/BuiltinAttributeInterfaces.td" + +def ReferenceAttr : AttrDef { + let summary = "An attribute that wraps a #hw.innerNameRef with !om.ref type"; + + let mnemonic = "ref"; + + let parameters = (ins + "circt::hw::InnerRefAttr":$innerRef + ); + + let assemblyFormat = [{ + `<` $innerRef `>` + }]; + + let extraClassDeclaration = [{ + mlir::Type getType(); + }]; +} + +def OMSymbolRefAttr : AttrDef { + let summary = "An attribute that wraps a FlatSymbolRefAttr type"; + + let mnemonic = "sym_ref"; + + let parameters = (ins + "mlir::FlatSymbolRefAttr":$ref + ); + + let builders = [ + // Get the SymbolRefAttr to the symbol represented by this operation. + AttrBuilderWithInferredContext<(ins "::mlir::Operation *":$op)>, + // Get the SymbolRefAttr to this symbol name. + AttrBuilderWithInferredContext<(ins "::mlir::StringAttr":$symName)> + ]; + + let assemblyFormat = [{ + `<` $ref `>` + }]; + + let extraClassDeclaration = [{ + mlir::Type getType(); + }]; +} + +def OMListAttr : AttrDef { + let summary = "An attribute that represents a list"; + + let mnemonic = "list"; + + let parameters = (ins + "mlir::Type": $elementType, + "mlir::ArrayAttr":$elements + ); + + // TODO: Use custom assembly format to infer an element type from elements. + let assemblyFormat = [{ + `<` $elementType `,` $elements `>` + }]; + + let genVerifyDecl = 1; + + let extraClassDeclaration = [{ + mlir::Type getType(); + }]; +} + +def MapAttr : AttrDef { + let summary = "An attribute that represents a string map"; + + let mnemonic = "map"; + + let parameters = (ins + "mlir::Type": $valueType, + "mlir::DictionaryAttr":$elements + ); + + // TODO: Use custom assembly format to infer a type from elements. + let assemblyFormat = [{ + `<` $valueType `,` $elements `>` + }]; + + let genVerifyDecl = 1; + + let extraClassDeclaration = [{ + mlir::Type getType(); + }]; +} + +def OMPathAttr : AttrDef { + let summary = "An attribute that represents an instance path"; + + let mnemonic = "path"; + + let parameters = (ins ArrayRefParameter<"::circt::om::PathElement">:$path); + + let genVerifyDecl = 1; + + let hasCustomAssemblyFormat = 1; + + let extraClassDeclaration = [{ + auto begin() const { return getPath().begin(); } + auto end() const { return getPath().end(); } + }]; +} + +def OMIntegerAttr : AttrDef { + let summary = "An attribute that represents an arbitrary integer"; + + let mnemonic = "integer"; + + let parameters = (ins "mlir::IntegerAttr":$value); + + let assemblyFormat = " `<` $value `>` "; + + let extraClassDeclaration = [{ + mlir::Type getType(); + }]; +} + +#endif // CIRCT_DIALECT_OM_OMATTRIBUTES_TD diff --git a/include/circt/Dialect/OM/OMDialect.h b/include/circt/Dialect/OM/OMDialect.h index afb49b801704..aaa1d9b3537e 100644 --- a/include/circt/Dialect/OM/OMDialect.h +++ b/include/circt/Dialect/OM/OMDialect.h @@ -16,5 +16,6 @@ #include "mlir/IR/Dialect.h" #include "circt/Dialect/OM/OMDialect.h.inc" +#include "circt/Dialect/OM/OMEnums.h.inc" #endif // CIRCT_DIALECT_OM_OMDIALECT_H diff --git a/include/circt/Dialect/OM/OMDialect.td b/include/circt/Dialect/OM/OMDialect.td index 5fd13c1fff97..ffb597d6a8c4 100644 --- a/include/circt/Dialect/OM/OMDialect.td +++ b/include/circt/Dialect/OM/OMDialect.td @@ -13,6 +13,8 @@ #ifndef CIRCT_DIALECT_OM_OMDIALECT #define CIRCT_DIALECT_OM_OMDIALECT +include "mlir/IR/OpBase.td" + def OMDialect : Dialect { let name = "om"; let cppNamespace = "::circt::om"; @@ -26,6 +28,24 @@ def OMDialect : Dialect { For more information about the Object Model dialect, see the [Object Model Dialect Rationale](./RationaleOM.md). }]; + + let useDefaultAttributePrinterParser = 1; + let useDefaultTypePrinterParser = 1; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + + let dependentDialects = [ + "circt::hw::HWDialect" + ]; + + let extraClassDeclaration = [{ + /// Register all OM types. + void registerTypes(); + + /// Register all OM types. + void registerAttributes(); + }]; } #endif // CIRCT_DIALECT_OM_OMDIALECT diff --git a/include/circt/Dialect/OM/OMEnums.td b/include/circt/Dialect/OM/OMEnums.td new file mode 100644 index 000000000000..d1182979d8be --- /dev/null +++ b/include/circt/Dialect/OM/OMEnums.td @@ -0,0 +1,35 @@ +//===- OMEnums.td - OM Enum Definition ---------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Definitions of OM enum attributes. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMENUMS_TD +#define CIRCT_DIALECT_OM_OMENUMS_TD + +include "circt/Dialect/OM/OMDialect.td" +include "mlir/IR/EnumAttr.td" + +let cppNamespace = "::circt::om" in { + +//===----------------------------------------------------------------------===// +// Target Kinds +//===----------------------------------------------------------------------===// + +def DontTouch : I32EnumAttrCase<"DontTouch", 0, "dont_touch">; +def Instance : I32EnumAttrCase<"Instance", 1, "instance">; +def MemberInstance: I32EnumAttrCase<"MemberInstance", 2, "member_instance">; +def MemberReference: I32EnumAttrCase<"MemberReference", 3, "member_reference">; +def Reference: I32EnumAttrCase<"Reference", 4, "reference">; +def TargetKind : I32EnumAttr<"TargetKind", "object model target kind", + [DontTouch, Instance, MemberInstance, MemberReference, Reference]> {} + +} + +#endif // CIRCT_DIALECT_OM_OMENUMS_TD diff --git a/include/circt/Dialect/OM/OMOpInterfaces.h b/include/circt/Dialect/OM/OMOpInterfaces.h new file mode 100644 index 000000000000..bfda5f89972f --- /dev/null +++ b/include/circt/Dialect/OM/OMOpInterfaces.h @@ -0,0 +1,20 @@ +//===- OMOpInterfaces.h - Object Model operation interfaces ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file contains the Object Model operation declarations. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMOPINTERFACES_H +#define CIRCT_DIALECT_OM_OMOPINTERFACES_H + +#include "mlir/IR/OpDefinition.h" + +#include "circt/Dialect/OM/OMOpInterfaces.h.inc" + +#endif // CIRCT_DIALECT_OM_OMOPINTERFACES_H diff --git a/include/circt/Dialect/OM/OMOpInterfaces.td b/include/circt/Dialect/OM/OMOpInterfaces.td new file mode 100644 index 000000000000..67e5640970f8 --- /dev/null +++ b/include/circt/Dialect/OM/OMOpInterfaces.td @@ -0,0 +1,57 @@ +//===- OMOpInterfaces.td - Object Model dialect op interfaces -------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This contains the Object Model dialect operation interfaces. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMOPINTERFACES_TD +#define CIRCT_DIALECT_OM_OMOPINTERFACES_TD + +include "mlir/IR/OpBase.td" + +def ClassLike : OpInterface<"ClassLike"> { + let cppNamespace = "circt::om"; + + let description = [{ + Common functionality for class-like operations. + }]; + + let methods = [ + InterfaceMethod<"Get the class-like symbol name", + "llvm::StringRef", "getSymName", (ins)>, + InterfaceMethod<"Get the class-like symbol name attribute", + "mlir::StringAttr", "getSymNameAttr", (ins)>, + InterfaceMethod<"Get the class-like symbol name attribute name", + "mlir::StringAttr", "getSymNameAttrName", (ins)>, + InterfaceMethod<"Get the class-like formal parameter names attribute", + "mlir::ArrayAttr", "getFormalParamNames", (ins)>, + InterfaceMethod<"Get the class-like formal parameter names attribute name", + "mlir::StringAttr", "getFormalParamNamesAttrName", (ins)>, + InterfaceMethod<"Get the class-like body region", + "mlir::Region &", "getBody", (ins)>, + InterfaceMethod<"Get the class-like body block", + "mlir::Block *", "getBodyBlock", (ins), + /*methodBody=*/[{ return $_op.getBodyBlock(); }]> + ]; +} + +def ClassFieldLike : OpInterface<"ClassFieldLike"> { + let cppNamespace = "circt::om"; + + let description = [{ + Common functionality for class-like field operations. + }]; + + let methods = [ + InterfaceMethod<"Get the class-like field's type", + "mlir::Type", "getType", (ins)> + ]; +} + +#endif // CIRCT_DIALECT_OM_OMOPINTERFACES_TD diff --git a/include/circt/Dialect/OM/OMOps.h b/include/circt/Dialect/OM/OMOps.h index 482dce19593a..7e766637e629 100644 --- a/include/circt/Dialect/OM/OMOps.h +++ b/include/circt/Dialect/OM/OMOps.h @@ -13,8 +13,14 @@ #ifndef CIRCT_DIALECT_OM_OMOPS_H #define CIRCT_DIALECT_OM_OMOPS_H +#include "circt/Dialect/OM/OMAttributes.h" +#include "circt/Dialect/OM/OMDialect.h" +#include "circt/Dialect/OM/OMOpInterfaces.h" +#include "circt/Dialect/OM/OMTypes.h" + #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/OpDefinition.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" #define GET_OP_CLASSES #include "circt/Dialect/OM/OM.h.inc" diff --git a/include/circt/Dialect/OM/OMOps.td b/include/circt/Dialect/OM/OMOps.td index d2cb2b2c377f..b024402cc033 100644 --- a/include/circt/Dialect/OM/OMOps.td +++ b/include/circt/Dialect/OM/OMOps.td @@ -13,21 +13,30 @@ #ifndef CIRCT_DIALECT_OM_OMOPS_TD #define CIRCT_DIALECT_OM_OMOPS_TD +include "circt/Dialect/OM/OMDialect.td" +include "circt/Dialect/OM/OMEnums.td" +include "circt/Dialect/OM/OMOpInterfaces.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" +include "mlir/IR/BuiltinAttributeInterfaces.td" +include "mlir/IR/BuiltinTypes.td" include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/RegionKindInterface.td" include "mlir/IR/SymbolInterfaces.td" class OMOp traits = []> : Op; //===----------------------------------------------------------------------===// -// Class definitions +// Shared definitions //===----------------------------------------------------------------------===// -def ClassOp : OMOp<"class", - [SingleBlock, NoTerminator, Symbol, SymbolTable, - HasParent<"mlir::ModuleOp">, - DeclareOpInterfaceMethods - ]> { +class OMClassLike traits = []> : + OMOp, + DeclareOpInterfaceMethods]> { + let arguments = (ins SymbolNameAttr:$sym_name, StrArrayAttr:$formalParamNames @@ -37,25 +46,406 @@ def ClassOp : OMOp<"class", SizedRegion<1>:$body ); + let builders = [ + OpBuilder<(ins "::mlir::Twine":$name)>, + OpBuilder<(ins "::mlir::Twine":$name, + "::mlir::ArrayRef<::mlir::StringRef>":$formalParamNames)>, + ]; + let hasCustomAssemblyFormat = 1; let hasVerifier = 1; +} +class OMClassFieldLike traits = []> : + OMOp]> { +} + +//===----------------------------------------------------------------------===// +// Class definitions +//===----------------------------------------------------------------------===// + +def ClassOp : OMClassLike<"class"> { let extraClassDeclaration = [{ mlir::Block *getBodyBlock() { return &getBody().front(); } + // This builds a ClassOp, and populates it with the CLassFieldOps. + // Build the ClassOp with `name` and `formalParamNames`. Then add + // ClassFieldOps for each name and type in `fieldNames` and `fieldTypes`. + circt::om::ClassOp static buildSimpleClassOp( + mlir::OpBuilder &odsBuilder, mlir::Location loc, mlir::Twine name, + mlir::ArrayRef formalParamNames, + mlir::ArrayRef fieldNames, + mlir::ArrayRef fieldTypes); + + // Implement RegionKindInterface. + static mlir::RegionKind getRegionKind(unsigned index) { + return mlir::RegionKind::Graph; + } }]; } -def ClassFieldOp : OMOp<"class.field", - [HasParent<"ClassOp">, Symbol]> { +def ClassFieldOp : OMClassFieldLike<"class.field", + [HasParent<"ClassOp">]> { let arguments = (ins SymbolNameAttr:$sym_name, AnyType:$value ); let assemblyFormat = [{ - $sym_name `,` $value `:` type($value) attr-dict + $sym_name `,` $value attr-dict `:` type($value) }]; } +//===----------------------------------------------------------------------===// +// External class definitions +//===----------------------------------------------------------------------===// + +def ClassExternOp : OMClassLike<"class.extern"> { + let extraClassDeclaration = [{ + mlir::Block *getBodyBlock() { return &getBody().front(); } + + // Implement RegionKindInterface. + static mlir::RegionKind getRegionKind(unsigned index) { + return mlir::RegionKind::Graph; + } + }]; +} + +def ClassExternFieldOp : OMClassFieldLike<"class.extern.field", + [HasParent<"ClassExternOp">]> { + let arguments = (ins + SymbolNameAttr:$sym_name, + TypeAttr:$type + ); + + let assemblyFormat = [{ + $sym_name attr-dict `:` $type + }]; +} + +//===----------------------------------------------------------------------===// +// Object instantiations and fields +//===----------------------------------------------------------------------===// + +def ObjectOp : OMOp<"object", + [DeclareOpInterfaceMethods, Pure]> { + let arguments = (ins + SymbolNameAttr:$className, + Variadic:$actualParams + ); + + let results = (outs + ClassType:$result + ); + + let builders = [ + OpBuilder<(ins "om::ClassOp":$classOp, "::mlir::ValueRange":$actualParams)> + ]; + + let assemblyFormat = [{ + $className `(` $actualParams `)` attr-dict `:` + functional-type($actualParams, $result) + }]; +} + +def ObjectFieldOp : OMOp<"object.field", + [DeclareOpInterfaceMethods, Pure]> { + let arguments = (ins + ClassType:$object, + FlatSymbolRefArrayAttr:$fieldPath + ); + + let results = (outs + AnyType:$result + ); + + let assemblyFormat = [{ + $object `,` $fieldPath attr-dict `:` functional-type($object, $result) + }]; +} + +//===----------------------------------------------------------------------===// +// Primitives and containers +//===----------------------------------------------------------------------===// + +def ConstantOp : OMOp<"constant", + [ConstantLike, Pure, AllTypesMatch<["value", "result"]>]> { + let arguments = (ins + TypedAttrInterface:$value + ); + + let results = (outs + AnyType:$result + ); + + let builders = [ + OpBuilder<(ins "::mlir::TypedAttr":$constVal)> + ]; + + let assemblyFormat = [{ + $value attr-dict + }]; + + let hasFolder = true; +} + +def ListCreateOp : OMOp<"list_create", [Pure, SameTypeOperands]> { + let summary = "Create a list of values"; + let description = [{ + Creates a list from a sequence of inputs. + + ``` + %list = om.list_create %a, %b, %c : !om.ref + ``` + }]; + + let arguments = (ins Variadic:$inputs); + let results = (outs + ListType:$result + ); + + let hasCustomAssemblyFormat = 1; +} + +def TupleCreateOp : OMOp<"tuple_create", [Pure, InferTypeOpInterface]> { + let summary = "Create a tuple of values"; + let description = [{ + Create a tuple from a sequence of inputs. + + ``` + %tuple = om.tuple_create %a, %b, %c : !om.ref, !om.string, !om.list + ``` + }]; + + let arguments = (ins Variadic:$inputs); + let results = (outs + TupleOf<[AnyType]>:$result + ); + + let assemblyFormat = [{ + $inputs attr-dict `:` type($inputs) + }]; + + let extraClassDeclaration = [{ + // Implement InferTypeOpInterface. + static ::mlir::LogicalResult inferReturnTypes( + ::mlir::MLIRContext *context, ::std::optional<::mlir::Location> location, + ::mlir::ValueRange operands, ::mlir::DictionaryAttr attributes, + ::mlir::OpaqueProperties, + ::mlir::RegionRange regions, + ::llvm::SmallVectorImpl<::mlir::Type> &inferredReturnTypes); + }]; + +} + +def TupleGetOp : OMOp<"tuple_get", [Pure, InferTypeOpInterface]> { + let summary = "Extract a value from a tuple"; + let description = [{ + Extract a value from a tuple. + + ``` + %value = om.tuple_get %a[0] : tuple> + ``` + }]; + + let arguments = (ins + TupleOf<[AnyType]>:$input, + I32Attr:$index + ); + + let results = (outs + AnyType:$result + ); + + let assemblyFormat = [{ + $input `[` $index `]` attr-dict `:` type($input) + }]; + + let extraClassDeclaration = [{ + // Implement InferTypeOpInterface. + static ::mlir::LogicalResult inferReturnTypes( + ::mlir::MLIRContext *context, ::std::optional<::mlir::Location> location, + ::mlir::ValueRange operands, ::mlir::DictionaryAttr attributes, + ::mlir::OpaqueProperties, ::mlir::RegionRange regions, + ::llvm::SmallVectorImpl<::mlir::Type> &inferredReturnTypes); + }]; +} + +def MapKeyValuePair: Type, + "a pair whose first element is an attribute", + "::mlir::TupleType">; + +def MapCreateOp : OMOp<"map_create", [Pure, SameTypeOperands]> { + let summary = "Create a map"; + let description = [{ + Creates a map from a sequence of inputs. + + ``` + %map = om.map_create %e1, %e2 : !om.string, i8 + ``` + where `%e1` and `e2` have !om.tuple and + `%map` has `!om.map` type. + }]; + + let arguments = (ins Variadic:$inputs); + let results = (outs + MapType:$result + ); + + let hasCustomAssemblyFormat = true; +} + +def BasePathCreateOp : OMOp<"basepath_create", [Pure, + DeclareOpInterfaceMethods + ]> { + let summary = "Produce a base path value"; + let description = [{ + Produces a value which represents a fragment of a hierarchical path to a + target. Given a base path, extend it with the name of a module instance, to + produce a new base path. The instance is identified via an NLA. Once the + final verilog name of the instance is known, this op can be converted into + a FrozenBasePathOp. + + Example: + ```mlir + hw.module @Foo() -> () { + hw.inst "bar" sym @bar @Bar() -> () + } + hw.hierpath @Path [@Foo::@bar] + om.class @OM(%basepath: !om.basepath) { + %0 = om.basepath_create %base @Path + } + ``` + }]; + let arguments = (ins BasePathType:$basePath, FlatSymbolRefAttr:$target); + let results = (outs BasePathType:$result); + let assemblyFormat = "$basePath $target attr-dict"; +} + +def PathCreateOp : OMOp<"path_create", [Pure, + DeclareOpInterfaceMethods + ]> { + let summary = "Produce a path value"; + let description = [{ + Produces a value which represents a hierarchical path to a hardware + target. + from a base path to a target. + + Example: + ```mlir + hw.module @Foo() -> () { + %wire = hw.wire sym @w: !i1 + } + hw.hierpath @Path [@Foo::@w] + om.class @OM(%basepath: !om.basepath) + %0 = om.path_create reference %basepath @Path + } + ``` + }]; + let arguments = (ins + TargetKind:$targetKind, + BasePathType:$basePath, + FlatSymbolRefAttr:$target + ); + let results = (outs PathType:$result); + let assemblyFormat = "$targetKind $basePath $target attr-dict"; +} + +def EmptyPathOp : OMOp<"path_empty", [Pure]> { + let summary = "Produce a path value to nothing"; + let description = [{ + Produces a value which represents a hierarchical path to nothing. + + Example: + ```mlir + om.class @OM() + %0 = om.path_empty + } + ``` + }]; + let results = (outs FrozenPathType:$result); + let assemblyFormat = "attr-dict"; +} + +def FrozenBasePathCreateOp : OMOp<"frozenbasepath_create", [Pure]> { + let summary = "Produce a frozen base path value"; + let description = [{ + Produces a value which represents a fragment of a hierarchical path to a + target. + + Example: + ```mlir + om.class @OM(%basepath: !om.basepath) + %0 = om.frozenbasepath_create %basepath "Foo/bar:Bar/baz" + } + ``` + }]; + let arguments = (ins FrozenBasePathType:$basePath, OMPathAttr:$path); + let results = (outs FrozenBasePathType:$result); + let assemblyFormat = "$basePath custom($path) attr-dict"; +} + +def FrozenPathCreateOp : OMOp<"frozenpath_create", [Pure]> { + let summary = "Produce a frozen path value"; + let description = [{ + Produces a value which represents a hierarchical path to a hardware + component from a base path to a target. + + Example: + ```mlir + om.class @OM(%basepath: !om.basepath) + %0 = om.frozenpath_create reference %base "Foo/bar:Bar>w.a" + } + ``` + }]; + let arguments = (ins + TargetKind:$targetKind, + FrozenBasePathType:$basePath, + OMPathAttr:$path, + StrAttr:$module, + StrAttr:$ref, + StrAttr:$field + ); + let results = (outs FrozenPathType:$result); + let assemblyFormat = [{ + $targetKind $basePath custom($path, $module, $ref, $field) + attr-dict + }]; +} + +def FrozenEmptyPathOp : OMOp<"frozenpath_empty", [Pure]> { + let summary = "Produce a frozen path value to nothing"; + let description = [{ + Produces a value which represents a hierarchical path to nothing. + + Example: + ```mlir + om.class @OM() + %0 = om.frozenpath_empty + } + ``` + }]; + let results = (outs FrozenPathType:$result); + let assemblyFormat = "attr-dict"; +} + +def AnyCastOp : OMOp<"any_cast", [Pure]> { + let summary = "Cast any value to any type."; + + let description = [{ + Casts any value to AnyType. This is useful for situations where a value of + AnyType is needed, but a value of some concrete type is known. + + In the evaluator, this is a noop, and the value of concrete type is used. + }]; + + let arguments = (ins AnyType:$input); + + let results = (outs OMAnyType:$result); + + let assemblyFormat = + "$input attr-dict `:` functional-type($input, $result)"; +} #endif // CIRCT_DIALECT_OM_OMOPS_TD diff --git a/include/circt/Dialect/OM/OMPasses.h b/include/circt/Dialect/OM/OMPasses.h new file mode 100644 index 000000000000..a29ea5ec86ad --- /dev/null +++ b/include/circt/Dialect/OM/OMPasses.h @@ -0,0 +1,31 @@ +//===- Passes.h - OM dialect passes --------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMPASSES_H +#define CIRCT_DIALECT_OM_OMPASSES_H + +#include "mlir/Pass/Pass.h" +#include + +namespace mlir { +class Pass; +} // namespace mlir + +namespace circt { +namespace om { + +std::unique_ptr createOMLinkModulesPass(); +std::unique_ptr createFreezePathsPass(); + +#define GEN_PASS_REGISTRATION +#include "circt/Dialect/OM/OMPasses.h.inc" + +} // namespace om +} // namespace circt + +#endif // CIRCT_DIALECT_OM_OMPASSES_H diff --git a/include/circt/Dialect/OM/OMPasses.td b/include/circt/Dialect/OM/OMPasses.td new file mode 100644 index 000000000000..79de81ec367d --- /dev/null +++ b/include/circt/Dialect/OM/OMPasses.td @@ -0,0 +1,27 @@ +//===- OMPasses.td - OM dialect passes ----------------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +include "mlir/Pass/PassBase.td" + +def FreezePaths: Pass<"om-freeze-paths", "mlir::ModuleOp"> { + let summary = "Hard code all path information"; + let description = [{ + Replaces paths to hardware with hard-coded string paths. This pass should + only run once the hierarchy will no longer change, and the final names for + objects have been decided. + }]; + let constructor = "circt::om::createFreezePathsPass()"; +} + +def LinkModules: Pass<"om-link-modules", "mlir::ModuleOp"> { + let summary = "Link separated OM modules into a single module"; + let description = [{ + Flatten nested modules and resolve external classes. + }]; + let constructor = "circt::om::createOMLinkModulesPass()"; +} diff --git a/include/circt/Dialect/OM/OMTypes.h b/include/circt/Dialect/OM/OMTypes.h new file mode 100644 index 000000000000..dd2cfa2b90ee --- /dev/null +++ b/include/circt/Dialect/OM/OMTypes.h @@ -0,0 +1,37 @@ +//===- OMTypes.h - Object Model type declarations -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file contains the Object Model type declarations. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMTYPES_H +#define CIRCT_DIALECT_OM_OMTYPES_H + +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Types.h" + +namespace circt::om { +// Return true if the type is a pair whose first element is either string or +// integer. +bool isMapKeyValuePairType(mlir::Type); + +namespace detail { +struct EnumElement { + mlir::StringAttr name; + mlir::Type type; +}; + +} // namespace detail + +} // namespace circt::om + +#define GET_TYPEDEF_CLASSES +#include "circt/Dialect/OM/OMTypes.h.inc" + +#endif // CIRCT_DIALECT_OM_OMTYPES_H diff --git a/include/circt/Dialect/OM/OMTypes.td b/include/circt/Dialect/OM/OMTypes.td new file mode 100644 index 000000000000..cfdea6530bcb --- /dev/null +++ b/include/circt/Dialect/OM/OMTypes.td @@ -0,0 +1,132 @@ +//===- OMTypes.td - Object Model dialect types ----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This contains the Object Model dialect type definitions. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMTYPES_TD +#define CIRCT_DIALECT_OM_OMTYPES_TD + +include "mlir/IR/AttrTypeBase.td" + +def ClassType : TypeDef { + let summary = "A type that represents a reference to a Class."; + + let mnemonic = "class.type"; + + let parameters = (ins + "mlir::FlatSymbolRefAttr":$className + ); + + let assemblyFormat = [{ + `<` $className `>` + }]; +} + +def ReferenceType : TypeDef { + let summary = "A type that represents a reference to a hardware entity."; + + let mnemonic = "ref"; +} + +def ListType : TypeDef { + let summary = "A type that represents a list."; + + let mnemonic = "list"; + let parameters = (ins "mlir::Type":$elementType); + let assemblyFormat = [{ + `<` $elementType `>` + }]; + + let builders = [ + AttrBuilderWithInferredContext<(ins "::mlir::Type":$elementType), [{ + return $_get(elementType.getContext(), elementType); + }]> + ]; +} + +def MapType : TypeDef { + let summary = [{A type that represents a map. A key type must be either + an integer or string type}]; + + let mnemonic = "map"; + let parameters = (ins "mlir::Type": $keyType, "mlir::Type":$valueType); + let assemblyFormat = [{ + `<` $keyType `,` $valueType `>` + }]; + + let builders = [ + AttrBuilderWithInferredContext<(ins "::mlir::Type":$keyType, "::mlir::Type":$valueType), [{ + return $_get(keyType.getContext(), keyType, valueType); + }]> + ]; + + let genVerifyDecl = 1; +} + +def SymbolRefType : TypeDef { + let summary = "A type that represents a reference to a flat symbol reference."; + + let mnemonic = "sym_ref"; +} + +def OMIntegerType : TypeDef { + let summary = "A type that represents an arbitrary width integer."; + + let mnemonic = "integer"; +} + +def StringType : TypeDef { + let summary = "A type that represents a string."; + + let mnemonic = "string"; +} + +def BasePathType : TypeDef { + let summary = "A fragment of a path to a hardware component"; + let mnemonic = "basepath"; +} + +def PathType : TypeDef { + let summary = "A path to a hardware component"; + let mnemonic = "path"; +} + +def FrozenBasePathType : TypeDef { + let summary = "A frozen fragment of a path to a hardware component"; + let mnemonic = "frozenbasepath"; +} + +def FrozenPathType : TypeDef { + let summary = "A frozen path to a hardware component"; + let mnemonic = "frozenpath"; +} + +def EnumType : TypeDef { + let summary = "An enum type"; + + let mnemonic = "enum"; + let parameters = ( + ins ArrayRefParameter< + "::circt::om::EnumType::EnumElement", + "enum fields">: $elements + ); + let hasCustomAssemblyFormat = 1; + let extraClassDeclaration = [{ + using EnumElement = ::circt::om::detail::EnumElement; + }]; +} + +def OMAnyType : TypeDef { + let summary = "A type that represents any valid OM type."; + + let mnemonic = "any"; +} + +#endif // CIRCT_DIALECT_OM_OMTYPES_TD diff --git a/include/circt/Dialect/OM/OMUtils.h b/include/circt/Dialect/OM/OMUtils.h new file mode 100644 index 000000000000..3abea9791903 --- /dev/null +++ b/include/circt/Dialect/OM/OMUtils.h @@ -0,0 +1,35 @@ +//===- OMUtils.h - OM Utility Functions -----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_OM_OMUTILS_H +#define CIRCT_DIALECT_OM_OMUTILS_H + +#include "circt/Support/LLVM.h" +#include "llvm/ADT/STLExtras.h" + +namespace circt::om { + +struct PathElement; +class PathAttr; + +/// Parse a target string of the form "Foo/bar:Bar/baz" in to a base path. +ParseResult parseBasePath(MLIRContext *context, StringRef spelling, + PathAttr &path); + +/// Parse a target string in to a path. +/// "Foo/bar:Bar/baz:Baz>wire.a[1]" +/// |--------------| Path +/// |--| Module +/// |--| Ref +/// |---| Field +ParseResult parsePath(MLIRContext *context, StringRef spelling, PathAttr &path, + StringAttr &module, StringAttr &ref, StringAttr &field); + +} // namespace circt::om + +#endif // CIRCT_DIALECT_OM_OMUTILS_H diff --git a/include/circt/Dialect/Pipeline/Pipeline.td b/include/circt/Dialect/Pipeline/Pipeline.td index ebd5b8bda0e0..6ad5d72a21cf 100644 --- a/include/circt/Dialect/Pipeline/Pipeline.td +++ b/include/circt/Dialect/Pipeline/Pipeline.td @@ -1,389 +1,17 @@ -//===- Pipeline.td - Pipeline Definitions ------------------*- tablegen -*-===// +//===- Pipeline.td - Pipeline dialect definition -----------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// -// Pipeline Ops are defined in tablegen. -// -//===----------------------------------------------------------------------===// - -#ifdef PIPELINE_OPS -#else -#define PIPELINE_OPS +#ifndef CIRCT_DIALECT_PIPELINE_PIPELINE_TD +#define CIRCT_DIALECT_PIPELINE_PIPELINE_TD include "mlir/IR/OpBase.td" -include "mlir/IR/SymbolInterfaces.td" -include "mlir/IR/RegionKindInterface.td" -include "mlir/Interfaces/CallInterfaces.td" -include "mlir/Interfaces/SideEffectInterfaces.td" -include "mlir/Interfaces/InferTypeOpInterface.td" -include "mlir/IR/EnumAttr.td" - -def Pipeline_Dialect : Dialect { - let name = "pipeline"; - let cppNamespace = "::circt::pipeline"; -} - -def PipelineOp : Op, - RegionKindInterface, - HasOnlyGraphRegion - ]> { - let summary = "pipeline operation"; - let description = [{ - The "pipeline.pipeline" operation represents a retimeable pipeline. - The pipeline contains a single block representing a graph region. Pipeline - stages are represented by `pipeline.rt.register` operations. Semantics - of values crossing register boundaries are defined by lowering passes. - The pipeline representation is centered around providing latency insensitive - values (valid signals between stages). Such signals can either be removed - (in the feedforward, statically scheduled case), used to control stalling - (feedback, statically scheduled case) or in conjunction with handshake signals - for dynamically scheduled pipelines. - A pipelines' latency sensitivity is based on the I/O of the pipeline - if - any in- or output port is an ESI channel, all ports are expected to be ESI - channels, and the pipeline is considered latency sensitive. - The internal representation of the pipeline is agnostic to the latency - insensitivity of the I/O. This is by design - allowing us a single source - of truth for lowering either latency sensitive or latency insensitive pipelines. - - A typical flow would go like this: - - An untimed datapath is defined: - ``` - pipeline.pipeline(%in0 : i32, %in1 : i32) -> (i32) { - ^bb0:(%arg0 : i32, %arg1: i32): - %add0 = comb.add %arg0, %arg1 : i32 - %add1 = comb.add %add0, %arg0 : i32 - %add2 = comb.add %add1, %arg1 : i32 - pipeline.return %add2 : i32 - } - ``` - The datapath is scheduled: - ``` - pipeline.pipeline(%in0 : i32, %in1 : i32) -> (i32) { - ^bb0:(%arg0 : i32, %arg1: i32, %go : i1): - %add0 = comb.add %arg0, %arg1 : i32 - - %s0_valid = pipeline.stage when %go - %add1 = comb.add %add0, %arg0 : i32 - - %s1_valid = pipeline.stage when %g1 - %add2 = comb.add %add1, %arg1 : i32 - - pipeline.return %add2 valid %s1_valid : i32 - } - ``` - - Stage-crossing dependencies are made explicit through registers. - ``` - pipeline.pipeline(%in0 : i32, %in1 : i32) -> (i32) { - ^bb0:(%arg0 : i32, %arg1: i32): - %add0 = comb.add %arg0, %arg1 : i32 - - %s0_valid, %add0_r = pipeline.stage.register when %go regs (%add0: i32) - %add1 = comb.add %add0_r, %arg0 : i32 - - %s1_valid, %add1_r = pipeline.stage.register when %g1 regs (%add1: i32) - %add2 = comb.add %add1_r, %arg1 : i32 - - pipeline.return %add2 valid %s1_valid : i32 - } - ``` - - This representation can then be lowered to statically or dynamically scheduled - pipelines. - }]; - - let arguments = (ins - Variadic:$inputs, I1:$clock, I1:$reset - ); - let results = (outs Variadic:$results); - let regions = (region SizedRegion<1>: $body); - let hasVerifier = 1; - - let assemblyFormat = [{ - `(` $inputs `)` `clock` $clock `reset` $reset attr-dict `:` functional-type($inputs, results) $body - }]; - - let extraClassDeclaration = [{ - // Returns true if this pipeline has a latency-insensitive interface. - // Latency sensitivity is defined based on whether _any_ `$input` is an - // ESI channel. - bool isLatencyInsensitive(); - bool isLatencySensitive() { return !isLatencyInsensitive(); } - - /// Returns the body of a Pipeline component. - Block *getBodyBlock() { - Region* region = &getOperation()->getRegion(0); - assert(region->hasOneBlock() && "The body should have one Block."); - return ®ion->front(); - } - - }]; -} - - -def PipelineStageOp : Op - ]> { - let summary = "Pipeline pipeline stage."; - let description = [{ - The `pipeline.stage` operation represents a stage separating register - in a pipeline. The stage does not define any explicit registers, but solely - defines a cut of a dataflow graph based on its lexical position in the - pipeline. Pipeline registers are made explicit through the register - materialization pass, wherein this op is replaced by - `pipeline.stage.register` operations. - }]; - - let arguments = (ins I1:$when); - let results = (outs I1:$valid); - - let assemblyFormat = [{ - `when` $when attr-dict - }]; - - let extraClassDeclaration = [{ - // Returns the index of this stage in the pipeline. - unsigned index() { - auto stageOps = getOperation()->getParentOfType().getOps(); - return std::distance(stageOps.begin(), llvm::find(stageOps, *this)); - } - }]; -} - -def PipelineStageRegisterOp : Op, - RangedTypesMatchWith<"result type matches operand", "regIns", "regOuts", - "llvm::make_range($_self.begin(), $_self.end())"> - ]> { - let summary = "Pipeline pipeline stage."; - let description = [{ - The `pipeline.stage` operation represents a stage separating register - in a pipeline with materialized register values. `pipeline.stage` and - `pipeline.stage.register` operations are not allowed to co-exist in the - same pipeline body. This is because, once register values are materialized, - all delays as well as knowledge about multicycle paths have been lowered - away. - }]; - - let arguments = (ins Variadic:$regIns, I1:$when); - let results = (outs Variadic:$regOuts, I1:$valid); - - let builders = [OpBuilder<(ins "mlir::Value":$when, "mlir::ValueRange":$regIns)>]; - - let assemblyFormat = [{ - `when` $when (`regs` $regIns^)? attr-dict (`:` type($regIns)^)? - }]; - - let extraClassDeclaration = [{ - // Returns the index of this stage in the pipeline. - unsigned index() { - auto stageOps = getOperation()->getParentOfType().getOps(); - return std::distance(stageOps.begin(), llvm::find(stageOps, *this)); - } - }]; -} - -def ReturnOp : Op { - let summary = "Pipeline dialect return."; - let description = [{ - The "return" operation represents a terminator of a `pipeline.pipeline`. - }]; - - let hasVerifier = 1; - let arguments = (ins Variadic:$outputs, I1:$valid); - let builders = [OpBuilder<(ins), [{ return; }]>]; - let assemblyFormat = [{ ($outputs^)? `valid` $valid attr-dict (`:` type($outputs)^)? }]; -} - -def PipelineWhileOp : Op { - let summary = "Pipeline dialect pipeline while-loop."; - let description = [{ - The `pipeline.while` operation represents a statically scheduled - pipeline stucture that executes while a condition is true. For more details, - see: https://llvm.discourse.group/t/rfc-representing-pipelined-loops/4171. - - A pipeline captures the result of scheduling, and is not generally safe to - transform, besides lowering to hardware dialects. For more discussion about - relaxing this, see: https://github.com/llvm/circt/issues/2204. - - This is the top-level operation representing a high-level pipeline. It is - not isolated from above, but could be if this is helpful. A pipeline - contains two regions: `condition` and `stages`. - - The pipeline may accept an optional `iter_args`, similar to the SCF dialect, - for representing loop-carried values like induction variables or reductions. - When the pipeline starts execution, the registers indicated as `iter_args` - by `pipeline.terminator` should be initialized to the initial - values specified in the `iter_args` section here. The `iter_args` relate to - the initiation interval of the loop. The maximum distance in stages between - where an `iter_arg` is used and where that `iter_arg` is registered must be - less than the loop's initiation interval. For example, with II=1, each - `iter_arg` must be used and registered in the same stage. - - The single-block `condition` region dictates the condition under which the - pipeline should execute. It has a `register` terminator, and the - pipeline initiates new iterations while the registered value is `true : i1`. - It may access SSA values dominating the pipeline, as well as `iter_args`, - which are block arguments. The body of the block may only contain - "combinational" operations, which are currently defined to be simple - arithmetic, comparisons, and selects from the `Standard` dialect. - - The single-block `stages` region wraps `pipeline.stage` - operations. It has a `pipeline.terminator` terminator, which can - both return results from the pipeline and register `iter_args`. Stages may - access SSA values dominating the pipeline, as well as `iter_args`, which are - block arguments. - }]; - - let arguments = (ins - I64Attr:$II, - OptionalAttr:$tripCount, - Variadic:$iterArgs - ); - - let results = (outs - Variadic:$results - ); - - let regions = (region - SizedRegion<1>:$condition, - SizedRegion<1>:$stages - ); - - let hasCustomAssemblyFormat = 1; - - let hasVerifier = 1; - - let skipDefaultBuilders = 1; - let builders = [ - OpBuilder<(ins "mlir::TypeRange":$resultTypes, "mlir::IntegerAttr":$II, - "std::optional": $tripCount, - "mlir::ValueRange":$iterArgs)> - ]; - - let extraClassDeclaration = [{ - Block &getCondBlock() { return getCondition().front(); } - Block &getStagesBlock() { return getStages().front(); } - }]; -} - -def PipelineWhileStageOp : Op]> { - let summary = "Pipeline dialect while pipeline stage."; - let description = [{ - This operation has a single-block region which dictates the operations that - may occur concurrently. - - It has a `start` attribute, which indicates the start cycle for this stage. - - It may have an optional `when` predicate, which supports conditional - execution for each stage. This is in addition to the `condition` region that - controls the execution of the whole pipeline. A stage with a `when` - predicate should only execute when the predicate is `true : i1`, and push a - bubble through the pipeline otherwise. - - It has a `register` terminator, which passes the concurrently - computed values forward to the next stage. - - Any stage may access `iter_args`. If a stage accesses an `iter_arg` after - the stage in which it is defined, it is up to lowering passes to preserve - this value until the last stage that needs it. - - Other than `iter_args`, stages may only access SSA values dominating the - pipeline or SSA values computed by any previous stage. This ensures the - stages capture the coarse-grained schedule of the pipeline and how values - feed forward and backward. - }]; - - let arguments = (ins - SI64Attr:$start, - Optional:$when - ); - - let results = (outs - Variadic:$results - ); - - let regions = (region - SizedRegion<1>:$body - ); - - let assemblyFormat = [{ - `start` `=` $start (`when` $when^)? $body (`:` qualified(type($results))^)? attr-dict - }]; - - let hasVerifier = 1; - - let skipDefaultBuilders = 1; - let builders = [ - OpBuilder<(ins "mlir::TypeRange":$resultTypes, "mlir::IntegerAttr":$start)> - ]; - - let extraClassDeclaration = [{ - Block &getBodyBlock() { return getBody().front(); } - unsigned getStageNumber(); - }]; -} - -def PipelineRegisterOp : Op, Terminator]> { - let summary = "Pipeline dialect pipeline register."; - let description = [{ - The `pipeline.register` terminates a pipeline stage and - "registers" the values specified as operands. These values become the - results of the stage. - }]; - - let arguments = (ins - Variadic:$operands - ); - - let assemblyFormat = [{ - $operands (`:` qualified(type($operands))^)? attr-dict - }]; - - let hasVerifier = 1; -} - -def PipelineTerminatorOp : Op, Terminator, AttrSizedOperandSegments]> { - let summary = "Pipeline dialect pipeline terminator."; - let description = [{ - The `pipeline.terminator` operation represents the terminator of - a `pipeline.while`. - - The `results` section accepts a variadic list of values which become the - pipeline’s return values. These must be results of a stage, and their types - must match the pipeline's return types. The results need not be defined in - the final stage, and it is up to lowering passes to preserve these values - until the final stage is complete. - - The `iter_args` section accepts a variadic list of values which become the - next iteration’s `iter_args`. These may be the results of any stage, and - their types must match the pipeline's `iter_args` types. - }]; - - let arguments = (ins - Variadic:$iter_args, - Variadic:$results - ); - - let assemblyFormat = [{ - `iter_args` `(` $iter_args `)` `,` - `results` `(` $results `)` `:` - functional-type($iter_args, $results) attr-dict - }]; - let hasVerifier = 1; -} +include "circt/Dialect/Pipeline/PipelineDialect.td" +include "circt/Dialect/Pipeline/PipelineOps.td" -#endif // PIPELINE_OPS +#endif // CIRCT_DIALECT_PIPELINE_PIPELINE_TD diff --git a/include/circt/Dialect/Pipeline/PipelineDialect.h b/include/circt/Dialect/Pipeline/PipelineDialect.h new file mode 100644 index 000000000000..b8ba2a1edcc1 --- /dev/null +++ b/include/circt/Dialect/Pipeline/PipelineDialect.h @@ -0,0 +1,18 @@ +//===- PipelineDialect.h - Pipeline dialect definition ------------ C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_PIPELINE_PIPELINEDIALECT_H +#define CIRCT_DIALECT_PIPELINE_PIPELINEDIALECT_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Dialect.h" + +#include "circt/Dialect/Pipeline/PipelineDialect.h.inc" + +#endif // CIRCT_DIALECT_PIPELINE_PIPELINEDIALECT_H diff --git a/include/circt/Dialect/Pipeline/PipelineDialect.td b/include/circt/Dialect/Pipeline/PipelineDialect.td new file mode 100644 index 000000000000..f6f6e6e235ba --- /dev/null +++ b/include/circt/Dialect/Pipeline/PipelineDialect.td @@ -0,0 +1,26 @@ +//===- Pipeline.td - Pipeline dialect definition -----------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_PIPELINE_DIALECT_TD +#define CIRCT_DIALECT_PIPELINE_DIALECT_TD + +include "mlir/IR/OpBase.td" + +def Pipeline_Dialect : Dialect { + let name = "pipeline"; + let cppNamespace = "::circt::pipeline"; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + + let dependentDialects = [ + "circt::seq::SeqDialect" + ]; +} + +#endif // CIRCT_DIALECT_PIPELINE_DIALECT_TD diff --git a/include/circt/Dialect/Pipeline/PipelineOps.h b/include/circt/Dialect/Pipeline/PipelineOps.h new file mode 100644 index 000000000000..f424536ffea9 --- /dev/null +++ b/include/circt/Dialect/Pipeline/PipelineOps.h @@ -0,0 +1,75 @@ +//===- PipelineOps.h - Pipeline dialect operations ---------------- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_PIPELINE_PIPELINEOPS_H +#define CIRCT_DIALECT_PIPELINE_PIPELINEOPS_H + +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/RegionKindInterface.h" +#include "mlir/IR/SymbolTable.h" +#include "mlir/Interfaces/CallInterfaces.h" +#include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" + +#include "circt/Dialect/Pipeline/PipelineDialect.h" +#include "circt/Dialect/Seq/SeqTypes.h" + +namespace circt { +namespace pipeline { +class StageOp; +class ScheduledPipelineOp; + +// StageKind defines the control semantics of a pipeline stages. +enum class StageKind { + // All stages in a pipeline without a stall signal is a continuous stage. + Continuous, + // Stallable stages are any stages which appear **before** the first + // non-stallable stage in the pipeline. + Stallable, + // Non-stallable stages are the stages explicitly marked as non-stallable by + // the user. + NonStallable, + // Runoff stages and stages that appear **after** (and by extension, + // **between** non-stallable stages). Runoff stages consider their own + // enablement wrt. the stall signal, as well as the enablement of the **last + // non-stallable register** (LNS) wrt. the runoff stage's position in the + // pipeline. + Runoff +}; + +namespace detail { + +// Returns the set of values defined outside of the given region, and the +// operation that defines the region. This will walk the entire region so +// should be used with care (or cache the results). +llvm::SmallVector getValuesDefinedOutsideRegion(Region ®ion); +} // namespace detail + +// Determines the stage which 'op' resides in within the pipeline. This is +// useful for analysis of the pipeline, wherein ops may reside in nested +// regions within different stages of the pipeline. +Block *getParentStageInPipeline(ScheduledPipelineOp pipeline, Operation *op); + +// Determines the stage which 'block' resides in within the pipeline. This is +// useful for analysis of the pipeline, wherein blocks may reside in nested +// regions within different stages of the pipeline. +Block *getParentStageInPipeline(ScheduledPipelineOp pipeline, Block *block); + +// Determines the stage which 'v' resides in within the pipeline. This is +// useful for analysis of the pipeline, wherein values may reside in nested +// regions within different stages of the pipeline. +Block *getParentStageInPipeline(ScheduledPipelineOp pipeline, Value v); + +} // namespace pipeline +} // namespace circt + +#define GET_OP_CLASSES +#include "circt/Dialect/Pipeline/Pipeline.h.inc" + +#endif // CIRCT_DIALECT_PIPELINE_PIPELINEOPS_H diff --git a/include/circt/Dialect/Pipeline/PipelineOps.td b/include/circt/Dialect/Pipeline/PipelineOps.td new file mode 100644 index 000000000000..518e85eb1c36 --- /dev/null +++ b/include/circt/Dialect/Pipeline/PipelineOps.td @@ -0,0 +1,397 @@ +//===- PipelineOps.td - Pipeline dialect operations --------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_PIPELINE_PIPELINEOPS_TD +#define CIRCT_DIALECT_PIPELINE_PIPELINEOPS_TD + +include "mlir/IR/OpBase.td" +include "mlir/IR/SymbolInterfaces.td" +include "mlir/IR/RegionKindInterface.td" +include "mlir/Interfaces/CallInterfaces.td" +include "mlir/Interfaces/SideEffectInterfaces.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/ControlFlowInterfaces.td" +include "mlir/IR/EnumAttr.td" +include "mlir/IR/OpAsmInterface.td" + +include "circt/Dialect/Pipeline/PipelineDialect.td" +include "circt/Dialect/Seq/SeqTypes.td" + + +class PipelineBase traits = []> : + Op + ])> { + let results = (outs Variadic:$dataOutputs, I1:$done); + let hasCustomAssemblyFormat = 1; + + let skipDefaultBuilders = 1; + + let builders = [ + OpBuilder<(ins + "TypeRange":$dataOutputs, + "ValueRange":$inputs, + "ArrayAttr":$inputNames, "ArrayAttr":$outputNames, + "Value":$clock, "Value":$reset, "Value":$go, + CArg<"Value", "{}">:$stall, + CArg<"StringAttr", "{}">:$name, + CArg<"ArrayAttr", "{}">:$stallability + )> + ]; + + /// Additional class declarations inside the pipeline op. + code extraModuleClassDeclaration = ?; + + // The entry block of the pipeline contains a lot of block arguments - the + // indices are kept away from the user and only "hardcoded" here, as well as + // in the printer/parser. + // Order: + // 1. Inputs + // 2. Stall (opt) + // 3. Clock + // 4. Reset + // 5. Go + let extraClassDeclaration = extraModuleClassDeclaration # [{ + // Returns the entry stage of this pipeline. + Block* getEntryStage() { + Region* region = &getOperation()->getRegion(0); + return ®ion->front(); + } + + // Returns true if this pipeline has a stall signal. + bool hasStall() { + return static_cast(getStall()); + } + + llvm::ArrayRef getInnerInputs() { + return getEntryStage()->getArguments().take_front(getInputs().size()); + } + + // Returns a list of external inputs to the pipeline. These are defined + // as any value which is defined outside the inner pipeline region. + // This will walk the entire pipeline, so it is recommended to cache the + // result if it is used multiple times. + llvm::SmallVector getExtInputs() { + return detail::getValuesDefinedOutsideRegion(getRegion()); + } + + // Gets the n'th stage of this pipeline + Block* getStage(unsigned n) { + auto& blocks = getRegion().getBlocks(); + assert(n < blocks.size() && "Stage index out of bounds"); + return &*std::next(blocks.begin(), n); + } + + // Returns the enable signal of the given pipeline stage. This is always + // the last block argument of a stage for anything but the entry stage. + Value getStageEnableSignal(size_t stageIdx) { + return getStageEnableSignal(getStage(stageIdx)); + } + + // Returns the enable signal for the given stage. The enable signal is always + // the last signal in the stage argument list. + Value getStageEnableSignal(Block* stage) { + return stage->getArguments().back(); + } + }]; +} + +def UnscheduledPipelineOp : PipelineBase<"unscheduled", [ + RegionKindInterface, + HasOnlyGraphRegion, + SingleBlockImplicitTerminator<"ReturnOp"> + ]> { + + let summary = "unscheduled pipeline operation"; + let description = [{ + The "pipeline.unscheduled" operation represents a pipeline that has not yet + been scheduled. It contains a single block representing a graph region of + operations to-be-scheduled into a pipeline. + Mainly serves as a container and entrypoint for scheduling. + + The interface of a `pipeline.unscheduled` is similar to that of a + `pipeline.scheduled`. Please refer to this op for further documentation + about the interface signals. + }]; + + let arguments = (ins + OptionalAttr:$name, Variadic:$inputs, Optional:$stall, + ClockType:$clock, I1:$reset, I1:$go, StrArrayAttr:$inputNames, + StrArrayAttr:$outputNames + ); + let regions = (region SizedRegion<1>: $body); + let extraModuleClassDeclaration = ""; +} + +def ScheduledPipelineOp : PipelineBase<"scheduled"> { + let summary = "Scheduled pipeline operation"; + let description = [{ + The `pipeline.scheduled` operation represents a scheduled pipeline. + The pipeline contains a single block representing a graph region. + + A `pipeline.scheduled` operation can exist in multiple phases, mainly + pertaining to when registers have been materialized (made explicit). + For an in-depth explanation, please refer to the Pipeline dialect rationale. + + A `pipeline.scheduled` supports a `stall` input. This signal is intended to + connect to all stages within the pipeline, and is used to stall the entirety + of the pipeline. It is lowering defined how stages choose to use this signal, + although in the common case, a `stall` signal would typically connect to + the clock-enable input of the stage-separating registers. + + The `go` input is used to start the pipeline. This value is fed through + the stages as the current stage valid/next stage enable signal. + Note: the op is currently only designed for pipelines with II=1. For + pipelines with II>1, a user must themselves maintain state about when + the pipeline is ready to accept new inputs. We plan to add support for + head-of-pipeline backpressure in the future. + + Any value defined outside the pipeline is considered an external input. An + external input will _not_ be registered. + + The pipeline may optionally be provided with an array of bits `stallability` + which is used to determine which stages are stallable. + - If not provided and the pipeline has a stall signal, all stages are stallable. + - If provided, and the pipeline has a stall signal, the number of bits must + match the number of stages in the pipeline. Each bit represents a stage, + in the order of which the stages appear wrt. the `pipeline.stage` operations. + A bit set to 1 indicates that the stage is stallable, and 0 indicates that + the stage is not stallable. + + The exit (non-registered) stage of a pipeline cannot be non-stallable, and + will always follow the stallability of the parent pipeline. + + For more information about non-stallable stages, and how these are lowered, + please refer to the Pipeline dialect rationale. + }]; + + + let arguments = (ins + OptionalAttr:$name, + Variadic:$inputs, + Optional:$stall, + ClockType:$clock, I1:$reset, I1:$go, + StrArrayAttr:$inputNames, StrArrayAttr:$outputNames, + OptionalAttr:$stallability + ); + let regions = (region AnyRegion:$body); + let hasVerifier = 1; + + code extraModuleClassDeclaration = [{ + static mlir::RegionKind getRegionKind(unsigned index) { + return mlir::RegionKind::SSACFG; + } + + // Returns all of the stages in this pipeline. + llvm::iplist& getStages() { + return getRegion().getBlocks(); + } + + size_t getNumStages() { + return getStages().size(); + } + + // Returns all of the stages in this pipeline. The stages are ordered + // with respect to their position in the pipeline as determined by the + // control flow of `pipeline.stage` operations. + // Stages are ordered from first (entry) to last (exit). + llvm::SmallVector getOrderedStages(); + + // Returns a map of stages to their index in the pipeline - this is + // with respect to 'getOrderedStages'. + llvm::DenseMap getStageMap(); + + // Returns the last stage in the pipeline. + Block* getLastStage(); + + // Adds a new stage to this pipeline. It is the users responsibility to + // modify other stages to point to this new stage. + Block* addStage(); + + // Returns true if the pipeline has entered the register materialized phase. + // This enables certain invariants such as "all values used within a stage + // must be defined within a stage". + bool isMaterialized(); + + // Returns the data arguments for a stage. The stage enable signal is _not_ part + // of the returned values. + llvm::ArrayRef getStageDataArgs(Block* stage) { + if(stage == getEntryStage()) + return getInnerInputs(); + + // Data arguments for all non-entry stages are all block arguments + // except for the last block arguments (which is the stage enable signal). + return stage->getArguments().drop_back(); + } + + // Returns the stage kind of the given stage index. + StageKind getStageKind(size_t stageIdx); + }]; +} + + +def StageOp : Op, + Pure, + Terminator + ]> { + let summary = "Pipeline stage terminator."; + let description = [{ + The `pipeline.stage` operation represents a stage terminator. It is used + to communicate: + 1. which stage (block) to transition to next + 2. which registers to build at this stage boundary + 3. which values to pass through to the next stage without registering + 4. An optional hierarchy of boolean values to be used for clock gates for + each register. + - The implicit '!stalled' gate will always be the first signal in the + hierarchy. Further signals are added to the hierarchy from left to + right. + + + Example: + ```mlir + pipeline.stage ^bb1 regs(%a : i32 gated by [%foo, %bar], %b : i1) pass(%c : i32) + ``` + }]; + + let arguments = (ins + Variadic:$registers, + Variadic:$passthroughs, + Variadic:$clockGates, + I64ArrayAttr:$clockGatesPerRegister, + OptionalAttr:$registerNames, + OptionalAttr:$passthroughNames); + let successors = (successor AnySuccessor:$nextStage); + let results = (outs); + let hasVerifier = 1; + let skipDefaultBuilders = 1; + + let assemblyFormat = [{ + $nextStage + custom($registers, type($registers), $clockGates, $clockGatesPerRegister, $registerNames) + custom($passthroughs, type($passthroughs), $passthroughNames) + attr-dict + }]; + + let extraClassDeclaration = [{ + // Set the destination stage. + void setNextStage(Block *block) { + setSuccessor(block); + } + + // Returns the list of clock gates for the given register. + ValueRange getClockGatesForReg(unsigned regIdx); + + // Returns the register name for a given register index. + StringAttr getRegisterName(unsigned regIdx) { + if(auto names = getOperation()->getAttrOfType("registerNames")) { + auto name = names[regIdx].cast(); + if(!name.strref().empty()) + return name; + } + + return {}; + } + + // Returns the passthrough name for a given passthrough index. + StringAttr getPassthroughName(unsigned passthroughIdx) { + if(auto names = getOperation()->getAttrOfType("passthroughNames")) { + auto name = names[passthroughIdx].cast(); + if(!name.strref().empty()) + return name; + } + + return {}; + } + + + }]; + + let builders = [ + OpBuilder<(ins "Block*":$dest, "ValueRange":$registers, "ValueRange":$passthroughs)>, + OpBuilder<(ins "Block*":$dest, "ValueRange":$registers, "ValueRange":$passthroughs, + // Clock gates per register, in the order of the registers. + "llvm::ArrayRef>":$clockGates, + CArg<"mlir::ArrayAttr", "{}">:$registerNames, CArg<"mlir::ArrayAttr", "{}">:$passthroughNames)> + ]; +} + +def ReturnOp : Op + ]> { + let summary = "Pipeline dialect return."; + let description = [{ + The "return" operation represents a terminator of a `pipeline.pipeline`. + }]; + + let hasVerifier = 1; + let arguments = (ins Variadic:$inputs); + let builders = [OpBuilder<(ins), [{ return; }]>]; + let assemblyFormat = [{ ($inputs^)? attr-dict (`:` type($inputs)^)? }]; +} + +def LatencyOp : Op, + RegionKindInterface, + HasOnlyGraphRegion, + ParentOneOf<["UnscheduledPipelineOp", "ScheduledPipelineOp"]> + ]> { + + let summary = "Pipeline dialect latency operation."; + let description = [{ + The `pipeline.latency` operation represents an operation for wrapping + multi-cycle operations. The operation declares a single block + wherein any operation may be placed within. The operation is not + `IsolatedFromAbove` meaning that the operation can reference values + defined outside of the operation (subject to the materialization + phase of the parent pipeline). + }]; + + let arguments = (ins ConfinedAttr]>:$latency); + let results = (outs Variadic:$results); + let regions = (region SizedRegion<1>:$body); + let hasVerifier = 1; + + let assemblyFormat = [{ + $latency `->` `(` type($results) `)` $body attr-dict + }]; + + let extraClassDeclaration = [{ + // Returns the body block of the latency operation. + Block* getBodyBlock() { + return &getBody().front(); + } + }]; +} + +def LatencyReturnOp : Op + ]> { + let summary = "Pipeline latency return operation."; + let description = [{ + The `pipeline.latency.return` operation represents a terminator of a + `pipeline.latency` operation. + }]; + + let hasVerifier = 1; + let arguments = (ins Variadic:$inputs); + let builders = [OpBuilder<(ins), [{ return; }]>]; + let assemblyFormat = [{ ($inputs^)? attr-dict (`:` type($inputs)^)? }]; +} + + +#endif // CIRCT_DIALECT_PIPELINE_PIPELINEOPS_TD diff --git a/include/circt/Dialect/Pipeline/PipelinePasses.td b/include/circt/Dialect/Pipeline/PipelinePasses.td index d21cc8432e90..3349eb4335c0 100644 --- a/include/circt/Dialect/Pipeline/PipelinePasses.td +++ b/include/circt/Dialect/Pipeline/PipelinePasses.td @@ -16,7 +16,7 @@ include "mlir/Pass/PassBase.td" -def ExplicitRegs : Pass<"pipeline-explicit-regs", "pipeline::PipelineOp"> { +def ExplicitRegs : Pass<"pipeline-explicit-regs"> { let summary = "Makes stage registers explicit."; let description = [{ Makes all stage-crossing def-use chains into explicit registers. @@ -25,12 +25,12 @@ def ExplicitRegs : Pass<"pipeline-explicit-regs", "pipeline::PipelineOp"> { let constructor = "circt::pipeline::createExplicitRegsPass()"; } -def ScheduleLinearPipeline : Pass<"pipeline-schedule-linear", "pipeline::PipelineOp"> { - let summary = "Schedules a linear pipeline."; +def ScheduleLinearPipeline : Pass<"pipeline-schedule-linear"> { + let summary = "Schedules `pipeline.unscheduled` operations."; let description = [{ - Schedules a linear pipeline based on operator latencies. + Schedules `pipeline.unscheduled` operations based on operator latencies. }]; - let dependentDialects = ["hw::HWDialect"]; + let dependentDialects = ["hw::HWDialect", "seq::SeqDialect"]; let constructor = "circt::pipeline::createScheduleLinearPipelinePass()"; } diff --git a/include/circt/Dialect/SSP/SSP.td b/include/circt/Dialect/SSP/SSP.td index 7d37f48f4b83..c39649e00098 100644 --- a/include/circt/Dialect/SSP/SSP.td +++ b/include/circt/Dialect/SSP/SSP.td @@ -31,6 +31,10 @@ def SSPDialect : Dialect { let cppNamespace = "::circt::ssp"; let useDefaultAttributePrinterParser = true; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + let extraClassDeclaration = [{ /// Register all SSP attributes. void registerAttributes(); diff --git a/include/circt/Dialect/SSP/SSPOps.td b/include/circt/Dialect/SSP/SSPOps.td index 6ee1a7fa56c9..848b720950b1 100644 --- a/include/circt/Dialect/SSP/SSPOps.td +++ b/include/circt/Dialect/SSP/SSPOps.td @@ -49,10 +49,10 @@ def InstanceOp : SSPOp<"instance", }]; let arguments = (ins OptionalAttr:$sym_name, StrAttr:$problemName, - OptionalAttr:$properties); + OptionalAttr:$sspProperties); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ - ($sym_name^)? `of` $problemName custom($properties) $body attr-dict + ($sym_name^)? `of` $problemName custom($sspProperties) $body attr-dict }]; let hasVerifier = true; @@ -77,10 +77,10 @@ def InstanceOp : SSPOp<"instance", let skipDefaultBuilders = true; let builders = [ OpBuilder<(ins "::mlir::StringAttr":$problemName, - CArg<"::mlir::ArrayAttr", "::mlir::ArrayAttr()">:$properties), [{ + CArg<"::mlir::ArrayAttr", "::mlir::ArrayAttr()">:$sspProperties), [{ $_state.addAttribute($_builder.getStringAttr("problemName"), problemName); - if (properties) - $_state.addAttribute($_builder.getStringAttr("properties"), properties); + if (sspProperties) + $_state.addAttribute($_builder.getStringAttr("sspProperties"), sspProperties); ::mlir::Region* region = $_state.addRegion(); region->push_back(new ::mlir::Block()); }]> @@ -169,8 +169,8 @@ def OperatorTypeOp : SSPOp<"operator_type", ``` }]; - let arguments = (ins SymbolNameAttr:$sym_name, OptionalAttr:$properties); - let assemblyFormat = "$sym_name custom($properties) attr-dict"; + let arguments = (ins SymbolNameAttr:$sym_name, OptionalAttr:$sspProperties); + let assemblyFormat = "$sym_name custom($sspProperties) attr-dict"; } def OperationOp : SSPOp<"operation", @@ -218,7 +218,7 @@ def OperationOp : SSPOp<"operation", let arguments = (ins Variadic:$operands, OptionalAttr:$sym_name, OptionalAttr:$dependences, - OptionalAttr:$properties); + OptionalAttr:$sspProperties); let results = (outs Variadic:$results); let hasCustomAssemblyFormat = true; @@ -238,15 +238,15 @@ def OperationOp : SSPOp<"operation", "::mlir::ValueRange":$operands, CArg<"::mlir::StringAttr", "::mlir::StringAttr()">:$sym_name, CArg<"::mlir::ArrayAttr", "::mlir::ArrayAttr()">:$dependences, - CArg<"::mlir::ArrayAttr", "::mlir::ArrayAttr()">:$properties), [{ + CArg<"::mlir::ArrayAttr", "::mlir::ArrayAttr()">:$sspProperties), [{ $_state.addTypes(::llvm::SmallVector<::mlir::Type>(numResults, $_builder.getNoneType())); $_state.addOperands(operands); if (sym_name) $_state.addAttribute(::mlir::SymbolTable::getSymbolAttrName(), sym_name); if (dependences) $_state.addAttribute($_builder.getStringAttr("dependences"), dependences); - if (properties) - $_state.addAttribute($_builder.getStringAttr("properties"), properties); + if (sspProperties) + $_state.addAttribute($_builder.getStringAttr("sspProperties"), sspProperties); }]> ]; } diff --git a/include/circt/Dialect/SSP/Utilities.h b/include/circt/Dialect/SSP/Utilities.h index 97c8d8af8b11..1031551e3c47 100644 --- a/include/circt/Dialect/SSP/Utilities.h +++ b/include/circt/Dialect/SSP/Utilities.h @@ -109,7 +109,7 @@ OperatorType loadOperatorType(ProblemT &prob, OperatorTypeOp oprOp, assert(!prob.hasOperatorType(opr)); prob.insertOperatorType(opr); loadOperatorTypeProperties( - prob, opr, oprOp.getPropertiesAttr()); + prob, opr, oprOp.getSspPropertiesAttr()); return opr; } @@ -145,7 +145,7 @@ ProblemT loadProblem(InstanceOp instOp, auto prob = ProblemT::get(instOp); loadInstanceProperties( - prob, instOp.getPropertiesAttr()); + prob, instOp.getSspPropertiesAttr()); if (auto instName = instOp.getSymNameAttr()) prob.setInstanceName(instName); @@ -171,7 +171,7 @@ ProblemT loadProblem(InstanceOp instOp, graphOp.walk([&](OperationOp opOp) { prob.insertOperation(opOp); loadOperationProperties( - prob, opOp, opOp.getPropertiesAttr()); + prob, opOp, opOp.getSspPropertiesAttr()); if (auto opName = opOp.getSymNameAttr()) prob.setOperationName(opOp, opName); diff --git a/include/circt/Dialect/SV/SVDialect.h b/include/circt/Dialect/SV/SVDialect.h index 0503936e814e..5acc4155bb56 100644 --- a/include/circt/Dialect/SV/SVDialect.h +++ b/include/circt/Dialect/SV/SVDialect.h @@ -25,22 +25,27 @@ namespace sv { /// Given string \p origName, generate a new name if it conflicts with any /// keyword or any other name in the map \p nextGeneratedNameIDs. Use the value /// of \p nextGeneratedNameIDs as a counter for suffix. Update the \p -/// nextGeneratedNameIDs with the generated name and return the StringRef. +/// nextGeneratedNameIDs with the generated name and return the StringRef. If +/// \p is true, then check against the reserved keywords in a case insensitive +/// manner. llvm::StringRef resolveKeywordConflict(llvm::StringRef origName, - llvm::StringMap &nextGeneratedNameIDs); + llvm::StringMap &nextGeneratedNameIDs, + bool caseInsensitiveKeywords); /// Legalize the specified name for use in SV output. Auto-uniquifies the name /// through \c resolveKeywordConflict if required. If the name is empty, a /// unique temp name is created. StringRef legalizeName(llvm::StringRef name, - llvm::StringMap &nextGeneratedNameIDs); + llvm::StringMap &nextGeneratedNameIDs, + bool caseInsensitiveKeywords); /// Check if a name is valid for use in SV output by only containing characters /// allowed in SV identifiers. /// -/// Call \c legalizeName() to obtain a legal version of the name. -bool isNameValid(llvm::StringRef name); +/// Call \c legalizeName() to obtain a legal version of the name. If \p +/// caseInsensitive is true, then the check will be done case insensitively. +bool isNameValid(llvm::StringRef name, bool caseInsensitiveKeywords); } // namespace sv } // namespace circt diff --git a/include/circt/Dialect/SV/SVDialect.td b/include/circt/Dialect/SV/SVDialect.td index d7283c1e8157..1ad2f1470c65 100644 --- a/include/circt/Dialect/SV/SVDialect.td +++ b/include/circt/Dialect/SV/SVDialect.td @@ -27,6 +27,9 @@ def SVDialect : Dialect { let useDefaultTypePrinterParser = 1; let useDefaultAttributePrinterParser = 1; + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + let extraClassDeclaration = [{ /// Register all SV types. void registerTypes(); diff --git a/include/circt/Dialect/SV/SVExpressions.td b/include/circt/Dialect/SV/SVExpressions.td index cacb96ea845f..34e2b398ce63 100644 --- a/include/circt/Dialect/SV/SVExpressions.td +++ b/include/circt/Dialect/SV/SVExpressions.td @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// include "mlir/Interfaces/InferTypeOpInterface.td" +include "circt/Dialect/HW/HWTypes.td" include "circt/Dialect/SV/SVTypes.td" def HWValueOrInOutType : AnyTypeOf<[HWValueType, InOutType]>; @@ -99,7 +100,8 @@ def VerbatimExprSEOp : SVOp<"verbatim.expr.se", [HasCustomSSAName]> { ]; } -def MacroRefExprOp : SVOp<"macro.ref", [Pure, HasCustomSSAName]> { +def MacroRefExprOp : SVOp<"macro.ref", [Pure, HasCustomSSAName, + DeclareOpInterfaceMethods]> { let summary = "Expression to refer to a SystemVerilog macro"; let description = [{ This operation produces a value by referencing a named macro. @@ -109,20 +111,24 @@ def MacroRefExprOp : SVOp<"macro.ref", [Pure, HasCustomSSAName]> { and emitted inline by the Verilog emitter. }]; - let arguments = (ins MacroIdentAttr:$ident); + let arguments = (ins FlatSymbolRefAttr:$macroName, Variadic:$inputs); let results = (outs HWValueOrInOutType:$result); - let assemblyFormat = "`<` $ident `>` attr-dict `:` type($result)"; + let assemblyFormat = "$macroName `(` $inputs `)` attr-dict `:` functional-type($inputs, $result)"; let builders = [ OpBuilder<(ins "Type":$resultType, "StringRef":$ident), "build(odsBuilder, odsState, resultType, " - "::circt::sv::MacroIdentAttr::get(" - "$_builder.getContext(), ident));"> + "FlatSymbolRefAttr::get($_builder.getContext(), ident), {});"> ]; + + let extraClassDeclaration = [{ + MacroDeclOp getReferencedMacro(const hw::HWSymbolCache *cache); + }]; } -def MacroRefExprSEOp : SVOp<"macro.ref.se", [HasCustomSSAName]> { +def MacroRefExprSEOp : SVOp<"macro.ref.se", [HasCustomSSAName, + DeclareOpInterfaceMethods]> { let summary = "Expression to refer to a SystemVerilog macro"; let description = [{ This operation produces a value by referencing a named macro. @@ -132,17 +138,20 @@ def MacroRefExprSEOp : SVOp<"macro.ref.se", [HasCustomSSAName]> { duplicated, but can be emitted inline by the Verilog emitter. }]; - let arguments = (ins MacroIdentAttr:$ident); + let arguments = (ins FlatSymbolRefAttr:$macroName, Variadic:$inputs); let results = (outs HWValueOrInOutType:$result); - let assemblyFormat = "`<` $ident `>` attr-dict `:` type($result)"; + let assemblyFormat = "$macroName `(` $inputs `)` attr-dict `:` functional-type($inputs, $result)"; let builders = [ OpBuilder<(ins "Type":$resultType, "StringRef":$ident), "build(odsBuilder, odsState, resultType, " - "::circt::sv::MacroIdentAttr::get(" - "$_builder.getContext(), ident));"> + "FlatSymbolRefAttr::get($_builder.getContext(), ident), {});"> ]; + + let extraClassDeclaration = [{ + MacroDeclOp getReferencedMacro(const hw::HWSymbolCache *cache); + }]; } def ConstantXOp : SVOp<"constantX", [Pure, HasCustomSSAName]> { @@ -188,7 +197,7 @@ def ConstantStrOp : SVOp<"constantStr", [Pure]> { }]; let arguments = (ins StrAttr:$str); - let results = (outs StringType:$result); + let results = (outs HWStringType:$result); let assemblyFormat = " $str attr-dict"; } @@ -232,6 +241,7 @@ def IndexedPartSelectOp std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results); }]; diff --git a/include/circt/Dialect/SV/SVInOutOps.td b/include/circt/Dialect/SV/SVInOutOps.td index 10cc804ce009..51c36966e0db 100644 --- a/include/circt/Dialect/SV/SVInOutOps.td +++ b/include/circt/Dialect/SV/SVInOutOps.td @@ -11,26 +11,28 @@ // //===----------------------------------------------------------------------===// +include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Types.td" include "mlir/Interfaces/InferTypeOpInterface.td" // Note that net declarations like 'wire' should not appear in an always block. def WireOp : SVOp<"wire", [NonProceduralOp, - DeclareOpInterfaceMethods]> { + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { let summary = "Define a new wire"; let description = [{ Declare a SystemVerilog Net Declaration of 'wire' type. See SV Spec 6.7, pp97. }]; - let arguments = (ins StrAttr:$name, OptionalAttr:$inner_sym); + let arguments = (ins StrAttr:$name, OptionalAttr:$inner_sym); let results = (outs InOutType:$result); let skipDefaultBuilders = 1; let builders = [ OpBuilder<(ins "::mlir::Type":$elementType, - CArg<"StringAttr", "StringAttr()">:$name, - CArg<"StringAttr", "StringAttr()">:$inner_sym)>, + CArg<"StringAttr", "StringAttr()">:$name, + CArg<"hw::InnerSymAttr", "hw::InnerSymAttr()">:$innerSym)>, OpBuilder<(ins "::mlir::Type":$elementType, CArg<"StringRef">:$name), [{ return build($_builder, $_state, elementType, $_builder.getStringAttr(name)); @@ -51,26 +53,32 @@ def WireOp : SVOp<"wire", [NonProceduralOp, } def RegOp : SVOp<"reg", [ - DeclareOpInterfaceMethods]> { + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { let summary = "Define a new `reg` in SystemVerilog"; let description = [{ Declare a SystemVerilog Variable Declaration of 'reg' type. See SV Spec 6.8, pp100. }]; - let arguments = (ins StrAttr:$name, OptionalAttr:$inner_sym); + let arguments = (ins + StrAttr:$name, + Optional:$init, + OptionalAttr:$inner_sym); let results = (outs InOutType:$result); let skipDefaultBuilders = 1; let builders = [ OpBuilder<(ins "::mlir::Type":$elementType, CArg<"StringAttr", "StringAttr()">:$name, - CArg<"StringAttr", "StringAttr()">:$inner_sym)> + CArg<"hw::InnerSymAttr", "hw::InnerSymAttr()">:$innerSym, + CArg<"mlir::Value", "{}">:$init)> ]; // We handle the name in a custom way, so we use a customer parser/printer. let assemblyFormat = [{ - (`sym` $inner_sym^)? `` custom($name) attr-dict + (`init` $init^)? (`sym` $inner_sym^)? `` custom($name) attr-dict `:` qualified(type($result)) + custom(ref(type($result)),ref($init), type($init)) }]; let hasCanonicalizeMethod = true; @@ -102,25 +110,35 @@ def XMROp : SVOp<"xmr", []> { let assemblyFormat = "(`isRooted` $isRooted^)? custom($path, $terminal) attr-dict `:` qualified(type($result))"; } -def XMRRefOp : SVOp<"xmr.ref", []> { - let summary = "Encode a reference to something with a hw.globalRef."; +def XMRRefOp : SVOp<"xmr.ref", [ + DeclareOpInterfaceMethods, + Pure +]> { + let summary = "Encode a reference to something with a hw.hierpath."; let description = [{ This represents a hierarchical path, but using something which the compiler can understand. In contrast to the XMROp (which models pure Verilog hierarchical paths which may not map to anything knowable in the circuit), - this op uses a `hw.globalRef` to refer to something which exists in the + this op uses a `hw.hierpath` to refer to something which exists in the circuit. Generally, this operation is always preferred for situations where hierarchical paths cannot be known statically and may change. + + `verbatimSuffix` should only be populated when the final operation on the + path is an instance of an external module. }]; let arguments = ( - ins NameRefAttr:$ref, - DefaultValuedAttr:$stringLeaf + ins FlatSymbolRefAttr:$ref, + DefaultValuedAttr:$verbatimSuffix ); let results = (outs InOutType:$result); let assemblyFormat = [{ - $ref ( $stringLeaf^ )? attr-dict `:` qualified(type($result)) + $ref ( $verbatimSuffix^ )? attr-dict `:` qualified(type($result)) + }]; + + let extraClassDeclaration = [{ + hw::HierPathOp getReferencedPath(const hw::HWSymbolCache *cache); }]; } @@ -178,6 +196,7 @@ def IndexedPartSelectInOutOp : SVOp<"indexed_part_select_inout", std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results); }]; @@ -208,28 +227,30 @@ def StructFieldInOutOp : SVOp<"struct_field_inout", std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results); }]; } -def LogicOp : SVOp<"logic", [DeclareOpInterfaceMethods]> { +def LogicOp : SVOp<"logic", [ + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { let summary = "Define a logic"; let description = [{ Declare a SystemVerilog Variable Declaration of 'logic' type. See SV Spec 6.8, pp100. }]; - let arguments = (ins StrAttr:$name, OptionalAttr:$inner_sym); + let arguments = (ins StrAttr:$name, OptionalAttr:$inner_sym); let results = (outs InOutType:$result); let skipDefaultBuilders = 1; let builders = [ OpBuilder<(ins "::mlir::Type":$elementType, - CArg<"StringAttr", "StringAttr()">:$name, - CArg<"StringAttr", "StringAttr()">:$inner_sym)>, + CArg<"StringAttr", "StringAttr()">:$name, + CArg<"hw::InnerSymAttr", "hw::InnerSymAttr()">:$innerSym)>, OpBuilder<(ins "::mlir::Type":$elementType, CArg<"StringRef">:$name), [{ return build($_builder, $_state, elementType, $_builder.getStringAttr(name)); diff --git a/include/circt/Dialect/SV/SVOps.h b/include/circt/Dialect/SV/SVOps.h index 7bf0335e4bb4..a8b80a036ddd 100644 --- a/include/circt/Dialect/SV/SVOps.h +++ b/include/circt/Dialect/SV/SVOps.h @@ -14,6 +14,7 @@ #define CIRCT_DIALECT_SV_OPS_H #include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/SV/SVAttributes.h" #include "circt/Dialect/SV/SVDialect.h" #include "circt/Dialect/SV/SVTypes.h" diff --git a/include/circt/Dialect/SV/SVPasses.h b/include/circt/Dialect/SV/SVPasses.h index c1b0ed1a4d05..8d12e64c216d 100644 --- a/include/circt/Dialect/SV/SVPasses.h +++ b/include/circt/Dialect/SV/SVPasses.h @@ -19,19 +19,25 @@ namespace circt { namespace sv { +#define GEN_PASS_DECL_HWELIMINATEINOUTPORTS +#include "circt/Dialect/SV/SVPasses.h.inc" + std::unique_ptr createPrettifyVerilogPass(); -std::unique_ptr createHWCleanupPass(); +std::unique_ptr createHWCleanupPass(bool mergeAlwaysBlocks = true); std::unique_ptr createHWStubExternalModulesPass(); std::unique_ptr createHWLegalizeModulesPass(); std::unique_ptr createSVTraceIVerilogPass(); std::unique_ptr createHWGeneratorCalloutPass(); +std::unique_ptr createHWEliminateInOutPortsPass( + const HWEliminateInOutPortsOptions &options = {}); std::unique_ptr createHWMemSimImplPass( - bool replSeqMem = false, bool ignoreReadEnableMem = false, + bool replSeqMem = false, bool ignoreReadEnable = false, bool addMuxPragmas = false, bool disableMemRandomization = false, bool disableRegRandomization = false, bool addVivadoRAMAddressConflictSynthesisBugWorkaround = false); std::unique_ptr createSVExtractTestCodePass(bool disableInstanceExtraction = false, + bool disableRegisterExtraction = false, bool disableModuleInlining = false); std::unique_ptr createHWExportModuleHierarchyPass(std::optional directory = {}); diff --git a/include/circt/Dialect/SV/SVPasses.td b/include/circt/Dialect/SV/SVPasses.td index 7fae19ed0a2d..b48ea4853dc2 100644 --- a/include/circt/Dialect/SV/SVPasses.td +++ b/include/circt/Dialect/SV/SVPasses.td @@ -24,6 +24,11 @@ def HWCleanup : Pass<"hw-cleanup", "hw::HWModuleOp"> { opportunities for other simpler passes (like canonicalize). }]; + let options = [ + Option<"mergeAlwaysBlocks", "merge-always-blocks", "bool", + "true", "Allow always and always_ff blocks to be merged"> + ]; + let constructor = "circt::sv::createHWCleanupPass()"; } @@ -100,7 +105,7 @@ def HWMemSimImpl : Pass<"hw-memory-sim", "ModuleOp"> { "Disable emission of register randomization code">, Option<"replSeqMem", "repl-seq-mem", "bool", "false", "Prepare seq mems for macro replacement">, - Option<"ignoreReadEnableMem", "ignore-read-enable-mem", "bool", + Option<"ignoreReadEnable", "ignore-read-enable", "bool", "false", "ignore the read enable signal, instead of assigning X on read disable">, Option<"addMuxPragmas", "add-mux-pragmas", "bool", "false", @@ -125,6 +130,8 @@ def SVExtractTestCode : Pass<"sv-extract-test-code", "ModuleOp"> { let options = [ Option<"disableInstanceExtraction", "disable-instance-extraction", "bool", "false", "Disable extracting instances only that feed test code">, + Option<"disableRegisterExtraction", "disable-register-extraction", "bool", + "false", "Disable extracting registers only that feed test code">, Option<"disableModuleInlining", "disable-module-inlining", "bool", "false", "Disable inlining modules that only feed test code"> ]; @@ -164,11 +171,25 @@ def HWExportModuleHierarchy : Pass<"hw-export-module-hierarchy", let constructor = "circt::sv::createHWExportModuleHierarchyPass()"; let dependentDialects = ["circt::sv::SVDialect"]; +} +def HWEliminateInOutPorts : Pass<"hw-eliminate-inout-ports", + "mlir::ModuleOp"> { + let summary = "Raises usages of inout ports into explicit input and output ports"; + let description = [{ + This pass raises usages of inout ports into explicit in- and output ports. + This is done by analyzing the usage of the inout ports and creating new + input and output ports at the using module, and subsequently moving the + inout read- and writes to the instantiation site. + }]; + let constructor = "circt::sv::createHWEliminateInOutPortsPass()"; + let dependentDialects = ["circt::sv::SVDialect"]; let options = [ - Option<"directoryName", "dir-name", "std::string", "\"./\"", - "Directory to emit into"> - ]; + Option<"readSuffix", "read-suffix", "std::string", "\"_rd\"", + "Suffix to be used for the port when the inout port has readers">, + Option<"writeSuffix", "write-suffix", "std::string", "\"_wr\"", + "Suffix to be used for the port when the inout port has writers"> + ]; } #endif // CIRCT_DIALECT_SV_SVPASSES diff --git a/include/circt/Dialect/SV/SVStatements.td b/include/circt/Dialect/SV/SVStatements.td index 8c54775dbd13..c269e1e0463d 100644 --- a/include/circt/Dialect/SV/SVStatements.td +++ b/include/circt/Dialect/SV/SVStatements.td @@ -11,14 +11,7 @@ //===----------------------------------------------------------------------===// include "mlir/IR/EnumAttr.td" - -/// Ensure symbol is one of the hw module.* types -def isModuleSymbol : AttrConstraint< - CPred< - "hw::isAnyModule(::mlir::SymbolTable::lookupNearestSymbolFrom(" - "&$_op, $_self.cast<::mlir::FlatSymbolRefAttr>().getValue()))" - >, "is module like">; - +include "mlir/IR/OpAsmInterface.td" //===----------------------------------------------------------------------===// // Control flow like-operations @@ -748,3 +741,117 @@ def ReadMemOp : SVOp<"readmem", [ProceduralOp]> { let assemblyFormat = "$dest `,` $filename `,` $base attr-dict `:` qualified(type($dest))"; } + +def ForOp : SVOp<"for", [SingleBlock, NoTerminator, ProceduralOp, + ProceduralRegion, OpAsmOpInterface, + AllTypesMatch<["lowerBound", "upperBound", "step"]>]> { + let summary = "System verilog for loop"; + + let description = [{ + The `sv.for` operation in System Verilog defines a for statement that requires + three SSA operands: `lowerBounds`, `upperBound`, and `step`. It functions + similarly to `scf.for`, where the loop iterates the induction variable from + `lowerBound` to `upperBound` with a step size of `step`, i.e: + + ``` + for (logic ... indVar = lowerBound; indVar < upperBound; indVar += step) begin + end + ``` + + It's important to note that since we are using a bit precise type instead of a Verilog + `integer` type, users must be cautious about potential overflow. For example, if + you wish to iterate over all 2-bit values, you must use a 3-bit value as the + induction variable type. + }]; + + let regions = (region SizedRegion<1>:$body); + let arguments = (ins HWIntegerType:$lowerBound, + HWIntegerType:$upperBound, + HWIntegerType:$step, + DefaultValuedAttr:$inductionVarName); + let results = (outs); + let builders = [ + OpBuilder<(ins "Value":$lowerBound, "Value":$upperBound, + "Value":$step, "StringRef":$inductionVarName, + "llvm::function_ref": $thenCtor)>, + OpBuilder<(ins "int64_t":$lowerBound, "int64_t":$upperBound, + "int64_t":$step, "IntegerType": $type, + "StringRef":$inductionVarName, + "llvm::function_ref": $thenCtor)> + ]; + let hasCustomAssemblyFormat = 1; + let hasCanonicalizeMethod = 1; + let extraClassDeclaration = [{ + Block* getBodyBlock() { return &getBody().front(); } + Value getInductionVar() { return getBodyBlock()->getArgument(0); } + void getAsmBlockArgumentNames(mlir::Region ®ion, + mlir::OpAsmSetValueNameFn setNameFn); + }]; +} + +//===----------------------------------------------------------------------===// +// Macros +//===----------------------------------------------------------------------===// + + +def MacroDeclOp : SVOp<"macro.decl", [Symbol]> { + let summary = "System verilog macro declaration"; + + let description = [{ + The `sv.macro.def` declares a macro in System Verilog. This is a + declaration; the body of the macro, which produces a verilog macro + definition is created with a `macro.def` operation. + + Lacking args will be a macro without "()". An empty args will be an empty "()". + + The verilog name is the spelling of the macro when emitting verilog. + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, + OptionalAttr:$args, + OptionalAttr:$verilogName); + let results = (outs); + + let assemblyFormat = [{ + $sym_name (`[` $verilogName^ `]`)? (`(` $args^ `)`)? attr-dict + }]; +} + + +def MacroDefOp : SVOp<"macro.def", + [DeclareOpInterfaceMethods]> { + let summary = "System verilog macro definition"; + + let description = [{ + The `sv.macro.def` defines a macro in System Verilog which optionally takes + a body. + + This is modeled similarly to verbatim in that the contents of the macro are + opaque (plain string). Given the general power of macros, this op does not + try to capture a return type. + + This operation produces a definition for the macro declaration referenced by + `sym_name`. Argument lists are picked up from that operation. + + sv.macro.def allows operand substitutions with {{0}} syntax. + }]; + + let arguments = (ins FlatSymbolRefAttr:$macroName, + StrAttr:$format_string, + DefaultValuedAttr:$symbols); + let results = (outs); + + let builders = [ + OpBuilder<(ins "StringRef":$macroName, "StringRef":$format_string), + "build($_builder, $_state, macroName, format_string," + "odsBuilder.getArrayAttr({}));"> + ]; + + let assemblyFormat = [{ + $macroName $format_string (`(` $symbols^ `)`)? attr-dict + }]; + + let extraClassDeclaration = [{ + MacroDeclOp getReferencedMacro(const hw::HWSymbolCache *cache); + }]; +} diff --git a/include/circt/Dialect/SV/SVTypeDecl.td b/include/circt/Dialect/SV/SVTypeDecl.td index 840738ed3fa9..ec6a4bcba0f4 100644 --- a/include/circt/Dialect/SV/SVTypeDecl.td +++ b/include/circt/Dialect/SV/SVTypeDecl.td @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +include "circt/Dialect/HW/HWOpInterfaces.td" include "mlir/IR/EnumAttr.td" //===----------------------------------------------------------------------===// @@ -209,7 +210,10 @@ def ModportType : SVType<"Modport"> { // Other operations //===----------------------------------------------------------------------===// -def InterfaceInstanceOp : SVOp<"interface.instance", [HasCustomSSAName]> { +def InterfaceInstanceOp : SVOp<"interface.instance", [ + HasCustomSSAName, + DeclareOpInterfaceMethods + ]> { let summary = "Instantiate an interface"; let description = [{ Use this to declare an instance of an interface: @@ -219,7 +223,7 @@ def InterfaceInstanceOp : SVOp<"interface.instance", [HasCustomSSAName]> { }]; let arguments = (ins StrAttr:$name, - OptionalAttr:$inner_sym); + OptionalAttr:$inner_sym); let results = (outs InterfaceType : $result); let assemblyFormat = [{ diff --git a/include/circt/Dialect/SV/SVTypes.td b/include/circt/Dialect/SV/SVTypes.td index b5ba10d2b49a..4a48eb4865b7 100644 --- a/include/circt/Dialect/SV/SVTypes.td +++ b/include/circt/Dialect/SV/SVTypes.td @@ -20,13 +20,6 @@ include "circt/Dialect/SV/SVDialect.td" // includable for other dialects, without polluting their output with SV types. class SVType : TypeDef { } -def StringType : SVType<"String"> { - let summary = "SystemVerilog string type"; - let description = "Defines the string type in verilog"; - - let mnemonic = "string"; -} - //===----------------------------------------------------------------------===// // InOut type and related helpers. //===----------------------------------------------------------------------===// diff --git a/include/circt/Dialect/SV/SVVerification.td b/include/circt/Dialect/SV/SVVerification.td index 89adb87fcc39..ee0e6037ef62 100644 --- a/include/circt/Dialect/SV/SVVerification.td +++ b/include/circt/Dialect/SV/SVVerification.td @@ -26,13 +26,17 @@ def DeferAssertAttr : I32EnumAttr< let cppNamespace = "circt::sv"; } +/// A constraint checking no substitutions if no message. +def NoSubstitutionsIfNoMessage : PredOpTrait<"has message if has substitutions", + CPred<"($message && !$message->empty()) || $substitutions.empty()">>; + /// A constraint forcing `cover` ops to have no message. def NoMessageVerif : PredOpTrait<"has no message", CPred<"!$message && $substitutions.empty()">>; /// Immediate verification operations defined by the SV standard. class ImmediateVerifOp traits = []> : - SVOp { + SVOp { let arguments = (ins I1:$expression, DeferAssertAttr:$defer, @@ -92,7 +96,7 @@ def CoverOp : ImmediateVerifOp<"cover", [NoMessageVerif]> { /// Concurrent verification operations defined by the SV standard. class ConcurrentVerifOp traits = []> : - SVOp { + SVOp { let arguments = (ins EventControlAttr:$event, I1:$clock, I1:$property, OptionalAttr:$label, diff --git a/include/circt/Dialect/SV/SVVisitors.h b/include/circt/Dialect/SV/SVVisitors.h index eebdfab00e69..1764afcbc725 100644 --- a/include/circt/Dialect/SV/SVVisitors.h +++ b/include/circt/Dialect/SV/SVVisitors.h @@ -43,7 +43,7 @@ class Visitor { // Type declarations. InterfaceOp, InterfaceSignalOp, InterfaceModportOp, InterfaceInstanceOp, GetModportOp, AssignInterfaceSignalOp, - ReadInterfaceSignalOp, + ReadInterfaceSignalOp, MacroDeclOp, MacroDefOp, // Verification statements. AssertOp, AssumeOp, CoverOp, AssertConcurrentOp, AssumeConcurrentOp, CoverConcurrentOp, @@ -57,6 +57,8 @@ class Visitor { ReadMemOp, // Generate statements GenerateOp, GenerateCaseOp, + // For statements + ForOp, // Sampled value functiions SampledOp>([&](auto expr) -> ResultType { return thisCast->visitSV(expr, args...); @@ -135,6 +137,8 @@ class Visitor { HANDLE(GetModportOp, Unhandled); HANDLE(AssignInterfaceSignalOp, Unhandled); HANDLE(ReadInterfaceSignalOp, Unhandled); + HANDLE(MacroDefOp, Unhandled); + HANDLE(MacroDeclOp, Unhandled); // Verification statements. HANDLE(AssertOp, Unhandled); @@ -165,6 +169,9 @@ class Visitor { HANDLE(GenerateOp, Unhandled); HANDLE(GenerateCaseOp, Unhandled); + // For loop. + HANDLE(ForOp, Unhandled); + // Sampled Value Functions HANDLE(SampledOp, Unhandled); #undef HANDLE diff --git a/include/circt/Dialect/Seq/CMakeLists.txt b/include/circt/Dialect/Seq/CMakeLists.txt index f8ba79b63904..431dd5e5f706 100644 --- a/include/circt/Dialect/Seq/CMakeLists.txt +++ b/include/circt/Dialect/Seq/CMakeLists.txt @@ -11,10 +11,23 @@ add_circt_dialect(Seq seq) add_circt_dialect_doc(Seq seq) +add_dependencies(circt-headers MLIRSeqIncGen) + +set(LLVM_TARGET_DEFINITIONS Seq.td) +mlir_tablegen(SeqEnums.h.inc -gen-enum-decls) +mlir_tablegen(SeqEnums.cpp.inc -gen-enum-defs) +add_public_tablegen_target(MLIRSeqEnumsIncGen) +add_dependencies(circt-headers MLIRSeqEnumsIncGen) + +mlir_tablegen(SeqAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect=seq) +mlir_tablegen(SeqAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect=seq) +add_public_tablegen_target(MLIRSeqAttributesIncGen) +add_dependencies(circt-headers MLIRSeqAttributesIncGen) set(LLVM_TARGET_DEFINITIONS SeqPasses.td) mlir_tablegen(SeqPasses.h.inc -gen-pass-decls) add_public_tablegen_target(CIRCTSeqTransformsIncGen) +add_dependencies(circt-headers CIRCTSeqTransformsIncGen) add_circt_doc(SeqPasses SeqPasses -gen-pass-doc) set(LLVM_TARGET_DEFINITIONS SeqOpInterfaces.td) diff --git a/include/circt/Dialect/Seq/Seq.td b/include/circt/Dialect/Seq/Seq.td index f0c23056b8f0..8320f52bfbf9 100644 --- a/include/circt/Dialect/Seq/Seq.td +++ b/include/circt/Dialect/Seq/Seq.td @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -#ifndef SEQ_TD -#define SEQ_TD +#ifndef CIRCT_DIALECT_SEQ_SEQ_TD +#define CIRCT_DIALECT_SEQ_SEQ_TD include "mlir/IR/AttrTypeBase.td" include "mlir/IR/OpBase.td" @@ -20,7 +20,8 @@ include "mlir/IR/SymbolInterfaces.td" include "mlir/Interfaces/SideEffectInterfaces.td" include "circt/Dialect/Seq/SeqDialect.td" +include "circt/Dialect/Seq/SeqAttributes.td" include "circt/Dialect/Seq/SeqTypes.td" include "circt/Dialect/Seq/SeqOps.td" -#endif // SEQ_TD +#endif // CIRCT_DIALECT_SEQ_SEQ_TD diff --git a/include/circt/Dialect/Seq/SeqAttributes.h b/include/circt/Dialect/Seq/SeqAttributes.h new file mode 100644 index 000000000000..4ba94072edfc --- /dev/null +++ b/include/circt/Dialect/Seq/SeqAttributes.h @@ -0,0 +1,20 @@ +//===- SeqAttributes.h - Declare Seq dialect attributes ----------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SEQ_SEQATTRIBUTES_H +#define CIRCT_DIALECT_SEQ_SEQATTRIBUTES_H + +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinTypes.h" + +#include "circt/Dialect/Seq/SeqEnums.h.inc" +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/Seq/SeqAttributes.h.inc" + +#endif // CIRCT_DIALECT_SEQ_SEQATTRIBUTES_H diff --git a/include/circt/Dialect/Seq/SeqAttributes.td b/include/circt/Dialect/Seq/SeqAttributes.td new file mode 100644 index 000000000000..bd79083296f7 --- /dev/null +++ b/include/circt/Dialect/Seq/SeqAttributes.td @@ -0,0 +1,66 @@ +//===- SeqAttributes.td - Attributes for Seq dialect -------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SEQ_SEQATTRIBUTES_TD +#define CIRCT_DIALECT_SEQ_SEQATTRIBUTES_TD + +include "circt/Dialect/Seq/SeqDialect.td" +include "mlir/IR/EnumAttr.td" +include "mlir/IR/BuiltinAttributeInterfaces.td" + +let cppNamespace = "circt::seq" in { + +// Constant clock values. +def ClockLow : I32EnumAttrCase<"Low", 0, "low">; +def ClockHigh : I32EnumAttrCase<"High", 1, "high">; +def ClockConstAttrImpl: I32EnumAttr<"ClockConst", "clock constant", + [ClockLow, ClockHigh]> { + let genSpecializedAttr = 0; +} +def ClockConstAttr : EnumAttr; + +// Read-under-write behavior +def RUW_Undefined : I32EnumAttrCase<"Undefined", 0, "undefined">; +def RUW_Old : I32EnumAttrCase<"Old", 1, "old">; +def RUW_New : I32EnumAttrCase<"New", 2, "new">; +def RUWAttr : I32EnumAttr<"RUW", "Read-Under-Write Behavior", + [RUW_Undefined, RUW_Old, RUW_New]>; + +// Write-under-write behavior +def WUW_Undefined : I32EnumAttrCase<"Undefined", 0, "undefined">; +def WUW_PortOrder : I32EnumAttrCase<"PortOrder", 1, "port_order">; +def WUWAttr : I32EnumAttr<"WUW", "Write-Under-Write Behavior", + [WUW_Undefined, WUW_PortOrder]>; + +} + +/// An attribute holding information about memory initialization. +def FirMemInitAttr : AttrDef { + let mnemonic = "firmem.init"; + let summary = "Memory initialization information"; + let description = [{ + This attribute captures what the initial contents of a memory should be. + At the moment this is modeled primarily with simulation in mind, where the + memory contents are pre-loaded from a file at simulation startup. + + The `filename` specifies a file on disk that contains the initial contents + for this memory. If `isBinary` is set, the file is interpreted as a binary + file, otherwise it is treated as hexadecimal. This is modeled after the + `$readmemh` and `$readmemb` SystemVerilog functions. If `isInline` is set, + the initialization is emitted directly in the memory model; otherwise it is + split out into a separate module that can be bound in. + }]; + let parameters = (ins + "mlir::StringAttr":$filename, + "bool":$isBinary, + "bool":$isInline + ); + let assemblyFormat = "`<` $filename `,` $isBinary `,` $isInline `>`"; +} + +#endif // CIRCT_DIALECT_SEQ_SEQATTRIBUTES_TD diff --git a/include/circt/Dialect/Seq/SeqDialect.td b/include/circt/Dialect/Seq/SeqDialect.td index 0088018824db..ad11cfceedf4 100644 --- a/include/circt/Dialect/Seq/SeqDialect.td +++ b/include/circt/Dialect/Seq/SeqDialect.td @@ -23,11 +23,18 @@ def SeqDialect : Dialect { let hasConstantMaterializer = 1; let useDefaultTypePrinterParser = 1; + let useDefaultAttributePrinterParser = 1; + + // This will be the default after next LLVM bump. + let usePropertiesForAttributes = 1; + let cppNamespace = "::circt::seq"; let extraClassDeclaration = [{ - /// Register all Seq types. + /// Register all types. void registerTypes(); + /// Register all attributes. + void registerAttributes(); }]; } diff --git a/include/circt/Dialect/Seq/SeqOps.h b/include/circt/Dialect/Seq/SeqOps.h index 085f48029cfc..003e3a4fa4e5 100644 --- a/include/circt/Dialect/Seq/SeqOps.h +++ b/include/circt/Dialect/Seq/SeqOps.h @@ -13,14 +13,20 @@ #ifndef CIRCT_DIALECT_SEQ_SEQOPS_H #define CIRCT_DIALECT_SEQ_SEQOPS_H +#include "mlir/Bytecode/BytecodeOpInterface.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/SymbolTable.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" #include "mlir/Interfaces/SideEffectInterfaces.h" +#include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Dialect/HW/HWOpInterfaces.h" #include "circt/Dialect/HW/HWTypes.h" +#include "circt/Dialect/Seq/SeqAttributes.h" #include "circt/Dialect/Seq/SeqDialect.h" #include "circt/Dialect/Seq/SeqOpInterfaces.h" #include "circt/Dialect/Seq/SeqTypes.h" +#include "circt/Support/BuilderUtils.h" #define GET_OP_CLASSES #include "circt/Dialect/Seq/Seq.h.inc" diff --git a/include/circt/Dialect/Seq/SeqOps.td b/include/circt/Dialect/Seq/SeqOps.td index dd8a0f895497..030355e7bb6a 100644 --- a/include/circt/Dialect/Seq/SeqOps.td +++ b/include/circt/Dialect/Seq/SeqOps.td @@ -12,8 +12,11 @@ // //===----------------------------------------------------------------------===// +include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Dialect/HW/HWTypes.td" +include "circt/Dialect/Seq/SeqAttributes.td" include "circt/Dialect/Seq/SeqOpInterfaces.td" +include "mlir/Interfaces/InferTypeOpInterface.td" // Base class for the operation in this dialect. class SeqOp traits = []> : @@ -21,75 +24,115 @@ class SeqOp traits = []> : def CompRegOp : SeqOp<"compreg", [Pure, Clocked, Resettable, - AllTypesMatch<["input", "data"/*, "resetValue"*/]>, - SameVariadicOperandSize, - DeclareOpInterfaceMethods ]> { + AllTypesMatch<["input", "data"]>, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + AttrSizedOperandSegments]> { // AllTypesMatch doesn't work with Optional types yet. let summary = "Register a value, storing it for one cycle"; let description = "See the Seq dialect rationale for a longer description"; - let arguments = (ins AnyType:$input, I1:$clk, StrAttr:$name, - Optional:$reset, Optional:$resetValue, - OptionalAttr:$sym_name); + let arguments = (ins + AnyType:$input, + ClockType:$clk, + OptionalAttr:$name, + Optional:$reset, + Optional:$resetValue, + Optional:$powerOnValue, + OptionalAttr:$inner_sym + ); let results = (outs AnyType:$data); - let hasCustomAssemblyFormat = 1; + let assemblyFormat = [{ + (`sym` $inner_sym^)? custom($name) $input `,` $clk + (`reset` $reset^ `,` $resetValue)? + (`powerOn` $powerOnValue^)? attr-dict `:` type($data) + custom(ref(type($data)), ref($resetValue), type($resetValue)) + custom(ref(type($data)), ref($powerOnValue), type($powerOnValue)) + }]; let hasVerifier = 1; let builders = [ - OpBuilder<(ins "Value":$input, "Value":$clk, "StringRef":$sym_name), [{ - return build($_builder, $_state, input.getType(), - input, clk, sym_name, Value(), Value(), - StringAttr::get($_builder.getContext(), sym_name)); - }]>, - OpBuilder<(ins "Value":$input, "Value":$clk, "Value":$reset, - "Value":$rstValue, "StringRef":$sym_name), [{ - return build($_builder, $_state, input.getType(), - input, clk, sym_name, reset, rstValue, - StringAttr::get($_builder.getContext(), sym_name)); + /// Create a register with an inner_sym matching the register's name. + OpBuilder<(ins "Value":$input, "Value":$clk, "StringAttrOrRef":$name), [{ + auto nameAttr = name.get($_builder.getContext()); + return build($_builder, $_state, input.getType(), input, clk, nameAttr, + /*reset*/ Value(), /*resetValue*/ Value(), + /*powerOnValue*/ Value(), hw::InnerSymAttr::get(nameAttr)); }]>, + /// Create a register with a reset, with an inner_sym matching the + /// register's name, and optional power-on value. + OpBuilder<(ins "Value":$input, "Value":$clk, "Value":$reset, "Value":$rstValue, + "StringAttrOrRef":$name, CArg<"Value", "{}">:$powerOnValue), [{ + auto nameAttr = name.get($_builder.getContext()); + return build($_builder, $_state, input.getType(), input, clk, nameAttr, + reset, rstValue, powerOnValue, hw::InnerSymAttr::get(nameAttr)); + }]> ]; } def CompRegClockEnabledOp : SeqOp<"compreg.ce", [Pure, Clocked, Resettable, - AllTypesMatch<["input", "data"/*, "resetValue"*/]>, - SameVariadicOperandSize, - DeclareOpInterfaceMethods ]> { + AllTypesMatch<["input", "data"]>, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + AttrSizedOperandSegments]> { // AllTypesMatch doesn't work with Optional types yet. let summary = "When enabled, register a value"; let description = "See the Seq dialect rationale for a longer description"; - let arguments = (ins AnyType:$input, I1:$clk, I1:$clockEnable, StrAttr:$name, - Optional:$reset, Optional:$resetValue, - OptionalAttr:$sym_name); + let arguments = (ins + AnyType:$input, + ClockType:$clk, + I1:$clockEnable, + OptionalAttr:$name, + Optional:$reset, + Optional:$resetValue, + Optional:$powerOnValue, + OptionalAttr:$inner_sym + ); let results = (outs AnyType:$data); - let hasCustomAssemblyFormat = 1; + let assemblyFormat = [{ + (`sym` $inner_sym^)? custom($name) $input `,` $clk `,` $clockEnable + (`reset` $reset^ `,` $resetValue)? + (`powerOn` $powerOnValue^)? attr-dict `:` type($data) + custom(ref(type($data)), ref($resetValue), type($resetValue)) + custom(ref(type($data)), ref($powerOnValue), type($powerOnValue)) + }]; let hasVerifier = 1; let builders = [ OpBuilder<(ins "Value":$input, "Value":$clk, "Value":$ce, - "StringRef":$sym_name), [{ - return build($_builder, $_state, input.getType(), - input, clk, ce, sym_name, Value(), Value(), - StringAttr::get($_builder.getContext(), sym_name)); + "StringRef":$name), [{ + auto nameAttr = StringAttr::get($_builder.getContext(), name); + return build($_builder, $_state, input.getType(), input, clk, ce, + nameAttr, /*reset*/ Value(), /*resetValue*/ Value(), + /*powerOnValue*/ Value(), + hw::InnerSymAttr::get(nameAttr)); }]>, OpBuilder<(ins "Value":$input, "Value":$clk, "Value":$ce, - "Value":$reset, "Value":$rstValue, "StringRef":$sym_name), + "Value":$reset, "Value":$rstValue, "StringRef":$name, + CArg<"Value", "{}">:$powerOnValue), [{ - return build($_builder, $_state, input.getType(), - input, clk, ce, sym_name, reset, rstValue, - StringAttr::get($_builder.getContext(), sym_name)); + auto nameAttr = StringAttr::get($_builder.getContext(), name); + return build($_builder, $_state, input.getType(), input, clk, ce, + nameAttr, reset, rstValue, powerOnValue, + hw::InnerSymAttr::get(nameAttr)); }]>, ]; } +//===----------------------------------------------------------------------===// +// FIRRTL-flavored register +//===----------------------------------------------------------------------===// + def FirRegOp : SeqOp<"firreg", [Pure, Clocked, Resettable, AllTypesMatch<["next", "data"/*, "resetValue"*/]>, SameVariadicOperandSize, MemoryEffects<[MemWrite, MemRead, MemAlloc]>, - DeclareOpInterfaceMethods]> { + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods]> { // AllTypesMatch doesn't work with Optional types yet. let summary = "Register with preset and sync or async reset"; @@ -108,7 +151,8 @@ def FirRegOp : SeqOp<"firreg", ``` %name = seq.firreg %next clock %clk [ sym @sym ] - [ reset (sync|async) %reset, %value ] : type + [ reset (sync|async) %reset, %value ] + [ preset value ] : type ``` Implicitly, all registers are pre-set to a randomized value. @@ -124,10 +168,15 @@ def FirRegOp : SeqOp<"firreg", ``` }]; - let arguments = (ins AnyType:$next, I1:$clk, StrAttr:$name, - OptionalAttr:$inner_sym, - Optional:$reset, Optional:$resetValue, - UnitAttr:$isAsync); + let arguments = (ins + AnyType:$next, + ClockType:$clk, + StrAttr:$name, + OptionalAttr:$inner_sym, + OptionalAttr:$preset, + Optional:$reset, Optional:$resetValue, + UnitAttr:$isAsync + ); let results = (outs AnyType:$data); let skipDefaultBuilders = 1; @@ -136,11 +185,11 @@ def FirRegOp : SeqOp<"firreg", let builders = [ OpBuilder<(ins "Value":$next, "Value":$clk, "StringAttr":$name, - CArg<"StringAttr", "{}">:$inner_sym)>, + CArg<"hw::InnerSymAttr", "{}">:$innerSym)>, OpBuilder<(ins "Value":$next, "Value":$clk, "StringAttr":$name, "Value":$reset, "Value":$resetValue, - CArg<"StringAttr", "{}">:$inner_sym, + CArg<"hw::InnerSymAttr", "{}">:$innerSym, CArg<"bool", "false">:$isAsync)> ]; @@ -150,9 +199,52 @@ def FirRegOp : SeqOp<"firreg", let extraClassDeclaration = [{ /// Check whether the register has a reset value. bool hasReset() { return !!getReset(); } + /// Check whether the register has a preset value. + bool hasPresetValue() { return !!getPresetAttr(); } }]; } +def FIFOOp : SeqOp<"fifo", [ + DeclareOpInterfaceMethods, + TypesMatchWith<"Input type should match output type", + "input", "output", [{ $_self }]>, + AttrSizedResultSegments + ]> { + let summary = "A high-level FIFO operation"; + let description = [{ + This operation represents a high-level abstraction of a FIFO. Access to the + FIFO is structural, and thus may be composed with other core RTL dialect + operations. + The fifo operation is configurable with the following parameters: + 1. Depth (cycles) + 2. Almost full/empty thresholds (optional). If not provided, these will + be asserted when the FIFO is full/empty. + + Like `seq.hlmem` there are no guarantees that all possible fifo configuration + are able to be lowered. Available lowering passes will pattern match on the + requested fifo configuration and attempt to provide a legal lowering. + }]; + + let arguments = (ins + HWIntegerType:$input, I1:$rdEn, I1:$wrEn, ClockType:$clk, I1:$rst, + ConfinedAttr]>:$depth, + OptionalAttr]>>:$almostFullThreshold, + OptionalAttr]>>:$almostEmptyThreshold + ); + + let results = (outs + HWIntegerType:$output, I1:$full, I1:$empty, Optional:$almostFull, + Optional:$almostEmpty); + + let assemblyFormat = [{ + `depth` $depth + custom($almostFullThreshold, type($almostFull)) + custom($almostEmptyThreshold, type($almostEmpty)) + `in` $input `rdEn` $rdEn `wrEn` $wrEn `clk` $clk `rst` $rst attr-dict `:` type($input) + }]; + let hasVerifier = 1; +} + def HLMemOp : SeqOp<"hlmem", [ Symbol, Clocked, @@ -161,7 +253,7 @@ def HLMemOp : SeqOp<"hlmem", [ let summary = "Instantiate a high-level memory."; let description = "See the Seq dialect rationale for a longer description"; - let arguments = (ins I1:$clk, I1:$rst, SymbolNameAttr:$sym_name); + let arguments = (ins ClockType:$clk, I1:$rst, SymbolNameAttr:$sym_name); let results = (outs HLMemType:$handle); let extraClassDeclaration = [{ @@ -188,10 +280,10 @@ class HLMemTypeIndexingConstraint def ReadPortOp : SeqOp<"read", [ DeclareOpInterfaceMethods, HLMemTypeIndexingConstraint<"memory", "addresses">, - AttrSizedOperandSegments + AttrSizedOperandSegments ]> { let summary = "Structural read access to a seq.hlmem, with an optional read enable signal."; - let arguments = (ins + let arguments = (ins HLMemType:$memory, Variadic:$addresses, Optional:$rdEn, @@ -219,3 +311,307 @@ def WritePortOp : SeqOp<"write", [ let results = (outs); let hasCustomAssemblyFormat = 1; } + +//===----------------------------------------------------------------------===// +// Clock Gate +//===----------------------------------------------------------------------===// + +def ClockGateOp : SeqOp<"clock_gate", [ + Pure, + DeclareOpInterfaceMethods, + AllTypesMatch<["input", "output"]> +]> { + let summary = "Safely gates a clock with an enable signal"; + let description = [{ + The `seq.clock_gate` enables and disables a clock safely, without glitches, + based on a boolean enable value. If the enable operand is 1, the output + clock produced by the clock gate is identical to the input clock. If the + enable operand is 0, the output clock is a constant zero. + + The `enable` operand is sampled at the rising edge of the input clock; any + changes on the enable before or after that edge are ignored and do not + affect the output clock. + + The `test_enable` operand is optional and if present is OR'd together with + the `enable` operand to determine whether the output clock is gated or not. + + The op can be referred to using an inner symbol. Upon translation, the + symbol will target the instance to the external module it lowers to. + + ``` + %gatedClock = seq.clock_gate %clock, %enable + %gatedClock = seq.clock_gate %clock, %enable, %test_enable + ``` + }]; + + let arguments = (ins + ClockType:$input, + I1:$enable, + Optional:$test_enable, + OptionalAttr:$inner_sym + ); + + let results = (outs ClockType:$output); + let hasFolder = 1; + let hasCanonicalizeMethod = 1; + let assemblyFormat = [{ + $input `,` $enable (`,` $test_enable^)? (`sym` $inner_sym^)? attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// Clock Multiplexer +//===----------------------------------------------------------------------===// + +def ClockMuxOp : SeqOp<"clock_mux", [Pure]> { + let summary = "Safely selects a clock based on a condition"; + let description = [{ + The `seq.clock_mux` op selects a clock from two options. If `cond` is + true, the first clock operand is selected to drive downstream logic. + Otherwise, the second clock is used. + + ``` + %clock = seq.clock_mux %cond, %trueClock, %falseClock + ``` + }]; + + let arguments = (ins I1:$cond, ClockType:$trueClock, ClockType:$falseClock); + let results = (outs ClockType:$result); + + let hasFolder = 1; + + let assemblyFormat = [{ + $cond `,` $trueClock `,` $falseClock attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// Clock Dividers +//===----------------------------------------------------------------------===// + +def ClockDivider : SeqOp<"clock_div", [Pure]> { + let summary = "Produces a clock divided by a power of two"; + let description = [{ + The output clock is phase-aligned to the input clock. + + ``` + %div_clock = seq.clock_div %clock by 1 + ``` + }]; + + let arguments = (ins ClockType:$clockIn, I8Attr:$pow2); + let results = (outs ClockType:$clockOut); + + let assemblyFormat = [{ + $clockIn `by` $pow2 attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// FIRRTL-flavored memory +//===----------------------------------------------------------------------===// + +def FirMemOp : SeqOp<"firmem", [ + MemoryEffects<[MemAlloc]>, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods +]> { + let summary = "A FIRRTL-flavored memory"; + let description = [{ + The `seq.firmem` op represents memories lowered from the FIRRTL dialect. It + is used to capture some of the peculiarities of what FIRRTL expects from + memories, while still representing them at the HW dialect level. + + A `seq.firmem` declares the memory and captures the memory-level parameters + such as width and depth or how read/write collisions are resolved. The read, + write, and read-write ports are expressed as separate operations that take + the declared memory as an operand. + }]; + + let arguments = (ins + I32Attr:$readLatency, + I32Attr:$writeLatency, + RUWAttr:$ruw, + WUWAttr:$wuw, + OptionalAttr:$name, + OptionalAttr:$inner_sym, + OptionalAttr:$init, + OptionalAttr:$prefix, + OptionalAttr:$output_file + ); + let results = (outs FirMemType:$memory); + + let assemblyFormat = [{ + (`sym` $inner_sym^)? `` custom($name) + $readLatency `,` $writeLatency `,` $ruw `,` $wuw + attr-dict `:` type($memory) + }]; +} + +class AddressMatchesFirMem : + TypesMatchWith<"address type should match clog2 of memory depth", + memoryValue, addressValue, [{ + IntegerType::get( + $_self.getContext(), + std::max(1U, llvm::Log2_64_Ceil(cast($_self).getDepth()))) + }]>; + +class DataMatchesFirMem : + TypesMatchWith<"data type should match memory width", + memoryValue, dataValue, [{ + IntegerType::get( + $_self.getContext(), + std::max(1U, cast($_self).getWidth())) + }]>; + +def FirMemReadOp : SeqOp<"firmem.read_port", [ + MemoryEffects<[MemRead]>, + AddressMatchesFirMem<"memory", "address">, + DataMatchesFirMem<"memory", "data"> +]> { + let summary = "A memory read port"; + let description = [{ + The `seq.firmem.read_port` op represents a read port on a `seq.firmem` + memory. It takes the memory as an operand, together with the address to + be read, the clock on which the read is synchronized, and an optional + enable. Omitting the enable operand has the same effect as passing a + constant `true` to it. + }]; + + let arguments = (ins + FirMemType:$memory, + AnySignlessInteger:$address, + ClockType:$clock, + Optional:$enable + ); + let results = (outs AnySignlessInteger:$data); + let assemblyFormat = [{ + $memory `[` $address `]` `,` `clock` $clock + (`enable` $enable^)? + attr-dict `:` type($memory) + }]; + let hasCanonicalizeMethod = 1; +} + +def FirMemWriteOp : SeqOp<"firmem.write_port", [ + MemoryEffects<[MemWrite]>, + AddressMatchesFirMem<"memory", "address">, + DataMatchesFirMem<"memory", "data">, + AttrSizedOperandSegments +]> { + let summary = "A memory write port"; + let description = [{ + The `seq.firmem.write_port` op represents a write port on a `seq.firmem` + memory. It takes the memory as an operand, together with the address and + data to be written, the clock on which the write is synchronized, an + optional enable, and and optional write mask. Omitting the enable operand + has the same effect as passing a constant `true` to it. Omitting the write + mask operand has the same effect as passing an all-ones value to it. A write + mask operand can only be present if the `seq.firmem` specifies a mask width; + otherwise it must be omitted. + }]; + + let arguments = (ins + FirMemType:$memory, + AnySignlessInteger:$address, + ClockType:$clock, + Optional:$enable, + AnySignlessInteger:$data, + Optional:$mask + ); + let assemblyFormat = [{ + $memory `[` $address `]` `=` $data `,` `clock` $clock + (`enable` $enable^)? (`mask` $mask^)? + attr-dict `:` type($memory) (`,` type($mask)^)? + }]; + let hasVerifier = 1; + let hasCanonicalizeMethod = 1; +} + +def FirMemReadWriteOp : SeqOp<"firmem.read_write_port", [ + MemoryEffects<[MemRead, MemWrite]>, + AddressMatchesFirMem<"memory", "address">, + DataMatchesFirMem<"memory", "writeData">, + DataMatchesFirMem<"memory", "readData">, + AttrSizedOperandSegments +]> { + let summary = "A memory read-write port"; + let description = [{ + The `seq.firmem.read_write_port` op represents a read-write port on a + `seq.firmem` memory. It takes the memory as an operand, together with the + address and data to be written, a mode operand indicating whether the port + should perform a read (`mode=0`) or a write (`mode=1`), the clock on which + the read and write is synchronized, an optional enable, and and optional + write mask. Omitting the enable operand has the same effect as passing a + constant `true` to it. Omitting the write mask operand has the same effect + as passing an all-ones value to it. A write mask operand can only be present + if the `seq.firmem` specifies a mask width; otherwise it must be omitted. + }]; + + let arguments = (ins + FirMemType:$memory, + AnySignlessInteger:$address, + ClockType:$clock, + Optional:$enable, + AnySignlessInteger:$writeData, + I1:$mode, + Optional:$mask + ); + let results = (outs AnySignlessInteger:$readData); + let assemblyFormat = [{ + $memory `[` $address `]` `=` $writeData `if` $mode `,` `clock` $clock + (`enable` $enable^)? (`mask` $mask^)? + attr-dict `:` type($memory) (`,` type($mask)^)? + }]; + let hasVerifier = 1; + let hasCanonicalizeMethod = 1; +} + +//===----------------------------------------------------------------------===// +// Tied-off clock +//===----------------------------------------------------------------------===// + +def ConstClockOp : SeqOp<"const_clock", [Pure, ConstantLike]> { + let summary = "Produce constant clock value"; + let description = [{ + The constant operation produces a constant clock value. + ``` + %clock = seq.const_clock low + ``` + }]; + + let arguments = (ins ClockConstAttr:$value); + let results = (outs ClockType:$result); + + let assemblyFormat = "$value attr-dict"; + + let hasFolder = 1; +} + +//===----------------------------------------------------------------------===// +// Clock cast +//===----------------------------------------------------------------------===// + +def ToClockOp : SeqOp<"to_clock", [Pure]> { + let summary = "Cast from a wire type to a clock type"; + + let arguments = (ins I1:$input); + let results = (outs ClockType:$output); + + let assemblyFormat = "$input attr-dict"; + + let hasFolder = 1; + let hasCanonicalizeMethod = 1; +} + +def FromClockOp : SeqOp<"from_clock", [Pure]> { + let summary = "Cast from a clock type to a wire type"; + + let arguments = (ins ClockType:$input); + let results = (outs I1:$output); + + let assemblyFormat = "$input attr-dict"; + + let hasFolder = 1; + let hasCanonicalizeMethod = 1; +} diff --git a/include/circt/Dialect/Seq/SeqPasses.h b/include/circt/Dialect/Seq/SeqPasses.h index c61ba75641c9..846e8014956f 100644 --- a/include/circt/Dialect/Seq/SeqPasses.h +++ b/include/circt/Dialect/Seq/SeqPasses.h @@ -19,19 +19,17 @@ namespace circt { namespace seq { -#define GEN_PASS_DECL +#define GEN_PASS_DECL_EXTERNALIZECLOCKGATE #include "circt/Dialect/Seq/SeqPasses.h.inc" -#undef GEN_PASS_DECL -std::unique_ptr createSeqLowerToSVPass(); -std::unique_ptr -createSeqFIRRTLLowerToSVPass(const LowerSeqFIRRTLToSVOptions &options = {}); std::unique_ptr createLowerSeqHLMemPass(); +std::unique_ptr +createExternalizeClockGatePass(const ExternalizeClockGateOptions &options = {}); +std::unique_ptr createLowerSeqFIFOPass(); /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION #include "circt/Dialect/Seq/SeqPasses.h.inc" - } // namespace seq } // namespace circt diff --git a/include/circt/Dialect/Seq/SeqPasses.td b/include/circt/Dialect/Seq/SeqPasses.td index 603a46d2f4fa..eb87dd7c7171 100644 --- a/include/circt/Dialect/Seq/SeqPasses.td +++ b/include/circt/Dialect/Seq/SeqPasses.td @@ -15,33 +15,40 @@ include "mlir/Pass/PassBase.td" -def LowerSeqToSV: Pass<"lower-seq-to-sv", "mlir::ModuleOp"> { - let summary = "Lower sequential ops to SV."; - let constructor = "circt::seq::createSeqLowerToSVPass()"; - let dependentDialects = ["circt::sv::SVDialect"]; +def LowerSeqFIFO : Pass<"lower-seq-fifo", "hw::HWModuleOp"> { + let summary = "Lower seq.fifo ops"; + let constructor = "circt::seq::createLowerSeqFIFOPass()"; + let dependentDialects = ["circt::hw::HWDialect", "circt::comb::CombDialect"]; } -def LowerSeqFIRRTLToSV: Pass<"lower-seq-firrtl-to-sv", "hw::HWModuleOp"> { - let summary = "Lower sequential firrtl ops to SV."; - let constructor = "circt::seq::createSeqFIRRTLLowerToSVPass()"; +def LowerSeqHLMem: Pass<"lower-seq-hlmem", "hw::HWModuleOp"> { + let summary = "Lowers seq.hlmem operations."; + let constructor = "circt::seq::createLowerSeqHLMemPass()"; let dependentDialects = ["circt::sv::SVDialect"]; +} + +def ExternalizeClockGate: Pass<"externalize-clock-gate", "mlir::ModuleOp"> { + let summary = "Convert seq.clock_gate ops into hw.module.extern instances"; + let constructor = "circt::seq::createExternalizeClockGatePass()"; + let dependentDialects = ["circt::hw::HWDialect", "circt::comb::CombDialect"]; let options = [ - Option<"disableRegRandomization", "disable-reg-randomization", "bool", "false", - "Disable emission of register randomization code">, - Option<"addVivadoRAMAddressConflictSynthesisBugWorkaround", - "add-vivado-ram-address-conflict-synthesis-bug-workaround", "bool", "false", - "Add a vivado attribute to specify a ram style of array registers"> + Option<"moduleName", "name", "std::string", "\"CKG\"", + "Name of the external clock gate module">, + Option<"inputName", "input", "std::string", "\"I\"", + "Name of the clock input">, + Option<"outputName", "output", "std::string", "\"O\"", + "Name of the gated clock output">, + Option<"enableName", "enable", "std::string", "\"E\"", + "Name of the enable input">, + Option<"testEnableName", "test-enable", "std::string", "\"TE\"", + "Name of the optional test enable input">, + Option<"instName", "instance-name", "std::string", "\"ckg\"", + "Name of the generated instances"> ]; let statistics = [ - Statistic<"numSubaccessRestored", "num-subaccess-restored", - "Number of lhs subaccess operations restored "> + Statistic<"numClockGatesConverted", "num-clock-gates-converted", + "Number of clock gates converted to external module instances"> ]; } -def LowerSeqHLMem: Pass<"lower-seq-hlmem", "hw::HWModuleOp"> { - let summary = "Lowers seq.hlmem operations."; - let constructor = "circt::seq::createLowerSeqHLMemPass()"; - let dependentDialects = ["circt::sv::SVDialect"]; -} - #endif // CIRCT_DIALECT_SEQ_SEQPASSES diff --git a/include/circt/Dialect/Seq/SeqTypes.h b/include/circt/Dialect/Seq/SeqTypes.h index 9aebe1f372c6..c7e2347f9693 100644 --- a/include/circt/Dialect/Seq/SeqTypes.h +++ b/include/circt/Dialect/Seq/SeqTypes.h @@ -23,4 +23,13 @@ #define GET_TYPEDEF_CLASSES #include "circt/Dialect/Seq/SeqTypes.h.inc" +namespace circt { +namespace seq { + +/// Returns true if the type is `i1` or `seq.clock` +bool isClockOrI1Type(Type ty); + +} // namespace seq +} // namespace circt + #endif // CIRCT_DIALECT_SEQ_SEQTYPES_H diff --git a/include/circt/Dialect/Seq/SeqTypes.td b/include/circt/Dialect/Seq/SeqTypes.td index 5ec64cecb8fe..bddee693f443 100644 --- a/include/circt/Dialect/Seq/SeqTypes.td +++ b/include/circt/Dialect/Seq/SeqTypes.td @@ -10,6 +10,10 @@ // //===----------------------------------------------------------------------===// +#ifndef CIRCT_DIALECT_SEQ_SEQTYPES +#define CIRCT_DIALECT_SEQ_SEQTYPES + +include "circt/Dialect/Seq/SeqDialect.td" include "mlir/IR/BuiltinTypeInterfaces.td" class SeqType traits = []> : TypeDef { } @@ -27,7 +31,7 @@ def HLMemType : SeqType<"HLMem",[ The HLMemType represents the type of an addressable memory structure. The type is inherently multidimensional. Dimensions must be known integer values. - Note: unidimensional memories are represented as <1x{element type}> - + Note: unidimensional memories are represented as <1x{element type}> - <{element type}> is illegal. }]; @@ -63,3 +67,30 @@ def HLMemType : SeqType<"HLMem",[ let hasCustomAssemblyFormat = 1; let genVerifyDecl = 1; } + +def FirMemType : SeqType<"FirMem"> { + let summary = "A FIRRTL-flavored memory"; + let description = [{ + The `!seq.firmem` type represents a FIRRTL-flavored memory declared by a + `seq.firmem` operation. It captures the parameters of the memory that are + relevant to the read, write, and read-write ports, such as width and depth. + }]; + let mnemonic = "firmem"; + let parameters = (ins + "uint64_t":$depth, + "uint32_t":$width, + OptionalParameter<"std::optional">:$maskWidth + ); + let assemblyFormat = "`<` $depth `x` $width (`,` `mask` $maskWidth^)? `>`"; +} + +def ClockType : SeqType<"Clock"> { + let summary = "A type for clock-carrying wires"; + let description = [{ + The `!seq.clock` type represents signals which can be used to drive the + clock input of sequential operations. + }]; + let mnemonic = "clock"; +} + +#endif // CIRCT_DIALECT_SEQ_SEQTYPES diff --git a/include/circt/Dialect/SystemC/SystemC.td b/include/circt/Dialect/SystemC/SystemC.td index f29088109fb4..40ffe8abfa20 100644 --- a/include/circt/Dialect/SystemC/SystemC.td +++ b/include/circt/Dialect/SystemC/SystemC.td @@ -15,7 +15,7 @@ include "mlir/IR/AttrTypeBase.td" include "mlir/IR/OpBase.td" -include "mlir/IR/FunctionInterfaces.td" +include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/IR/OpAsmInterface.td" include "mlir/IR/RegionKindInterface.td" include "mlir/IR/SymbolInterfaces.td" diff --git a/include/circt/Dialect/SystemC/SystemCDialect.td b/include/circt/Dialect/SystemC/SystemCDialect.td index 88092a625721..94939c8ed4ab 100644 --- a/include/circt/Dialect/SystemC/SystemCDialect.td +++ b/include/circt/Dialect/SystemC/SystemCDialect.td @@ -35,6 +35,9 @@ def SystemCDialect : Dialect { }]; let useDefaultTypePrinterParser = 0; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; } #endif // CIRCT_DIALECT_SYSTEMC_SYSTEMCDIALECT diff --git a/include/circt/Dialect/SystemC/SystemCExpressions.td b/include/circt/Dialect/SystemC/SystemCExpressions.td index 9f98414cd21a..555f3bf0aafb 100644 --- a/include/circt/Dialect/SystemC/SystemCExpressions.td +++ b/include/circt/Dialect/SystemC/SystemCExpressions.td @@ -31,6 +31,7 @@ def SignalReadOp : SystemCOp<"signal.read", [Pure, std::optional loc, ValueRange operands, DictionaryAttr attrs, + mlir::OpaqueProperties properties, mlir::RegionRange regions, SmallVectorImpl &results) { results.push_back(systemc::getSignalBaseType(operands[0].getType())); diff --git a/include/circt/Dialect/SystemC/SystemCOps.h b/include/circt/Dialect/SystemC/SystemCOps.h index 08181d978959..2d6417f68b2c 100644 --- a/include/circt/Dialect/SystemC/SystemCOps.h +++ b/include/circt/Dialect/SystemC/SystemCOps.h @@ -22,8 +22,8 @@ #include "circt/Support/LLVM.h" #include "mlir/Dialect/EmitC/IR/EmitC.h" #include "mlir/IR/BuiltinOps.h" -#include "mlir/IR/FunctionInterfaces.h" #include "mlir/Interfaces/CallInterfaces.h" +#include "mlir/Interfaces/FunctionInterfaces.h" #include "mlir/Interfaces/InferTypeOpInterface.h" #define GET_OP_CLASSES diff --git a/include/circt/Dialect/SystemC/SystemCStatements.td b/include/circt/Dialect/SystemC/SystemCStatements.td index 16e0d60113a2..586333a9fb3b 100644 --- a/include/circt/Dialect/SystemC/SystemCStatements.td +++ b/include/circt/Dialect/SystemC/SystemCStatements.td @@ -93,7 +93,7 @@ def SensitiveOp : SystemCOp<"sensitive", [HasParent<"CtorOp">]> { def InstanceDeclOp : SystemCOp<"instance.decl", [ DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, HasCustomSSAName, SystemCNameDeclOpInterface, HasParent<"SCModuleOp"> @@ -130,11 +130,8 @@ def InstanceDeclOp : SystemCOp<"instance.decl", [ return getInstanceHandle().getType().cast(); } - StringRef getInstanceName() { return getName(); } - StringAttr getInstanceNameAttr() { return getNameAttr(); } - /// Lookup the module for the symbol. This returns null on invalid IR. - Operation *getReferencedModule(const hw::HWSymbolCache *cache); + Operation *getReferencedModuleCached(const hw::HWSymbolCache *cache); //===------------------------------------------------------------------===// // SymbolOpInterface Methods @@ -142,6 +139,11 @@ def InstanceDeclOp : SystemCOp<"instance.decl", [ /// An InstanceDeclOp may not optionally define a symbol. bool isOptionalSymbol() { return false; } + + //===------------------------------------------------------------------===// + // PortList Methods + //===------------------------------------------------------------------===// + SmallVector<::circt::hw::PortInfo> getPortList(); }]; } @@ -255,6 +257,10 @@ def CallIndirectOp : SystemCOp<"cpp.call_indirect", [ }]>]; let extraClassDeclaration = [{ + MutableOperandRange getArgOperandsMutable() { + return getCalleeOperandsMutable(); + } + /// Get the argument operands to the called function. operand_range getArgOperands() { return {arg_operand_begin(), arg_operand_end()}; @@ -265,6 +271,13 @@ def CallIndirectOp : SystemCOp<"cpp.call_indirect", [ /// Return the callee of this operation. CallInterfaceCallable getCallableForCallee() { return getCallee(); } + + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + assert(0 && "setCallee not possible"); + abort(); + } + }]; let assemblyFormat = [{ @@ -321,6 +334,10 @@ def CallOp : SystemCOp<"cpp.call", [ let extraClassDeclaration = [{ FunctionType getCalleeType(); + MutableOperandRange getArgOperandsMutable() { + return getCalleeOperandsMutable(); + } + /// Get the argument operands to the called function. operand_range getArgOperands() { return {arg_operand_begin(), arg_operand_end()}; @@ -333,6 +350,12 @@ def CallOp : SystemCOp<"cpp.call", [ CallInterfaceCallable getCallableForCallee() { return (*this)->getAttrOfType("callee"); } + + /// Set the callee for this operation. + void setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + (*this)->setAttr(getCalleeAttrName(), callee.get()); + } + }]; let assemblyFormat = [{ diff --git a/include/circt/Dialect/SystemC/SystemCStructure.td b/include/circt/Dialect/SystemC/SystemCStructure.td index 3457116b1f61..128b410a30ad 100644 --- a/include/circt/Dialect/SystemC/SystemCStructure.td +++ b/include/circt/Dialect/SystemC/SystemCStructure.td @@ -13,7 +13,7 @@ def SCModuleOp : SystemCOp<"module", [ Symbol, - DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, FunctionOpInterface, IsolatedFromAbove, SingleBlock, @@ -60,16 +60,6 @@ def SCModuleOp : SystemCOp<"module", [ return RegionKind::Graph; } - /// Returns the argument types of this function. - ArrayRef getArgumentTypes() { - return getFunctionType().getInputs(); - } - - /// Returns the result types of this function. - ArrayRef getResultTypes() { - return getFunctionType().getResults(); - } - // Use FunctionOpInterface traits's getFunctionBody method. using mlir::detail::FunctionOpInterfaceTrait::getFunctionBody; @@ -83,16 +73,15 @@ def SCModuleOp : SystemCOp<"module", [ llvm::filter_iterator>>; - void getPortInfoList(SmallVectorImpl &portInfoList); - PortDirectionRange getPortsOfDirection(hw::PortDirection direction); + PortDirectionRange getPortsOfDirection(hw::ModulePort::Direction direction); PortDirectionRange getInputPorts() { - return getPortsOfDirection(hw::PortDirection::INPUT); + return getPortsOfDirection(hw::ModulePort::Direction::Input); } PortDirectionRange getOutputPorts() { - return getPortsOfDirection(hw::PortDirection::OUTPUT); + return getPortsOfDirection(hw::ModulePort::Direction::Output); } PortDirectionRange getInOutPorts() { - return getPortsOfDirection(hw::PortDirection::INOUT); + return getPortsOfDirection(hw::ModulePort::Direction::InOut); } // Return the Ctor operation in this module's body or create one if none @@ -102,20 +91,11 @@ def SCModuleOp : SystemCOp<"module", [ // Return the Destructor operation in this module's body or create one if // none exists yet. systemc::DestructorOp getOrCreateDestructor(); - }]; - - let extraClassDefinition = [{ - size_t $cppClass::getNumPorts() { - return getPortNames().size(); - } - circt::hw::InnerSymAttr $cppClass::getPortSymbolAttr(size_t portIndex) { - for (NamedAttribute argAttr : - mlir::function_interface_impl::getArgAttrs(*this, portIndex)) - if (auto sym = argAttr.getValue().dyn_cast()) - return sym; - return circt::hw::InnerSymAttr(); - } + //===------------------------------------------------------------------===// + // PortList Methods + //===------------------------------------------------------------------===// + SmallVector<::circt::hw::PortInfo> getPortList(); }]; } @@ -322,7 +302,6 @@ def DestructorOp : SystemCOp<"cpp.destructor", [ def FuncOp : SystemCOp<"cpp.func", [ AutomaticAllocationScope, - CallableOpInterface, FunctionOpInterface, IsolatedFromAbove, OpAsmOpInterface, @@ -416,31 +395,9 @@ def FuncOp : SystemCOp<"cpp.func", [ /// return null in the case of an external callable object, e.g. an external /// function. ::mlir::Region *getCallableRegion() { - return isExternal() ? nullptr : &getBody(); - } - - /// Returns the results types that the callable region produces when - /// executed. - ArrayRef getCallableResults() { - return getFunctionType().getResults(); - } - - /// Returns the argument attributes for all callable region arguments or - /// null if there are none. - ArrayAttr getCallableArgAttrs() { - return getArgAttrs().value_or(nullptr); + return &getBody(); } - /// Returns the result attributes for all callable region results or - /// null if there are none. - ArrayAttr getCallableResAttrs() { - return getResAttrs().value_or(nullptr); - } - - //===------------------------------------------------------------------===// - // FunctionOpInterface Methods - //===------------------------------------------------------------------===// - /// Returns the argument types of this function. ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } diff --git a/include/circt/Dialect/Verif/CMakeLists.txt b/include/circt/Dialect/Verif/CMakeLists.txt new file mode 100644 index 000000000000..b95dc2e169bb --- /dev/null +++ b/include/circt/Dialect/Verif/CMakeLists.txt @@ -0,0 +1,2 @@ +add_circt_dialect(Verif verif) +add_circt_doc(VerifOps Dialects/VerifOps -gen-op-doc) diff --git a/include/circt/Dialect/Verif/Verif.td b/include/circt/Dialect/Verif/Verif.td new file mode 100644 index 000000000000..30fae85b0ab5 --- /dev/null +++ b/include/circt/Dialect/Verif/Verif.td @@ -0,0 +1,15 @@ +//===- Verif.td - Verif dialect definition -----------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_VERIF_VERIF_TD +#define CIRCT_DIALECT_VERIF_VERIF_TD + +include "circt/Dialect/Verif/VerifDialect.td" +include "circt/Dialect/Verif/VerifOps.td" + +#endif // CIRCT_DIALECT_VERIF_VERIF_TD diff --git a/include/circt/Dialect/Verif/VerifDialect.h b/include/circt/Dialect/Verif/VerifDialect.h new file mode 100644 index 000000000000..99735e84519a --- /dev/null +++ b/include/circt/Dialect/Verif/VerifDialect.h @@ -0,0 +1,18 @@ +//===- VerifDialect.h - Verif dialect definition ----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_VERIF_VERIFDIALECT_H +#define CIRCT_DIALECT_VERIF_VERIFDIALECT_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Dialect.h" + +#include "circt/Dialect/Verif/VerifDialect.h.inc" + +#endif // CIRCT_DIALECT_VERIF_VERIFDIALECT_H diff --git a/include/circt/Dialect/Verif/VerifDialect.td b/include/circt/Dialect/Verif/VerifDialect.td new file mode 100644 index 000000000000..b7e13c4b4a2d --- /dev/null +++ b/include/circt/Dialect/Verif/VerifDialect.td @@ -0,0 +1,25 @@ +//===- VerifDialect.td - Verif dialect definition ----------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_VERIF_VERIFDIALECT_TD +#define CIRCT_DIALECT_VERIF_VERIFDIALECT_TD + +include "mlir/IR/OpBase.td" + +def VerifDialect : Dialect { + let name = "verif"; + let summary = "Verification constructs and utilities."; + // See `docs/Dialect/Verif.md` for detailed dialect documentation. + let cppNamespace = "circt::verif"; + let hasConstantMaterializer = 1; + + // This will be the default after next LLVM bump. + let usePropertiesForAttributes = 1; +} + +#endif // CIRCT_DIALECT_VERIF_VERIFDIALECT_TD diff --git a/include/circt/Dialect/Verif/VerifOps.h b/include/circt/Dialect/Verif/VerifOps.h new file mode 100644 index 000000000000..f1c8b4e62174 --- /dev/null +++ b/include/circt/Dialect/Verif/VerifOps.h @@ -0,0 +1,21 @@ +//===- VerifOps.h - Verif dialect operations --------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_VERIF_VERIFOPS_H +#define CIRCT_DIALECT_VERIF_VERIFOPS_H + +#include "circt/Dialect/HW/HWDialect.h" +#include "circt/Dialect/HW/HWTypes.h" +#include "circt/Dialect/Verif/VerifDialect.h" +#include "mlir/Bytecode/BytecodeOpInterface.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" + +#define GET_OP_CLASSES +#include "circt/Dialect/Verif/Verif.h.inc" + +#endif // CIRCT_DIALECT_VERIF_VERIFOPS_H diff --git a/include/circt/Dialect/Verif/VerifOps.td b/include/circt/Dialect/Verif/VerifOps.td new file mode 100644 index 000000000000..baaafe127edf --- /dev/null +++ b/include/circt/Dialect/Verif/VerifOps.td @@ -0,0 +1,112 @@ +//===- VerifOps.td - Verif dialect operations --------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_VERIF_VERIFOPS_TD +#define CIRCT_DIALECT_VERIF_VERIFOPS_TD + +include "circt/Dialect/Verif/VerifDialect.td" +include "circt/Dialect/LTL/LTLTypes.td" +include "circt/Dialect/HW/HWTypes.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +class VerifOp traits = []> : + Op; + +//===----------------------------------------------------------------------===// +// Assertions +//===----------------------------------------------------------------------===// + +class AssertLikeOp traits = []> : + VerifOp { + let arguments = (ins LTLAnyPropertyType:$property, + OptionalAttr:$label); + let assemblyFormat = [{ + $property (`label` $label^)? attr-dict `:` type($property) + }]; +} + +def AssertOp : AssertLikeOp<"assert"> { + let summary = "Assert that a property holds."; +} + +def AssumeOp : AssertLikeOp<"assume"> { + let summary = "Assume that a property holds."; +} + +def CoverOp : AssertLikeOp<"cover"> { + let summary = "Ensure that a property can hold."; +} + +//===----------------------------------------------------------------------===// +// Printing Formatted Messages +//===----------------------------------------------------------------------===// + +def FormatVerilogStringOp : VerifOp<"format_verilog_string", [ + Pure + ]> { + let summary = "Creates a formatted string."; + let description = [{ + Creates a formatted string suitable for printing via the `verif.print` op. + The formatting syntax is expected to be identical to verilog string + formatting to keep things simple for emission. + If we in the future would like to be less tied to verilog formatting, + please ask your friendly neighbourhood compiler engineer to e.g. implement + a `FormatStringOp` which itself may lower to a `FormatVerilogStringOp`. + }]; + let arguments = (ins + StrAttr:$formatString, + Variadic:$substitutions); + + let results = (outs HWStringType:$str); + let assemblyFormat = [{ + $formatString `(` $substitutions `)` `:` type($substitutions) attr-dict + }]; +} + +def PrintOp : VerifOp<"print", []> { + let summary = "Prints a message."; + let arguments = (ins HWStringType:$string); + let assemblyFormat = [{ + $string attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// Reset and Power-Cycling Detection +//===----------------------------------------------------------------------===// + +def HasBeenResetOp : VerifOp<"has_been_reset", [Pure]> { + let summary = "Check that a proper reset has been seen."; + let description = [{ + The result of `verif.has_been_reset` reads as 0 immediately after simulation + startup and after each power-cycle in a power-aware simulation. The result + remains 0 before and during reset and only switches to 1 after the reset is + deasserted again. + + This is a useful utility to disable the evaluation of assertions and other + verification constructs in the IR before the circuit being tested has been + properly reset. Verification failures due to uninitialized or randomized + initial state can thus be prevented. + + Using the result of `verif.has_been_reset` to enable verification is more + powerful and proper than just disabling verification during reset. The + latter does not properly handle the period of time between simulation + startup or power-cycling and the start of reset. `verif.has_been_reset` is + guaranteed to produce a 0 value in that period, as well as during the reset. + }]; + let arguments = (ins I1:$clock, I1:$reset, BoolAttr:$async); + let results = (outs I1:$result); + let assemblyFormat = [{ + $clock `,` custom($async, "\"async\"", "\"sync\"") + $reset attr-dict + }]; + let hasFolder = true; +} + +#endif // CIRCT_DIALECT_VERIF_VERIFOPS_TD diff --git a/include/circt/Dialect/Verif/VerifVisitors.h b/include/circt/Dialect/Verif/VerifVisitors.h new file mode 100644 index 000000000000..7ef700c8fff3 --- /dev/null +++ b/include/circt/Dialect/Verif/VerifVisitors.h @@ -0,0 +1,59 @@ +//===- VerifVisitors.h - Verif dialect visitors -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_VERIF_VERIFVISITORS_H +#define CIRCT_DIALECT_VERIF_VERIFVISITORS_H + +#include "circt/Dialect/Verif/VerifOps.h" +#include "llvm/ADT/TypeSwitch.h" + +namespace circt { +namespace verif { +template +class Visitor { +public: + ResultType dispatchVerifVisitor(Operation *op, ExtraArgs... args) { + auto *thisCast = static_cast(this); + return TypeSwitch(op) + .template Case([&](auto op) -> ResultType { + return thisCast->visitVerif(op, args...); + }) + .Default([&](auto) -> ResultType { + return thisCast->visitInvalidVerif(op, args...); + }); + } + + /// This callback is invoked on any non-verif operations. + ResultType visitInvalidVerif(Operation *op, ExtraArgs... args) { + op->emitOpError("is not a verif operation"); + abort(); + } + + /// This callback is invoked on any verif operations that were not handled by + /// their concrete `visitVerif(...)` callback. + ResultType visitUnhandledVerif(Operation *op, ExtraArgs... args) { + return ResultType(); + } + +#define HANDLE(OPTYPE, OPKIND) \ + ResultType visitVerif(OPTYPE op, ExtraArgs... args) { \ + return static_cast(this)->visit##OPKIND##Verif(op, \ + args...); \ + } + + HANDLE(AssertOp, Unhandled); + HANDLE(AssumeOp, Unhandled); + HANDLE(CoverOp, Unhandled); +#undef HANDLE +}; + +} // namespace verif +} // namespace circt + +#endif // CIRCT_DIALECT_VERIF_VERIFVISITORS_H diff --git a/include/circt/Firtool/Firtool.h b/include/circt/Firtool/Firtool.h new file mode 100644 index 000000000000..6e347355e41a --- /dev/null +++ b/include/circt/Firtool/Firtool.h @@ -0,0 +1,354 @@ +//===- Firtool.h - Definitions for the firtool pipeline setup ---*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This library parses options for firtool and sets up its pipeline. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_FIRTOOL_FIRTOOL_H +#define CIRCT_FIRTOOL_FIRTOOL_H + +#include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Dialect/Seq/SeqPasses.h" +#include "circt/Support/LLVM.h" +#include "mlir/Pass/PassManager.h" +#include "llvm/Support/CommandLine.h" + +namespace circt { +namespace firtool { +// Remember to sync changes to C-API +struct FirtoolOptions { + llvm::cl::OptionCategory &category; + + llvm::cl::opt outputFilename{ + "o", llvm::cl::desc("Output filename, or directory for split output"), + llvm::cl::value_desc("filename"), llvm::cl::init("-"), + llvm::cl::cat(category)}; + + llvm::cl::opt disableAnnotationsUnknown{ + "disable-annotation-unknown", + llvm::cl::desc("Ignore unknown annotations when parsing"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt disableAnnotationsClassless{ + "disable-annotation-classless", + llvm::cl::desc("Ignore annotations without a class when parsing"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt lowerAnnotationsNoRefTypePorts{ + "lower-annotations-no-ref-type-ports", + llvm::cl::desc( + "Create real ports instead of ref type ports when resolving " + "wiring problems inside the LowerAnnotations pass"), + llvm::cl::init(false), llvm::cl::Hidden, llvm::cl::cat(category)}; + + llvm::cl::opt + preserveAggregate{ + "preserve-aggregate", llvm::cl::desc("Specify input file format:"), + llvm::cl::values( + clEnumValN(circt::firrtl::PreserveAggregate::None, "none", + "Preserve no aggregate"), + clEnumValN(circt::firrtl::PreserveAggregate::OneDimVec, "1d-vec", + "Preserve only 1d vectors of ground type"), + clEnumValN(circt::firrtl::PreserveAggregate::Vec, "vec", + "Preserve only vectors"), + clEnumValN(circt::firrtl::PreserveAggregate::All, "all", + "Preserve vectors and bundles")), + llvm::cl::init(circt::firrtl::PreserveAggregate::None), + llvm::cl::cat(category)}; + + llvm::cl::opt preserveMode{ + "preserve-values", + llvm::cl::desc("Specify the values which can be optimized away"), + llvm::cl::values( + clEnumValN(firrtl::PreserveValues::Strip, "strip", + "Strip all names. No name is preserved"), + clEnumValN(firrtl::PreserveValues::None, "none", + "Names could be preserved by best-effort unlike `strip`"), + clEnumValN(firrtl::PreserveValues::Named, "named", + "Preserve values with meaningful names"), + clEnumValN(firrtl::PreserveValues::All, "all", + "Preserve all values")), + llvm::cl::init(firrtl::PreserveValues::None), llvm::cl::cat(category)}; + + llvm::cl::opt enableDebugInfo{ + "g", llvm::cl::desc("Enable the generation of debug information"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + // Build mode options. + enum BuildMode { BuildModeDebug, BuildModeRelease }; + llvm::cl::opt buildMode{ + "O", llvm::cl::desc("Controls how much optimization should be performed"), + llvm::cl::values(clEnumValN(BuildModeDebug, "debug", + "Compile with only necessary optimizations"), + clEnumValN(BuildModeRelease, "release", + "Compile with optimizations")), + llvm::cl::cat(category)}; + + llvm::cl::opt disableOptimization{ + "disable-opt", llvm::cl::desc("Disable optimizations"), + llvm::cl::cat(category)}; + + llvm::cl::opt exportChiselInterface{ + "export-chisel-interface", + llvm::cl::desc("Generate a Scala Chisel interface to the top level " + "module of the firrtl circuit"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt chiselInterfaceOutDirectory{ + "chisel-interface-out-dir", + llvm::cl::desc( + "The output directory for generated Chisel interface files"), + llvm::cl::init(""), llvm::cl::cat(category)}; + + llvm::cl::opt vbToBV{ + "vb-to-bv", + llvm::cl::desc("Transform vectors of bundles to bundles of vectors"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt dedup{ + "dedup", llvm::cl::desc("Deduplicate structurally identical modules"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt noDedup{ + "no-dedup", + llvm::cl::desc("Disable deduplication of structurally identical modules"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt companionMode{ + "grand-central-companion-mode", + llvm::cl::desc("Specifies the handling of Grand Central companions"), + ::llvm::cl::values( + clEnumValN(firrtl::CompanionMode::Bind, "bind", + "Lower companion instances to SystemVerilog binds"), + clEnumValN(firrtl::CompanionMode::Instantiate, "instantiate", + "Instantiate companions in the design"), + clEnumValN(firrtl::CompanionMode::Drop, "drop", + "Remove companions from the design")), + llvm::cl::init(firrtl::CompanionMode::Bind), + llvm::cl::Hidden, + llvm::cl::cat(category)}; + + llvm::cl::opt disableAggressiveMergeConnections{ + "disable-aggressive-merge-connections", + llvm::cl::desc( + "Disable aggressive merge connections (i.e. merge all field-level " + "connections into bulk connections)"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt disableHoistingHWPassthrough{ + "disable-hoisting-hw-passthrough", + llvm::cl::desc("Disable hoisting HW passthrough signals"), + llvm::cl::init(true), llvm::cl::Hidden, llvm::cl::cat(category)}; + + llvm::cl::opt emitOMIR{ + "emit-omir", llvm::cl::desc("Emit OMIR annotations to a JSON file"), + llvm::cl::init(true), llvm::cl::cat(category)}; + + llvm::cl::opt omirOutFile{ + "output-omir", llvm::cl::desc("File name for the output omir"), + llvm::cl::init(""), llvm::cl::cat(category)}; + + llvm::cl::opt lowerMemories{ + "lower-memories", + llvm::cl::desc("Lower memories to have memories with masks as an " + "array with one memory per ground type"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt blackBoxRootPath{ + "blackbox-path", + llvm::cl::desc( + "Optional path to use as the root of black box annotations"), + llvm::cl::value_desc("path"), llvm::cl::init(""), + llvm::cl::cat(category)}; + + llvm::cl::opt replSeqMem{ + "repl-seq-mem", + llvm::cl::desc("Replace the seq mem for macro replacement and emit " + "relevant metadata"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt replSeqMemFile{ + "repl-seq-mem-file", llvm::cl::desc("File name for seq mem metadata"), + llvm::cl::init(""), llvm::cl::cat(category)}; + + llvm::cl::opt extractTestCode{ + "extract-test-code", llvm::cl::desc("Run the extract test code pass"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt ignoreReadEnableMem{ + "ignore-read-enable-mem", + llvm::cl::desc("Ignore the read enable signal, instead of " + "assigning X on read disable"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + enum class RandomKind { None, Mem, Reg, All }; + + llvm::cl::opt disableRandom{ + llvm::cl::desc( + "Disable random initialization code (may break semantics!)"), + llvm::cl::values( + clEnumValN(RandomKind::Mem, "disable-mem-randomization", + "Disable emission of memory randomization code"), + clEnumValN(RandomKind::Reg, "disable-reg-randomization", + "Disable emission of register randomization code"), + clEnumValN(RandomKind::All, "disable-all-randomization", + "Disable emission of all randomization code")), + llvm::cl::init(RandomKind::None), llvm::cl::cat(category)}; + + llvm::cl::opt outputAnnotationFilename{ + "output-annotation-file", + llvm::cl::desc("Optional output annotation file"), + llvm::cl::CommaSeparated, llvm::cl::value_desc("filename"), + llvm::cl::cat(category)}; + + llvm::cl::opt enableAnnotationWarning{ + "warn-on-unprocessed-annotations", + llvm::cl::desc( + "Warn about annotations that were not removed by lower-to-hw"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt addMuxPragmas{ + "add-mux-pragmas", + llvm::cl::desc("Annotate mux pragmas for memory array access"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt emitChiselAssertsAsSVA{ + "emit-chisel-asserts-as-sva", + llvm::cl::desc("Convert all chisel asserts into SVA"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt emitSeparateAlwaysBlocks{ + "emit-separate-always-blocks", + llvm::cl::desc( + "Prevent always blocks from being merged and emit constructs into " + "separate always blocks whenever possible"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt etcDisableInstanceExtraction{ + "etc-disable-instance-extraction", + llvm::cl::desc("Disable extracting instances only that feed test code"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt etcDisableRegisterExtraction{ + "etc-disable-register-extraction", + llvm::cl::desc("Disable extracting registers that only feed test code"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt etcDisableModuleInlining{ + "etc-disable-module-inlining", + llvm::cl::desc("Disable inlining modules that only feed test code"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt addVivadoRAMAddressConflictSynthesisBugWorkaround{ + "add-vivado-ram-address-conflict-synthesis-bug-workaround", + llvm::cl::desc( + "Add a vivado specific SV attribute (* ram_style = " + "\"distributed\" *) to unpacked array registers as a workaronud " + "for a vivado synthesis bug that incorrectly modifies " + "address conflict behavivor of combinational memories"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + //===----------------------------------------------------------------------=== + // External Clock Gate Options + //===----------------------------------------------------------------------=== + + seq::ExternalizeClockGateOptions clockGateOpts; + + llvm::cl::opt ckgModuleName{ + "ckg-name", llvm::cl::desc("Clock gate module name"), + llvm::cl::location(clockGateOpts.moduleName), + llvm::cl::init("EICG_wrapper"), llvm::cl::cat(category)}; + + llvm::cl::opt ckgInputName{ + "ckg-input", llvm::cl::desc("Clock gate input port name"), + llvm::cl::location(clockGateOpts.inputName), llvm::cl::init("in"), + llvm::cl::cat(category)}; + + llvm::cl::opt ckgOutputName{ + "ckg-output", llvm::cl::desc("Clock gate output port name"), + llvm::cl::location(clockGateOpts.outputName), llvm::cl::init("out"), + llvm::cl::cat(category)}; + + llvm::cl::opt ckgEnableName{ + "ckg-enable", llvm::cl::desc("Clock gate enable port name"), + llvm::cl::location(clockGateOpts.enableName), llvm::cl::init("en"), + llvm::cl::cat(category)}; + + llvm::cl::opt ckgTestEnableName{ + "ckg-test-enable", + llvm::cl::desc("Clock gate test enable port name (optional)"), + llvm::cl::location(clockGateOpts.testEnableName), + llvm::cl::init("test_en"), llvm::cl::cat(category)}; + + llvm::cl::opt exportModuleHierarchy{ + "export-module-hierarchy", + llvm::cl::desc("Export module and instance hierarchy as JSON"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + llvm::cl::opt stripFirDebugInfo{ + "strip-fir-debug-info", + llvm::cl::desc( + "Disable source fir locator information in output Verilog"), + llvm::cl::init(true), llvm::cl::cat(category)}; + + llvm::cl::opt stripDebugInfo{ + "strip-debug-info", + llvm::cl::desc("Disable source locator information in output Verilog"), + llvm::cl::init(false), llvm::cl::cat(category)}; + + bool isRandomEnabled(RandomKind kind) const { + return disableRandom != RandomKind::All && disableRandom != kind; + } + + firrtl::PreserveValues::PreserveMode getPreserveMode() const { + if (!buildMode.getNumOccurrences()) + return preserveMode; + switch (buildMode) { + case BuildModeDebug: + return firrtl::PreserveValues::Named; + case BuildModeRelease: + return firrtl::PreserveValues::None; + } + llvm_unreachable("unknown build mode"); + } + + FirtoolOptions(llvm::cl::OptionCategory &category) : category(category) {} +}; + +LogicalResult populatePreprocessTransforms(mlir::PassManager &pm, + const FirtoolOptions &opt); + +LogicalResult populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, + const FirtoolOptions &opt, + StringRef inputFilename); + +LogicalResult populateLowFIRRTLToHW(mlir::PassManager &pm, + const FirtoolOptions &opt); + +LogicalResult populateHWToSV(mlir::PassManager &pm, const FirtoolOptions &opt); + +LogicalResult populateExportVerilog(mlir::PassManager &pm, + const FirtoolOptions &opt, + std::unique_ptr os); + +LogicalResult populateExportVerilog(mlir::PassManager &pm, + const FirtoolOptions &opt, + llvm::raw_ostream &os); + +LogicalResult populateExportSplitVerilog(mlir::PassManager &pm, + const FirtoolOptions &opt, + llvm::StringRef directory); + +LogicalResult populateFinalizeIR(mlir::PassManager &pm, + const FirtoolOptions &opt); + +} // namespace firtool +} // namespace circt + +#endif // CIRCT_FIRTOOL_FIRTOOL_H diff --git a/include/circt/InitAllDialects.h b/include/circt/InitAllDialects.h index 87c5d008771a..d0eee8b8b7d0 100644 --- a/include/circt/InitAllDialects.h +++ b/include/circt/InitAllDialects.h @@ -14,9 +14,11 @@ #ifndef CIRCT_INITALLDIALECTS_H_ #define CIRCT_INITALLDIALECTS_H_ -#include "circt/Dialect/Arc/Dialect.h" +#include "circt/Dialect/Arc/ArcDialect.h" #include "circt/Dialect/Calyx/CalyxDialect.h" #include "circt/Dialect/Comb/CombDialect.h" +#include "circt/Dialect/DC/DCDialect.h" +#include "circt/Dialect/Debug/DebugDialect.h" #include "circt/Dialect/ESI/ESIDialect.h" #include "circt/Dialect/FIRRTL/CHIRRTLDialect.h" #include "circt/Dialect/FIRRTL/FIRRTLDialect.h" @@ -24,16 +26,20 @@ #include "circt/Dialect/HW/HWDialect.h" #include "circt/Dialect/HWArith/HWArithDialect.h" #include "circt/Dialect/Handshake/HandshakeDialect.h" +#include "circt/Dialect/Ibis/IbisDialect.h" #include "circt/Dialect/Interop/InteropDialect.h" #include "circt/Dialect/LLHD/IR/LLHDDialect.h" +#include "circt/Dialect/LTL/LTLDialect.h" +#include "circt/Dialect/LoopSchedule/LoopScheduleDialect.h" #include "circt/Dialect/MSFT/MSFTDialect.h" #include "circt/Dialect/Moore/MooreDialect.h" #include "circt/Dialect/OM/OMDialect.h" -#include "circt/Dialect/Pipeline/Pipeline.h" +#include "circt/Dialect/Pipeline/PipelineDialect.h" #include "circt/Dialect/SSP/SSPDialect.h" #include "circt/Dialect/SV/SVDialect.h" #include "circt/Dialect/Seq/SeqDialect.h" #include "circt/Dialect/SystemC/SystemCDialect.h" +#include "circt/Dialect/Verif/VerifDialect.h" #include "mlir/IR/Dialect.h" namespace circt { @@ -46,22 +52,28 @@ inline void registerAllDialects(mlir::DialectRegistry ®istry) { calyx::CalyxDialect, chirrtl::CHIRRTLDialect, comb::CombDialect, + dc::DCDialect, + debug::DebugDialect, esi::ESIDialect, firrtl::FIRRTLDialect, fsm::FSMDialect, handshake::HandshakeDialect, + hw::HWDialect, + hwarith::HWArithDialect, interop::InteropDialect, + ibis::IbisDialect, llhd::LLHDDialect, - msft::MSFTDialect, + loopschedule::LoopScheduleDialect, + ltl::LTLDialect, moore::MooreDialect, - hw::HWDialect, - seq::SeqDialect, - ssp::SSPDialect, + msft::MSFTDialect, om::OMDialect, pipeline::PipelineDialect, + seq::SeqDialect, + ssp::SSPDialect, sv::SVDialect, - hwarith::HWArithDialect, - systemc::SystemCDialect + systemc::SystemCDialect, + verif::VerifDialect >(); // clang-format on } diff --git a/include/circt/InitAllPasses.h b/include/circt/InitAllPasses.h index ebf7304d571b..18522c387041 100644 --- a/include/circt/InitAllPasses.h +++ b/include/circt/InitAllPasses.h @@ -16,15 +16,19 @@ #include "circt/Conversion/ExportVerilog.h" #include "circt/Conversion/Passes.h" -#include "circt/Dialect/Arc/Passes.h" +#include "circt/Dialect/Arc/ArcPasses.h" #include "circt/Dialect/Calyx/CalyxPasses.h" +#include "circt/Dialect/Comb/CombPasses.h" +#include "circt/Dialect/DC/DCPasses.h" #include "circt/Dialect/ESI/ESIDialect.h" #include "circt/Dialect/FIRRTL/Passes.h" #include "circt/Dialect/FSM/FSMPasses.h" #include "circt/Dialect/HW/HWPasses.h" #include "circt/Dialect/Handshake/HandshakePasses.h" +#include "circt/Dialect/Ibis/IbisPasses.h" #include "circt/Dialect/LLHD/Transforms/Passes.h" #include "circt/Dialect/MSFT/MSFTPasses.h" +#include "circt/Dialect/OM/OMPasses.h" #include "circt/Dialect/Pipeline/PipelinePasses.h" #include "circt/Dialect/SSP/SSPPasses.h" #include "circt/Dialect/SV/SVPasses.h" @@ -44,14 +48,18 @@ inline void registerAllPasses() { // Standard Passes arc::registerPasses(); calyx::registerPasses(); + comb::registerPasses(); + dc::registerPasses(); esi::registerESIPasses(); firrtl::registerPasses(); fsm::registerPasses(); llhd::initLLHDTransformationPasses(); msft::registerPasses(); + om::registerPasses(); seq::registerPasses(); sv::registerPasses(); handshake::registerPasses(); + ibis::registerPasses(); hw::registerPasses(); pipeline::registerPasses(); ssp::registerPasses(); diff --git a/include/circt/InitAllTranslations.h b/include/circt/InitAllTranslations.h index e87c1c498a6e..b622ed6132e5 100644 --- a/include/circt/InitAllTranslations.h +++ b/include/circt/InitAllTranslations.h @@ -16,6 +16,7 @@ #include "circt/Dialect/FIRRTL/FIREmitter.h" #include "circt/Dialect/FIRRTL/FIRParser.h" #include "circt/Dialect/MSFT/ExportTcl.h" +#include "circt/Target/DebugInfo.h" #include "circt/Target/ExportSystemC.h" #ifndef CIRCT_INITALLTRANSLATIONS_H @@ -28,11 +29,11 @@ namespace circt { // automatically. inline void registerAllTranslations() { static bool initOnce = []() { - esi::registerESITranslations(); calyx::registerToCalyxTranslation(); firrtl::registerFromFIRFileTranslation(); firrtl::registerToFIRFileTranslation(); ExportSystemC::registerExportSystemCTranslation(); + debug::registerTranslations(); return true; }(); (void)initOnce; diff --git a/include/circt/LogicalEquivalence/Circuit.h b/include/circt/LogicalEquivalence/Circuit.h new file mode 100644 index 000000000000..afc10b14d485 --- /dev/null +++ b/include/circt/LogicalEquivalence/Circuit.h @@ -0,0 +1,119 @@ +//===-- Circuit.h - intermediate representation for circuits ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// This file defines an intermediate representation for circuits acting as +/// an abstraction for constraints defined over an SMT's solver context. +/// +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE +#ifndef TOOLS_CIRCT_LEC_CIRCUIT_H +#define TOOLS_CIRCT_LEC_CIRCUIT_H + +#include "Solver.h" +#include "circt/Dialect/Comb/CombDialect.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/Value.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include +#include + +namespace circt { + +/// The representation of a circuit within a logical engine. +/// +/// This class defines a circuit as an abstraction of its underlying +/// logical constraints. Its various methods act in a builder pattern fashion, +/// declaring new constraints over a Z3 context. +class Solver::Circuit { +public: + Circuit(llvm::Twine name, Solver &solver) : name(name.str()), solver(solver) { + assignments = 0; + }; + /// Add an input to the circuit; internally a new value gets allocated. + void addInput(mlir::Value); + /// Add an output to the circuit. + void addOutput(mlir::Value); + /// Recover the inputs. + llvm::ArrayRef getInputs(); + /// Recover the outputs. + llvm::ArrayRef getOutputs(); + + // `hw` dialect operations. + void addConstant(mlir::Value result, const mlir::APInt &value); + void addInstance(llvm::StringRef instanceName, circt::hw::HWModuleOp op, + mlir::OperandRange arguments, mlir::ResultRange results); + + // `comb` dialect operations. + void performAdd(mlir::Value result, mlir::OperandRange operands); + void performAnd(mlir::Value result, mlir::OperandRange operands); + void performConcat(mlir::Value result, mlir::OperandRange operands); + void performDivS(mlir::Value result, mlir::Value lhs, mlir::Value rhs); + void performDivU(mlir::Value result, mlir::Value lhs, mlir::Value rhs); + void performExtract(mlir::Value result, mlir::Value input, uint32_t lowBit); + mlir::LogicalResult performICmp(mlir::Value result, + circt::comb::ICmpPredicate predicate, + mlir::Value lhs, mlir::Value rhs); + void performModS(mlir::Value result, mlir::Value lhs, mlir::Value rhs); + void performModU(mlir::Value result, mlir::Value lhs, mlir::Value rhs); + void performMul(mlir::Value result, mlir::OperandRange operands); + void performMux(mlir::Value result, mlir::Value cond, mlir::Value trueValue, + mlir::Value falseValue); + void performOr(mlir::Value result, mlir::OperandRange operands); + void performParity(mlir::Value result, mlir::Value input); + void performReplicate(mlir::Value result, mlir::Value input); + void performShl(mlir::Value result, mlir::Value lhs, mlir::Value rhs); + void performShrS(mlir::Value result, mlir::Value lhs, mlir::Value rhs); + void performShrU(mlir::Value result, mlir::Value lhs, mlir::Value rhs); + void performSub(mlir::Value result, mlir::OperandRange operands); + void performXor(mlir::Value result, mlir::OperandRange operands); + +private: + /// Helper function for performing a variadic operation: it executes a lambda + /// over a range of operands. + void variadicOperation( + mlir::Value result, mlir::OperandRange operands, + llvm::function_ref + operation); + /// Returns the expression allocated for the input value in the logical + /// backend if one has been allocated - otherwise allocates and returns a new + /// expression + z3::expr fetchOrAllocateExpr(mlir::Value value); + /// Allocates a constant value in the logical backend and returns its + /// representing expression. + void allocateConstant(mlir::Value opResult, const mlir::APInt &opValue); + /// Constrains the result of a MLIR operation to be equal a given logical + /// express, simulating an assignment. + void constrainResult(mlir::Value &result, z3::expr &expr); + + /// Convert from bitvector to bool sort. + z3::expr bvToBool(const z3::expr &condition); + /// Convert from a boolean sort to the corresponding 1-width bitvector. + z3::expr boolToBv(const z3::expr &condition); + + /// The name of the circuit; it corresponds to its scope within the parsed IR. + std::string name; + /// A counter for how many assignments have occurred; it's used to uniquely + /// name new values as they have to be represented within the logical engine's + /// context. + unsigned assignments; + /// The solver environment the circuit belongs to. + Solver &solver; + /// The list for the circuit's inputs. + llvm::SmallVector inputs; + /// The list for the circuit's outputs. + llvm::SmallVector outputs; + /// A map from IR values to their corresponding logical representation. + llvm::DenseMap exprTable; +}; + +} // namespace circt + +#endif // TOOLS_CIRCT_LEC_CIRCUIT_H diff --git a/include/circt/LogicalEquivalence/LogicExporter.h b/include/circt/LogicalEquivalence/LogicExporter.h new file mode 100644 index 000000000000..15cf952c8266 --- /dev/null +++ b/include/circt/LogicalEquivalence/LogicExporter.h @@ -0,0 +1,57 @@ +//===- LogicExporter.cpp - class to extrapolate CIRCT IR logic --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// This file defines the logic-exporting class for the `circt-lec` tool. +/// +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE +#ifndef TOOLS_CIRCT_LEC_LOGICEXPORTER_H +#define TOOLS_CIRCT_LEC_LOGICEXPORTER_H + +#include "Solver.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/Comb/CombVisitors.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/HW/HWVisitors.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Support/LogicalResult.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace circt { + +/// A class traversing MLIR IR to extrapolate the logic of a given circuit. +/// +/// This class implements a MLIR exporter which searches the IR for the +/// specified `hw.module` describing a circuit. It will then traverse its +/// operations and collect the underlying logical constraints within an +/// abstract circuit representation. +class LogicExporter { +public: + LogicExporter(llvm::StringRef moduleName, Solver::Circuit *circuit) + : moduleName(moduleName), circuit(circuit) {} + + /// Initializes the exporting by visiting the builtin module. + mlir::LogicalResult run(mlir::ModuleOp &module); + mlir::LogicalResult run(hw::HWModuleOp &module); + +private: + // For Solver::Circuit::addInstance to access Visitor::visitHW. + friend Solver::Circuit; + + /// The specified module name to look for when traversing the input file. + std::string moduleName; + /// The circuit representation to hold the logical constraints extracted + /// from the IR. + Solver::Circuit *circuit; +}; + +} // namespace circt + +#endif // TOOLS_CIRCT_LEC_LOGICEXPORTER_H diff --git a/include/circt/LogicalEquivalence/Solver.h b/include/circt/LogicalEquivalence/Solver.h new file mode 100644 index 000000000000..baf41d20cabe --- /dev/null +++ b/include/circt/LogicalEquivalence/Solver.h @@ -0,0 +1,79 @@ +//===-- Solver.h - SMT solver interface -------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// This file defines a SMT solver interface for the `circt-lec` tool. +/// +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE +#ifndef TOOLS_CIRCT_LEC_SOLVER_H +#define TOOLS_CIRCT_LEC_SOLVER_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Value.h" +#include + +namespace circt { + +/// A satisfiability checker for circuit equivalence +/// +/// This class interfaces with an external SMT solver acting as a logical +/// engine. First spawn two circuits through `addCircuit`; after collecting +/// their logical constraints, the `solve` method will compare them and report +/// whether they result to be equivalent or, when not, also printing a model +/// acting as a counterexample. +class Solver { +public: + Solver(mlir::MLIRContext *mlirCtx, bool statisticsOpt); + ~Solver() = default; + + /// Solve the equivalence problem between the two circuits, then present the + /// results to the user. + mlir::LogicalResult solve(); + + class Circuit; + /// Create a new circuit to be compared and return it. + Circuit *addCircuit(llvm::StringRef name); + +private: + /// Prints a model satisfying the solved constraints. + void printModel(); + /// Prints the constraints which were added to the solver. + /// Compared to solver.assertions().to_string() this method exposes each + /// assertion as a z3::expression for eventual in-depth debugging. + void printAssertions(); + /// Prints the internal statistics of the SMT solver for benchmarking purposes + /// and operational insight. + void printStatistics(); + + /// Formulates additional constraints which are satisfiable if only if the + /// two circuits which are being compared are NOT equivalent, in which case + /// there would be a model acting as a counterexample. + /// The procedure fails when detecting a mismatch of arity or type between + /// the inputs and outputs of the circuits. + mlir::LogicalResult constrainCircuits(); + + /// A map from internal solver symbols to the IR values they represent. + llvm::DenseMap symbolTable; + /// The two circuits to be compared. + Circuit *circuits[2]; + /// The MLIR context of reference, owning all the MLIR entities. + mlir::MLIRContext *mlirCtx; + /// The Z3 context of reference, owning all the declared values, constants + /// and expressions. + z3::context context; + /// The Z3 solver acting as the logical engine backend. + z3::solver solver; + /// The value of the `statistics` command-line option. + bool statisticsOpt; +}; + +} // namespace circt + +#endif // TOOLS_CIRCT_LEC_SOLVER_H diff --git a/include/circt/LogicalEquivalence/Utility.h b/include/circt/LogicalEquivalence/Utility.h new file mode 100644 index 000000000000..ecbd3fec90d4 --- /dev/null +++ b/include/circt/LogicalEquivalence/Utility.h @@ -0,0 +1,71 @@ +//===-- Utility.h - collection of utility functions and macros --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// This header provides a variety of utility functions and macros for use +/// throughout the tool. +/// +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE +#ifndef TOOLS_CIRCT_LEC_UTILITY_H +#define TOOLS_CIRCT_LEC_UTILITY_H + +#include "mlir/IR/Value.h" +#include "mlir/Support/IndentedOstream.h" +#include "llvm/ADT/APInt.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" +#include + +namespace lec { +// Defining persistent output streams such that text will be printed in +// accordance with the globally set indentation level. +inline mlir::raw_indented_ostream &dbgs() { + static auto stream = mlir::raw_indented_ostream(llvm::dbgs()); + return stream; +} + +inline mlir::raw_indented_ostream &errs() { + static auto stream = mlir::raw_indented_ostream(llvm::errs()); + return stream; +} + +inline mlir::raw_indented_ostream &outs() { + static auto stream = mlir::raw_indented_ostream(llvm::outs()); + return stream; +} + +/// RAII struct to indent the output streams. +struct Scope { + mlir::raw_indented_ostream::DelimitedScope indentDbgs = lec::dbgs().scope(); + mlir::raw_indented_ostream::DelimitedScope indentErrs = lec::errs().scope(); + mlir::raw_indented_ostream::DelimitedScope indentOuts = lec::outs().scope(); +}; + +/// Helper function to provide a common debug formatting for z3 expressions. +inline void printExpr(const z3::expr &expr) { + lec::dbgs() << "symbol: " << expr.to_string() << "\n"; + lec::dbgs() << "sort: " << expr.get_sort().to_string() << "\n"; + lec::dbgs() << "expression id: " << expr.id() << "\n"; + lec::dbgs() << "expression hash: " << expr.hash() << "\n"; +} + +/// Helper function to provide a common debug formatting for MLIR values. +inline void printValue(const mlir::Value &value) { + lec::dbgs() << "value: " << value << "\n"; + lec::dbgs() << "type: " << value.getType() << "\n"; + lec::dbgs() << "value hash: " << mlir::hash_value(value) << "\n"; +} + +/// Helper function to provide a common debug formatting for MLIR APInt'egers. +inline void printAPInt(const mlir::APInt &value) { + lec::dbgs() << "APInt: " << value.getZExtValue() << "\n"; +} +} // namespace lec + +#endif // TOOLS_CIRCT_LEC_UTILITY_H diff --git a/include/circt/Reduce/GenericReductions.h b/include/circt/Reduce/GenericReductions.h new file mode 100644 index 000000000000..d7fb87341a03 --- /dev/null +++ b/include/circt/Reduce/GenericReductions.h @@ -0,0 +1,23 @@ +//===- GenericReductions.h - Generic reduction patterns ---------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_REDUCE_GENERICREDUCTIONS_H +#define CIRCT_REDUCE_GENERICREDUCTIONS_H + +#include "circt/Reduce/Reduction.h" + +namespace circt { + +/// Populate reduction patterns that are not specific to certain operations or +/// dialects +void populateGenericReducePatterns(MLIRContext *context, + ReducePatternSet &patterns); + +} // namespace circt + +#endif // CIRCT_REDUCE_GENERICREDUCTIONS_H diff --git a/tools/circt-reduce/Reduction.h b/include/circt/Reduce/Reduction.h similarity index 63% rename from tools/circt-reduce/Reduction.h rename to include/circt/Reduce/Reduction.h index 2f4afe370a30..e47a4682d810 100644 --- a/tools/circt-reduce/Reduction.h +++ b/include/circt/Reduce/Reduction.h @@ -1,4 +1,4 @@ -//===- Reduction.h - Reductions for circt-reduce --------------------------===// +//===- Reduction.h - Reduction datastructure decl. for circt-reduce -------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,31 +6,17 @@ // //===----------------------------------------------------------------------===// // -// This file defines abstract reduction patterns for the 'circt-reduce' tool. +// This file defines datastructures to handle reduction patterns. // //===----------------------------------------------------------------------===// #ifndef CIRCT_REDUCE_REDUCTION_H #define CIRCT_REDUCE_REDUCTION_H -#include -#include - +#include "circt/Support/LLVM.h" #include "mlir/IR/BuiltinOps.h" -#include "llvm/ADT/StringRef.h" - -namespace llvm { -template -class function_ref; -} // namespace llvm - -namespace mlir { -struct LogicalResult; -class MLIRContext; -class Operation; -class Pass; -class PassManager; -} // namespace mlir +#include "mlir/Pass/PassManager.h" +#include "llvm/ADT/SmallVector.h" namespace circt { @@ -52,12 +38,12 @@ struct Reduction { /// benefit measure where a higher number means that applying the pattern /// leads to a bigger reduction and zero means that the patten does not /// match and thus cannot be applied at all. - virtual uint64_t match(mlir::Operation *op) = 0; + virtual uint64_t match(Operation *op) = 0; /// Apply the reduction to a specific operation. If the returned result /// indicates that the application failed, the resulting module is treated the /// same as if the tester marked it as uninteresting. - virtual mlir::LogicalResult rewrite(mlir::Operation *op) = 0; + virtual LogicalResult rewrite(Operation *op) = 0; /// Return a human-readable name for this reduction pattern. virtual std::string getName() const = 0; @@ -86,37 +72,81 @@ struct Reduction { virtual bool isOneShot() const { return false; } /// An optional callback for reductions to communicate removal of operations. - std::function notifyOpErasedCallback = nullptr; + std::function notifyOpErasedCallback = nullptr; - void notifyOpErased(mlir::Operation *op) { + void notifyOpErased(Operation *op) { if (notifyOpErasedCallback) notifyOpErasedCallback(op); } }; +template +struct OpReduction : public Reduction { + uint64_t match(Operation *op) override { + if (auto concreteOp = dyn_cast(op)) + return match(concreteOp); + return 0; + } + LogicalResult rewrite(Operation *op) override { + return rewrite(cast(op)); + } + + virtual uint64_t match(OpTy op) { return 1; } + virtual LogicalResult rewrite(OpTy op) = 0; +}; + /// A reduction pattern that applies an `mlir::Pass`. struct PassReduction : public Reduction { - PassReduction(mlir::MLIRContext *context, std::unique_ptr pass, + PassReduction(MLIRContext *context, std::unique_ptr pass, bool canIncreaseSize = false, bool oneShot = false); - uint64_t match(mlir::Operation *op) override; - mlir::LogicalResult rewrite(mlir::Operation *op) override; + uint64_t match(Operation *op) override; + LogicalResult rewrite(Operation *op) override; std::string getName() const override; bool acceptSizeIncrease() const override { return canIncreaseSize; } bool isOneShot() const override { return oneShot; } protected: - mlir::MLIRContext *const context; + MLIRContext *const context; std::unique_ptr pm; - llvm::StringRef passName; + StringRef passName; bool canIncreaseSize; bool oneShot; }; -/// Calls the function `add` with each available reduction, in the order they -/// should be applied. -void createAllReductions( - mlir::MLIRContext *context, - llvm::function_ref)> add); +class ReducePatternSet { +public: + template + void add(Args &&...args) { + reducePatternsWithBenefit.push_back( + {std::make_unique(std::forward(args)...), Benefit}); + } + + void filter(const std::function &pred); + void sortByBenefit(); + size_t size() const; + + Reduction &operator[](size_t idx) const; + +private: + SmallVector, unsigned>> + reducePatternsWithBenefit; +}; + +/// A dialect interface to provide reduction patterns to a reducer tool. +struct ReducePatternDialectInterface + : public mlir::DialectInterface::Base { + ReducePatternDialectInterface(Dialect *dialect) : Base(dialect) {} + + virtual void populateReducePatterns(ReducePatternSet &patterns) const = 0; +}; + +struct ReducePatternInterfaceCollection + : public mlir::DialectInterfaceCollection { + using Base::Base; + + // Collect the reduce patterns defined by each dialect. + void populateReducePatterns(ReducePatternSet &patterns) const; +}; } // namespace circt diff --git a/include/circt/Reduce/ReductionUtils.h b/include/circt/Reduce/ReductionUtils.h new file mode 100644 index 000000000000..f41e9da22bb2 --- /dev/null +++ b/include/circt/Reduce/ReductionUtils.h @@ -0,0 +1,27 @@ +//===- ReductionUtils.h - Reduction pattern utilities -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_REDUCE_REDUCTIONUTILS_H +#define CIRCT_REDUCE_REDUCTIONUTILS_H + +#include "circt/Support/LLVM.h" + +namespace circt { +// Forward declarations. +struct Reduction; + +namespace reduce { + +/// Starting at the given `op`, traverse through it and its operands and erase +/// operations that have no more uses. +void pruneUnusedOps(Operation *initialOp, Reduction &reduction); + +} // namespace reduce +} // namespace circt + +#endif // CIRCT_REDUCE_REDUCTIONUTILS_H diff --git a/tools/circt-reduce/Tester.h b/include/circt/Reduce/Tester.h similarity index 99% rename from tools/circt-reduce/Tester.h rename to include/circt/Reduce/Tester.h index 3671433c6e28..86aa62a3fd26 100644 --- a/tools/circt-reduce/Tester.h +++ b/include/circt/Reduce/Tester.h @@ -13,9 +13,6 @@ #ifndef CIRCT_REDUCE_TESTER_H #define CIRCT_REDUCE_TESTER_H -#include -#include - #include "circt/Support/LLVM.h" #include "mlir/IR/BuiltinOps.h" #include "llvm/ADT/SmallString.h" diff --git a/include/circt/Scheduling/Algorithms.h b/include/circt/Scheduling/Algorithms.h index b11987c146fe..db5c80816511 100644 --- a/include/circt/Scheduling/Algorithms.h +++ b/include/circt/Scheduling/Algorithms.h @@ -48,8 +48,9 @@ LogicalResult scheduleSimplex(SharedOperatorsProblem &prob, Operation *lastOp); /// heuristic. The approach tries to determine the smallest feasible initiation /// interval, and to minimize the start time of the given \p lastOp, but /// optimality is not guaranteed. Fails if the dependence graph contains cycles -/// that do not include at least one edge with a non-zero distance, or \p prob -/// does not include \p lastOp. +/// that do not include at least one edge with a non-zero distance, \p prob +/// does not include \p lastOp, or \p lastOp is not the unique sink of the +/// dependence graph. LogicalResult scheduleSimplex(ModuloProblem &prob, Operation *lastOp); /// Solve the acyclic, chaining-enabled problem using linear programming and a diff --git a/include/circt/Support/BuilderUtils.h b/include/circt/Support/BuilderUtils.h index b1323dfd328c..daacf5a806b9 100644 --- a/include/circt/Support/BuilderUtils.h +++ b/include/circt/Support/BuilderUtils.h @@ -9,6 +9,8 @@ #ifndef CIRCT_SUPPORT_BUILDERUTILS_H #define CIRCT_SUPPORT_BUILDERUTILS_H +#include + #include "circt/Support/LLVM.h" #include "llvm/ADT/TypeSwitch.h" @@ -20,23 +22,27 @@ namespace circt { /// The `get` function can then be used to obtain a `StringAttr` from any of the /// possible variants `StringAttrOrRef` can take. class StringAttrOrRef { - using Value = llvm::PointerUnion; - Value value; + std::variant value; public: StringAttrOrRef() : value() {} StringAttrOrRef(StringAttr attr) : value(attr) {} - StringAttrOrRef(const StringRef &str) - : value(const_cast(&str)) {} - StringAttrOrRef(const Twine &twine) : value(const_cast(&twine)) {} + StringAttrOrRef(const StringRef &str) : value(str) {} + StringAttrOrRef(const char *ptr) : value(ptr) {} + StringAttrOrRef(const std::string &str) : value(StringRef(str)) {} + StringAttrOrRef(const Twine &twine) : value(twine) {} /// Return the represented string as a `StringAttr`. StringAttr get(MLIRContext *context) const { - return TypeSwitch(value) - .Case([&](auto value) { return value; }) - .Case( - [&](auto value) { return StringAttr::get(context, *value); }) - .Default([](auto) { return StringAttr{}; }); + if (auto *attr = std::get_if(&value)) + return *attr; + if (auto *ref = std::get_if(&value)) + return StringAttr::get(context, *ref); + if (auto *twine = std::get_if(&value)) + return StringAttr::get(context, *twine); + if (auto *ptr = std::get_if(&value)) + return StringAttr::get(context, *ptr); + return StringAttr{}; } }; diff --git a/include/circt/Support/CMakeLists.txt b/include/circt/Support/CMakeLists.txt new file mode 100644 index 000000000000..e63fd5422fa0 --- /dev/null +++ b/include/circt/Support/CMakeLists.txt @@ -0,0 +1 @@ +add_mlir_interface(InstanceGraphInterface) diff --git a/include/circt/Support/CustomDirectiveImpl.h b/include/circt/Support/CustomDirectiveImpl.h index 0dfe47011937..bea18f2012ae 100644 --- a/include/circt/Support/CustomDirectiveImpl.h +++ b/include/circt/Support/CustomDirectiveImpl.h @@ -61,6 +61,31 @@ void elideImplicitSSAName(OpAsmPrinter &printer, Operation *op, DictionaryAttr attrs, SmallVectorImpl &elides); +/// Print/parse binary operands type only when types are different. +/// optional-bin-op-types := type($lhs) (, type($rhs))? +void printOptionalBinaryOpTypes(OpAsmPrinter &p, Operation *op, Type lhs, + Type rhs); +ParseResult parseOptionalBinaryOpTypes(OpAsmParser &parser, Type &lhs, + Type &rhs); + +//===----------------------------------------------------------------------===// +// KeywordBool Custom Directive +//===----------------------------------------------------------------------===// + +/// Parse a boolean as one of two keywords. The `trueKeyword` will result in a +/// true boolean; the `falseKeyword` will result in a false boolean. +/// +/// labeled-bool ::= (true-label | false-label) +ParseResult parseKeywordBool(OpAsmParser &parser, BoolAttr &attr, + StringRef trueKeyword, StringRef falseKeyword); + +/// Print a boolean as one of two keywords. If the boolean is true, the +/// `trueKeyword` is used; if it is false, the `falseKeyword` is used. +/// +/// labeled-bool ::= (true-label | false-label) +void printKeywordBool(OpAsmPrinter &printer, Operation *op, BoolAttr attr, + StringRef trueKeyword, StringRef falseKeyword); + } // namespace circt #endif // CIRCT_SUPPORT_CUSTOMDIRECTIVEIMPL_H diff --git a/include/circt/Support/FoldUtils.h b/include/circt/Support/FoldUtils.h new file mode 100644 index 000000000000..9261e4ceac9c --- /dev/null +++ b/include/circt/Support/FoldUtils.h @@ -0,0 +1,42 @@ +//===- FoldUtils.h - Common folder and canonicalizer utilities --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_FOLDUTILS_H +#define CIRCT_SUPPORT_FOLDUTILS_H + +#include "mlir/IR/BuiltinAttributes.h" +#include "llvm/ADT/APInt.h" + +namespace circt { + +/// Determine the integer value of a constant operand. +static inline std::optional getConstantInt(Attribute operand) { + if (!operand) + return {}; + if (auto attr = dyn_cast(operand)) + return attr.getValue(); + return {}; +} + +/// Determine whether a constant operand is a zero value. +static inline bool isConstantZero(Attribute operand) { + if (auto cst = getConstantInt(operand)) + return cst->isZero(); + return false; +} + +/// Determine whether a constant operand is a one value. +static inline bool isConstantOne(Attribute operand) { + if (auto cst = getConstantInt(operand)) + return cst->isOne(); + return false; +} + +} // namespace circt + +#endif // CIRCT_SUPPORT_FOLDUTILS_H diff --git a/include/circt/Dialect/HW/InstanceGraphBase.h b/include/circt/Support/InstanceGraph.h similarity index 68% rename from include/circt/Dialect/HW/InstanceGraphBase.h rename to include/circt/Support/InstanceGraph.h index cb06f61ea700..04423cb7943b 100644 --- a/include/circt/Dialect/HW/InstanceGraphBase.h +++ b/include/circt/Support/InstanceGraph.h @@ -1,4 +1,4 @@ -//===- InstanceGraphBase.h - Instance graph ---------------------*- C++ -*-===// +//===- InstanceGraph.h - Instance graph -------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,18 +10,22 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_HW_INSTANCEGRAPHBASE_H -#define CIRCT_DIALECT_HW_INSTANCEGRAPHBASE_H +#ifndef CIRCT_SUPPORT_INSTANCEGRAPH_H +#define CIRCT_SUPPORT_INSTANCEGRAPH_H -#include "circt/Dialect/HW/HWOps.h" #include "circt/Support/LLVM.h" +#include "mlir/IR/OpDefinition.h" #include "llvm/ADT/GraphTraits.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/iterator.h" #include "llvm/Support/DOTGraphTraits.h" +/// The InstanceGraph op interface, see InstanceGraphInterface.td for more +/// details. +#include "circt/Support/InstanceGraphInterface.h" + namespace circt { -namespace hw { +namespace igraph { namespace detail { /// This just maps a iterator of references to an iterator of addresses. @@ -51,7 +55,12 @@ class InstanceRecord : public llvm::ilist_node_with_parent { public: /// Get the instance-like op that this is tracking. - HWInstanceLike getInstance() const { return instance; } + template + auto getInstance() { + if constexpr (std::is_same::value) + return instance; + return dyn_cast_or_null(instance.getOperation()); + } /// Get the module where the instantiation lives. InstanceGraphNode *getParent() const { return parent; } @@ -64,10 +73,10 @@ class InstanceRecord void erase(); private: - friend class InstanceGraphBase; + friend class InstanceGraph; friend class InstanceGraphNode; - InstanceRecord(InstanceGraphNode *parent, HWInstanceLike instance, + InstanceRecord(InstanceGraphNode *parent, InstanceOpInterface instance, InstanceGraphNode *target) : parent(parent), instance(instance), target(target) {} InstanceRecord(const InstanceRecord &) = delete; @@ -76,7 +85,7 @@ class InstanceRecord InstanceGraphNode *parent; /// The InstanceLike that this is tracking. - HWInstanceLike instance; + InstanceOpInterface instance; /// This is the module which the instance-like is instantiating. InstanceGraphNode *target; @@ -96,7 +105,12 @@ class InstanceGraphNode : public llvm::ilist_node { InstanceGraphNode() : module(nullptr) {} /// Get the module that this node is tracking. - HWModuleLike getModule() const { return module; } + template + auto getModule() { + if constexpr (std::is_same::value) + return module; + return cast(module.getOperation()); + } /// Iterate the instance records in this module. using iterator = detail::AddressIterator; @@ -143,7 +157,7 @@ class InstanceGraphNode : public llvm::ilist_node { /// Record a new instance op in the body of this module. Returns a newly /// allocated InstanceRecord which will be owned by this node. - InstanceRecord *addInstance(HWInstanceLike instance, + InstanceRecord *addInstance(InstanceOpInterface instance, InstanceGraphNode *target); private: @@ -155,7 +169,7 @@ class InstanceGraphNode : public llvm::ilist_node { void recordUse(InstanceRecord *record); /// The module. - HWModuleLike module; + ModuleOpInterface module; /// List of instance operations in this module. This member owns the /// InstanceRecords, which may be pointed to by other InstanceGraphNode's use @@ -166,7 +180,7 @@ class InstanceGraphNode : public llvm::ilist_node { InstanceRecord *firstUse = nullptr; // Provide access to the constructor. - friend class InstanceGraphBase; + friend class InstanceGraph; }; /// This graph tracks modules and where they are instantiated. This is intended @@ -175,32 +189,39 @@ class InstanceGraphNode : public llvm::ilist_node { /// /// To use this class, retrieve a cached copy from the analysis manager: /// auto &instanceGraph = getAnalysis(getOperation()); -class InstanceGraphBase { +class InstanceGraph { /// This is the list of InstanceGraphNodes in the graph. using NodeList = llvm::iplist; public: - virtual ~InstanceGraphBase(); + /// Create a new module graph of a circuit. Must be called on the parent + /// operation of ModuleOpInterface ops. + InstanceGraph(Operation *parent); + InstanceGraph(const InstanceGraph &) = delete; + virtual ~InstanceGraph() = default; /// Look up an InstanceGraphNode for a module. - InstanceGraphNode *lookup(HWModuleLike op); + InstanceGraphNode *lookup(ModuleOpInterface op); /// Lookup an module by name. InstanceGraphNode *lookup(StringAttr name); /// Lookup an InstanceGraphNode for a module. - InstanceGraphNode *operator[](HWModuleLike op) { return lookup(op); } + InstanceGraphNode *operator[](ModuleOpInterface op) { return lookup(op); } /// Look up the referenced module from an InstanceOp. This will use a /// hashtable lookup to find the module, where /// InstanceOp.getReferencedModule() will be a linear search through the IR. - HWModuleLike getReferencedModule(HWInstanceLike op); + template + auto getReferencedModule(InstanceOpInterface op) { + return cast(getReferencedModuleImpl(op).getOperation()); + } /// Check if child is instantiated by a parent. - bool isAncestor(HWModuleLike child, HWModuleLike parent); + bool isAncestor(ModuleOpInterface child, ModuleOpInterface parent); /// Get the node corresponding to the top-level module of a circuit. - virtual InstanceGraphNode *getTopLevelNode() = 0; + virtual InstanceGraphNode *getTopLevelNode() { return nullptr; } /// Get the nodes corresponding to the inferred top-level modules of a /// circuit. @@ -210,8 +231,8 @@ class InstanceGraphBase { Operation *getParent() { return parent; } /// Returns pointer to member of operation list. - static NodeList InstanceGraphBase::*getSublistAccess(Operation *) { - return &InstanceGraphBase::nodes; + static NodeList InstanceGraph::*getSublistAccess(Operation *) { + return &InstanceGraph::nodes; } /// Iterate through all modules. @@ -228,7 +249,7 @@ class InstanceGraphBase { // on a CircuitOp or a ModuleOp. /// Add a newly created module to the instance graph. - virtual InstanceGraphNode *addModule(HWModuleLike module); + virtual InstanceGraphNode *addModule(ModuleOpInterface module); /// Remove this module from the instance graph. This will also remove all /// InstanceRecords in this module. All instances of this module must have @@ -237,13 +258,11 @@ class InstanceGraphBase { /// Replaces an instance of a module with another instance. The target module /// of both InstanceOps must be the same. - virtual void replaceInstance(HWInstanceLike inst, HWInstanceLike newInst); + virtual void replaceInstance(InstanceOpInterface inst, + InstanceOpInterface newInst); protected: - /// Create a new module graph of a circuit. Must be called on the parent - /// operation of HWModuleLike ops. - InstanceGraphBase(Operation *parent); - InstanceGraphBase(const InstanceGraphBase &) = delete; + ModuleOpInterface getReferencedModuleImpl(InstanceOpInterface op); /// Get the node corresponding to the module. If the node has does not exist /// yet, it will be created. @@ -262,34 +281,73 @@ class InstanceGraphBase { llvm::SmallVector inferredTopLevelNodes; }; -/// An absolute instance path. -using InstancePath = ArrayRef; +struct InstancePathCache; -template -inline static T &formatInstancePath(T &into, const InstancePath &path) { - into << "$root"; - for (auto inst : path) - into << "/" << inst.instanceName() << ":" << inst.referencedModuleName(); - return into; -} +/** + * An instance path composed of a series of instances. + */ +class InstancePath final { +public: + InstancePath() = default; + + InstanceOpInterface top() const { + assert(!empty() && "instance path is empty"); + return path[0]; + } + + InstanceOpInterface leaf() const { + assert(!empty() && "instance path is empty"); + return path.back(); + } + + InstancePath dropFront() const { return InstancePath(path.drop_front()); } + + InstanceOpInterface operator[](size_t idx) const { return path[idx]; } + ArrayRef::iterator begin() const { return path.begin(); } + ArrayRef::iterator end() const { return path.end(); } + size_t size() const { return path.size(); } + bool empty() const { return path.empty(); } + + /// Print the path to any stream-like object. + template + void print(T &into) const { + into << "$root"; + for (auto inst : path) + into << "/" << inst.getInstanceName() << ":" + << inst.getReferencedModuleName(); + } + +private: + // Only the path cache is allowed to create paths. + friend struct InstancePathCache; + InstancePath(ArrayRef path) : path(path) {} + + ArrayRef path; +}; template static T &operator<<(T &os, const InstancePath &path) { - return formatInstancePath(os, path); + return path.print(os); } /// A data structure that caches and provides absolute paths to module instances /// in the IR. struct InstancePathCache { /// The instance graph of the IR. - InstanceGraphBase &instanceGraph; + InstanceGraph &instanceGraph; - explicit InstancePathCache(InstanceGraphBase &instanceGraph) + explicit InstancePathCache(InstanceGraph &instanceGraph) : instanceGraph(instanceGraph) {} - ArrayRef getAbsolutePaths(HWModuleLike op); + ArrayRef getAbsolutePaths(ModuleOpInterface op); /// Replace an InstanceOp. This is required to keep the cache updated. - void replaceInstance(HWInstanceLike oldOp, HWInstanceLike newOp); + void replaceInstance(InstanceOpInterface oldOp, InstanceOpInterface newOp); + + /// Append an instance to a path. + InstancePath appendInstance(InstancePath path, InstanceOpInterface inst); + + /// Prepend an instance to a path. + InstancePath prependInstance(InstanceOpInterface inst, InstancePath path); private: /// An allocator for individual instance paths and entire path lists. @@ -297,22 +355,19 @@ struct InstancePathCache { /// Cached absolute instance paths. DenseMap> absolutePathsCache; - - /// Append an instance to a path. - InstancePath appendInstance(InstancePath path, HWInstanceLike inst); }; -} // namespace hw +} // namespace igraph } // namespace circt // Graph traits for modules. template <> -struct llvm::GraphTraits { - using NodeType = circt::hw::InstanceGraphNode; +struct llvm::GraphTraits { + using NodeType = circt::igraph::InstanceGraphNode; using NodeRef = NodeType *; // Helper for getting the module referenced by the instance op. - static NodeRef getChild(const circt::hw::InstanceRecord *record) { + static NodeRef getChild(const circt::igraph::InstanceRecord *record) { return record->getTarget(); } @@ -330,12 +385,12 @@ struct llvm::GraphTraits { // Provide graph traits for iterating the modules in inverse order. template <> -struct llvm::GraphTraits> { - using NodeType = circt::hw::InstanceGraphNode; +struct llvm::GraphTraits> { + using NodeType = circt::igraph::InstanceGraphNode; using NodeRef = NodeType *; // Helper for getting the module containing the instance op. - static NodeRef getParent(const circt::hw::InstanceRecord *record) { + static NodeRef getParent(const circt::igraph::InstanceRecord *record) { return record->getParent(); } @@ -355,44 +410,44 @@ struct llvm::GraphTraits> { // Graph traits for the common instance graph. template <> -struct llvm::GraphTraits - : public llvm::GraphTraits { - using nodes_iterator = circt::hw::InstanceGraphBase::iterator; +struct llvm::GraphTraits + : public llvm::GraphTraits { + using nodes_iterator = circt::igraph::InstanceGraph::iterator; - static NodeRef getEntryNode(circt::hw::InstanceGraphBase *graph) { + static NodeRef getEntryNode(circt::igraph::InstanceGraph *graph) { return graph->getTopLevelNode(); } // NOLINTNEXTLINE(readability-identifier-naming) - static nodes_iterator nodes_begin(circt::hw::InstanceGraphBase *graph) { + static nodes_iterator nodes_begin(circt::igraph::InstanceGraph *graph) { return graph->begin(); } // NOLINTNEXTLINE(readability-identifier-naming) - static nodes_iterator nodes_end(circt::hw::InstanceGraphBase *graph) { + static nodes_iterator nodes_end(circt::igraph::InstanceGraph *graph) { return graph->end(); } }; // Graph traits for DOT labeling. template <> -struct llvm::DOTGraphTraits +struct llvm::DOTGraphTraits : public llvm::DefaultDOTGraphTraits { using DefaultDOTGraphTraits::DefaultDOTGraphTraits; - static std::string getNodeLabel(circt::hw::InstanceGraphNode *node, - circt::hw::InstanceGraphBase *) { + static std::string getNodeLabel(circt::igraph::InstanceGraphNode *node, + circt::igraph::InstanceGraph *) { // The name of the graph node is the module name. - return node->getModule().moduleName().str(); + return node->getModule().getModuleName().str(); } template - static std::string getEdgeAttributes(const circt::hw::InstanceGraphNode *node, - Iterator it, - circt::hw::InstanceGraphBase *) { + static std::string + getEdgeAttributes(const circt::igraph::InstanceGraphNode *node, Iterator it, + circt::igraph::InstanceGraph *) { // Set an edge label that is the name of the instance. auto *instanceRecord = *it.getCurrent(); auto instanceOp = instanceRecord->getInstance(); - return ("label=" + instanceOp.instanceName()).str(); + return ("label=" + instanceOp.getInstanceName()).str(); } }; -#endif // CIRCT_DIALECT_HW_INSTANCEGRAPHBASE_H +#endif // CIRCT_SUPPORT_INSTANCEGRAPH_H diff --git a/include/circt/Support/InstanceGraphInterface.h b/include/circt/Support/InstanceGraphInterface.h new file mode 100644 index 000000000000..0a09f7f4fb91 --- /dev/null +++ b/include/circt/Support/InstanceGraphInterface.h @@ -0,0 +1,24 @@ +//===- InstanceGraphInterface.h - Instance graph interface ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares stuff related to the instance graph interface. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_INSTANCEGRAPHINTERFACE_H +#define CIRCT_SUPPORT_INSTANCEGRAPHINTERFACE_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/OpDefinition.h" +#include "mlir/IR/SymbolTable.h" + +/// The InstanceGraph op interface, see InstanceGraphInterface.td for more +/// details. +#include "circt/Support/InstanceGraphInterface.h.inc" + +#endif // CIRCT_SUPPORT_INSTANCEGRAPHINTERFACE_H diff --git a/include/circt/Support/InstanceGraphInterface.td b/include/circt/Support/InstanceGraphInterface.td new file mode 100644 index 000000000000..eab045a83325 --- /dev/null +++ b/include/circt/Support/InstanceGraphInterface.td @@ -0,0 +1,78 @@ +//===- InstanceGraphInterface.td - Interface for instance graphs --------*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file contains interfaces and other utilities for interacting with the +// generic CIRCT instance graph. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_INSTANCEGRAPH_INSTANCEGRAPHINTERFACE_TD +#define CIRCT_SUPPORT_INSTANCEGRAPH_INSTANCEGRAPHINTERFACE_TD + +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/OpBase.td" + +def InstanceGraphInstanceOpInterface : OpInterface<"InstanceOpInterface"> { + let description = [{ + This interface provides hooks for an instance-like operation. + }]; + let cppNamespace = "::circt::igraph"; + + let methods = [ + InterfaceMethod<"Get the name of the instance", + "::llvm::StringRef", "getInstanceName", (ins)>, + + InterfaceMethod<"Get the name of the instance", + "::mlir::StringAttr", "getInstanceNameAttr", (ins)>, + + InterfaceMethod<"Get the name of the instantiated module", + "::llvm::StringRef", "getReferencedModuleName", (ins), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ return $_op.getModuleName(); }]>, + + InterfaceMethod<"Get the name of the instantiated module", + "::mlir::StringAttr", "getReferencedModuleNameAttr", (ins), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ return $_op.getModuleNameAttr().getAttr(); }]>, + + InterfaceMethod<[{ + Get the referenced module (slow, unsafe). This function directly accesses + the parent operation to lookup a symbol, which is unsafe in many contexts. + }], + "::mlir::Operation *", "getReferencedModuleSlow", (ins)>, + + InterfaceMethod<"Get the referenced module via a symbol table.", + "::mlir::Operation *", "getReferencedModule", (ins "SymbolTable&":$symtbl), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ + return symtbl.lookup(getReferencedModuleName()); + }]>, + ]; +} + +def InstanceGraphModuleOpInterface : OpInterface<"ModuleOpInterface"> { + let description = [{ + This interface provides hooks for a module-like operation. + }]; + let cppNamespace = "::circt::igraph"; + + let methods = [ + InterfaceMethod<"Get the module name", + "::llvm::StringRef", "getModuleName", (ins), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ return $_op.getModuleNameAttr().getValue(); }]>, + + InterfaceMethod<"Get the module name", + "::mlir::StringAttr", "getModuleNameAttr", (ins), + /*methodBody=*/[{}], + /*defaultImplementation=*/[{ return $_op.getNameAttr(); }]>, + ]; + +} + +#endif // CIRCT_SUPPORT_INSTANCEGRAPH_INSTANCEGRAPHINTERFACE_TD diff --git a/include/circt/Support/LLVM.h b/include/circt/Support/LLVM.h index 10bae5ed00cc..02fe1a82159f 100644 --- a/include/circt/Support/LLVM.h +++ b/include/circt/Support/LLVM.h @@ -35,36 +35,37 @@ // classes here should be imported from the `mlir` namespace, not the `llvm` // namespace. namespace circt { -using mlir::APFloat; -using mlir::APInt; -using mlir::APSInt; -using mlir::ArrayRef; -using mlir::BitVector; -using mlir::cast; -using mlir::cast_or_null; -using mlir::DenseMap; -using mlir::DenseMapInfo; -using mlir::DenseSet; -using mlir::dyn_cast; -using mlir::dyn_cast_or_null; -using mlir::function_ref; -using mlir::isa; -using mlir::isa_and_nonnull; -using mlir::iterator_range; -using mlir::MutableArrayRef; -using mlir::PointerUnion; -using mlir::raw_ostream; -using mlir::SmallPtrSet; -using mlir::SmallPtrSetImpl; -using mlir::SmallString; -using mlir::SmallVector; -using mlir::SmallVectorImpl; -using mlir::StringLiteral; -using mlir::StringRef; -using mlir::StringSet; -using mlir::TinyPtrVector; -using mlir::Twine; -using mlir::TypeSwitch; +using mlir::APFloat; // NOLINT(misc-unused-using-decls) +using mlir::APInt; // NOLINT(misc-unused-using-decls) +using mlir::APSInt; // NOLINT(misc-unused-using-decls) +using mlir::ArrayRef; // NOLINT(misc-unused-using-decls) +using mlir::BitVector; // NOLINT(misc-unused-using-decls) +using mlir::cast; // NOLINT(misc-unused-using-decls) +using mlir::cast_or_null; // NOLINT(misc-unused-using-decls) +using mlir::DenseMap; // NOLINT(misc-unused-using-decls) +using mlir::DenseMapInfo; // NOLINT(misc-unused-using-decls) +using mlir::DenseSet; // NOLINT(misc-unused-using-decls) +using mlir::dyn_cast; // NOLINT(misc-unused-using-decls) +using mlir::dyn_cast_or_null; // NOLINT(misc-unused-using-decls) +using mlir::function_ref; // NOLINT(misc-unused-using-decls) +using mlir::isa; // NOLINT(misc-unused-using-decls) +using mlir::isa_and_nonnull; // NOLINT(misc-unused-using-decls) +using mlir::iterator_range; // NOLINT(misc-unused-using-decls) +using mlir::MutableArrayRef; // NOLINT(misc-unused-using-decls) +using mlir::PointerUnion; // NOLINT(misc-unused-using-decls) +using mlir::raw_ostream; // NOLINT(misc-unused-using-decls) +using mlir::SetVector; // NOLINT(misc-unused-using-decls) +using mlir::SmallPtrSet; // NOLINT(misc-unused-using-decls) +using mlir::SmallPtrSetImpl; // NOLINT(misc-unused-using-decls) +using mlir::SmallString; // NOLINT(misc-unused-using-decls) +using mlir::SmallVector; // NOLINT(misc-unused-using-decls) +using mlir::SmallVectorImpl; // NOLINT(misc-unused-using-decls) +using mlir::StringLiteral; // NOLINT(misc-unused-using-decls) +using mlir::StringRef; // NOLINT(misc-unused-using-decls) +using mlir::StringSet; // NOLINT(misc-unused-using-decls) +using mlir::TinyPtrVector; // NOLINT(misc-unused-using-decls) +using mlir::Twine; // NOLINT(misc-unused-using-decls) +using mlir::TypeSwitch; // NOLINT(misc-unused-using-decls) } // namespace circt // Forward declarations of LLVM classes to be imported in to the circt @@ -73,11 +74,14 @@ namespace llvm { template class SmallDenseMap; +template +class SmallSet; } // namespace llvm // Import things we want into our namespace. namespace circt { -using llvm::SmallDenseMap; +using llvm::SmallDenseMap; // NOLINT(misc-unused-using-decls) +using llvm::SmallSet; // NOLINT(misc-unused-using-decls) } // namespace circt // Forward declarations of classes to be imported in to the circt namespace. @@ -87,6 +91,7 @@ class AsmParser; class AsmPrinter; class Attribute; class Block; +class TypedAttr; class IRMapping; class BlockArgument; class BoolAttr; @@ -101,6 +106,7 @@ class Dialect; class DialectAsmParser; class DialectAsmPrinter; class DictionaryAttr; +class DistinctAttr; class ElementsAttr; class FileLineColLoc; class FlatSymbolRefAttr; @@ -124,6 +130,7 @@ class NoneType; class OpAsmDialectInterface; class OpAsmParser; class OpAsmPrinter; +class OpaqueProperties; class OpBuilder; class OperandRange; class Operation; @@ -159,10 +166,13 @@ class WalkResult; enum class RegionKind; struct CallInterfaceCallable; struct LogicalResult; -struct MemRefAccess; struct OperationState; class OperationName; +namespace affine { +struct MemRefAccess; +} // namespace affine + template class FailureOr; template @@ -205,6 +215,7 @@ using mlir::Dialect; // NOLINT(misc-unused-using-decls) using mlir::DialectAsmParser; // NOLINT(misc-unused-using-decls) using mlir::DialectAsmPrinter; // NOLINT(misc-unused-using-decls) using mlir::DictionaryAttr; // NOLINT(misc-unused-using-decls) +using mlir::DistinctAttr; // NOLINT(misc-unused-using-decls) using mlir::ElementsAttr; // NOLINT(misc-unused-using-decls) using mlir::failed; // NOLINT(misc-unused-using-decls) using mlir::failure; // NOLINT(misc-unused-using-decls) @@ -223,7 +234,6 @@ using mlir::IRMapping; // NOLINT(misc-unused-using-decls) using mlir::Location; // NOLINT(misc-unused-using-decls) using mlir::LocationAttr; // NOLINT(misc-unused-using-decls) using mlir::LogicalResult; // NOLINT(misc-unused-using-decls) -using mlir::MemRefAccess; // NOLINT(misc-unused-using-decls) using mlir::MemRefType; // NOLINT(misc-unused-using-decls) using mlir::MLIRContext; // NOLINT(misc-unused-using-decls) using mlir::ModuleOp; // NOLINT(misc-unused-using-decls) @@ -231,6 +241,7 @@ using mlir::MutableOperandRange; // NOLINT(misc-unused-using-decls) using mlir::NamedAttribute; // NOLINT(misc-unused-using-decls) using mlir::NamedAttrList; // NOLINT(misc-unused-using-decls) using mlir::NoneType; // NOLINT(misc-unused-using-decls) +using mlir::OpaqueProperties; // NOLINT(misc-unused-using-decls) using mlir::OpAsmDialectInterface; // NOLINT(misc-unused-using-decls) using mlir::OpAsmParser; // NOLINT(misc-unused-using-decls) using mlir::OpAsmPrinter; // NOLINT(misc-unused-using-decls) @@ -265,6 +276,7 @@ using mlir::TupleType; // NOLINT(misc-unused-using-decls) using mlir::Type; // NOLINT(misc-unused-using-decls) using mlir::TypeAttr; // NOLINT(misc-unused-using-decls) using mlir::TypeConverter; // NOLINT(misc-unused-using-decls) +using mlir::TypedAttr; // NOLINT(misc-unused-using-decls) using mlir::TypeID; // NOLINT(misc-unused-using-decls) using mlir::TypeRange; // NOLINT(misc-unused-using-decls) using mlir::TypeStorage; // NOLINT(misc-unused-using-decls) @@ -274,6 +286,7 @@ using mlir::Value; // NOLINT(misc-unused-using-decls) using mlir::ValueRange; // NOLINT(misc-unused-using-decls) using mlir::VectorType; // NOLINT(misc-unused-using-decls) using mlir::WalkResult; // NOLINT(misc-unused-using-decls) +using mlir::affine::MemRefAccess; // NOLINT(misc-unused-using-decls) namespace OpTrait = mlir::OpTrait; } // namespace circt diff --git a/include/circt/Support/LoweringOptions.h b/include/circt/Support/LoweringOptions.h index eb6af5e77ede..59ce7aadc966 100644 --- a/include/circt/Support/LoweringOptions.h +++ b/include/circt/Support/LoweringOptions.h @@ -72,6 +72,10 @@ struct LoweringOptions { /// Yosys). bool disallowPackedArrays = false; + /// If true, eliminate packed struct assignments in favor of a wire + + /// assignments to the individual fields. + bool disallowPackedStructAssignments = false; + /// If true, do not emit SystemVerilog locally scoped "automatic" or logic /// declarations - emit top level wire and reg's instead. bool disallowLocalVariables = false; @@ -154,6 +158,14 @@ struct LoweringOptions { /// If true, do not emit a version comment at the top of each verilog file. bool omitVersionComment = false; + + /// If true, then unique names that collide with keywords case insensitively. + /// This is used to avoid stricter lint warnings which, e.g., treat "REG" as a + /// Verilog keyword. + bool caseInsensitiveKeywords = false; + + /// If true, then update the the mlir to include output verilog locations. + bool emitVerilogLocations = false; }; } // namespace circt diff --git a/include/circt/Support/LoweringOptionsParser.h b/include/circt/Support/LoweringOptionsParser.h index cfde6011ac93..fa5426be8d45 100644 --- a/include/circt/Support/LoweringOptionsParser.h +++ b/include/circt/Support/LoweringOptionsParser.h @@ -48,7 +48,8 @@ struct LoweringOptionsOption "locationInfoStyle={plain,wrapInAtSquareBracket,none}, " "disallowPortDeclSharing, printDebugInfo, " "disallowExpressionInliningInPorts, disallowMuxInlining, " - "emitWireInPort, emitBindComments"), + "emitWireInPort, emitBindComments, omitVersionComment, " + "caseInsensitiveKeywords"), llvm::cl::cat(cat), llvm::cl::value_desc("option")} {} }; diff --git a/include/circt/Support/Namespace.h b/include/circt/Support/Namespace.h index 9bcda9c36fd1..949ec82efdd3 100644 --- a/include/circt/Support/Namespace.h +++ b/include/circt/Support/Namespace.h @@ -28,7 +28,11 @@ namespace circt { /// namespaces implementations. class Namespace { public: - Namespace() {} + Namespace() { + // This fills an entry for an empty string beforehand so that `newName` + // doesn't return an empty string. + nextIndex.insert({"", 0}); + } Namespace(const Namespace &other) = default; Namespace(Namespace &&other) : nextIndex(std::move(other.nextIndex)) {} diff --git a/include/circt/Support/ParsingUtils.h b/include/circt/Support/ParsingUtils.h new file mode 100644 index 000000000000..2d7cd3c71728 --- /dev/null +++ b/include/circt/Support/ParsingUtils.h @@ -0,0 +1,57 @@ +//===- ParsingUtils.h - CIRCT parsing common functions ----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Utilities to help with parsing. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_PARSINGUTILS_H +#define CIRCT_SUPPORT_PARSINGUTILS_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/OpImplementation.h" + +namespace circt { +namespace parsing_util { + +/// Get a name from an SSA value string, if said value name is not a +/// number. +static inline StringAttr getNameFromSSA(MLIRContext *context, StringRef name) { + if (!name.empty()) { + // Ignore numeric names like %42 + assert(name.size() > 1 && name[0] == '%' && "Unknown MLIR name"); + if (isdigit(name[1])) + name = StringRef(); + else + name = name.drop_front(); + } + return StringAttr::get(context, name); +} + +//===----------------------------------------------------------------------===// +// Initializer lists +//===----------------------------------------------------------------------===// + +/// Parses an initializer. +/// An initializer list is a list of operands, types and names on the format: +/// (%arg = %input : type, ...) +ParseResult parseInitializerList( + mlir::OpAsmParser &parser, + llvm::SmallVector &inputArguments, + llvm::SmallVector &inputOperands, + llvm::SmallVector &inputTypes, ArrayAttr &inputNames); + +// Prints an initializer list. +void printInitializerList(OpAsmPrinter &p, ValueRange ins, + ArrayRef args); + +} // namespace parsing_util +} // namespace circt + +#endif // CIRCT_SUPPORT_PARSINGUTILS_H diff --git a/include/circt/Support/Passes.h b/include/circt/Support/Passes.h index d678e008eda6..00f461557148 100644 --- a/include/circt/Support/Passes.h +++ b/include/circt/Support/Passes.h @@ -55,6 +55,10 @@ class VerbosePassInstrumentation : public mlir::PassInstrumentation { } } }; + +/// Create a simple canonicalizer pass. +std::unique_ptr createSimpleCanonicalizerPass(); + } // namespace circt #endif // CIRCT_SUPPORT_PASSES_H diff --git a/include/circt/Support/PrettyPrinter.h b/include/circt/Support/PrettyPrinter.h index c105a0891956..f27ddfe3e6ee 100644 --- a/include/circt/Support/PrettyPrinter.h +++ b/include/circt/Support/PrettyPrinter.h @@ -50,7 +50,7 @@ enum class IndentStyle { Visual, Block }; class Token { public: - enum class Kind { String, Break, Begin, End }; + enum class Kind { String, Break, Begin, End, Callback }; struct TokenInfo { Kind kind; // Common initial sequence. @@ -72,6 +72,11 @@ class Token { struct EndInfo : public TokenInfo { // Nothing }; + // This can be used to associate a callback with the print event on the + // tokens stream. Since tokens strictly follow FIFO order on a queue, each + // CallbackToken can uniquely identify the data it is associated with, when it + // is added and popped from the queue. + struct CallbackInfo : public TokenInfo {}; private: union { @@ -80,6 +85,7 @@ class Token { BreakInfo breakInfo; BeginInfo beginInfo; EndInfo endInfo; + CallbackInfo callbackInfo; } data; protected: @@ -93,6 +99,8 @@ class Token { return t.data.beginInfo; if constexpr (k == Kind::End) return t.data.endInfo; + if constexpr (k == Kind::Callback) + return t.data.callbackInfo; llvm_unreachable("unhandled token kind"); } @@ -156,6 +164,10 @@ struct BeginToken : public TokenBase { struct EndToken : public TokenBase {}; +struct CallbackToken : public TokenBase { + CallbackToken() = default; +}; + //===----------------------------------------------------------------------===// // PrettyPrinter //===----------------------------------------------------------------------===// @@ -167,6 +179,8 @@ class PrettyPrinter { virtual ~Listener(); /// No tokens referencing external memory are present. virtual void clear(){}; + /// Listener for print event. + virtual void print(){}; }; /// PrettyPrinter for specified stream. @@ -199,7 +213,7 @@ class PrettyPrinter { void addTokens(R &&tokens) { // Don't invoke listener until range processed, we own it now. { - llvm::SaveAndRestore save(listener, nullptr); + llvm::SaveAndRestore save(donotClear, true); for (Token &t : tokens) add(t); } @@ -301,6 +315,9 @@ class PrettyPrinter { /// Hook for Token storage events. Listener *listener = nullptr; + /// Flag to identify a state when the clear cannot be called. + bool donotClear = false; + /// Threshold for walking scan state and "rebasing" totals/offsets. static constexpr decltype(leftTotal) rebaseThreshold = 1UL << (std::numeric_limits::digits - 3); diff --git a/include/circt/Support/PrettyPrinterHelpers.h b/include/circt/Support/PrettyPrinterHelpers.h index eab653426f69..c1c1b90b0b8f 100644 --- a/include/circt/Support/PrettyPrinterHelpers.h +++ b/include/circt/Support/PrettyPrinterHelpers.h @@ -20,6 +20,7 @@ #include "llvm/Support/Allocator.h" #include "llvm/Support/StringSaver.h" #include "llvm/Support/raw_ostream.h" +#include namespace circt { namespace pretty { @@ -166,6 +167,37 @@ class TokenStringSaver : public PrettyPrinter::Listener { void clear() override; }; +/// Note: Callable class must implement a callable with signature: +/// void (Data) +template +class PrintEventAndStorageListener : public TokenStringSaver { + + /// List of all the unique data associated with each callback token. + /// The fact that tokens on a stream can never be printed out of order, + /// ensures that CallbackTokens are always added and invoked in FIFO order, + /// hence no need to record an index into the Data list. + std::queue dataQ; + /// The storage for the callback, as a function object. + CallableTy &callable; + +public: + PrintEventAndStorageListener(CallableTy &c) : callable(c) {} + + /// PrettyPrinter::Listener::print -- indicates all the preceding tokens on + /// the stream have been printed. + /// This is invoked when the CallbackToken is printed. + void print() override { + std::invoke(callable, dataQ.front()); + dataQ.pop(); + } + /// Get a token with the obj data. + CallbackToken getToken(DataTy obj) { + // Insert data onto the list. + dataQ.push(obj); + return CallbackToken(); + } +}; + //===----------------------------------------------------------------------===// // Streaming support. //===----------------------------------------------------------------------===// @@ -208,6 +240,8 @@ struct PPSaveString { template class TokenStream : public TokenBuilder { using Base = TokenBuilder; + +protected: TokenStringSaver &saver; public: @@ -337,6 +371,28 @@ class TokenStream : public TokenBuilder { } }; +/// Wrap the TokenStream with a helper for CallbackTokens, to record the print +/// events on the stream. +template +class TokenStreamWithCallback : public TokenStream { + using Base = TokenStream; + PrintEventAndStorageListener &saver; + + const bool enableCallback; + +public: + TokenStreamWithCallback( + PPTy &pp, PrintEventAndStorageListener &saver, + bool enableCallback) + : TokenStream(pp, saver), saver(saver), + enableCallback(enableCallback) {} + /// Add a Callback token. + void addCallback(DataType d) { + if (enableCallback) + Base::addToken(saver.getToken(d)); + } +}; } // end namespace pretty } // end namespace circt diff --git a/include/circt/Target/DebugInfo.h b/include/circt/Target/DebugInfo.h new file mode 100644 index 000000000000..f1a0f7de3718 --- /dev/null +++ b/include/circt/Target/DebugInfo.h @@ -0,0 +1,57 @@ +//===- DebugInfo.h - Debug info emission ------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares entry points to emit debug information. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_TARGET_DEBUGINFO_H +#define CIRCT_TARGET_DEBUGINFO_H + +#include "circt/Support/LLVM.h" +#include "llvm/Support/raw_ostream.h" + +namespace circt { +namespace debug { + +/// Register all debug information emission flavors as from-MLIR translations. +void registerTranslations(); + +/// Dump the debug information in the given `module` in a human-readable format. +LogicalResult dumpDebugInfo(Operation *module, llvm::raw_ostream &os); + +/// Options for HGLDD emission. +struct EmitHGLDDOptions { + /// A prefix prepended to all source file locations. This is useful if the + /// tool ingesting the HGLDD file is run from a different directory and + /// requires help finding the source files. + StringRef sourceFilePrefix = ""; + /// A prefix prepended to all output file locations. This is useful if the + /// tool ingesting the HGLDD file expects generated output files to be + /// reported relative to a different directory. + StringRef outputFilePrefix = ""; + /// The directory in which to place HGLDD output files. + StringRef outputDirectory = ""; +}; + +/// Serialize the debug information in the given `module` into the HGLDD format +/// and writes it to `output`. +LogicalResult emitHGLDD(Operation *module, llvm::raw_ostream &os, + const EmitHGLDDOptions &options = {}); + +/// Serialize the debug information in the given `module` into the HGLDD format +/// and emit one companion HGLDD file per emitted HDL file. This requires that +/// a prior emission pass such as `ExportVerilog` has annotated emission +/// locations on the operations in `module`. +LogicalResult emitSplitHGLDD(Operation *module, + const EmitHGLDDOptions &options = {}); + +} // namespace debug +} // namespace circt + +#endif // CIRCT_TARGET_DEBUGINFO_H diff --git a/include/circt/Transforms/Passes.h b/include/circt/Transforms/Passes.h index 1c48f6d30d8d..b6d4fdb85756 100644 --- a/include/circt/Transforms/Passes.h +++ b/include/circt/Transforms/Passes.h @@ -13,6 +13,7 @@ #ifndef CIRCT_TRANSFORMS_PASSES_H #define CIRCT_TRANSFORMS_PASSES_H +#include "circt/Dialect/HW/HWOpInterfaces.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/Pass/Pass.h" #include @@ -23,10 +24,13 @@ namespace circt { // Passes //===----------------------------------------------------------------------===// +std::unique_ptr createMapArithToCombPass(); std::unique_ptr createFlattenMemRefPass(); std::unique_ptr createFlattenMemRefCallsPass(); std::unique_ptr createStripDebugInfoWithPredPass( const std::function &pred); +std::unique_ptr createMaximizeSSAPass(); +std::unique_ptr createInsertMergeBlocksPass(); //===----------------------------------------------------------------------===// // Utility functions. @@ -36,6 +40,73 @@ std::unique_ptr createStripDebugInfoWithPredPass( // shape of size 1). bool isUniDimensional(mlir::MemRefType memref); +// Returns true if the region is into maximal SSA form i.e., if all the values +// within the region are in maximal SSA form. +bool isRegionSSAMaximized(Region ®ion); + +/// Strategy class to control the behavior of SSA maximization. The class +/// exposes overridable filter functions to dynamically select which blocks, +/// block arguments, operations, and operation results should be put into +/// maximal SSA form. All filter functions should return true whenever the +/// entity they operate on should be considered for SSA maximization. By +/// default, all filter functions always return true. +class SSAMaximizationStrategy { +public: + /// Determines whether a block should have the values it defines (i.e., block + /// arguments and operation results within the block) SSA maximized. + virtual bool maximizeBlock(Block *block); + /// Determines whether a block argument should be SSA maximized. + virtual bool maximizeArgument(BlockArgument arg); + /// Determines whether an operation should have its results SSA maximized. + virtual bool maximizeOp(Operation *op); + /// Determines whether an operation's result should be SSA maximized. + virtual bool maximizeResult(OpResult res); + + virtual ~SSAMaximizationStrategy() = default; +}; + +/// Converts a single value within a function into maximal SSA form. This +/// removes any implicit dataflow of this specific value within the enclosing +/// function. The function adds new block arguments wherever necessary to carry +/// the value explicitly between blocks. +/// Succeeds when it was possible to convert the value into maximal SSA form. +LogicalResult maximizeSSA(Value value, PatternRewriter &rewriter); + +/// Considers all of an operation's results for SSA maximization, following a +/// provided strategy. This removes any implicit dataflow of the selected +/// operation's results within the enclosing function. The function adds new +/// block arguments wherever necessary to carry the results explicitly between +/// blocks. Succeeds when it was possible to convert the selected operation's +/// results into maximal SSA form. +LogicalResult maximizeSSA(Operation *op, SSAMaximizationStrategy &strategy, + PatternRewriter &rewriter); + +/// Considers all values defined by a block (i.e., block arguments and operation +/// results within the block) for SSA maximization, following a provided +/// strategy. This removes any implicit dataflow of the selected values within +/// the enclosing function. The function adds new block arguments wherever +/// necessary to carry the values explicitly between blocks. Succeeds when it +/// was possible to convert the selected values defined by the block into +/// maximal SSA form. +LogicalResult maximizeSSA(Block *block, SSAMaximizationStrategy &strategy, + PatternRewriter &rewriter); + +/// Considers all blocks within a region for SSA maximization, following a +/// provided strategy. This removes any implicit dataflow of the values defined +/// by selected blocks within the region. The function adds new block arguments +/// wherever necessary to carry the region's values explicitly between blocks. +/// Succeeds when it was possible to convert all of the values defined by +/// selected blocks into maximal SSA form. +LogicalResult maximizeSSA(Region ®ion, SSAMaximizationStrategy &strategy, + PatternRewriter &rewriter); + +/// Manually run merge block insertion on a region. +/// +/// This transformation does treat loops like a single block and thus does not +/// affect them. +LogicalResult insertMergeBlocks(mlir::Region &r, + mlir::ConversionPatternRewriter &rewriter); + //===----------------------------------------------------------------------===// // Registration //===----------------------------------------------------------------------===// diff --git a/include/circt/Transforms/Passes.td b/include/circt/Transforms/Passes.td index f0d23dc1b060..424ccf8c7f77 100644 --- a/include/circt/Transforms/Passes.td +++ b/include/circt/Transforms/Passes.td @@ -53,4 +53,51 @@ def StripDebugInfoWithPred : Pass<"strip-debuginfo-with-pred", "::mlir::ModuleOp "intended to be used for testing."> ]; } +def MapArithToCombPass : Pass<"map-arith-to-comb"> { + let summary = "Map arith ops to combinational logic"; + let description = [{ + A pass which does a simple `arith` to `comb` mapping wherever possible. + This pass will not convert: + * floating point operations + * operations using `vector`-typed values + + This **does not** intend to be the definitive lowering/HLS pass of `arith` + operations in CIRCT (hence the name "map" instead of e.g. "lower"). + Rather, it provides a simple way (mostly for testing purposes) to map + `arith` operations. + }]; + let constructor = "circt::createMapArithToCombPass()"; + let dependentDialects = ["circt::hw::HWDialect, mlir::arith::ArithDialect, circt::comb::CombDialect"]; +} + +def InsertMergeBlocks : Pass<"insert-merge-blocks", "::mlir::ModuleOp"> { + let summary = "Insert explicit merge blocks"; + let description = [{ + This pass inserts additional merge blocks for each block with more than + two successors. A merge block is a block that only contains one operation, + a terminator, and has two predecessors. + The order successors are merged together mirrors the order different control + paths are created. Thus, each block with two successors will have a corresponding + merge block. + + This pass brings the CFG into a canonical form for further transformations. + + Treats loops and sub-CFGs with irregular control flow like single blocks. + }]; + let constructor = "circt::createInsertMergeBlocksPass()"; + let dependentDialects = ["mlir::cf::ControlFlowDialect", "mlir::func::FuncDialect"]; +} + +def MaximizeSSA : Pass<"maximize-ssa", "::mlir::ModuleOp"> { + let summary = "Convert every function in the module into maximal SSA form"; + let description = [{ + Convert the region within every function into maximal SSA form. This + ensures that every value used within a block is also defined within the + block, making dataflow explicit and removing block dominance-based dataflow + semantics. The pass achieves this by adding block arguments wherever + necessary to forward values to the block(s) where they are used. + }]; + let constructor = "circt::createMaximizeSSAPass()"; +} + #endif // CIRCT_TRANSFORMS_PASSES diff --git a/include/circt/Types.td b/include/circt/Types.td index 86b93f429f9b..d13a84074824 100644 --- a/include/circt/Types.td +++ b/include/circt/Types.td @@ -26,4 +26,36 @@ def APSIntAttr : Attr()">, let convertFromStorage = "$_self.getAPSInt()"; } +// Compared to the standard ArrayAttr, the accessor to an ArrayRefAttr will +// convert the attribute to an ArrayRef, while an ArrayAttr accessor just +// returns itself. +def ArrayRefAttr : + ArrayAttrBase< + CPred<"::llvm::isa<::mlir::ArrayAttr>($_self)">, "array attribute"> { + let returnType = [{ ::llvm::ArrayRef }]; + let convertFromStorage = [{ $_self.getValue() }]; +} + +def DistinctAttr : Attr()">, + "distinct attribute"> { + let storageType = [{ ::mlir::DistinctAttr }]; + let returnType = [{ ::mlir::DistinctAttr }]; + let convertFromStorage = "$_self"; +} + +def FloatAttr : Attr()">, + "float attribute"> { + let storageType = [{ ::mlir::FloatAttr }]; + let returnType = [{ ::mlir::FloatAttr }]; + let convertFromStorage = "$_self"; +} + +def DoubleAttr : Attr()">, + CPred<"cast<::mlir::FloatAttr>($_self).getType().isF64()">]>, + "double-precision float"> { + let storageType = [{ ::mlir::FloatAttr }]; + let returnType = [{ ::mlir::FloatAttr }]; + let convertFromStorage = "$_self"; +} + #endif // CIRCT_TYPES diff --git a/integration_test/Bindings/Python/dialects/esi.py b/integration_test/Bindings/Python/dialects/esi.py index f2d1929cd247..2f7d6f702ea2 100644 --- a/integration_test/Bindings/Python/dialects/esi.py +++ b/integration_test/Bindings/Python/dialects/esi.py @@ -1,5 +1,4 @@ # REQUIRES: bindings_python -# REQUIRES: capnp # RUN: %PYTHON% %s | FileCheck %s import circt @@ -15,11 +14,37 @@ print() # CHECK: !esi.any + list_type = esi.ListType.get(IntegerType.get_signless(3)) + print(list_type) + print() + # CHECK: !esi.list + + channel_type = esi.ChannelType.get(IntegerType.get_signless(16)) + print(channel_type) + print() + # CHECK: !esi.channel + + bundle_type = esi.BundleType.get( + [("i16chan", esi.ChannelDirection.TO, channel_type)], False) + print(bundle_type) + # CHECK: !esi.bundle<[!esi.channel to "i16chan"]> + assert (not bundle_type.resettable) + for bchan in bundle_type.channels: + print(bchan) + # CHECK: ('i16chan', , Type(!esi.channel)) + print() + + bundle_type = esi.BundleType.get( + [("i16chan", esi.ChannelDirection.FROM, channel_type)], True) + print(bundle_type) + # CHECK: !esi.bundle<[!esi.channel from "i16chan"] reset > + assert (bundle_type.resettable) + print() + # CHECK-LABEL: === testGen called with op: -# CHECK: %0:2 = esi.service.impl_req svc @HostComms impl as "test"(%clk) : (i1) -> (i8, !esi.channel) { -# CHECK: %2 = esi.service.req.to_client <@HostComms::@Recv>(["m1", "loopback_tohw"]) : !esi.channel -# CHECK: esi.service.req.to_server %m1.loopback_fromhw -> <@HostComms::@Send>(["m1", "loopback_fromhw"]) : !esi.channel +# CHECK: %0:2 = esi.service.impl_req #esi.appid<"mstop"> svc @HostComms impl as "test"(%clk) : (i1) -> (i8, !esi.bundle<[!esi.channel to "recv"]>) { +# CHECK: %2 = esi.service.impl_req.req <@HostComms::@Recv>([#esi.appid<"loopback_tohw">]) : !esi.bundle<[!esi.channel to "recv"]> def testGen(reqOp: esi.ServiceImplementReqOp) -> bool: print("=== testGen called with op:") reqOp.print() @@ -32,21 +57,20 @@ def testGen(reqOp: esi.ServiceImplementReqOp) -> bool: with Context() as ctx: circt.register_dialects(ctx) mod = Module.parse(""" +!recvI8 = !esi.bundle<[!esi.channel to "recv"]> + esi.service.decl @HostComms { - esi.service.to_server @Send : !esi.channel - esi.service.to_client @Recv : !esi.channel + esi.service.to_client @Recv : !recvI8 } -msft.module @MsTop {} (%clk: i1) -> (chksum: i8) { - %c = esi.service.instance svc @HostComms impl as "test" (%clk) : (i1) -> (i8) - msft.instance @m1 @MsLoopback (%clk) : (i1) -> () - msft.output %c : i8 +hw.module @MsTop (in %clk : i1, out chksum : i8) { + %c = esi.service.instance #esi.appid<"mstop"> svc @HostComms impl as "test" (%clk) : (i1) -> (i8) + hw.instance "m1" @MsLoopback (clk: %clk: i1) -> () + hw.output %c : i8 } -msft.module @MsLoopback {} (%clk: i1) -> () { - %dataIn = esi.service.req.to_client <@HostComms::@Recv> (["loopback_tohw"]) : !esi.channel - esi.service.req.to_server %dataIn -> <@HostComms::@Send> (["loopback_fromhw"]) : !esi.channel - msft.output +hw.module @MsLoopback (in %clk : i1) { + %dataIn = esi.service.req.to_client <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) : !recvI8 } """) pm = passmanager.PassManager.parse("builtin.module(esi-connect-services)") diff --git a/integration_test/Bindings/Python/dialects/esi_appid.py b/integration_test/Bindings/Python/dialects/esi_appid.py new file mode 100644 index 000000000000..3ec144c1c8d5 --- /dev/null +++ b/integration_test/Bindings/Python/dialects/esi_appid.py @@ -0,0 +1,93 @@ +# REQUIRES: bindings_python +# RUN: %PYTHON% %s +# RUN: %PYTHON% %s 2> %t | FileCheck %s +# RUN: cat %t | FileCheck --check-prefix=ERR %s + +import circt +from circt.dialects import esi, hw + +import circt.ir as ir +import circt.passmanager +import sys + +# CHECK-LABEL: === appid === +# ERR-LABEL: === appid errors === +print("=== appid ===") +print("=== appid errors ===", file=sys.stderr) +sys.stderr.flush() + +with ir.Context() as ctx, ir.Location.unknown(): + circt.register_dialects(ctx) + unknown = ir.Location.unknown() + + appid1 = esi.AppIDAttr.get("foo", 4) + # CHECK: appid1: #esi.appid<"foo"[4]>, foo, 4 + print(f"appid1: {appid1}, {appid1.name}, {appid1.index}") + + appid_path1 = esi.AppIDPathAttr.get(ir.FlatSymbolRefAttr.get("Foo"), [appid1]) + # CHECK: appid_path1: #esi.appid_path<@Foo[<"foo"[4]>]>, @Foo, 1, #esi.appid<"foo"[4]> + print(f"appid_path1: {appid_path1}, {appid_path1.root}, " + f"{len(appid_path1)}, {appid_path1[0]}") + + # Valid appid tree. + mainmod = ir.Module.create() + with ir.InsertionPoint(mainmod.body): + extmod = hw.HWModuleExternOp(name='ExternModA', + input_ports=[], + output_ports=[]) + mymod3 = hw.HWModuleOp(name='MyMod', input_ports=[], output_ports=[]) + with ir.InsertionPoint(mymod3.add_entry_block()): + inst = extmod.instantiate("inst1", sym_name="inst1") + inst.operation.attributes["esi.appid"] = esi.AppIDAttr.get("bar", 2) + hw.OutputOp([]) + + top = hw.HWModuleOp(name='Top', input_ports=[], output_ports=[]) + with ir.InsertionPoint(top.add_entry_block()): + mymod3.instantiate("myMod", sym_name="myMod") + ext_inst = extmod.instantiate("ext_inst1", sym_name="ext_inst1") + ext_inst.operation.attributes["esi.appid"] = esi.AppIDAttr.get("ext", 0) + hw.OutputOp([]) + appid_idx = esi.AppIDIndex(mainmod.operation) + + # CHECK: hw.module.extern @ExternModA() + # CHECK: hw.module @MyMod() + # CHECK: hw.instance "inst1" sym @inst1 @ExternModA() -> () {esi.appid = #esi.appid<"bar"[2]>} + # CHECK: hw.module @Top() + # CHECK: hw.instance "myMod" sym @myMod @MyMod() -> () + # CHECK: hw.instance "ext_inst1" sym @ext_inst1 @ExternModA() -> () {esi.appid = #esi.appid<"ext"[0]>} + print(mainmod) + + appids = appid_idx.get_child_appids_of(top) + # CHECK: [#esi.appid<"ext"[0]>, #esi.appid<"bar"[2]>] + print(appids) + path = appid_idx.get_appid_path(top, esi.AppIDAttr.get("bar", 2), + ir.Location.file("msft.py", 12, 0)) + # CHECK: [#hw.innerNameRef<@Top::@myMod>, #hw.innerNameRef<@MyMod::@inst1>] + print(path) + + # Invalid appid queries + # ERR: error: could not find appid '#esi.appid<"NoExist"[2]>' + path = appid_idx.get_appid_path(top, esi.AppIDAttr.get("NoExist", 2), unknown) + assert path is None + + # Invalid appid tree. + mod2 = ir.Module.create() + with ir.InsertionPoint(mod2.body): + extmod = hw.HWModuleExternOp(name='ExternModA', + input_ports=[], + output_ports=[]) + + mymod = hw.HWModuleOp(name='MyMod', input_ports=[], output_ports=[]) + with ir.InsertionPoint(mymod.add_entry_block()): + inst = extmod.instantiate("inst1", sym_name="inst1") + inst.operation.attributes["esi.appid"] = esi.AppIDAttr.get("bar", 2) + hw.OutputOp([]) + + top = hw.HWModuleOp(name='Top', input_ports=[], output_ports=[]) + with ir.InsertionPoint(top.add_entry_block()): + mymod.instantiate("myMod1", sym_name="myMod1") + mymod.instantiate("myMod2", sym_name="myMod2") + hw.OutputOp([]) + + # ERR: error: 'hw.instance' op Found multiple identical AppIDs in same module + appid_idx = esi.AppIDIndex(mod2.operation) diff --git a/integration_test/Bindings/Python/dialects/hw.py b/integration_test/Bindings/Python/dialects/hw.py index 7133f35aeb6d..c4e90c81d79b 100644 --- a/integration_test/Bindings/Python/dialects/hw.py +++ b/integration_test/Bindings/Python/dialects/hw.py @@ -3,6 +3,7 @@ import circt from circt.dialects import hw +from circt.support import attribute_to_var from circt.ir import (Context, Location, InsertionPoint, IntegerType, IntegerAttr, Module, StringAttr, TypeAttr) @@ -94,6 +95,19 @@ def build(module): # CHECK: #hw.param.verbatim<"this is verbatim"> print(pverbatim) + outfile = hw.OutputFileAttr.get_from_filename(StringAttr.get("file.txt"), + True, True) + print(outfile) + # CHECK: #hw.output_file<"file.txt", excludeFromFileList, includeReplicatedOps> + + inner_sym = hw.InnerSymAttr.get(StringAttr.get("some_sym")) + # CHECK: #hw + print(inner_sym) + # CHECK: "some_sym" + print(inner_sym.symName) + # CHECK: some_sym + print(attribute_to_var(inner_sym)) + inner_ref = hw.InnerRefAttr.get(StringAttr.get("some_module"), StringAttr.get("some_instance")) # CHECK: #hw.innerNameRef<@some_module::@some_instance> @@ -103,6 +117,20 @@ def build(module): # CHECK: "some_instance" print(inner_ref.name) - global_ref = hw.GlobalRefAttr.get(StringAttr.get("foo")) - # CHECK: #hw.globalNameRef<@foo> - print(global_ref) + ports = [ + hw.ModulePort(StringAttr.get("out"), i1, hw.ModulePortDirection.OUTPUT), + hw.ModulePort(StringAttr.get("in1"), i2, hw.ModulePortDirection.INPUT), + hw.ModulePort(StringAttr.get("in2"), i32, hw.ModulePortDirection.INPUT), + hw.ModulePort(StringAttr.get("in3"), i32, hw.ModulePortDirection.INOUT) + ] + module_type = hw.ModuleType.get(ports) + # CHECK: !hw.modty + print(module_type) + # CHECK-NEXT: [IntegerType(i2), IntegerType(i32), Type(!hw.inout)] + print(module_type.input_types) + # CHECK-NEXT: [IntegerType(i1)] + print(module_type.output_types) + # CHECK-NEXT: ['in1', 'in2', 'in3'] + print(module_type.input_names) + # CHECK-NEXT: ['out'] + print(module_type.output_names) diff --git a/integration_test/Bindings/Python/dialects/ltl.py b/integration_test/Bindings/Python/dialects/ltl.py new file mode 100644 index 000000000000..a6e3ffc56230 --- /dev/null +++ b/integration_test/Bindings/Python/dialects/ltl.py @@ -0,0 +1,17 @@ +# REQUIRES: bindings_python +# RUN: %PYTHON% %s | FileCheck %s + +import circt + +from circt.dialects import hw, ltl +from circt.ir import Context, Location, Module, InsertionPoint, IntegerAttr, IntegerType + +with Context() as ctx, Location.unknown(): + circt.register_dialects(ctx) + m = Module.create() + with InsertionPoint(m.body): + i1 = IntegerType.get_signless(1) + true = hw.ConstantOp(IntegerAttr.get(i1, 1)) + andOp = ltl.AndOp([true, true]) + # CHECK: ltl.and %true, %true : i1, i1 + print(andOp) diff --git a/integration_test/Bindings/Python/dialects/msft.py b/integration_test/Bindings/Python/dialects/msft.py index 0f3a6f31f279..ed500ca57203 100644 --- a/integration_test/Bindings/Python/dialects/msft.py +++ b/integration_test/Bindings/Python/dialects/msft.py @@ -1,4 +1,5 @@ # REQUIRES: bindings_python +# RUN: %PYTHON% %s # RUN: %PYTHON% %s 2> %t | FileCheck %s # RUN: cat %t | FileCheck --check-prefix=ERR %s @@ -9,6 +10,12 @@ import circt.passmanager import sys +# CHECK-LABEL: === pd === +# ERR-LABEL: === pd errors === +print("=== pd ===") +print("=== pd errors ===", file=sys.stderr) +sys.stderr.flush() + with ir.Context() as ctx, ir.Location.unknown(): circt.register_dialects(ctx) i32 = ir.IntegerType.get_signless(32) @@ -16,34 +23,28 @@ mod = ir.Module.create() with ir.InsertionPoint(mod.body): - extmod = msft.MSFTModuleExternOp(name='MyExternMod', - input_ports=[], - output_ports=[]) - - entity_extern = msft.EntityExternOp.create("tag", "extra details") + extmod = hw.HWModuleExternOp(name='MyExternMod', + input_ports=[], + output_ports=[]) - op = msft.MSFTModuleOp(name='MyWidget', input_ports=[], output_ports=[]) + op = hw.HWModuleOp(name='MyWidget', input_ports=[], output_ports=[]) with ir.InsertionPoint(op.add_entry_block()): - msft.OutputOp([]) + hw.OutputOp([]) - top = msft.MSFTModuleOp(name='top', input_ports=[], output_ports=[]) + top = hw.HWModuleOp(name='top', input_ports=[], output_ports=[]) with ir.InsertionPoint(top.add_entry_block()): - msft.OutputOp([]) + hw.OutputOp([]) - msft_mod = msft.MSFTModuleOp(name='msft_mod', - input_ports=[], - output_ports=[], - parameters=ir.DictAttr.get( - {"WIDTH": ir.IntegerAttr.get(i32, 8)})) + msft_mod = hw.HWModuleOp(name='msft_mod', input_ports=[], output_ports=[]) with ir.InsertionPoint(msft_mod.add_entry_block()): - msft.OutputOp([]) + hw.OutputOp([]) with ir.InsertionPoint.at_block_terminator(op.body.blocks[0]): - ext_inst = extmod.instantiate("ext1") + ext_inst = extmod.instantiate("ext1", sym_name="ext1") with ir.InsertionPoint.at_block_terminator(top.body.blocks[0]): - path = op.instantiate("inst1") - minst = msft_mod.instantiate("minst") + path = op.instantiate("inst1", sym_name="inst1") + minst = msft_mod.instantiate("minst", sym_name="minst") # CHECK: #msft.physloc physAttr = msft.PhysLocationAttr.get(msft.M20K, x=2, y=6, num=1) @@ -61,9 +62,9 @@ print(path) # CHECK-NEXT: [#hw.innerNameRef<@top::@inst1>, #hw.innerNameRef<@MyWidget::@ext1>] - # CHECK: msft.module @MyWidget {} () - # CHECK: msft.output - # CHECK: msft.module @msft_mod {WIDTH = 8 : i32} () + # CHECK: hw.module @MyWidget() + # CHECK: hw.module @msft_mod() + mod.operation.verify() mod.operation.print() db = msft.PlacementDB(mod) @@ -108,11 +109,6 @@ rc = seeded_pdb.place(dyn_inst, physAttr, "|foo_subpath", ir.Location.current) assert rc - # Temporarily ditch support for external entities - # external_path = ir.ArrayAttr.get( - # [ir.FlatSymbolRefAttr.get(entity_extern.sym_name.value)]) - # rc = seeded_pdb.add_placement(physAttr2, external_path, "", entity_extern) - # assert rc nearest = seeded_pdb.get_nearest_free_in_column(msft.M20K, 2, 4) assert isinstance(nearest, msft.PhysLocationAttr) @@ -256,11 +252,6 @@ def print_placement(loc, locOp): # CHECK: set_location_assignment M20K_X2_Y6_N1 -to $parent|inst1|ext1|foo_subpath print(mod) pm = circt.passmanager.PassManager.parse( - "builtin.module(msft-lower-instances,lower-msft-to-hw,msft-export-tcl{tops=top})" - ) + "builtin.module(msft-lower-instances,msft-export-tcl{tops=top})") pm.run(mod.operation) circt.export_verilog(mod, sys.stdout) - - appid1 = msft.AppIDAttr.get("foo", 4) - # CHECK: appid1: #msft.appid<"foo"[4]>, foo, 4 - print(f"appid1: {appid1}, {appid1.name}, {appid1.index}") diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py new file mode 100644 index 000000000000..6edc107ec012 --- /dev/null +++ b/integration_test/Bindings/Python/dialects/om.py @@ -0,0 +1,233 @@ +# REQUIRES: bindings_python +# RUN: %PYTHON% %s | FileCheck %s + +import circt +from circt.dialects import om +from circt.ir import Context, InsertionPoint, Location, Module +from circt.support import var_to_attribute + +from dataclasses import dataclass + +with Context() as ctx, Location.unknown(): + circt.register_dialects(ctx) + + module = Module.parse(""" + module { + om.class @node() { + %0 = om.constant #om.list : !om.list + %1 = om.constant "Component.inst1.foo" : !om.string + om.class.field @field2, %1 : !om.string + } + + om.class @comp( + %inst1_propOut_bore: !om.class.type<@node>, + %inst2_propOut_bore: !om.class.type<@node>) { + om.class.field @field2, %inst1_propOut_bore : !om.class.type<@node> + om.class.field @field3, %inst2_propOut_bore : !om.class.type<@node> + } + + om.class @Client() { + %0 = om.object @node() : () -> !om.class.type<@node> + %2 = om.object @comp(%0, %0) : (!om.class.type<@node>, !om.class.type<@node>) -> !om.class.type<@comp> + + om.class.field @client_omnode_0_OMIROut, %2 : !om.class.type<@comp> + om.class.field @node0_OMIROut, %0 : !om.class.type<@node> + om.class.field @node1_OMIROut, %0 : !om.class.type<@node> + } + + %sym = om.constant #om.ref<<@Root::@x>> : !om.ref + + om.class @Test(%param: !om.integer) { + om.class.field @field, %param : !om.integer + + %c_14 = om.constant #om.integer<14> : !om.integer + %0 = om.object @Child(%c_14) : (!om.integer) -> !om.class.type<@Child> + om.class.field @child, %0 : !om.class.type<@Child> + + om.class.field @reference, %sym : !om.ref + + %list = om.constant #om.list : !om.list + om.class.field @list, %list : !om.list + + %tuple = om.tuple_create %list, %c_14: !om.list, !om.integer + om.class.field @tuple, %tuple : tuple, !om.integer> + + %c_15 = om.constant #om.integer<15> : !om.integer + %1 = om.object @Child(%c_15) : (!om.integer) -> !om.class.type<@Child> + %list_child = om.list_create %0, %1: !om.class.type<@Child> + %2 = om.object @Nest(%list_child) : (!om.list>) -> !om.class.type<@Nest> + om.class.field @nest, %2 : !om.class.type<@Nest> + + %3 = om.constant #om.map, b = #om.integer<32>}> : !om.map + om.class.field @map, %3 : !om.map + + %x = om.constant "X" : !om.string + %y = om.constant "Y" : !om.string + %entry1 = om.tuple_create %x, %c_14: !om.string, !om.integer + %entry2 = om.tuple_create %y, %c_15: !om.string, !om.integer + + %map = om.map_create %entry1, %entry2: !om.string, !om.integer + om.class.field @map_create, %map : !om.map + } + + om.class @Child(%0: !om.integer) { + om.class.field @foo, %0 : !om.integer + } + + om.class @Nest(%0: !om.list>) { + om.class.field @list_child, %0 : !om.list> + } + + hw.module @Root(in %clock: i1) { + %0 = sv.wire sym @x : !hw.inout + } + + om.class @Paths(%basepath: !om.frozenbasepath) { + %0 = om.frozenbasepath_create %basepath "Foo/bar" + %1 = om.frozenpath_create reference %0 "Bar/baz:Baz>w" + om.class.field @path, %1 : !om.frozenpath + + %3 = om.frozenpath_empty + om.class.field @deleted, %3 : !om.frozenpath + } + } + """) + + evaluator = om.Evaluator(module) + +# Test instantiate failure. + +try: + obj = evaluator.instantiate("Test") +except ValueError as e: + # CHECK: actual parameter list length (0) does not match + # CHECK: actual parameters: + # CHECK: formal parameters: + # CHECK: unable to instantiate object, see previous error(s) + print(e) + +# Test get field failure. + +try: + obj = evaluator.instantiate("Test", 42) + obj.foo +except ValueError as e: + # CHECK: field "foo" does not exist + # CHECK: see current operation: + # CHECK: unable to get field, see previous error(s) + print(e) + +# Test instantiate success. + +obj = evaluator.instantiate("Test", 42) + +assert isinstance(obj.type, om.ClassType) + +# CHECK: Test +print(obj.type.name) + +# CHECK: 42 +print(obj.field) + +# location of the om.class.field @field +# CHECK: loc("-":28:7) +print(obj.get_field_loc("field")) + +# CHECK: 14 +print(obj.child.foo) +# CHECK: loc("-":61:7) +print(obj.child.get_field_loc("foo")) +# CHECK: ('Root', 'x') +print(obj.reference) +(fst, snd) = obj.tuple +# CHECK: 14 +print(snd) + +# CHECK: loc("-":40:7) +print(obj.get_field_loc("tuple")) + +try: + print(obj.tuple[3]) +except IndexError as e: + # CHECK: tuple index out of range + print(e) + +for (name, field) in obj: + # location from om.class.field @child, %0 : !om.class.type<@Child> + # CHECK: name: child, field: w + +print(obj.deleted) +# CHECK: OMDeleted + +paths_class = [ + cls for cls in module.body + if hasattr(cls, "sym_name") and cls.sym_name.value == "Paths" +][0] +base_path_type = paths_class.regions[0].blocks[0].arguments[0].type +assert isinstance(base_path_type, om.BasePathType) + +paths_fields = [ + op for op in paths_class.regions[0].blocks[0] + if isinstance(op, om.ClassFieldOp) +] +for paths_field in paths_fields: + assert isinstance(paths_field.value.type, om.PathType) diff --git a/integration_test/Bindings/Python/dialects/rtl.py b/integration_test/Bindings/Python/dialects/rtl.py index a43c09e0085d..ee330d743973 100644 --- a/integration_test/Bindings/Python/dialects/rtl.py +++ b/integration_test/Bindings/Python/dialects/rtl.py @@ -31,7 +31,7 @@ m = Module.create() with InsertionPoint(m.body): - # CHECK: hw.module @MyWidget(%my_input: i32) -> (my_output: i32) + # CHECK: hw.module @MyWidget(in %my_input : i32, out my_output : i32) # CHECK: hw.output %my_input : i32 op = hw.HWModuleOp( name='MyWidget', @@ -39,7 +39,7 @@ output_ports=[('my_output', i32)], body_builder=lambda module: hw.OutputOp([module.my_input])) - # CHECK: hw.module.extern @FancyThing(%input0: i32) -> (output0: i32) + # CHECK: hw.module.extern @FancyThing(in %input0 : i32, out output0 : i32) extern = hw.HWModuleExternOp(name="FancyThing", input_ports=[("input0", i32)], output_ports=[("output0", i32)]) @@ -101,7 +101,7 @@ def instance_builder_body(module): instance_builder_tests = hw.HWModuleOp(name="instance_builder_tests", body_builder=instance_builder_body) - # CHECK: hw.module @block_args_test(%[[PORT_NAME:.+]]: i32) -> + # CHECK: hw.module @block_args_test(in %[[PORT_NAME:.+]] : i32, out # CHECK: hw.output %[[PORT_NAME]] hw.HWModuleOp(name="block_args_test", input_ports=[("foo", i32)], diff --git a/integration_test/Bindings/Python/dialects/rtl_errors.py b/integration_test/Bindings/Python/dialects/rtl_errors.py index 47b961ff29de..857b97be8a2b 100644 --- a/integration_test/Bindings/Python/dialects/rtl_errors.py +++ b/integration_test/Bindings/Python/dialects/rtl_errors.py @@ -61,7 +61,7 @@ def instance_builder_body(module): # Note, the error here is actually caught and printed below. # CHECK: Uninitialized backedges remain in circuit! # CHECK: Backedge: [[PORT_NAME:.+]] - # CHECK: InstanceOf: hw.module @one_input(%[[PORT_NAME]]: i32) + # CHECK: InstanceOf: hw.module @one_input(in %[[PORT_NAME]] : i32) # CHECK: Instance: hw.instance "inst1" @one_input({{.+}}) inst1 = one_input.instantiate("inst1") diff --git a/integration_test/Bindings/Python/dialects/seq.py b/integration_test/Bindings/Python/dialects/seq.py index 4da15c3b5b51..de6befbc96c1 100644 --- a/integration_test/Bindings/Python/dialects/seq.py +++ b/integration_test/Bindings/Python/dialects/seq.py @@ -13,6 +13,7 @@ with Context() as ctx, Location.unknown(): circt.register_dialects(ctx) + clk = seq.ClockType.get(ctx) i1 = IntegerType.get_signless(1) i32 = IntegerType.get_signless(32) @@ -23,26 +24,31 @@ def top(module): # CHECK: %[[RESET_VAL:.+]] = hw.constant 0 reg_reset = hw.ConstantOp.create(i32, 0).result + # CHECK: %[[POWERON_VAL:.+]] = hw.constant 42 + poweron_value = hw.ConstantOp.create(i32, 42).result # CHECK: %[[INPUT_VAL:.+]] = hw.constant 45 reg_input = hw.ConstantOp.create(i32, 45).result - # CHECK: %[[DATA_VAL:.+]] = seq.compreg %[[INPUT_VAL]], %clk, %rst, %[[RESET_VAL]] + # CHECK: %[[DATA_VAL:.+]] = seq.compreg %[[INPUT_VAL]], %clk reset %rst, %[[RESET_VAL]] powerOn %[[POWERON_VAL]] reg = seq.CompRegOp(i32, reg_input, module.clk, reset=module.rst, reset_value=reg_reset, + power_on_value=poweron_value, name="my_reg") # CHECK: seq.compreg %[[INPUT_VAL]], %clk seq.reg(reg_input, module.clk) - # CHECK: seq.compreg %[[INPUT_VAL]], %clk, %rst, %{{.+}} + # CHECK: seq.compreg %[[INPUT_VAL]], %clk reset %rst, %{{.+}} seq.reg(reg_input, module.clk, reset=module.rst) # CHECK: %[[RESET_VALUE:.+]] = hw.constant 123 - # CHECK: seq.compreg %[[INPUT_VAL]], %clk, %rst, %[[RESET_VALUE]] + # CHECK: seq.compreg %[[INPUT_VAL]], %clk reset %rst, %[[RESET_VALUE]] custom_reset = hw.ConstantOp.create(i32, 123).result seq.reg(reg_input, module.clk, reset=module.rst, reset_value=custom_reset) # CHECK: %FuBar = seq.compreg {{.+}} seq.reg(reg_input, module.clk, name="FuBar") + # CHECK: seq.compreg sym @FuBar + seq.reg(reg_input, module.clk, sym_name="FuBar") # CHECK: %reg1 = seq.compreg %[[INPUT_VAL]], %clk {sv.attributes = [#sv.attribute<"no_merge">]} : i32 sv_attr = sv.SVAttributeAttr.get("no_merge") @@ -66,7 +72,7 @@ def top(module): hw.OutputOp([reg.data]) hw.HWModuleOp(name="top", - input_ports=[("clk", i1), ("rst", i1)], + input_ports=[("clk", clk), ("rst", i1)], output_ports=[("result", i32)], body_builder=top) diff --git a/integration_test/Bindings/Python/dialects/verif.py b/integration_test/Bindings/Python/dialects/verif.py new file mode 100644 index 000000000000..b1c690eb536b --- /dev/null +++ b/integration_test/Bindings/Python/dialects/verif.py @@ -0,0 +1,17 @@ +# REQUIRES: bindings_python +# RUN: %PYTHON% %s | FileCheck %s + +import circt + +from circt.dialects import hw, verif +from circt.ir import Context, Location, Module, InsertionPoint, IntegerAttr, IntegerType + +with Context() as ctx, Location.unknown(): + circt.register_dialects(ctx) + m = Module.create() + with InsertionPoint(m.body): + i1 = IntegerType.get_signless(1) + true = hw.ConstantOp(IntegerAttr.get(i1, 1)) + assertOp = verif.AssertOp(true) + # CHECK: verif.assert %true + print(assertOp) diff --git a/integration_test/Bindings/Python/support/conversion.py b/integration_test/Bindings/Python/support/conversion.py new file mode 100644 index 000000000000..2dc67c0c4d36 --- /dev/null +++ b/integration_test/Bindings/Python/support/conversion.py @@ -0,0 +1,12 @@ +# REQUIRES: bindings_python +# RUN: %PYTHON% %s | FileCheck %s + +from circt.ir import ArrayAttr, Context, StringAttr +from circt.support import attribute_to_var + +with Context(): + string_attr = StringAttr.get("foo") + array_attr = ArrayAttr.get([string_attr]) + array = attribute_to_var(array_attr) + # CHECK: ['foo'] + print(array) diff --git a/integration_test/Bindings/Tcl/Inputs/simple.mlir b/integration_test/Bindings/Tcl/Inputs/simple.mlir index e77c6a04d786..d0e60c77f5be 100644 --- a/integration_test/Bindings/Tcl/Inputs/simple.mlir +++ b/integration_test/Bindings/Tcl/Inputs/simple.mlir @@ -1,29 +1,28 @@ -hw.module.extern @ichi(%a: i2, %b: i3) -> (%c: i4, %d: i5) +hw.module.extern @ichi(in %a: i2, in %b: i3, out c: i4, out d: i5) -hw.module @owo() -> (%owo_result : i32) { +hw.module @owo(out owo_result : i32) { %0 = hw.constant 3 : i32 hw.output %0 : i32 } -hw.module @uwu() -> () { } +hw.module @uwu() { } -hw.module @nya(%nya_input : i32) -> () { - hw.instance "uwu1" @uwu() : () -> () +hw.module @nya(in %nya_input : i32) { + hw.instance "uwu1" @uwu() -> () } -hw.module @test() -> (%test_result : i32) { +hw.module @test(out test_result : i32) { %myArray1 = sv.wire : !hw.inout> - %0 = hw.instance "owo1" @owo() : () -> (i32) - hw.instance "nya1" @nya(%0) : (i32) -> () + %0 = hw.instance "owo1" @owo() -> ("owo_result" : i32) + hw.instance "nya1" @nya("nya_input": %0 : i32) -> () hw.output %0 : i32 } -hw.module @always() -> () { +hw.module @always() { %clock = hw.constant 1 : i1 - %3 = sv.wire : !hw.inout + %3 = sv.reg : !hw.inout %false = hw.constant 0 : i1 sv.alwaysff(posedge %clock) { sv.passign %3, %false : i1 } hw.output } - diff --git a/integration_test/Bindings/Tcl/simple.tcl b/integration_test/Bindings/Tcl/simple.tcl index 3e48e9b90393..3e4feccf7c73 100644 --- a/integration_test/Bindings/Tcl/simple.tcl +++ b/integration_test/Bindings/Tcl/simple.tcl @@ -6,27 +6,27 @@ set circuit [circt load MLIR [lindex $argv 2]/integration_test/Bindings/Tcl/Inpu puts $circuit # CHECK: module { -# CHECK: hw.module.extern @ichi(%a: i2, %b: i3) -> (%c: i4, %d: i5) -# CHECK: hw.module @owo() -> (%owo_result: i32) { +# CHECK: hw.module.extern @ichi(in %a : i2, in %b : i3, out c : i4, out d : i5) +# CHECK: hw.module @owo(out owo_result : i32) { # CHECK: %c3_i32 = hw.constant 3 : i32 # CHECK: hw.output %c3_i32 : i32 # CHECK: } # CHECK: hw.module @uwu() { # CHECK: hw.output # CHECK: } -# CHECK: hw.module @nya(%nya_input: i32) { -# CHECK: hw.instance "uwu1" @uwu() : () -> () +# CHECK: hw.module @nya(in %nya_input : i32) { +# CHECK: hw.instance "uwu1" @uwu() -> () # CHECK: hw.output # CHECK: } -# CHECK: hw.module @test() -> (%test_result: i32) { +# CHECK: hw.module @test(out test_result : i32) { # CHECK: %myArray1 = sv.wire : !hw.inout> -# CHECK: %owo1.owo_result = hw.instance "owo1" @owo() : () -> i32 -# CHECK: hw.instance "nya1" @nya(%owo1.owo_result) : (i32) -> () +# CHECK: %owo1.owo_result = hw.instance "owo1" @owo() -> (owo_result: i32) +# CHECK: hw.instance "nya1" @nya(nya_input: %owo1.owo_result: i32) -> () # CHECK: hw.output %owo1.owo_result : i32 # CHECK: } # CHECK: hw.module @always() { # CHECK: %true = hw.constant true -# CHECK: %0 = sv.wire : !hw.inout +# CHECK: %0 = sv.reg : !hw.inout # CHECK: %false = hw.constant false # CHECK: sv.alwaysff(posedge %true) { # CHECK: sv.passign %0, %false : i1 diff --git a/integration_test/CMakeLists.txt b/integration_test/CMakeLists.txt index 01aa7d1d9bca..ee3eb2360766 100644 --- a/integration_test/CMakeLists.txt +++ b/integration_test/CMakeLists.txt @@ -7,9 +7,10 @@ set(CIRCT_INTEGRATION_TEST_DEPENDS circt-opt circt-translate circt-rtl-sim - esi-cosim-runner + esi-collateral firtool hlstool + ibistool handshake-runner ) @@ -21,9 +22,25 @@ endif() # If ESI Cosim is available to build then enable its tests. if (TARGET EsiCosimDpiServer) list(APPEND CIRCT_INTEGRATION_TEST_DEPENDS EsiCosimDpiServer) + list(APPEND CIRCT_INTEGRATION_TEST_DEPENDS esi-cosim-runner) get_property(ESI_COSIM_LIB_DIR TARGET EsiCosimDpiServer PROPERTY LIBRARY_OUTPUT_DIRECTORY) set(ESI_COSIM_PATH ${ESI_COSIM_LIB_DIR}/libEsiCosimDpiServer.so) endif() +get_target_property(ESI_COLLATERAL_PATH esi-collateral BINARY_DIR) + +# If the ESI runtime build and test is enabled, require those components. +llvm_canonicalize_cmake_booleans(ESI_RUNTIME) +if (ESI_RUNTIME) + list(APPEND CIRCT_INTEGRATION_TEST_DEPENDS + ESIRuntime + ESIPythonRuntime + ) +endif() + +# Enable circt-lec tests if it is built. +if(CIRCT_LEC_ENABLED) + list(APPEND CIRCT_INTEGRATION_TEST_DEPENDS circt-lec) +endif() set(CIRCT_INTEGRATION_TIMEOUT 60) # Set a 60s timeout on individual tests. configure_lit_site_cfg( diff --git a/integration_test/Dialect/Calyx/custom_port_name.mlir b/integration_test/Dialect/Calyx/custom_port_name.mlir index 65ae839cdb1b..ea8fde2cc40a 100644 --- a/integration_test/Dialect/Calyx/custom_port_name.mlir +++ b/integration_test/Dialect/Calyx/custom_port_name.mlir @@ -1,15 +1,8 @@ // This test checks that the custom port name gets propagated all the way down to Verilog -// RUN: circt-opt %s \ -// RUN: --lower-scf-to-calyx -canonicalize \ -// RUN: --calyx-remove-comb-groups --canonicalize \ -// RUN: --lower-calyx-to-fsm --canonicalize \ -// RUN: --materialize-calyx-to-fsm --canonicalize \ -// RUN: --calyx-remove-groups-fsm --canonicalize \ -// RUN: --lower-calyx-to-hw --canonicalize \ -// RUN: --convert-fsm-to-sv | FileCheck %s +// RUN: hlstool --calyx-hw --ir %s | FileCheck %s // CHECK: hw.module @control -// CHECK: hw.module @main(%a: i32, %b: i32, %clk: i1, %reset: i1, %go: i1) -> (out: i1, done: i1) +// CHECK: hw.module @main(in %a : i32, in %b : i32, in %clk : i1, in %reset : i1, in %go : i1, out out : i1, out done : i1) // CHECK: hw.instance "controller" @control func.func @main(%arg0 : i32 {calyx.port_name = "a"}, %arg1 : i32 {calyx.port_name = "b"}) -> (i1 {calyx.port_name = "out"}) { %0 = arith.cmpi slt, %arg0, %arg1 : i32 diff --git a/integration_test/Dialect/Calyx/memory_loop.mlir b/integration_test/Dialect/Calyx/memory_loop.mlir index 53aa497e1387..9a76219bc14d 100644 --- a/integration_test/Dialect/Calyx/memory_loop.mlir +++ b/integration_test/Dialect/Calyx/memory_loop.mlir @@ -1,11 +1,6 @@ // This test lowers an SCF construct through Calyx, FSM and (TODO) // to RTL. -// RUN: circt-opt %s \ -// RUN: --lower-scf-to-calyx -canonicalize \ -// RUN: --calyx-remove-comb-groups --canonicalize \ -// RUN: --lower-calyx-to-fsm --canonicalize \ -// RUN: --materialize-calyx-to-fsm --canonicalize \ -// RUN: --calyx-remove-groups-fsm --canonicalize | FileCheck %s +// RUN: hlstool --calyx-hw --output-level=post-compile --ir %s | FileCheck %s // This is the end of the road for this example since there (as of writing) // does not yet exist a lowering for calyx.memory operations. diff --git a/integration_test/Dialect/Calyx/simple_arith.mlir b/integration_test/Dialect/Calyx/simple_arith.mlir index 224896913877..5167531ac2c2 100644 --- a/integration_test/Dialect/Calyx/simple_arith.mlir +++ b/integration_test/Dialect/Calyx/simple_arith.mlir @@ -1,13 +1,6 @@ // This test lowers an SCF construct through Calyx, FSM and (TODO) // to RTL. -// RUN: circt-opt %s \ -// RUN: --lower-scf-to-calyx -canonicalize \ -// RUN: --calyx-remove-comb-groups --canonicalize \ -// RUN: --lower-calyx-to-fsm --canonicalize \ -// RUN: --materialize-calyx-to-fsm --canonicalize \ -// RUN: --calyx-remove-groups-fsm --canonicalize \ -// RUN: --lower-calyx-to-hw --canonicalize \ -// RUN: --convert-fsm-to-sv | FileCheck %s +// RUN: hlstool %s --calyx-hw --ir --output-level=sv | FileCheck %s // TODO: ... simulate the hardware! diff --git a/integration_test/Dialect/DC/dc_cocotb.py b/integration_test/Dialect/DC/dc_cocotb.py new file mode 100644 index 000000000000..10d3c95d61b3 --- /dev/null +++ b/integration_test/Dialect/DC/dc_cocotb.py @@ -0,0 +1,168 @@ +import cocotb.clock +from cocotb.triggers import RisingEdge, ReadOnly +import ctypes +from ctypes import * +from dataclasses import dataclass + + +def struct_type(fieldTypes): + # cocotb requires struct values to be written/read as cstructs. + # This places assumptions on the struct naming standard of DCToHW, + # that is, the fields are named field0, field1, ... + # For now, all tests require that all fields are of the same type, hence + # the single fieldType argument. + class MyStructType(ctypes.BigEndianStructure): + _fields_ = [(f"field{x}", fieldTypes[x]) for x in range(len(fieldTypes))] + + def __eq__(self, other): + # Comparison is just defined as equality of all fields. + for fld in self._fields_: + if getattr(self, fld[0]) != getattr(other, fld[0]): + return False + return True + + return MyStructType + + +def to_struct(tuple, fieldType): + return struct_type([fieldType for _ in range(len(tuple))])(*tuple) + + +def from_struct(bytes, n, fieldType): + cStructType = struct_type([fieldType for _ in range(1)]) + return cStructType.from_buffer_copy(bytes.buff) + + +@dataclass +class DCPort: + """ + Helper class that encapsulates a DC port from the DUT. + """ + dut: object + ready: object + valid: object + + def isReady(self): + return self.ready.value.is_resolvable and self.ready.value == 1 + + def setReady(self, v): + self.ready.value = v + + def isValid(self): + return self.valid.value.is_resolvable and self.valid.value == 1 + + def setValid(self, v): + self.valid.value = v + + async def waitUntilReady(self): + while (not self.isReady()): + await RisingEdge(self.dut.clock) + + async def waitUntilValid(self): + while (not self.isValid()): + await RisingEdge(self.dut.clock) + + async def awaitDC(self): + # Make sure that changes to ready are propagated before it is checked. + await ReadOnly() + directSend = self.isReady() + await self.waitUntilReady() + + if (directSend): + # If it was initially ready, the DC happens in the current cycle. + # Thus the invalidation has to wait until the next cycle + await RisingEdge(self.dut.clock) + + self.setValid(0) + + async def send(self, val=None): + self.setValid(1) + await self.awaitDC() + + async def awaitNOutputs(self, n): + assert (self.isReady()) + for _ in range(n): + await self.waitUntilValid() + await RisingEdge(self.dut.clock) + + +@dataclass +class DCDataPort(DCPort): + """ + A !dc.value<...>-typed port. + """ + data: object + + async def send(self, val): + self.data.value = val + await super().send() + + async def checkOutputs(self, results, converter=lambda x: x): + # converter is a function that optionally converts the data field. + assert (self.isReady()) + for res in results: + await self.waitUntilValid() + conv_value = converter(self.data.value) + assert conv_value == res, f"Expected {res}, got {conv_value}" + await RisingEdge(self.dut.clock) + + +def _findPort(dut, name): + """ + Checks if dut has a port of the provided name. Either throws an exception or + returns a DCPort that encapsulates the dut's interface. + """ + readyName = f"{name}_ready" + validName = f"{name}_valid" + dataName = f"{name}" + if (not hasattr(dut, readyName) or not hasattr(dut, validName)): + raise Exception(f"dut does not have a port named {name}") + + ready = getattr(dut, readyName) + valid = getattr(dut, validName) + data = getattr(dut, dataName, None) + + # Needed, as it otherwise would try to resolve the value + if not isinstance(data, type(None)): + return DCDataPort(dut, ready, valid, data) + + isCtrl = not hasattr(dut, f"{name}_data_field0") + + if (isCtrl): + return DCPort(dut, ready, valid) + + raise Exception(f"Port {name} is neither a control nor a data port") + + +def getPorts(dut, inNames, outNames): + """ + Helper function to produce in and out ports for the provided dut. + """ + ins = [_findPort(dut, name) for name in inNames] + outs = [_findPort(dut, name) for name in outNames] + return ins, outs + + +async def initDut(dut, inNames, outNames): + """ + Initializes a dut by adding a clock, setting initial valid and ready flags, + and performing a reset. + """ + ins, outs = getPorts(dut, inNames, outNames) + + # Create a 10us period clock on port clock + clock = cocotb.clock.Clock(dut.clock, 10, units="us") + cocotb.start_soon(clock.start()) # Start the clock + + for i in ins: + i.setValid(0) + + for o in outs: + o.setReady(1) + + # Reset + dut.reset.value = 1 + await RisingEdge(dut.clock) + dut.reset.value = 0 + await RisingEdge(dut.clock) + return ins, outs diff --git a/integration_test/Dialect/DC/lit.local.cfg.py b/integration_test/Dialect/DC/lit.local.cfg.py new file mode 100644 index 000000000000..e7b34cbf0781 --- /dev/null +++ b/integration_test/Dialect/DC/lit.local.cfg.py @@ -0,0 +1,9 @@ +# Exclude any python file in any nested subdirectory +import glob, os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +for pyfile in glob.glob(os.path.join(dir_path, "**", "*.py")): + # remove dir from pyfile + config.excludes.add(os.path.basename(pyfile)) + +config.excludes.add('dc_cocotb.py') diff --git a/integration_test/Dialect/DC/max/max.mlir b/integration_test/Dialect/DC/max/max.mlir new file mode 100644 index 000000000000..2af607aa2315 --- /dev/null +++ b/integration_test/Dialect/DC/max/max.mlir @@ -0,0 +1,49 @@ +// REQUIRES: iverilog,cocotb + +// RUN: circt-opt %s \ +// RUN: --lower-dc-to-hw \ +// RUN: --lower-esi-to-physical --lower-esi-ports --lower-esi-to-hw \ +// RUN: --export-verilog -o %t_low.mlir > %t.sv + +// RUN: circt-cocotb-driver.py \ +// RUN: --objdir=%T \ +// RUN: --topLevel=top \ +// RUN: --pythonModule=max \ +// RUN: --pythonFolders="%S,%S/.." %t.sv 2>&1 | FileCheck %s + + +// CHECK: ** TEST +// CHECK-NEXT: ******************************** +// CHECK-NEXT: ** max.oneInput +// CHECK-NEXT: ** max.multipleInputs +// CHECK-NEXT: ******************************** +// CHECK-NEXT: ** TESTS=2 PASS=2 FAIL=0 SKIP=0 +// CHECK-NEXT: ******************************** + +hw.module @top(in %in0: !dc.value, in %in1: !dc.value, in %in2: !dc.value, in %in3: !dc.value, in %in4: !dc.value, in %in5: !dc.value, in %in6: !dc.value, in %in7: !dc.value, in %in8: !dc.token, in %clock : i1, in %reset : i1, out out0: !dc.value, out out1: !dc.token) { + %token, %outputs = dc.unpack %in0 : !dc.value + %token_0, %outputs_1 = dc.unpack %in1 : !dc.value + %token_2, %outputs_3 = dc.unpack %in2 : !dc.value + %token_4, %outputs_5 = dc.unpack %in3 : !dc.value + %token_6, %outputs_7 = dc.unpack %in4 : !dc.value + %token_8, %outputs_9 = dc.unpack %in5 : !dc.value + %token_10, %outputs_11 = dc.unpack %in6 : !dc.value + %token_12, %outputs_13 = dc.unpack %in7 : !dc.value + %0 = comb.icmp sge %outputs, %outputs_1 : i64 + %1 = comb.mux %0, %outputs, %outputs_1 : i64 + %2 = comb.icmp sge %outputs_3, %outputs_5 : i64 + %3 = comb.mux %2, %outputs_3, %outputs_5 : i64 + %4 = comb.icmp sge %outputs_7, %outputs_9 : i64 + %5 = comb.mux %4, %outputs_7, %outputs_9 : i64 + %6 = comb.icmp sge %outputs_11, %outputs_13 : i64 + %7 = comb.mux %6, %outputs_11, %outputs_13 : i64 + %8 = comb.icmp sge %1, %3 : i64 + %9 = comb.mux %8, %1, %3 : i64 + %10 = comb.icmp sge %5, %7 : i64 + %11 = comb.mux %10, %5, %7 : i64 + %12 = comb.icmp sge %9, %11 : i64 + %14 = comb.mux %12, %9, %11 : i64 + %13 = dc.join %token, %token_0, %token_2, %token_4, %token_6, %token_8, %token_10, %token_12 + %15 = dc.pack %13, %14 : i64 + hw.output %15, %in8 : !dc.value, !dc.token +} diff --git a/integration_test/Dialect/DC/max/max.py b/integration_test/Dialect/DC/max/max.py new file mode 100644 index 000000000000..61ffbd646a51 --- /dev/null +++ b/integration_test/Dialect/DC/max/max.py @@ -0,0 +1,58 @@ +import cocotb +from dc_cocotb import initDut +import random + +inNames = [f"in{i}" for i in range(8)] + ["in8"] +random.seed(0) + + +def genInOutPair(): + inputs = [random.randint(0, 1000) for _ in range(8)] + output = max(inputs) + return inputs, output + + +@cocotb.test() +async def oneInput(dut): + ins, [out0, outCtrl] = await initDut(dut, inNames, ["out0", "out1"]) + inCtrl = ins[-1] + inPorts = ins[:-1] + + inputs, output = genInOutPair() + resCheck = cocotb.start_soon(out0.checkOutputs([output])) + + sends = [ + cocotb.start_soon(inPort.send(data)) + for [inPort, data] in zip(inPorts, inputs) + ] + inCtrlSend = cocotb.start_soon(inCtrl.send()) + + for s in sends: + await s + await inCtrlSend + + await resCheck + + +@cocotb.test() +async def multipleInputs(dut): + ins, [out0, outCtrl] = await initDut(dut, inNames, ["out0", "out1"]) + inCtrl = ins[-1] + inPorts = ins[:-1] + + inOutPairs = [genInOutPair() for _ in range(8)] + resCheck = cocotb.start_soon( + out0.checkOutputs([out for (_, out) in inOutPairs])) + + for (inputs, _) in inOutPairs: + sends = [ + cocotb.start_soon(inPort.send(data)) + for [inPort, data] in zip(inPorts, inputs) + ] + inCtrlSend = cocotb.start_soon(inCtrl.send()) + + for s in sends: + await s + await inCtrlSend + + await resCheck diff --git a/integration_test/Dialect/ESI/cosim/cosim_only.sv b/integration_test/Dialect/ESI/cosim/cosim_only.sv new file mode 100644 index 000000000000..7dae2f933859 --- /dev/null +++ b/integration_test/Dialect/ESI/cosim/cosim_only.sv @@ -0,0 +1,70 @@ +// REQUIRES: esi-cosim, rtl-sim +// RUN: esi-cosim-runner.py --exec %s.py %s + + +import Cosim_DpiPkg::*; + +module top( + input logic clk, + input logic rst +); + + Cosim_Manifest #( + .COMPRESSED_MANIFEST_SIZE(30) + ) __manifest ( + // zlib-compressed JSON: + // { + // "api_version": 1 + // } + .compressed_manifest('{ + 8'h78, 8'h9C, 8'hAB, 8'hE6, 8'h52, 8'h50, 8'h50, 8'h4A, + 8'h2C, 8'hC8, 8'h8C, 8'h2F, 8'h4B, 8'h2D, 8'h2A, 8'hCE, + 8'hCC, 8'hCF, 8'h53, 8'hB2, 8'h52, 8'h30, 8'hE4, 8'hAA, + 8'h05, 8'h00, 8'h4D, 8'h63, 8'h06, 8'hBB + }) + ); + + wire DataOutValid; + wire DataOutReady = 1; + wire [23:0] DataOut; + + logic DataInValid; + logic DataInReady; + logic [31:0] DataIn; + + Cosim_Endpoint_FromHost #( + .FROM_HOST_TYPE_ID("i24"), + .FROM_HOST_SIZE_BITS(24) + ) fromHost ( + .* + ); + + Cosim_Endpoint_ToHost #( + .TO_HOST_TYPE_ID("i32"), + .TO_HOST_SIZE_BITS(32) + ) toHost ( + .* + ); + + always@(posedge clk) + begin + if (~rst) + begin + if (DataOutValid && DataOutReady) + begin + $display("[%d] Recv'd: %h", $time(), DataOut); + DataIn <= {8'b0, DataOut}; + end + DataInValid <= DataOutValid && DataOutReady; + + if (DataInValid && DataInReady) + begin + $display("[%d] Sent: %h", $time(), DataIn); + end + end + else + begin + DataInValid <= 0; + end + end +endmodule diff --git a/integration_test/Dialect/ESI/cosim/cosim_only.sv.py b/integration_test/Dialect/ESI/cosim/cosim_only.sv.py new file mode 100644 index 000000000000..e8726bc4a5fc --- /dev/null +++ b/integration_test/Dialect/ESI/cosim/cosim_only.sv.py @@ -0,0 +1,17 @@ +# Test driver for mmio cosim. + +import json +import sys +import zlib + +import loopback as test + +rpc = test.LoopbackTester(sys.argv[2], sys.argv[1]) +print(rpc.cosim.list().wait()) +rpc.test_list() +rpc.test_3bytes(5) + +compressed_mani = rpc.cosim.getCompressedManifest().wait().compressedManifest +mani_str = zlib.decompress(compressed_mani).decode("ascii") +mani = json.loads(mani_str) +assert (mani["api_version"] == 1) diff --git a/integration_test/ESI/cosim/lit.local.cfg.py b/integration_test/Dialect/ESI/cosim/lit.local.cfg.py similarity index 100% rename from integration_test/ESI/cosim/lit.local.cfg.py rename to integration_test/Dialect/ESI/cosim/lit.local.cfg.py diff --git a/integration_test/Dialect/ESI/cosim/loopback.mlir b/integration_test/Dialect/ESI/cosim/loopback.mlir new file mode 100644 index 000000000000..c190e24051ac --- /dev/null +++ b/integration_test/Dialect/ESI/cosim/loopback.mlir @@ -0,0 +1,29 @@ +// REQUIRES: esi-cosim +// RUN: rm -rf %t6 && mkdir %t6 && cd %t6 +// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata > %t4.mlir +// RUN: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --export-split-verilog -o %t3.mlir +// RUN: cd .. +// RUN: esi-cosim-runner.py --exec %S/loopback.py %t6/*.sv + +!sendI8 = !esi.bundle<[!esi.channel to "send"]> +!recvI8 = !esi.bundle<[!esi.channel to "recv"]> + +esi.service.decl @HostComms { + esi.service.to_server @Send : !sendI8 + esi.service.to_client @Recv : !recvI8 +} + +hw.module @Loopback (in %clk: !seq.clock) { + %dataInBundle = esi.service.req.to_client <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) {esi.appid=#esi.appid<"loopback_tohw">} : !recvI8 + %dataOut = esi.bundle.unpack from %dataInBundle : !recvI8 + %dataOutBundle = esi.bundle.pack %dataOut : !sendI8 + esi.service.req.to_server %dataOutBundle -> <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !sendI8 +} + +esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} + +hw.module @top(in %clk: !seq.clock, in %rst: i1) { + esi.service.instance #esi.appid<"cosim"> svc @HostComms impl as "cosim" (%clk, %rst) : (!seq.clock, i1) -> () + hw.instance "m1" @Loopback (clk: %clk: !seq.clock) -> () {esi.appid=#esi.appid<"loopback_inst"[0]>} + hw.instance "m2" @Loopback (clk: %clk: !seq.clock) -> () {esi.appid=#esi.appid<"loopback_inst"[1]>} +} diff --git a/integration_test/Dialect/ESI/cosim/loopback.py b/integration_test/Dialect/ESI/cosim/loopback.py new file mode 100755 index 000000000000..ce76b5df195f --- /dev/null +++ b/integration_test/Dialect/ESI/cosim/loopback.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 + +import binascii +import random +import esi_cosim + + +class LoopbackTester(esi_cosim.CosimBase): + """Provides methods to test the loopback simulations.""" + + def test_list(self): + ifaces = self.cosim.list().wait().ifaces + assert len(ifaces) > 0 + + def test_two_chan_loopback(self, num_msgs): + to_hw = self.openEP("top.loopback_inst5B05D_loopback_tohw_recv") + from_hw = self.openEP("top.loopback_inst5B05D_loopback_fromhw_send") + for _ in range(num_msgs): + i = random.randint(0, 2**8 - 1) + print(f"Sending {i}") + to_hw.sendFromHost(msg=i.to_bytes(1, 'little')).wait() + result = self.readMsg(from_hw) + result_int = int.from_bytes(result, 'little') + print(f"Got {result_int}") + assert (result_int == i) + + def write_3bytes(self, ep): + r = random.randrange(0, 2**24 - 1) + data = r.to_bytes(3, 'little') + print(f'Sending: {r:8x} as {binascii.hexlify(data)}') + ep.sendFromHost(msg=data).wait() + return r + + def read_3bytes(self, ep): + data = self.readMsg(ep) + i = int.from_bytes(data, 'little') + print(f"Recv'd: {i:8x} as {binascii.hexlify(data)}") + return i + + def test_3bytes(self, num_msgs=50): + send_ep = self.openEP("top.fromHost", from_host_type="i24") + recv_ep = self.openEP("top.toHost", to_host_type="i32") + print("Testing writes") + dataSent = list() + for _ in range(num_msgs): + dataSent.append(self.write_3bytes(send_ep)) + print() + print("Testing reads") + dataRecv = list() + for _ in range(num_msgs): + dataRecv.append(self.read_3bytes(recv_ep)) + send_ep.close().wait() + recv_ep.close().wait() + assert dataSent == dataRecv + + +if __name__ == "__main__": + import sys + rpc = LoopbackTester(sys.argv[2], sys.argv[1]) + print(rpc.list()) + rpc.test_two_chan_loopback(25) diff --git a/integration_test/Dialect/ESI/cosim/lowlevel_cosim_only.sv b/integration_test/Dialect/ESI/cosim/lowlevel_cosim_only.sv new file mode 100644 index 000000000000..cc9fc0a8b9c8 --- /dev/null +++ b/integration_test/Dialect/ESI/cosim/lowlevel_cosim_only.sv @@ -0,0 +1,94 @@ +// REQUIRES: esi-cosim +// RUN: esi-cosim-runner.py --exec %s.py %s + +// Test the low level cosim MMIO functionality. This test has 1024 32-bit +// registers as a memory. It is an error to write to register 0. + +import Cosim_DpiPkg::*; + +module top( + input logic clk, + input logic rst +); + + // MMIO read: address channel. + logic arvalid; + logic arready; + logic [31:0] araddr; + + // MMIO read: data response channel. + reg rvalid; + logic rready; + reg [31:0] rdata; + reg [1:0] rresp; + + // MMIO write: address channel. + logic awvalid; + reg awready; + logic [31:0] awaddr; + + // MMIO write: data channel. + logic wvalid; + reg wready; + logic [31:0] wdata; + + // MMIO write: write response channel. + reg bvalid; + logic bready; + reg [1:0] bresp; + + Cosim_MMIO mmio ( + .clk(clk), + .rst(rst), + .arvalid(arvalid), + .arready(arready), + .araddr(araddr), + .rvalid(rvalid), + .rready(rready), + .rdata(rdata), + .rresp(rresp), + .awvalid(awvalid), + .awready(awready), + .awaddr(awaddr), + .wvalid(wvalid), + .wready(wready), + .wdata(wdata), + .bvalid(bvalid), + .bready(bready), + .bresp(bresp) + ); + + reg [31:0] regs [1023:0]; + + assign arready = 1; + assign rdata = regs[araddr >> 3]; + assign rresp = araddr == 0 ? 3 : 0; + always@(posedge clk) begin + if (rst) begin + rvalid <= 0; + end else begin + if (arvalid) + rvalid <= 1; + if (rready && rvalid) + rvalid <= 0; + end + end + + wire write = awvalid && wvalid && !bvalid; + assign awready = write; + assign wready = write; + always@(posedge clk) begin + if (rst) begin + bvalid <= 0; + end else begin + if (bvalid && bready) + bvalid <= 0; + if (write) begin + bvalid <= 1; + bresp <= awaddr == 0 ? 3 : 0; + regs[awaddr >> 3] <= wdata; + end + end + end + +endmodule diff --git a/integration_test/Dialect/ESI/cosim/lowlevel_cosim_only.sv.py b/integration_test/Dialect/ESI/cosim/lowlevel_cosim_only.sv.py new file mode 100644 index 000000000000..83b18e23bab3 --- /dev/null +++ b/integration_test/Dialect/ESI/cosim/lowlevel_cosim_only.sv.py @@ -0,0 +1,26 @@ +# Test driver for mmio cosim. + +import esi_cosim +import capnp +import sys + +c = esi_cosim.LowLevel(sys.argv[2], sys.argv[1]) +r = c.low.readMMIO(40).wait() +print(f"data resp: 0x{r.data:x}") + +try: + c.low.readMMIO(0).wait() + assert False, "above should have thrown exception" +except capnp.lib.capnp.KjException: + pass + +c.low.writeMMIO(32, 86).wait() +r = c.low.readMMIO(32).wait() +print(f"data resp: 0x{r.data:x}") +assert r.data == 86 + +try: + c.low.writeMMIO(0, 86).wait() + assert False, "above should have thrown exception" +except capnp.lib.capnp.KjException: + pass diff --git a/integration_test/ESI/primitives/primitive_tb.sv b/integration_test/Dialect/ESI/primitives/primitive_tb.sv similarity index 100% rename from integration_test/ESI/primitives/primitive_tb.sv rename to integration_test/Dialect/ESI/primitives/primitive_tb.sv diff --git a/integration_test/Dialect/ESI/runtime/basic_mmio.sv b/integration_test/Dialect/ESI/runtime/basic_mmio.sv new file mode 100644 index 000000000000..0794251d6080 --- /dev/null +++ b/integration_test/Dialect/ESI/runtime/basic_mmio.sv @@ -0,0 +1,94 @@ +// REQUIRES: esi-cosim, esi-runtime +// RUN: esi-cosim-runner.py --exec %s.py %s + +// Test the low level cosim MMIO functionality. This test has 1024 64-bit +// registers as a memory. It is an error to write to register 0. + +import Cosim_DpiPkg::*; + +module top( + input logic clk, + input logic rst +); + + // MMIO read: address channel. + logic arvalid; + logic arready; + logic [31:0] araddr; + + // MMIO read: data response channel. + reg rvalid; + logic rready; + reg [31:0] rdata; + reg [1:0] rresp; + + // MMIO write: address channel. + logic awvalid; + reg awready; + logic [31:0] awaddr; + + // MMIO write: data channel. + logic wvalid; + reg wready; + logic [31:0] wdata; + + // MMIO write: write response channel. + reg bvalid; + logic bready; + reg [1:0] bresp; + + Cosim_MMIO mmio ( + .clk(clk), + .rst(rst), + .arvalid(arvalid), + .arready(arready), + .araddr(araddr), + .rvalid(rvalid), + .rready(rready), + .rdata(rdata), + .rresp(rresp), + .awvalid(awvalid), + .awready(awready), + .awaddr(awaddr), + .wvalid(wvalid), + .wready(wready), + .wdata(wdata), + .bvalid(bvalid), + .bready(bready), + .bresp(bresp) + ); + + reg [31:0] regs [1023:0]; + + assign arready = 1; + assign rdata = regs[araddr >> 3]; + assign rresp = araddr == 0 ? 3 : 0; + always@(posedge clk) begin + if (rst) begin + rvalid <= 0; + end else begin + if (arvalid) + rvalid <= 1; + if (rready && rvalid) + rvalid <= 0; + end + end + + wire write = awvalid && wvalid && !bvalid; + assign awready = write; + assign wready = write; + always@(posedge clk) begin + if (rst) begin + bvalid <= 0; + end else begin + if (bvalid && bready) + bvalid <= 0; + if (write) begin + bvalid <= 1; + bresp <= awaddr == 0 ? 3 : 0; + regs[awaddr >> 3] <= wdata; + end + end + end + +endmodule diff --git a/integration_test/Dialect/ESI/runtime/basic_mmio.sv.py b/integration_test/Dialect/ESI/runtime/basic_mmio.sv.py new file mode 100644 index 000000000000..74d55807e386 --- /dev/null +++ b/integration_test/Dialect/ESI/runtime/basic_mmio.sv.py @@ -0,0 +1,28 @@ +import esi +import os +import sys + +conn = f"{sys.argv[1]}:{sys.argv[2]}" + +acc = esi.Accelerator("cosim", conn) +mmio = acc.get_service_mmio() + +r = mmio.read(40) +print(f"data resp: 0x{r:x}") + +try: + mmio.read(0) + assert False, "above should have thrown exception" +except Exception: + print("caught expected exception") + +mmio.write(32, 86) +r = mmio.read(32) +print(f"data resp: 0x{r:x}") +assert r == 86 + +try: + mmio.write(0, 44) + assert False, "above should have thrown exception" +except Exception: + print("caught expected exception") diff --git a/integration_test/Dialect/ESI/runtime/lit.local.cfg.py b/integration_test/Dialect/ESI/runtime/lit.local.cfg.py new file mode 100644 index 000000000000..410bacbe84ac --- /dev/null +++ b/integration_test/Dialect/ESI/runtime/lit.local.cfg.py @@ -0,0 +1,2 @@ +# Don't treat the Python files in this directory as tests. +config.suffixes.remove('.py') diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir b/integration_test/Dialect/ESI/runtime/loopback.mlir new file mode 100644 index 000000000000..3668122e8a74 --- /dev/null +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir @@ -0,0 +1,29 @@ +// REQUIRES: esi-cosim +// RUN: rm -rf %t6 && mkdir %t6 && cd %t6 +// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata > %t4.mlir +// RUN: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --export-split-verilog -o %t3.mlir +// RUN: cd .. +// RUN: esi-cosim-runner.py --exec %s.py %t6/*.sv + +!sendI8 = !esi.bundle<[!esi.channel to "send"]> +!recvI8 = !esi.bundle<[!esi.channel to "recv"]> + +esi.service.decl @HostComms { + esi.service.to_server @Send : !sendI8 + esi.service.to_client @Recv : !recvI8 +} + +hw.module @Loopback (in %clk: !seq.clock) { + %dataInBundle = esi.service.req.to_client <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) {esi.appid=#esi.appid<"loopback_tohw">} : !recvI8 + %dataOut = esi.bundle.unpack from %dataInBundle : !recvI8 + %dataOutBundle = esi.bundle.pack %dataOut : !sendI8 + esi.service.req.to_server %dataOutBundle -> <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !sendI8 +} + +esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} + +hw.module @top(in %clk: !seq.clock, in %rst: i1) { + esi.service.instance #esi.appid<"cosim"> svc @HostComms impl as "cosim" (%clk, %rst) : (!seq.clock, i1) -> () + hw.instance "m1" @Loopback (clk: %clk: !seq.clock) -> () {esi.appid=#esi.appid<"loopback_inst"[0]>} + hw.instance "m2" @Loopback (clk: %clk: !seq.clock) -> () {esi.appid=#esi.appid<"loopback_inst"[1]>} +} diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.py b/integration_test/Dialect/ESI/runtime/loopback.mlir.py new file mode 100644 index 000000000000..9721312ce070 --- /dev/null +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.py @@ -0,0 +1,10 @@ +import esi +import os +import sys + +conn = f"{sys.argv[1]}:{sys.argv[2]}" + +acc = esi.Accelerator("cosim", conn) + +assert acc.sysinfo().esi_version() == 1 +assert acc.manifest._manifest['api_version'] == 1 diff --git a/integration_test/ESI/supplements/integers.sv b/integration_test/Dialect/ESI/supplements/integers.sv similarity index 100% rename from integration_test/ESI/supplements/integers.sv rename to integration_test/Dialect/ESI/supplements/integers.sv diff --git a/integration_test/ESI/supplements/lit.local.cfg b/integration_test/Dialect/ESI/supplements/lit.local.cfg similarity index 100% rename from integration_test/ESI/supplements/lit.local.cfg rename to integration_test/Dialect/ESI/supplements/lit.local.cfg diff --git a/integration_test/ESI/system/basic.mlir b/integration_test/Dialect/ESI/system/basic.mlir similarity index 65% rename from integration_test/ESI/system/basic.mlir rename to integration_test/Dialect/ESI/system/basic.mlir index cb28bf58dcd2..91ef2d65a3d9 100644 --- a/integration_test/ESI/system/basic.mlir +++ b/integration_test/Dialect/ESI/system/basic.mlir @@ -1,14 +1,14 @@ // REQUIRES: rtl-sim -// RUN: circt-opt %s --lower-esi-to-physical --lower-esi-ports --lower-esi-to-hw -verify-diagnostics > %t1.mlir +// RUN: circt-opt %s --lower-esi-to-physical --lower-esi-ports --lower-esi-to-hw --lower-seq-to-sv -verify-diagnostics > %t1.mlir // RUN: circt-opt %t1.mlir -export-verilog -verify-diagnostics -o t3.mlir > %t2.sv -// RUN: circt-rtl-sim.py %t2.sv %BININC%/circt/Dialect/ESI/ESIPrimitives.sv %S/../supplements/integers.sv --cycles 150 | FileCheck %s +// RUN: circt-rtl-sim.py %t2.sv %ESI_COLLATERAL_PATH%/ESIPrimitives.sv %S/../supplements/integers.sv --cycles 150 | FileCheck %s -hw.module.extern @IntCountProd(%clk: i1, %rst: i1) -> (ints: !esi.channel) attributes {esi.bundle} -hw.module.extern @IntAcc(%clk: i1, %rst: i1, %ints: !esi.channel) -> (totalOut: i32) attributes {esi.bundle} -hw.module @top(%clk: i1, %rst: i1) -> (totalOut: i32) { - %intStream = hw.instance "prod" @IntCountProd(clk: %clk: i1, rst: %rst: i1) -> (ints: !esi.channel) +hw.module.extern @IntCountProd(in %clk: !seq.clock, in %rst: i1, out ints: !esi.channel) attributes {esi.bundle} +hw.module.extern @IntAcc(in %clk: !seq.clock, in %rst: i1, in %ints: !esi.channel, out totalOut: i32) attributes {esi.bundle} +hw.module @top(in %clk: !seq.clock, in %rst: i1, out totalOut: i32) { + %intStream = hw.instance "prod" @IntCountProd(clk: %clk: !seq.clock, rst: %rst: i1) -> (ints: !esi.channel) %intStreamBuffered = esi.buffer %clk, %rst, %intStream {stages=2, name="intChan"} : i32 - %totalOut = hw.instance "acc" @IntAcc(clk: %clk: i1, rst: %rst: i1, ints: %intStreamBuffered: !esi.channel) -> (totalOut: i32) + %totalOut = hw.instance "acc" @IntAcc(clk: %clk: !seq.clock, rst: %rst: i1, ints: %intStreamBuffered: !esi.channel) -> (totalOut: i32) hw.output %totalOut : i32 } // CHECK: [driver] Starting simulation diff --git a/integration_test/Dialect/FSM/variable/lit.local.cfg.py b/integration_test/Dialect/FSM/variable/lit.local.cfg.py new file mode 100644 index 000000000000..ea0fd211e807 --- /dev/null +++ b/integration_test/Dialect/FSM/variable/lit.local.cfg.py @@ -0,0 +1,4 @@ +# Due to intermittent failures, this test will be disabled until more work +# goes into the FSM dialect. +# See https://github.com/llvm/circt/issues/4745 +config.excludes.add('top.mlir') diff --git a/integration_test/Dialect/FSM/variable/top.mlir b/integration_test/Dialect/FSM/variable/top.mlir index be6cf3ba9c8f..ee35100672c6 100644 --- a/integration_test/Dialect/FSM/variable/top.mlir +++ b/integration_test/Dialect/FSM/variable/top.mlir @@ -1,3 +1,4 @@ +// NOTE: @mortbopet: this test is currently disabled, see lit.local.cfg.py in this directory // REQUIRES: verilator // RUN: circt-opt %s --convert-fsm-to-sv --canonicalize --lower-seq-to-sv --export-split-verilog -o %t2.mlir // RUN: circt-rtl-sim.py --compileargs="-I%T/.." top.sv %S/driver.cpp --no-default-driver | FileCheck %s diff --git a/integration_test/Dialect/Handshake/buffer_init_none/buffer_init_none.mlir b/integration_test/Dialect/Handshake/buffer_init_none/buffer_init_none.mlir index f13953631277..6688f6e8bfd5 100644 --- a/integration_test/Dialect/Handshake/buffer_init_none/buffer_init_none.mlir +++ b/integration_test/Dialect/Handshake/buffer_init_none/buffer_init_none.mlir @@ -1,18 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=buffer_init_none --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=buffer_init_none --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=buffer_init_none --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=buffer_init_none --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=buffer_init_none --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=buffer_init_none --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/buffer_initial_values/buffer_initial_values.mlir b/integration_test/Dialect/Handshake/buffer_initial_values/buffer_initial_values.mlir index ccf038be28e3..15a7769760d8 100644 --- a/integration_test/Dialect/Handshake/buffer_initial_values/buffer_initial_values.mlir +++ b/integration_test/Dialect/Handshake/buffer_initial_values/buffer_initial_values.mlir @@ -1,19 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=buffer_initial_values --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=buffer_initial_values --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=buffer_initial_values --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=buffer_initial_values --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=buffer_initial_values --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=buffer_initial_values --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/conditional_modification/conditional_modification.mlir b/integration_test/Dialect/Handshake/conditional_modification/conditional_modification.mlir index 875c069ae237..5a46cc4b4102 100644 --- a/integration_test/Dialect/Handshake/conditional_modification/conditional_modification.mlir +++ b/integration_test/Dialect/Handshake/conditional_modification/conditional_modification.mlir @@ -1,22 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=conditional_modification --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=conditional_modification --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=conditional_modification --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// Locking the circt should yield the same result -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=conditional_modification --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=conditional_modification --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=conditional_modification --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=conditional_modification --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK-NEXT: ******************************** diff --git a/integration_test/Dialect/Handshake/dot/dot.mlir b/integration_test/Dialect/Handshake/dot/dot.mlir index bb06a5e9d111..1599c618e7a8 100644 --- a/integration_test/Dialect/Handshake/dot/dot.mlir +++ b/integration_test/Dialect/Handshake/dot/dot.mlir @@ -1,15 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies +// RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=dot --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=dot --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=dot --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=dot --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=dot --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/helper.py b/integration_test/Dialect/Handshake/helper.py index 500f6e961b4c..ffb715df636f 100644 --- a/integration_test/Dialect/Handshake/helper.py +++ b/integration_test/Dialect/Handshake/helper.py @@ -1,5 +1,35 @@ import cocotb.clock from cocotb.triggers import RisingEdge, ReadOnly +import ctypes +from ctypes import * + + +def struct_type(fieldTypes): + # cocotb requires struct values to be written/read as cstructs. + # This places assumptions on the struct naming standard of HandshakeToHW, + # that is, the fields are named field0, field1, ... + # For now, all tests require that all fields are of the same type, hence + # the single fieldType argument. + class MyStructType(ctypes.BigEndianStructure): + _fields_ = [(f"field{x}", fieldTypes[x]) for x in range(len(fieldTypes))] + + def __eq__(self, other): + # Comparison is just defined as equality of all fields. + for fld in self._fields_: + if getattr(self, fld[0]) != getattr(other, fld[0]): + return False + return True + + return MyStructType + + +def to_struct(tuple, fieldType): + return struct_type([fieldType for _ in range(len(tuple))])(*tuple) + + +def from_struct(bytes, n, fieldType): + cStructType = struct_type([fieldType for _ in range(1)]) + return cStructType.from_buffer_copy(bytes.buff) class HandshakePort: @@ -69,38 +99,13 @@ async def send(self, val): self.data.value = val await super().send() - async def checkOutputs(self, results): - assert (self.isReady()) - for res in results: - await self.waitUntilValid() - assert (self.data.value == res) - await RisingEdge(self.dut.clock) - - -class HandshakeTuplePort(HandshakePort): - """ - A handshaked port that sends a tuple. - """ - - def __init__(self, dut, rdy, val, fields): - super().__init__(dut, rdy, val) - self.fields = fields - - async def send(self, val): - assert (len(list(val)) == len(self.fields)) - for (f, v) in zip(self.fields, list(val)): - f.value = v - - await super().send() - - async def checkOutputs(self, results): + async def checkOutputs(self, results, converter=lambda x: x): + # converter is a function that optionally converts the data field. assert (self.isReady()) for res in results: await self.waitUntilValid() - - assert (len(list(res)) == len(self.fields)) - for (f, r) in zip(self.fields, list(res)): - assert (f.value == r) + conv_value = converter(self.data.value) + assert conv_value == res, f"Expected {res}, got {conv_value}" await RisingEdge(self.dut.clock) @@ -111,7 +116,7 @@ def _findPort(dut, name): """ readyName = f"{name}_ready" validName = f"{name}_valid" - dataName = f"{name}_data" + dataName = f"{name}" if (not hasattr(dut, readyName) or not hasattr(dut, validName)): raise Exception(f"dut does not have a port named {name}") @@ -119,13 +124,8 @@ def _findPort(dut, name): valid = getattr(dut, validName) data = getattr(dut, dataName, None) - if data is None: - # Try with just the base name - this is the case for HandshakeToHW (ESI standard) - data = getattr(dut, name, None) - # Needed, as it otherwise would try to resolve the value - hasData = not isinstance(data, type(None)) - if hasData: + if not isinstance(data, type(None)): return HandshakeDataPort(dut, ready, valid, data) isCtrl = not hasattr(dut, f"{name}_data_field0") @@ -133,13 +133,7 @@ def _findPort(dut, name): if (isCtrl): return HandshakePort(dut, ready, valid) - fields = [] - i = 0 - while hasattr(dut, f"{name}_data_field{i}"): - fields.append(getattr(dut, f"{name}_data_field{i}")) - i += 1 - - return HandshakeTuplePort(dut, ready, valid, fields) + raise Exception(f"Port {name} is neither a control nor a data port") def getPorts(dut, inNames, outNames): diff --git a/integration_test/Dialect/Handshake/lit.local.cfg.py b/integration_test/Dialect/Handshake/lit.local.cfg.py index 0ed9a6202c63..fa20069d1f2a 100644 --- a/integration_test/Dialect/Handshake/lit.local.cfg.py +++ b/integration_test/Dialect/Handshake/lit.local.cfg.py @@ -6,5 +6,4 @@ # remove dir from pyfile config.excludes.add(os.path.basename(pyfile)) -config.excludes.add('cocotb_driver.py') config.excludes.add('helper.py') diff --git a/integration_test/Dialect/Handshake/matmul/matmul.mlir b/integration_test/Dialect/Handshake/matmul/matmul.mlir index dbe61ed9bf07..5fa54d624a1f 100644 --- a/integration_test/Dialect/Handshake/matmul/matmul.mlir +++ b/integration_test/Dialect/Handshake/matmul/matmul.mlir @@ -1,15 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies +// RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=matmul --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=matmul --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=matmul --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=matmul --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=matmul --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/max/max.mlir b/integration_test/Dialect/Handshake/max/max.mlir index fe4faef272d4..95d32a6798db 100644 --- a/integration_test/Dialect/Handshake/max/max.mlir +++ b/integration_test/Dialect/Handshake/max/max.mlir @@ -1,18 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=max --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=max --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=max --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=max --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=max --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=max --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK-NEXT: ******************************** @@ -24,21 +16,21 @@ // Computes the maximum of all inputs func.func @top(%in0: i64, %in1: i64, %in2: i64, %in3: i64, %in4: i64, %in5: i64, %in6: i64, %in7: i64) -> i64 { - %c0 = arith.cmpi slt, %in0, %in1 : i64 + %c0 = arith.cmpi sge, %in0, %in1 : i64 %t0 = arith.select %c0, %in0, %in1 : i64 - %c1 = arith.cmpi slt, %in2, %in3 : i64 + %c1 = arith.cmpi sge, %in2, %in3 : i64 %t1 = arith.select %c1, %in2, %in3 : i64 - %c2 = arith.cmpi slt, %in4, %in5 : i64 + %c2 = arith.cmpi sge, %in4, %in5 : i64 %t2 = arith.select %c2, %in4, %in5 : i64 - %c3 = arith.cmpi slt, %in6, %in7 : i64 + %c3 = arith.cmpi sge, %in6, %in7 : i64 %t3 = arith.select %c3, %in6, %in7 : i64 - %c4 = arith.cmpi slt, %t0, %t1 : i64 + %c4 = arith.cmpi sge, %t0, %t1 : i64 %t4 = arith.select %c4, %t0, %t1 : i64 - %c5 = arith.cmpi slt, %t2, %t3 : i64 + %c5 = arith.cmpi sge, %t2, %t3 : i64 %t5 = arith.select %c5, %t2, %t3 : i64 - %c6 = arith.cmpi slt, %t4, %t5 : i64 + %c6 = arith.cmpi sge, %t4, %t5 : i64 %t6 = arith.select %c6, %t4, %t5 : i64 return %t6 : i64 } diff --git a/integration_test/Dialect/Handshake/mix_std_hs/mix_std_hs.mlir b/integration_test/Dialect/Handshake/mix_std_hs/mix_std_hs.mlir index 4d76e1891c89..994a154799a4 100644 --- a/integration_test/Dialect/Handshake/mix_std_hs/mix_std_hs.mlir +++ b/integration_test/Dialect/Handshake/mix_std_hs/mix_std_hs.mlir @@ -1,22 +1,8 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --ir | firtool -format=mlir -o %T --split-verilog --lowering-options=disallowLocalVariables && \ -// RUN: hlstool %S/kernel.mlir --dynamic-firrtl --buffering-strategy=all --ir | firtool -format=mlir -o %T --split-verilog --lowering-options=disallowLocalVariables && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=mix_std_hs --pythonFolder=%S %T/*.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --ir | firtool -format=mlir -o %T --split-verilog --lowering-options=disallowLocalVariables && \ -// RUN: hlstool %S/kernel.mlir --dynamic-firrtl --buffering-strategy=allFIFO --ir | firtool -format=mlir -o %T --split-verilog --lowering-options=disallowLocalVariables && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=mix_std_hs --pythonFolder=%S %T/*.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --ir | firtool -format=mlir -o %T --split-verilog --lowering-options=disallowLocalVariables && \ -// RUN: hlstool %S/kernel.mlir --dynamic-firrtl --buffering-strategy=cycles --ir | firtool -format=mlir -o %T --split-verilog --lowering-options=disallowLocalVariables && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=mix_std_hs --pythonFolder=%S %T/*.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --dynamic-parallelism=locking --ir | firtool -format=mlir -o %T --split-verilog --lowering-options=disallowLocalVariables && \ -// RUN: hlstool %S/kernel.mlir --dynamic-firrtl --buffering-strategy=all --dynamic-parallelism=locking --ir | firtool -format=mlir -o %T --split-verilog --lowering-options=disallowLocalVariables && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=mix_std_hs --pythonFolder=%S %T/*.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --split-verilog --lowering-options=disallowLocalVariables,disallowPackedStructAssignments -o %T-cycles +// RUN: hlstool %S/kernel.mlir --dynamic-hw --buffering-strategy=cycles --split-verilog --lowering-options=disallowLocalVariables,disallowPackedStructAssignments -o %T-cycles +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=mix_std_hs --pythonFolder="%S,%S/.." %T-cycles/*.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/mix_std_hs/mix_std_hs.py b/integration_test/Dialect/Handshake/mix_std_hs/mix_std_hs.py index 889862fd36fe..9cfea1c88a65 100644 --- a/integration_test/Dialect/Handshake/mix_std_hs/mix_std_hs.py +++ b/integration_test/Dialect/Handshake/mix_std_hs/mix_std_hs.py @@ -1,6 +1,7 @@ import cocotb -from helper import initDut +from helper import initDut, to_struct, from_struct import random +import ctypes random.seed(0) @@ -23,10 +24,16 @@ async def oneInput(dut): ["out0", "outCtrl"]) inputs = [(8, 8, 4, 8, 5, 3, 1, 0)] - outputs = [getOutput(i) for i in inputs] - resCheck = cocotb.start_soon(out0.checkOutputs(outputs)) + outputs = [to_struct(getOutput(i), ctypes.c_uint64) for i in inputs] - in0Send = cocotb.start_soon(in0.send(inputs[0])) + # Run checkOutputs using a converter that decodes the long binary value into + # the expected struct. + resCheck = cocotb.start_soon( + out0.checkOutputs( + outputs, + converter=lambda x: from_struct(x, len(inputs[0]), ctypes.c_uint64))) + + in0Send = cocotb.start_soon(in0.send(to_struct(inputs[0], ctypes.c_uint64))) inCtrlSend = cocotb.start_soon(inCtrl.send()) await in0Send @@ -47,12 +54,14 @@ async def multipleInputs(dut): N = 10 inputs = [randomTuple() for _ in range(N)] - outputs = [getOutput(i) for i in inputs] - resCheck = cocotb.start_soon(out0.checkOutputs(outputs)) + outputs = [to_struct(getOutput(i), ctypes.c_uint64) for i in inputs] + resCheck = cocotb.start_soon( + out0.checkOutputs( + outputs, + converter=lambda x: from_struct(x, len(inputs[0]), ctypes.c_uint64))) for i in inputs: - print("in: ", i) - in0Send = cocotb.start_soon(in0.send(i)) + in0Send = cocotb.start_soon(in0.send(to_struct(i, ctypes.c_uint64))) inCtrlSend = cocotb.start_soon(inCtrl.send()) await in0Send diff --git a/integration_test/Dialect/Handshake/multiple_loops/multiple_loops.mlir b/integration_test/Dialect/Handshake/multiple_loops/multiple_loops.mlir index 193620dbe932..5c1c6ca08d33 100644 --- a/integration_test/Dialect/Handshake/multiple_loops/multiple_loops.mlir +++ b/integration_test/Dialect/Handshake/multiple_loops/multiple_loops.mlir @@ -1,22 +1,14 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=multiple_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=multiple_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=multiple_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=multiple_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=multiple_loops --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=multiple_loops --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // Locking the circt should yield the same result -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=multiple_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=multiple_loops --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[NUM:.*]] PASS=[[NUM]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/nested_diamonds/nested_diamonds.mlir b/integration_test/Dialect/Handshake/nested_diamonds/nested_diamonds.mlir index 65c1caae0e52..9b48e6b8f208 100644 --- a/integration_test/Dialect/Handshake/nested_diamonds/nested_diamonds.mlir +++ b/integration_test/Dialect/Handshake/nested_diamonds/nested_diamonds.mlir @@ -1,26 +1,17 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: circt-opt %s --insert-merge-blocks | \ -// RUN: hlstool --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_diamonds --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: circt-opt %s --insert-merge-blocks | \ -// RUN: hlstool --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_diamonds --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: circt-opt %s --insert-merge-blocks | \ -// RUN: hlstool --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_diamonds --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=nested_diamonds --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s +// @mortbopet: this is currently disabled due to deadlocking. // RUN: circt-opt %s --insert-merge-blocks | \ -// RUN: hlstool --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_diamonds --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=nested_diamonds --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // Locking the circt should yield the same result -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_diamonds --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv +// DISABLED: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=nested_diamonds --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[NUM:.*]] PASS=[[NUM]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/nested_loops/nested_loops.mlir b/integration_test/Dialect/Handshake/nested_loops/nested_loops.mlir index 5d3a7bc5738c..12fac5ccbd26 100644 --- a/integration_test/Dialect/Handshake/nested_loops/nested_loops.mlir +++ b/integration_test/Dialect/Handshake/nested_loops/nested_loops.mlir @@ -1,23 +1,15 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=nested_loops --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=nested_loops --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // Locking the circt should yield the same result -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=nested_loops --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=nested_loops --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[NUM:.*]] PASS=[[NUM]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/sync_backedge/sync_backedge.mlir b/integration_test/Dialect/Handshake/sync_backedge/sync_backedge.mlir index 4f0f14524823..ccab23d46764 100644 --- a/integration_test/Dialect/Handshake/sync_backedge/sync_backedge.mlir +++ b/integration_test/Dialect/Handshake/sync_backedge/sync_backedge.mlir @@ -1,18 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies +// RUN: hlstool %s --dynamic-hw --input-level core --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=sync_backedge --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=sync_backedge --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=sync_backedge --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=sync_backedge --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-hw --ir-input-level 1 --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=sync_backedge --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --input-level core --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=sync_backedge --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 @@ -29,7 +21,7 @@ handshake.func @top(%arg0: i64, %arg1: none, ...) -> (i64, none) attributes {arg %trueResult_0, %falseResult_1 = cond_br %6#0, %3#1 : none // introduce additional delay to simulate a slow branch %buffer = buffer [10] seq %trueResult_0 : none - %result, %index = control_merge %buffer, %falseResult_1 : none + %result, %index = control_merge %buffer, %falseResult_1 : none, index %7:2 = fork [2] %result : none %8 = mux %index [%trueResult, %falseResult] : index, i64 %9:2 = fork [2] %8 : i64 diff --git a/integration_test/Dialect/Handshake/sync_op/sync_op.mlir b/integration_test/Dialect/Handshake/sync_op/sync_op.mlir index 1628589bdd97..20a7303c21b1 100644 --- a/integration_test/Dialect/Handshake/sync_op/sync_op.mlir +++ b/integration_test/Dialect/Handshake/sync_op/sync_op.mlir @@ -1,18 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies +// RUN: hlstool %s --dynamic-hw --input-level core --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=sync_op --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=sync_op --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=sync_op --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=sync_op --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-hw --ir-input-level 1 --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=sync_op --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --input-level core --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=sync_op --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/task_pipelining/task_pipelining.mlir b/integration_test/Dialect/Handshake/task_pipelining/task_pipelining.mlir index fb33e3408f66..3d56654dec54 100644 --- a/integration_test/Dialect/Handshake/task_pipelining/task_pipelining.mlir +++ b/integration_test/Dialect/Handshake/task_pipelining/task_pipelining.mlir @@ -1,22 +1,14 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=task_pipelining --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=task_pipelining --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=task_pipelining --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: hlstool %s --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=task_pipelining --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=task_pipelining --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=task_pipelining --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // Locking the circt should yield the same result -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=task_pipelining --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=task_pipelining --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK-NEXT: ******************************** diff --git a/integration_test/Dialect/Handshake/tp_memory/tp_memory.mlir b/integration_test/Dialect/Handshake/tp_memory/tp_memory.mlir index c45dfdddc49e..52a4157c4167 100644 --- a/integration_test/Dialect/Handshake/tp_memory/tp_memory.mlir +++ b/integration_test/Dialect/Handshake/tp_memory/tp_memory.mlir @@ -1,22 +1,14 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tp_memory --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tp_memory --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tp_memory --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - // RUN: hlstool %s --sv-trace-iverilog --dynamic-hw --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tp_memory --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=tp_memory --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// RUN: hlstool %s --sv-trace-iverilog --dynamic-hw --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=tp_memory --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // Locking the circt should yield the same result -// RUN: hlstool %s --dynamic-firrtl --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tp_memory --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --buffering-strategy=all --dynamic-parallelism=locking --verilog --lowering-options=disallowLocalVariables > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=tp_memory --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK-NEXT: ******************************** diff --git a/integration_test/Dialect/Handshake/tuple_input/tuple_input.mlir b/integration_test/Dialect/Handshake/tuple_input/tuple_input.mlir index 588c5b227318..3d05df4d4dc8 100644 --- a/integration_test/Dialect/Handshake/tuple_input/tuple_input.mlir +++ b/integration_test/Dialect/Handshake/tuple_input/tuple_input.mlir @@ -1,15 +1,10 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies +// RUN: hlstool %s --dynamic-hw --input-level core --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables,disallowPackedStructAssignments > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=tuple_input --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tuple_input --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tuple_input --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tuple_input --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --input-level core --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables,disallowPackedStructAssignments > %t.sv && \ +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=tuple_input --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Handshake/tuple_input/tuple_input.py b/integration_test/Dialect/Handshake/tuple_input/tuple_input.py index 39c4d23b77f8..56f7ce4371df 100644 --- a/integration_test/Dialect/Handshake/tuple_input/tuple_input.py +++ b/integration_test/Dialect/Handshake/tuple_input/tuple_input.py @@ -1,5 +1,6 @@ import cocotb -from helper import initDut +from helper import initDut, to_struct +import ctypes @cocotb.test() @@ -8,8 +9,7 @@ async def oneInput(dut): ["out0", "outCtrl"]) resCheck = cocotb.start_soon(out0.checkOutputs([42])) - - in0Send = cocotb.start_soon(in0.send((24, 18))) + in0Send = cocotb.start_soon(in0.send(to_struct((24, 18), ctypes.c_uint32))) inCtrlSend = cocotb.start_soon(inCtrl.send()) await in0Send diff --git a/integration_test/Dialect/Handshake/tuple_packing/tuple_packing.mlir b/integration_test/Dialect/Handshake/tuple_packing/tuple_packing.mlir index 4aefc3959594..4ff9a99f96a0 100644 --- a/integration_test/Dialect/Handshake/tuple_packing/tuple_packing.mlir +++ b/integration_test/Dialect/Handshake/tuple_packing/tuple_packing.mlir @@ -1,15 +1,7 @@ // REQUIRES: iverilog,cocotb -// This test is executed with all different buffering strategies - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=all --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tuple_packing --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=allFIFO --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tuple_packing --pythonFolder=%S %t.sv 2>&1 | FileCheck %s - -// RUN: hlstool %s --dynamic-firrtl --ir-input-level 1 --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables > %t.sv && \ -// RUN: %PYTHON% %S/../cocotb_driver.py --objdir=%T --topLevel=top --pythonModule=tuple_packing --pythonFolder=%S %t.sv 2>&1 | FileCheck %s +// RUN: hlstool %s --dynamic-hw --input-level core --buffering-strategy=cycles --verilog --lowering-options=disallowLocalVariables,disallowPackedStructAssignments > %t.sv +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=top --pythonModule=tuple_packing --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s // CHECK: ** TEST // CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 diff --git a/integration_test/Dialect/Ibis/end_to_end.mlir b/integration_test/Dialect/Ibis/end_to_end.mlir new file mode 100644 index 000000000000..7e3ab023a36a --- /dev/null +++ b/integration_test/Dialect/Ibis/end_to_end.mlir @@ -0,0 +1,99 @@ +// RUN: ibistool -lo %s + +// A class hierarchy with a shared parent, and accessing between the children + +ibis.class @C1 { + %this = ibis.this @C1 + %out = ibis.port.output @out : i32 + %c0 = hw.constant 42 : i32 + ibis.port.write %out, %c0 : !ibis.portref +} + +ibis.class @C2 { + %this = ibis.this @C2 + + %go_port = ibis.port.input @go : i1 + %clk_port = ibis.port.input @clk : !seq.clock + %rst_port = ibis.port.input @rst : i1 + %done_port = ibis.port.output @done : i1 + %out_port = ibis.port.output @out : i32 + + ibis.container @MyMethod { + %t = ibis.this @MyMethod + + // Grab parent go, clk, reset inputs - note that the requested direction of + // these are flipped wrt. the defined direction of the ports. The semantics + // are now that get_port defines the intended usage of the port (in => i'll write to the port, out => i'll read from the port). + %parent = ibis.path [ + #ibis.step> + ] + %go_ref = ibis.get_port %parent, @go : !ibis.scoperef<@C2> -> !ibis.portref + %go = ibis.port.read %go_ref : !ibis.portref + %clk_ref = ibis.get_port %parent, @clk : !ibis.scoperef<@C2> -> !ibis.portref + %clk = ibis.port.read %clk_ref : !ibis.portref + %rst_ref = ibis.get_port %parent, @rst : !ibis.scoperef<@C2> -> !ibis.portref + %rst = ibis.port.read %rst_ref : !ibis.portref + + // Grab sibling c1's output + %sibling = ibis.path [ + #ibis.step, + #ibis.step, + #ibis.step> + ] + %sibling_out_ref = ibis.get_port %sibling, @out : !ibis.scoperef<@C1> -> !ibis.portref + %sibling_out = ibis.port.read %sibling_out_ref : !ibis.portref + + %res, %done = pipeline.scheduled(%a0 : i32 = %sibling_out) clock(%clk) reset(%rst) go(%go) entryEn(%s0_enable) -> (out : i32) { + %0 = comb.mul %a0, %a0 : i32 + pipeline.stage ^bb1 + ^bb1(%s1_enable : i1): + %1 = comb.mul %0, %a0 : i32 + pipeline.stage ^bb2 + ^bb2(%s2_enable : i1): + %2 = comb.sub %1, %0 : i32 + pipeline.stage ^bb3 + ^bb3(%s3_enable : i1): + pipeline.return %2 : i32 + } + + // Assign parent done port and output + %parent_done_ref = ibis.get_port %parent, @done : !ibis.scoperef<@C2> -> !ibis.portref + ibis.port.write %parent_done_ref, %done : !ibis.portref + %parent_out_ref = ibis.get_port %parent, @out : !ibis.scoperef<@C2> -> !ibis.portref + ibis.port.write %parent_out_ref, %res : !ibis.portref + } +} + +ibis.class @Parent { + %this = ibis.this @Parent + %c1 = ibis.instance @c1, @C1 + %c2 = ibis.instance @c2, @C2 + + %go = ibis.port.input @go : i1 + %clk = ibis.port.input @clk : !seq.clock + %rst = ibis.port.input @rst : i1 + + %done = ibis.port.output @done : i1 + %out = ibis.port.output @out : i32 + + // Wire up to c2 + %go_ref = ibis.get_port %c2, @go : !ibis.scoperef<@C2> -> !ibis.portref + %go_val = ibis.port.read %go : !ibis.portref + ibis.port.write %go_ref, %go_val : !ibis.portref + + %clk_ref = ibis.get_port %c2, @clk : !ibis.scoperef<@C2> -> !ibis.portref + %clk_val = ibis.port.read %clk : !ibis.portref + ibis.port.write %clk_ref, %clk_val : !ibis.portref + + %rst_ref = ibis.get_port %c2, @rst : !ibis.scoperef<@C2> -> !ibis.portref + %rst_val = ibis.port.read %rst : !ibis.portref + ibis.port.write %rst_ref, %rst_val : !ibis.portref + + %done_ref = ibis.get_port %c2, @done : !ibis.scoperef<@C2> -> !ibis.portref + %done_val = ibis.port.read %done_ref : !ibis.portref + ibis.port.write %done, %done_val : !ibis.portref + + %out_ref = ibis.get_port %c2, @out : !ibis.scoperef<@C2> -> !ibis.portref + %out_val = ibis.port.read %out_ref : !ibis.portref + ibis.port.write %out, %out_val : !ibis.portref +} diff --git a/integration_test/Dialect/Pipeline/lit.local.cfg.py b/integration_test/Dialect/Pipeline/lit.local.cfg.py new file mode 100644 index 000000000000..bb6463d891db --- /dev/null +++ b/integration_test/Dialect/Pipeline/lit.local.cfg.py @@ -0,0 +1,6 @@ +import glob, os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +for pyfile in glob.glob(os.path.join(dir_path, "**", "*.py"), recursive=True): + # remove dir from pyfile + config.excludes.add(os.path.basename(pyfile)) diff --git a/integration_test/Dialect/Pipeline/nonstallable/nonstallable_helper.py b/integration_test/Dialect/Pipeline/nonstallable/nonstallable_helper.py new file mode 100644 index 000000000000..806779c59e18 --- /dev/null +++ b/integration_test/Dialect/Pipeline/nonstallable/nonstallable_helper.py @@ -0,0 +1,87 @@ +import cocotb +from cocotb.triggers import Timer +import cocotb.clock + +# Value that will be adjusted and assigned to the dut input argument, every +# clock cycle. This is mainly to assist manual verification of the VCD trace/ +v = 1 + + +async def clock(dut): + global v + dut.clock.value = 0 + await Timer(1, units='ns') + v += 1 + dut.arg0.value = v + dut.clock.value = 1 + await Timer(1, units='ns') + + +async def initDut(dut): + """ + Initializes a dut by adding a clock, setting initial valid and ready flags, + and performing a reset. + """ + # Reset + dut.reset.value = 1 + await clock(dut) + dut.reset.value = 0 + await clock(dut) + + +async def nonstallable_test(dut, stageStallability): + """ + Runs a test of a non-stallable pipeline. + + Provided the number of stages in the pipeline and the indices of the non-stallable + stages, the test will: + + 1. for NStallCycles : range(0 to nStages - 1): + 1. for fillCycles : range(0 to nStages - 1): + 1. fill the pipeline with $fillCycles valid tokens + 2. raise the stall signal + 3. wait for NStallCycles + 4. While doing so, check that len(nonStallableStageIdxs) dut.done assertions + occur + 5. deassert stall + 6. check that the expected amount of bubbles exit the pipeline + """ + nStages = len(stageStallability) + numNonstallableStages = sum( + [1 if not stallable else 0 for stallable in stageStallability]) + + for nStallCycles in range(0, nStages * 2): + for fillCycles in range(0, nStages + 1): + print(f"nStallCycles: {nStallCycles}, fillCycles: {fillCycles}") + # Reset the dut + dut.go.value = 0 + dut.stall.value = 0 + dut.arg0.value = 42 + await initDut(dut) + dut.stall.value = 0 + + # Fill the pipeline with fillCycles valid tokens + dut.go.value = 1 + for i in range(fillCycles): + await clock(dut) + dut.go.value = 0 + + nBufferedTokensExpected = min(numNonstallableStages, nStallCycles, + fillCycles) + nBubblesExpected = nBufferedTokensExpected + + # Raise the stall signal. We now expect that numNonStallableStages dut.done + # assertions will occur in a row. + sequence = [] + dut.stall.value = 1 + for cycle in range(nStallCycles + nStages): + if cycle > nStallCycles: + dut.stall.value = 0 + + await Timer(1, units='ns') + sequence.append(int(dut.done)) + await clock(dut) + + # Check that exactly fill_cycles tokens exited the pipeline + nTokensExited = sum(sequence) + assert nTokensExited == fillCycles, f"Expected {fillCycles} tokens to exit the pipeline, but {nTokensExited} did" diff --git a/integration_test/Dialect/Pipeline/nonstallable/test1/nonstallable_test1.mlir b/integration_test/Dialect/Pipeline/nonstallable/test1/nonstallable_test1.mlir new file mode 100644 index 000000000000..c0a038939f08 --- /dev/null +++ b/integration_test/Dialect/Pipeline/nonstallable/test1/nonstallable_test1.mlir @@ -0,0 +1,30 @@ +// REQUIRES: iverilog,cocotb + +// RUN: circt-opt %s -pipeline-explicit-regs -lower-pipeline-to-hw -lower-seq-to-sv -sv-trace-iverilog -export-verilog \ +// RUN: -o %t.mlir > %t.sv + +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=nonstallable_test1 \ +// RUN: --pythonModule=nonstallable_test1 --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + + +// CHECK: ** TEST +// CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 + +hw.module @nonstallable_test1(in %arg0: i32, in %go: i1, in %clock: !seq.clock, in %reset: i1, in %stall: i1, out out: i32, out done: i1) { + %out, %done = pipeline.scheduled "nonstallable_test1"(%a0 : i32 = %arg0) + stall(%stall) clock(%clock) reset(%reset) go(%go) entryEn(%s0_enable) + {stallability = [true, false, false, true, true]} -> (out : i32) { + pipeline.stage ^bb1 + ^bb1(%s1_enable: i1): + pipeline.stage ^bb2 + ^bb2(%s2_enable: i1): + pipeline.stage ^bb3 + ^bb3(%s3_enable: i1): + pipeline.stage ^bb4 + ^bb4(%s4_enable: i1): + pipeline.stage ^bb5 + ^bb5(%s5_enable: i1): + pipeline.return %a0 : i32 + } + hw.output %out, %done : i32, i1 +} diff --git a/integration_test/Dialect/Pipeline/nonstallable/test1/nonstallable_test1.py b/integration_test/Dialect/Pipeline/nonstallable/test1/nonstallable_test1.py new file mode 100644 index 000000000000..93e439af1445 --- /dev/null +++ b/integration_test/Dialect/Pipeline/nonstallable/test1/nonstallable_test1.py @@ -0,0 +1,7 @@ +import cocotb +from nonstallable_helper import nonstallable_test + + +@cocotb.test() +async def test1(dut): + await nonstallable_test(dut, [0, 1, 1, 0, 0]) diff --git a/integration_test/Dialect/Pipeline/nonstallable/test2/nonstallable_test2.mlir b/integration_test/Dialect/Pipeline/nonstallable/test2/nonstallable_test2.mlir new file mode 100644 index 000000000000..20ccdfe9810c --- /dev/null +++ b/integration_test/Dialect/Pipeline/nonstallable/test2/nonstallable_test2.mlir @@ -0,0 +1,30 @@ +// REQUIRES: iverilog,cocotb + +// RUN: circt-opt %s -pipeline-explicit-regs -lower-pipeline-to-hw -lower-seq-to-sv -sv-trace-iverilog -export-verilog \ +// RUN: -o %t.mlir > %t.sv + +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=nonstallable_test2 \ +// RUN: --pythonModule=nonstallable_test2 --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + + +// CHECK: ** TEST +// CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 + +hw.module @nonstallable_test2(in %arg0: i32, in %go: i1, in %clock: !seq.clock, in %reset: i1, in %stall: i1, out out: i32, out done : i1) { + %out, %done = pipeline.scheduled "nonstallable_test2"(%a0 : i32 = %arg0) + stall(%stall) clock(%clock) reset(%reset) go(%go) entryEn(%s0_enable) + {stallability = [true, false, true, false, true]} -> (out : i32) { + pipeline.stage ^bb1 + ^bb1(%s1_enable: i1): + pipeline.stage ^bb2 + ^bb2(%s2_enable: i1): + pipeline.stage ^bb3 + ^bb3(%s3_enable: i1): + pipeline.stage ^bb4 + ^bb4(%s4_enable: i1): + pipeline.stage ^bb5 + ^bb5(%s5_enable: i1): + pipeline.return %a0 : i32 + } + hw.output %out, %done : i32, i1 +} diff --git a/integration_test/Dialect/Pipeline/nonstallable/test2/nonstallable_test2.py b/integration_test/Dialect/Pipeline/nonstallable/test2/nonstallable_test2.py new file mode 100644 index 000000000000..413df1cb17fa --- /dev/null +++ b/integration_test/Dialect/Pipeline/nonstallable/test2/nonstallable_test2.py @@ -0,0 +1,8 @@ +import cocotb + +from nonstallable_helper import nonstallable_test + + +@cocotb.test() +async def test1(dut): + await nonstallable_test(dut, [0, 1, 0, 1, 0]) diff --git a/integration_test/Dialect/Pipeline/simple/simple.mlir b/integration_test/Dialect/Pipeline/simple/simple.mlir new file mode 100644 index 000000000000..b41f5d8ba34e --- /dev/null +++ b/integration_test/Dialect/Pipeline/simple/simple.mlir @@ -0,0 +1,37 @@ +// REQUIRES: iverilog,cocotb + +// Test 1: default lowering + +// RUN: circt-opt %s -pipeline-explicit-regs -lower-pipeline-to-hw -lower-seq-to-sv -sv-trace-iverilog -export-verilog \ +// RUN: -o %t.mlir > %t.sv + +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=simple \ +// RUN: --pythonModule=simple --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// Test 2: Clock-gate implementation + +// RUN: circt-opt %s -pipeline-explicit-regs -lower-pipeline-to-hw="clock-gate-regs" -lower-seq-to-sv -sv-trace-iverilog -export-verilog \ +// RUN: -o %t.mlir > %t_cg.sv + +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=simple \ +// RUN: --pythonModule=simple --pythonFolder="%S,%S/.." %t_cg.sv 2>&1 | FileCheck %s + + +// CHECK: ** TEST +// CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 + +hw.module @simple(in %arg0 : i32, in %arg1 : i32, in %go : i1, in %clock : !seq.clock, in %reset : i1, out out: i32, out done : i1) { + %out, %done = pipeline.scheduled(%a0 : i32 = %arg0, %a1 : i32 = %arg1) clock(%clock) reset(%reset) go(%go) entryEn(%s0_enable) -> (out: i32) { + %add0 = comb.add %a0, %a1 : i32 + pipeline.stage ^bb1 + + ^bb1(%s1_enable : i1): + %add1 = comb.add %add0, %a0 : i32 + pipeline.stage ^bb2 + + ^bb2(%s2_enable : i1): + %add2 = comb.add %add1, %add0 : i32 + pipeline.return %add2 : i32 + } + hw.output %out, %done : i32, i1 +} diff --git a/integration_test/Dialect/Pipeline/simple/simple.py b/integration_test/Dialect/Pipeline/simple/simple.py new file mode 100644 index 000000000000..f0d6eeeb7eb0 --- /dev/null +++ b/integration_test/Dialect/Pipeline/simple/simple.py @@ -0,0 +1,40 @@ +import cocotb +from cocotb.triggers import Timer +import cocotb.clock + + +async def clock(dut): + dut.clock.value = 0 + await Timer(1, units='ns') + dut.clock.value = 1 + await Timer(1, units='ns') + + +async def initDut(dut): + """ + Initializes a dut by adding a clock, setting initial valid and ready flags, + and performing a reset. + """ + # Reset + dut.reset.value = 1 + await clock(dut) + dut.reset.value = 0 + await clock(dut) + + +@cocotb.test() +async def test1(dut): + dut.go.value = 0 + await initDut(dut) + + dut.arg0.value = 42 + dut.arg1.value = 24 + dut.go.value = 1 + + while dut.done != 1: + await clock(dut) + dut.go.value = 0 + dut.arg0.value = 0 + dut.arg1.value = 0 + + assert dut.out == 174, f"Expected 174, got {dut.out}" diff --git a/integration_test/Dialect/Pipeline/stall/stallTest.mlir b/integration_test/Dialect/Pipeline/stall/stallTest.mlir new file mode 100644 index 000000000000..a1c1ff08507d --- /dev/null +++ b/integration_test/Dialect/Pipeline/stall/stallTest.mlir @@ -0,0 +1,37 @@ +// REQUIRES: iverilog,cocotb + +// Test 1: default lowering (input muxing) + +// RUN: circt-opt %s -pipeline-explicit-regs -lower-pipeline-to-hw -lower-seq-to-sv -sv-trace-iverilog -export-verilog \ +// RUN: -o %t.mlir > %t.sv + +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=stallTest \ +// RUN: --pythonModule=stallTest --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + +// Test 2: Clock-gate implementation + +// RUN: circt-opt %s -pipeline-explicit-regs -lower-pipeline-to-hw="clock-gate-regs" -lower-seq-to-sv -sv-trace-iverilog -export-verilog \ +// RUN: -o %t_clockgated.mlir > %t.sv + +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=stallTest \ +// RUN: --pythonModule=stallTest --pythonFolder="%S,%S/.." %t.sv 2>&1 | FileCheck %s + + +// CHECK: ** TEST +// CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 + +hw.module @stallTest(in %arg0 : i32, in %arg1 : i32, in %go : i1, in %stall : i1, in %clock : !seq.clock, in %reset : i1, out out: i32, out done : i1) { + %out, %done = pipeline.scheduled(%a0 : i32 = %arg0, %a1 : i32 = %arg1) stall(%stall) clock(%clock) reset(%reset) go(%go) entryEn(%s0_enable) -> (out: i32) { + %add0 = comb.add %a0, %a1 : i32 + pipeline.stage ^bb1 + + ^bb1(%s1_enable : i1): + %add1 = comb.add %add0, %a0 : i32 + pipeline.stage ^bb2 + + ^bb2(%s2_enable : i1): + %add2 = comb.add %add1, %add0 : i32 + pipeline.return %add2 : i32 + } + hw.output %out, %done : i32, i1 +} diff --git a/integration_test/Dialect/Pipeline/stall/stallTest.py b/integration_test/Dialect/Pipeline/stall/stallTest.py new file mode 100644 index 000000000000..6d9214acb8e3 --- /dev/null +++ b/integration_test/Dialect/Pipeline/stall/stallTest.py @@ -0,0 +1,65 @@ +import cocotb +from cocotb.triggers import Timer +import cocotb.clock + + +async def clock(dut): + dut.clock.value = 0 + await Timer(1, units='ns') + dut.clock.value = 1 + await Timer(1, units='ns') + + +async def initDut(dut): + """ + Initializes a dut by adding a clock, setting initial valid and ready flags, + and performing a reset. + """ + # Reset + dut.reset.value = 1 + await clock(dut) + dut.reset.value = 0 + await clock(dut) + + +def ref(v1, v2): + x1 = v1 + v2 + x2 = x1 + v1 + return x1 + x2 + + +@cocotb.test() +async def test1(dut): + dut.go.value = 0 + dut.stall.value = 0 + await initDut(dut) + + v1 = 42 + v2 = 24 + resref = ref(v1, v2) + + # Get values into the first stage + dut.arg0.value = v1 + dut.arg1.value = v2 + dut.go.value = 1 + await clock(dut) + + # Stall for 2 cycles. Done should not be asserted nor the output correct. + dut.go.value = 0 + dut.arg0.value = 0 + dut.arg1.value = 0 + dut.stall.value = 1 + for i in range(2): + await clock(dut) + assert dut.done != 1, "DUT should not be done when stalling" + + # Unstall and wait for 1 clock cycles - this should propagate the values through + # the remaining stage. + dut.stall.value = 0 + await clock(dut) + assert dut.done == 1, "DUT should be done after unstalling" + assert dut.out == resref, f"Expected {resref}, got {dut.out}" + + # Clock once more, done should be deasserted. + await clock(dut) + assert dut.done == 0, "DUT should not be done after pipeline has finished" diff --git a/integration_test/Dialect/Seq/fifo/fifo.mlir b/integration_test/Dialect/Seq/fifo/fifo.mlir new file mode 100644 index 000000000000..54c85cd3b40d --- /dev/null +++ b/integration_test/Dialect/Seq/fifo/fifo.mlir @@ -0,0 +1,13 @@ +// REQUIRES: iverilog,cocotb + +// RUN: circt-opt %s --lower-seq-fifo --lower-seq-hlmem --lower-seq-to-sv --sv-trace-iverilog --export-verilog -o %t.mlir > %t.sv +// RUN: circt-cocotb-driver.py --objdir=%T --topLevel=fifo \ +// RUN: --pythonModule=fifo --pythonFolder="%S,%S/.." %t.sv 2>&1 + +// CHECK: ** TEST +// CHECK: ** TESTS=[[N:.*]] PASS=[[N]] FAIL=0 SKIP=0 + +hw.module @fifo(in %clk : !seq.clock, in %rst : i1, in %inp : i32, in %rdEn : i1, in %wrEn : i1, out out: i32, out empty: i1, out full: i1, out almost_empty : i1, out almost_full : i1) { + %out, %full, %empty, %almostFull, %almostEmpty = seq.fifo depth 4 almost_full 2 almost_empty 1 in %inp rdEn %rdEn wrEn %wrEn clk %clk rst %rst : i32 + hw.output %out, %empty, %full, %almostEmpty, %almostFull : i32, i1, i1, i1, i1 +} diff --git a/integration_test/Dialect/Seq/fifo/fifo.py b/integration_test/Dialect/Seq/fifo/fifo.py new file mode 100644 index 000000000000..8a6d8dbe44d9 --- /dev/null +++ b/integration_test/Dialect/Seq/fifo/fifo.py @@ -0,0 +1,106 @@ +import cocotb +from cocotb.triggers import Timer +import cocotb.clock + + +async def clock(dut): + dut.clk.value = 0 + await Timer(1, units='ns') + dut.clk.value = 1 + await Timer(1, units='ns') + + +async def initDut(dut): + # Reset + dut.rst.value = 1 + await clock(dut) + dut.rst.value = 0 + await clock(dut) + + +async def write(dut, value): + dut.inp.value = value + dut.wrEn.value = 1 + await clock(dut) + dut.wrEn.value = 0 + await clock(dut) + + +async def combRead(dut, out): + dut.rdEn.value = 1 + # Combinational reads, so let the model settle before reading + await Timer(1, units='ns') + return dut.out.value + + +async def read(dut): + data = await combRead(dut, dut.out.value) + await clock(dut) + dut.rdEn.value = 0 + await clock(dut) + return data + + +async def readWrite(dut, value): + dut.inp.value = value + dut.wrEn.value = 1 + data = await combRead(dut, dut.out.value) + await clock(dut) + dut.rdEn.value = 0 + dut.wrEn.value = 0 + await clock(dut) + return data + + +FIFO_DEPTH = 4 +FIFO_ALMOST_FULL = 2 +FIFO_ALMOST_EMPTY = 1 + + +async def test_separate_read_write(dut): + # Run a test where we incrementally write and read values from 1 to FIFO_DEPTH values + for i in range(1, FIFO_DEPTH): + for j in range(i): + await write(dut, 42 + j) + + if i >= FIFO_ALMOST_FULL: + assert dut.almost_full.value == 1 + + if i <= FIFO_ALMOST_EMPTY: + assert dut.almost_empty.value == 1 + + if i == FIFO_DEPTH: + assert dut.full.value == 1 + + for j in range(i): + assert await read(dut) == 42 + j + + assert dut.empty.value == 1 + + +async def test_concurrent_read_write(dut): + # Fill up the FIFO halfway and concurrently read and write. Should be able + # to do this continuously. + counter = 0 + for i in range(FIFO_DEPTH // 2): + await write(dut, counter) + counter += 1 + + for i in range(FIFO_DEPTH * 2): + expected_value = counter - FIFO_DEPTH // 2 + print("expected_value: ", expected_value) + assert await readWrite(dut, counter) == expected_value + assert dut.full.value == 0 + assert dut.empty.value == 0 + counter += 1 + + +@cocotb.test() +async def test1(dut): + dut.inp.value = 0 + dut.rdEn.value = 0 + dut.wrEn.value = 0 + await initDut(dut) + + await test_separate_read_write(dut) + await test_concurrent_read_write(dut) diff --git a/integration_test/Dialect/Seq/lit.local.cfg.py b/integration_test/Dialect/Seq/lit.local.cfg.py new file mode 100644 index 000000000000..dc4990b742a3 --- /dev/null +++ b/integration_test/Dialect/Seq/lit.local.cfg.py @@ -0,0 +1,6 @@ +import glob, os + +dir_path = os.path.dirname(os.path.realpath(__file__)) +for pyfile in glob.glob(os.path.join(dir_path, "**", "*.py")): + # remove dir from pyfile + config.excludes.add(os.path.basename(pyfile)) diff --git a/integration_test/Dialect/Seq/registers.mlir b/integration_test/Dialect/Seq/registers.mlir index c51fd2a9145a..bac8f555e35e 100644 --- a/integration_test/Dialect/Seq/registers.mlir +++ b/integration_test/Dialect/Seq/registers.mlir @@ -1,5 +1,5 @@ // REQUIRES: verilator -// RUN: circt-opt %s -lower-seq-firrtl-to-sv -export-verilog -verify-diagnostics -o %t2.mlir > %t1.sv +// RUN: circt-opt %s -lower-seq-firrtl-init-to-sv -lower-seq-to-sv -export-verilog -verify-diagnostics -o %t2.mlir > %t1.sv // RUN: verilator --lint-only +1364-1995ext+v %t1.sv // RUN: verilator --lint-only +1364-2001ext+v %t1.sv // RUN: verilator --lint-only +1364-2005ext+v %t1.sv @@ -8,9 +8,10 @@ // RUN: verilator --lint-only +1800-2012ext+sv %t1.sv // RUN: verilator --lint-only +1800-2017ext+sv %t1.sv -sv.verbatim "`define INIT_RANDOM_PROLOG_" +sv.macro.decl @INIT_RANDOM_PROLOG_ +sv.macro.def @INIT_RANDOM_PROLOG_ "" -hw.module @top(%clk: i1, %rst: i1) { +hw.module @top(in %clk: !seq.clock, in %rst: i1) { %cst0 = hw.constant 0 : i32 %cst1 = hw.constant 1 : i32 @@ -24,7 +25,8 @@ hw.module @top(%clk: i1, %rst: i1) { %nextB = comb.add %rB, %rA : i32 %nextC = comb.add %rC, %rB : i32 - sv.alwaysff(posedge %clk) { + %clock = seq.from_clock %clk + sv.alwaysff(posedge %clock) { %fd = hw.constant 0x80000002 : i32 sv.fwrite %fd, "%d %d %d\n"(%rA, %rB, %rC) : i32, i32, i32 } diff --git a/integration_test/ESI/cosim/basic.mlir b/integration_test/ESI/cosim/basic.mlir deleted file mode 100644 index a3268c35d0d8..000000000000 --- a/integration_test/ESI/cosim/basic.mlir +++ /dev/null @@ -1,48 +0,0 @@ -// REQUIRES: esi-cosim -// RUN: circt-opt %s --lower-esi-to-physical --lower-esi-ports --lower-esi-to-hw | circt-opt --export-verilog -o %t3.mlir > %t1.sv -// RUN: circt-translate %s -export-esi-capnp -verify-diagnostics > %t2.capnp -// RUN: esi-cosim-runner.py --schema %t2.capnp %s %t1.sv %S/../supplements/integers.sv -// PY: import basic -// PY: rpc = basic.BasicSystemTester(rpcschemapath, simhostport) -// PY: print(rpc.list()) -// PY: rpc.testIntAcc(25) -// PY: rpc.testVectorSum(25) -// PY: rpc.testCrypto(25) - -hw.module.extern @IntAccNoBP(%clk: i1, %rst: i1, %ints: !esi.channel) -> (totalOut: !esi.channel) attributes {esi.bundle} -hw.module.extern @IntArrSum(%clk: i1, %rst: i1, %arr: !esi.channel>) -> (totalOut: !esi.channel>) attributes {esi.bundle} - -hw.module @ints(%clk: i1, %rst: i1) { - %intsIn = esi.cosim %clk, %rst, %intsTotalBuffered, "TestEP" : !esi.channel -> !esi.channel - %intsInBuffered = esi.buffer %clk, %rst, %intsIn {stages=2, name="intChan"} : i32 - %intsTotal = hw.instance "acc" @IntAccNoBP(clk: %clk: i1, rst: %rst: i1, ints: %intsInBuffered: !esi.channel) -> (totalOut: !esi.channel) - %intsTotalBuffered = esi.buffer %clk, %rst, %intsTotal {stages=2, name="totalChan"} : i32 -} - -hw.module @array(%clk: i1, %rst: i1) { - %arrIn = esi.cosim %clk, %rst, %arrTotalBuffered, "TestEP" : !esi.channel> -> !esi.channel> - %arrInBuffered = esi.buffer %clk, %rst, %arrIn {stages=2, name="arrChan"} : !hw.array<4 x si13> - %arrTotal = hw.instance "acc" @IntArrSum(clk: %clk: i1, rst: %rst: i1, arr: %arrInBuffered: !esi.channel>) -> (totalOut: !esi.channel>) - %arrTotalBuffered = esi.buffer %clk, %rst, %arrTotal {stages=2, name="totalChan"} : !hw.array<2 x ui24> -} - -!DataPkt = !hw.struct> -!pktChan = !esi.channel -!Config = !hw.struct> -!cfgChan = !esi.channel - -hw.module.extern @Encryptor(%clk: i1, %rst: i1, %in: !pktChan, %cfg: !cfgChan) -> (x: !pktChan) attributes {esi.bundle} - -hw.module @structs(%clk:i1, %rst:i1) -> () { - %compressedData = hw.instance "otpCryptor" @Encryptor(clk: %clk: i1, rst: %rst: i1, in: %inputData: !pktChan, cfg: %cfg: !cfgChan) -> (x: !pktChan) - %inputData = esi.cosim %clk, %rst, %compressedData, "CryptoData" : !pktChan -> !pktChan - %c0 = hw.constant 0 : i1 - %null, %nullReady = esi.wrap.vr %c0, %c0 : i1 - %cfg = esi.cosim %clk, %rst, %null, "CryptoConfig" : !esi.channel -> !cfgChan -} - -hw.module @top(%clk: i1, %rst: i1) { - hw.instance "ints" @ints (clk: %clk: i1, rst: %rst: i1) -> () - hw.instance "array" @array(clk: %clk: i1, rst: %rst: i1) -> () - hw.instance "structs" @structs(clk: %clk: i1, rst: %rst: i1) -> () -} diff --git a/integration_test/ESI/cosim/basic.py b/integration_test/ESI/cosim/basic.py deleted file mode 100755 index 226b5b59c074..000000000000 --- a/integration_test/ESI/cosim/basic.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/python3 - -import random -import esi_cosim - - -class BasicSystemTester(esi_cosim.CosimBase): - """Provides methods to test the 'basic' simulation.""" - - def testIntAcc(self, num_msgs): - ep = self.openEP("top.ints.TestEP", - sendType=self.schema.I32, - recvType=self.schema.I32) - sum = 0 - for _ in range(num_msgs): - i = random.randint(0, 77) - sum += i - print(f"Sending {i}") - ep.send(self.schema.I32.new_message(i=i)) - result = self.readMsg(ep, self.schema.I32) - print(f"Got {result}") - assert (result.i == sum) - - def testVectorSum(self, num_msgs): - ep = self.openEP("top.array.TestEP", - sendType=self.schema.ArrayOf2xUi24, - recvType=self.schema.ArrayOf4xSi13) - for _ in range(num_msgs): - # Since the result is unsigned, we need to make sure the sum is - # never negative. - arr = [ - random.randint(-468, 777), - random.randint(500, 1250), - random.randint(-468, 777), - random.randint(500, 1250) - ] - print(f"Sending {arr}") - ep.send(self.schema.ArrayOf4xSi13.new_message(l=arr)) - result = self.readMsg(ep, self.schema.ArrayOf2xUi24) - print(f"Got {result}") - assert (result.l[0] == arr[0] + arr[1]) - assert (result.l[1] == arr[2] + arr[3]) - - def testCrypto(self, num_msgs): - ep = self.openEP("top.structs.CryptoData", - sendType=self.schema.Struct15822124641382404136, - recvType=self.schema.Struct15822124641382404136) - cfg = self.openEP("top.structs.CryptoConfig", - sendType=self.schema.I1, - recvType=self.schema.Struct14745270011869700302) - - cfgWritten = False - for _ in range(num_msgs): - blob = [random.randint(0, 255) for x in range(32)] - print(f"Sending data {blob}") - ep.send( - self.schema.Struct15822124641382404136.new_message(encrypted=False, - blob=blob)) - - if not cfgWritten: - # Check that messages queue up properly waiting for the config. - otp = [random.randint(0, 255) for x in range(32)] - cfg.send( - self.schema.Struct14745270011869700302.new_message(encrypt=True, - otp=otp)) - cfgWritten = True - - result = self.readMsg(ep, self.schema.Struct15822124641382404136) - expectedResults = [x ^ y for (x, y) in zip(otp, blob)] - print(f"Got {blob}") - print(f"Exp {expectedResults}") - assert (list(result.blob) == expectedResults) diff --git a/integration_test/ESI/cosim/cosim_only.sv b/integration_test/ESI/cosim/cosim_only.sv deleted file mode 100644 index b9b143cc4895..000000000000 --- a/integration_test/ESI/cosim/cosim_only.sv +++ /dev/null @@ -1,59 +0,0 @@ -// REQUIRES: esi-cosim -// RUN: esi-cosim-runner.py %s %s -// PY: import loopback as test -// PY: rpc = test.LoopbackTester(rpcschemapath, simhostport) -// PY: print(rpc.cosim.list().wait()) -// PY: rpc.test_list() -// PY: rpc.test_open_close() -// PY: rpc.test_3bytes(5) - -import Cosim_DpiPkg::*; - -module top( - input logic clk, - input logic rst -); - localparam int TYPE_SIZE_BITS = - (1 * 64) + // root message - (1 * 64) + // list header - (1 * 64); // list of length 3 bytes, rounded up to multiples of 8 bytes - - wire DataOutValid; - wire DataOutReady = 1; - wire [TYPE_SIZE_BITS-1:0] DataOut; - - logic DataInValid; - logic DataInReady; - logic [TYPE_SIZE_BITS-1:0] DataIn; - - Cosim_Endpoint #( - .RECV_TYPE_ID(1), - .RECV_TYPE_SIZE_BITS(TYPE_SIZE_BITS), - .SEND_TYPE_ID(1), - .SEND_TYPE_SIZE_BITS(TYPE_SIZE_BITS) - ) ep ( - .* - ); - - always@(posedge clk) - begin - if (~rst) - begin - if (DataOutValid && DataOutReady) - begin - $display("[%d] Recv'd: %h", $time(), DataOut); - DataIn <= DataOut; - end - DataInValid <= DataOutValid && DataOutReady; - - if (DataInValid && DataInReady) - begin - $display("[%d] Sent: %h", $time(), DataIn); - end - end - else - begin - DataInValid <= 0; - end - end -endmodule diff --git a/integration_test/ESI/cosim/loopback.mlir b/integration_test/ESI/cosim/loopback.mlir deleted file mode 100644 index aa15f58dfcdc..000000000000 --- a/integration_test/ESI/cosim/loopback.mlir +++ /dev/null @@ -1,37 +0,0 @@ -// REQUIRES: esi-cosim -// RUN: rm -rf %t6 && mkdir %t6 && cd %t6 -// RUN: circt-opt %s --esi-connect-services --esi-emit-collateral=schema-file=%t2.capnp > %t4.mlir -// RUN: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-ports --lower-esi-to-hw --export-split-verilog -o %t3.mlir -// RUN: circt-translate %t4.mlir -export-esi-capnp -verify-diagnostics > %t2.capnp -// RUN: cd .. -// RUN: esi-cosim-runner.py --schema %t2.capnp --exec %S/loopback.py %t6/*.sv - - -hw.module @intLoopback(%clk:i1, %rst:i1) -> () { - %cosimRecv = esi.cosim %clk, %rst, %bufferedResp, "IntTestEP" {name_ext="loopback"} : !esi.channel -> !esi.channel - %bufferedResp = esi.buffer %clk, %rst, %cosimRecv {stages=1} : i32 -} - -!KeyText = !hw.struct, key: !hw.array<4xi8>> -hw.module @twoListLoopback(%clk:i1, %rst:i1) -> () { - %cosim = esi.cosim %clk, %rst, %resp, "KeyTextEP" : !esi.channel -> !esi.channel - %resp = esi.buffer %clk, %rst, %cosim {stages=4} : !KeyText -} - -esi.service.decl @HostComms { - esi.service.to_server @Send : !esi.channel - esi.service.to_client @Recv : !esi.channel -} - -hw.module @TwoChanLoopback(%clk: i1) -> () { - %dataIn = esi.service.req.to_client <@HostComms::@Recv> (["loopback_tohw"]) : !esi.channel - esi.service.req.to_server %dataIn -> <@HostComms::@Send> (["loopback_fromhw"]) : !esi.channel -} - -hw.module @top(%clk:i1, %rst:i1) -> () { - hw.instance "intLoopbackInst" @intLoopback(clk: %clk: i1, rst: %rst: i1) -> () - hw.instance "twoListLoopbackInst" @twoListLoopback(clk: %clk: i1, rst: %rst: i1) -> () - - esi.service.instance svc @HostComms impl as "cosim" (%clk, %rst) : (i1, i1) -> () - hw.instance "TwoChanLoopback" @TwoChanLoopback(clk: %clk: i1) -> () -} diff --git a/integration_test/ESI/cosim/loopback.py b/integration_test/ESI/cosim/loopback.py deleted file mode 100755 index dfe7cf7a3aa8..000000000000 --- a/integration_test/ESI/cosim/loopback.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/python3 - -import binascii -import random -import esi_cosim - - -class LoopbackTester(esi_cosim.CosimBase): - """Provides methods to test the loopback simulations.""" - - def test_list(self): - ifaces = self.cosim.list().wait().ifaces - assert len(ifaces) > 0 - - def test_open_close(self): - ifaces = self.cosim.list().wait().ifaces - openResp = self.cosim.open(ifaces[0]).wait() - assert openResp.iface is not None - ep = openResp.iface - ep.close().wait() - - def test_two_chan_loopback(self, num_msgs): - to_hw = self.openEP("top.TwoChanLoopback_loopback_tohw", - sendType=self.schema.I1, - recvType=self.schema.I8) - from_hw = self.openEP("top.TwoChanLoopback_loopback_fromhw", - sendType=self.schema.I8, - recvType=self.schema.I1) - for _ in range(num_msgs): - data = random.randint(0, 2**8 - 1) - print(f"Sending {data}") - to_hw.send(self.schema.I8.new_message(i=data)) - result = self.readMsg(from_hw, self.schema.I8) - print(f"Got {result}") - assert (result.i == data) - - def test_i32(self, num_msgs): - ep = self.openEP("top.intLoopbackInst.IntTestEP.loopback", - sendType=self.schema.I32, - recvType=self.schema.I32) - for _ in range(num_msgs): - data = random.randint(0, 2**32 - 1) - print(f"Sending {data}") - ep.send(self.schema.I32.new_message(i=data)) - result = self.readMsg(ep, self.schema.I32) - print(f"Got {result}") - assert (result.i == data) - - def write_3bytes(self, ep): - r = random.randrange(0, 2**24 - 1) - data = r.to_bytes(3, 'big') - print(f'Sending: {binascii.hexlify(data)}') - ep.send(self.schema.UntypedData.new_message(data=data)).wait() - return data - - def read_3bytes(self, ep): - dataMsg = self.readMsg(ep, self.schema.UntypedData) - data = dataMsg.data - print(binascii.hexlify(data)) - return data - - def test_3bytes(self, num_msgs=50): - ep = self.openEP("top.ep") - print("Testing writes") - dataSent = list() - for _ in range(num_msgs): - dataSent.append(self.write_3bytes(ep)) - print() - print("Testing reads") - dataRecv = list() - for _ in range(num_msgs): - dataRecv.append(self.read_3bytes(ep)) - ep.close().wait() - assert dataSent == dataRecv - - def test_keytext(self, num_msgs=50): - cStructType = self.schema.Struct17798359158705484171 - ep = self.openEP("top.twoListLoopbackInst.KeyTextEP", - sendType=cStructType, - recvType=cStructType) - kts = [] - for i in range(num_msgs): - kt = cStructType.new_message( - key=[random.randrange(0, 255) for x in range(4)], - text=[random.randrange(0, 16000) for x in range(6)]) - kts.append(kt) - ep.send(kt).wait() - - for i in range(num_msgs): - kt = self.readMsg(ep, cStructType) - print(f"expected: {kts[i]}") - print(f"got: {kt}") - assert list(kt.key) == list(kts[i].key) - assert list(kt.text) == list(kts[i].text) - - -if __name__ == "__main__": - import os - import sys - rpc = LoopbackTester(sys.argv[2], f"{os.uname()[1]}:{sys.argv[1]}") - print(rpc.list()) - rpc.test_two_chan_loopback(25) - rpc.test_i32(25) - rpc.test_keytext(25) diff --git a/integration_test/EmitVerilog/basic.mlir b/integration_test/EmitVerilog/basic.mlir index 2224e547ce22..9d3d6dc58e72 100644 --- a/integration_test/EmitVerilog/basic.mlir +++ b/integration_test/EmitVerilog/basic.mlir @@ -5,7 +5,7 @@ module { // The HW dialect doesn't have any sequential constructs yet. So don't do // much. - hw.module @top(%clk: i1, %rst: i1) { + hw.module @top(in %clk: i1, in %rst: i1) { %c1 = hw.instance "aaa" @AAA () -> (f: i1) %c1Shl = hw.instance "shl" @shl (a: %c1: i1) -> (b: i1) sv.always posedge %clk { @@ -14,12 +14,12 @@ module { } } - hw.module @AAA() -> (f: i1) { + hw.module @AAA(out f: i1) { %z = hw.constant 1 : i1 hw.output %z : i1 } - hw.module @shl(%a: i1) -> (b: i1) { + hw.module @shl(in %a: i1, out b: i1) { %0 = comb.shl %a, %a : i1 hw.output %0 : i1 } diff --git a/integration_test/EmitVerilog/lint.mlir b/integration_test/EmitVerilog/lint.mlir index 49d90b30267d..bf08e4cba758 100644 --- a/integration_test/EmitVerilog/lint.mlir +++ b/integration_test/EmitVerilog/lint.mlir @@ -8,44 +8,44 @@ // RUN: verilator --lint-only --top-module exprInlineTestIssue439 %t1.sv // RUN: verilator --lint-only --top-module StructDecls %t1.sv -hw.module @B(%a: i1) -> (b: i1, c: i1) { +hw.module @B(in %a: i1, out b: i1, out c: i1) { %0 = comb.or %a, %a : i1 %1 = comb.and %a, %a : i1 hw.output %0, %1 : i1, i1 } -hw.module @A(%d: i1, %e: i1) -> (f: i1) { +hw.module @A(in %d: i1, in %e: i1, out f: i1) { %1 = comb.mux %d, %d, %e : i1 hw.output %1 : i1 } -hw.module @AAA(%d: i1, %e: i1) -> (f: i1) { +hw.module @AAA(in %d: i1, in %e: i1, out f: i1) { %z = hw.constant 0 : i1 hw.output %z : i1 } -hw.module @AB(%w: i1, %x: i1) -> (y: i1, z: i1) { +hw.module @AB(in %w: i1, in %x: i1, out y: i1, out z: i1) { %w2 = hw.instance "a1" @AAA(d: %w: i1, e: %w1: i1) -> (f: i1) %w1, %y = hw.instance "b1" @B(a: %w2: i1) -> (b: i1, c: i1) hw.output %y, %x : i1, i1 } -hw.module @shl(%a: i1) -> (b: i1) { +hw.module @shl(in %a: i1, out b: i1) { %0 = comb.shl %a, %a : i1 hw.output %0 : i1 } -hw.module @TESTSIMPLE(%a: i4, %b: i4, %cond: i1, %array: !hw.array<10xi4>, - %uarray: !hw.uarray<16xi8>) -> ( - r0: i4, r1: i4, r2: i4, r3: i4, - r4: i4, r5: i4, r6: i4, r7: i4, - r8: i4, r9: i4, r10: i4, r11: i4, - r12: i4, r13: i1, - r14: i1, r15: i1, r16: i1, r17: i1, - r18: i1, r19: i1, r20: i1, r21: i1, - r22: i1, r23: i1, - r24: i12, r25: i2, r27: i4, r28: i4, - r29: !hw.array<3xi4> +hw.module @TESTSIMPLE(in %a: i4, in %b: i4, in %cond: i1, in %array: !hw.array<10xi4>, + in %uarray: !hw.uarray<16xi8>, + out r0: i4, out r1: i4, out r2: i4, out r3: i4, + out r4: i4, out r5: i4, out r6: i4, out r7: i4, + out r8: i4, out r9: i4, out r10: i4, out r11: i4, + out r12: i4, out r13: i1, + out r14: i1, out r15: i1, out r16: i1, out r17: i1, + out r18: i1, out r19: i1, out r20: i1, out r21: i1, + out r22: i1, out r23: i1, + out r24: i12, out r25: i2, out r27: i4, out r28: i4, + out r29: !hw.array<3xi4> ) { %0 = comb.add %a, %b : i4 @@ -91,7 +91,7 @@ hw.module @TESTSIMPLE(%a: i4, %b: i4, %cond: i1, %array: !hw.array<10xi4>, i12,i2, i4, i4, !hw.array<3xi4> } -hw.module @exprInlineTestIssue439(%clk: i1) { +hw.module @exprInlineTestIssue439(in %clk: i1) { %c = hw.constant 0 : i32 sv.always posedge %clk { @@ -102,7 +102,7 @@ hw.module @exprInlineTestIssue439(%clk: i1) { } } -hw.module @casts(%in1: i64) -> (r1: !hw.array<5xi8>) { +hw.module @casts(in %in1: i64, out r1: !hw.array<5xi8>) { %bits = hw.bitcast %in1 : (i64) -> !hw.array<64xi1> %idx = hw.constant 10 : i6 %midBits = hw.array_slice %bits[%idx] : (!hw.array<64xi1>) -> !hw.array<40xi1> @@ -115,7 +115,7 @@ hw.module @StructDecls() { %reg2 = sv.reg : !hw.inout>> } -hw.module @UniformArrayCreate() -> (arr: !hw.array<5xi8>) { +hw.module @UniformArrayCreate(out arr: !hw.array<5xi8>) { %c0_i8 = hw.constant 0 : i8 %arr = hw.array_create %c0_i8, %c0_i8, %c0_i8, %c0_i8, %c0_i8 : i8 hw.output %arr : !hw.array<5xi8> diff --git a/integration_test/EmitVerilog/standards-verilator.mlir b/integration_test/EmitVerilog/standards-verilator.mlir index 0cad922809f9..ed6fbcbac3ed 100644 --- a/integration_test/EmitVerilog/standards-verilator.mlir +++ b/integration_test/EmitVerilog/standards-verilator.mlir @@ -16,12 +16,13 @@ // RUN: verilator --lint-only +1800-2012ext+sv %t1.2012.sv // RUN: verilator --lint-only +1800-2017ext+sv %t1.2017.sv -hw.module @top(%clock : i1, %reset: i1, - %a: i4, - %s: !hw.struct, - %parray: !hw.array<10xi4>, - %uarray: !hw.uarray<16xi8>) - -> (r0: i4, r1: i4) { +hw.module @top(in %clock : i1, in %reset: i1, + in %a: i4, + in %s: !hw.struct, + in %parray: !hw.array<10xi4>, + in %uarray: !hw.uarray<16xi8>, + out r0: i4, + out r1: i4) { %0 = comb.or %a, %a : i4 %1 = comb.and %a, %a : i4 diff --git a/integration_test/EmitVerilog/standards-vlog.mlir b/integration_test/EmitVerilog/standards-vlog.mlir index b138de2775b8..f1cb401304bf 100644 --- a/integration_test/EmitVerilog/standards-vlog.mlir +++ b/integration_test/EmitVerilog/standards-vlog.mlir @@ -16,12 +16,13 @@ // RUN: vlog -lint -sv -sv12compat %t1.2012.sv // RUN: vlog -lint -sv -sv17compat %t1.2017.sv -hw.module @top(%clock : i1, %reset: i1, - %a: i4, - %s: !hw.struct, - %parray: !hw.array<10xi4>, - %uarray: !hw.uarray<16xi8>) - -> (r0: i4, r1: i4) { +hw.module @top(in %clock : i1, in %reset: i1, + in %a: i4, + in %s: !hw.struct, + in %parray: !hw.array<10xi4>, + in %uarray: !hw.uarray<16xi8>, + out r0: i4, + out r1: i4) { %0 = comb.or %a, %a : i4 %1 = comb.and %a, %a : i4 @@ -32,6 +33,6 @@ hw.module @top(%clock : i1, %reset: i1, %fd = hw.constant 0x80000002 : i32 sv.fwrite %fd, "Yo\n" } - + hw.output %0, %1 : i4, i4 } diff --git a/integration_test/EmitVerilog/sv-interfaces.mlir b/integration_test/EmitVerilog/sv-interfaces.mlir index 307e61c209db..c3b14ef0e386 100644 --- a/integration_test/EmitVerilog/sv-interfaces.mlir +++ b/integration_test/EmitVerilog/sv-interfaces.mlir @@ -14,10 +14,10 @@ module { // TODO: This ugly bit is because we don't yet have ExportVerilog support // for modports as module port declarations. - hw.module.extern @Rcvr (%m: !sv.modport<@data_vr::@data_in>) + hw.module.extern @Rcvr (in %m: !sv.modport<@data_vr::@data_in>) sv.verbatim "module Rcvr (data_vr.data_in m);\nendmodule" - hw.module @top (%clk: i1, %rst: i1) { + hw.module @top (in %clk: i1, in %rst: i1) { %iface = sv.interface.instance : !sv.interface<@data_vr> %ifaceInPort = sv.modport.get %iface @data_in : diff --git a/integration_test/Target/ExportSystemC/basic.mlir b/integration_test/Target/ExportSystemC/basic.mlir index fe8f176ead5e..780878164927 100644 --- a/integration_test/Target/ExportSystemC/basic.mlir +++ b/integration_test/Target/ExportSystemC/basic.mlir @@ -1,6 +1,6 @@ // REQUIRES: clang-tidy, systemc // RUN: circt-translate %s --export-systemc > %t.cpp -// RUN: clang-tidy --extra-arg=-frtti %t.cpp +// RUN: clang-tidy --extra-arg=-frtti %t.cpp -- emitc.include <"systemc.h"> emitc.include <"tuple"> diff --git a/integration_test/Target/ExportSystemC/func.mlir b/integration_test/Target/ExportSystemC/func.mlir index d5bc7ec4457d..eebf9be884f2 100644 --- a/integration_test/Target/ExportSystemC/func.mlir +++ b/integration_test/Target/ExportSystemC/func.mlir @@ -1,6 +1,6 @@ // REQUIRES: clang-tidy // RUN: circt-translate %s --export-systemc > %t.cpp -// RUN: clang-tidy --extra-arg=-frtti %t.cpp +// RUN: clang-tidy --extra-arg=-frtti %t.cpp -- emitc.include <"stdint.h"> emitc.include <"functional"> diff --git a/integration_test/circt-lec/builtin.mlir b/integration_test/circt-lec/builtin.mlir new file mode 100644 index 000000000000..4955ab2c6b0f --- /dev/null +++ b/integration_test/circt-lec/builtin.mlir @@ -0,0 +1,10 @@ +// These tests will be only enabled if circt-lec is built. +// REQUIRES: circt-lec + +// builtin.module implementation +// RUN: circt-lec %s -v=false | FileCheck %s --check-prefix=BUILTIN_MODULE +// BUILTIN_MODULE: c1 == c2 + +hw.module @basic(in %in: i1, out out: i1) { + hw.output %in : i1 +} diff --git a/integration_test/circt-lec/comb.mlir b/integration_test/circt-lec/comb.mlir new file mode 100644 index 000000000000..3f7dff224b2f --- /dev/null +++ b/integration_test/circt-lec/comb.mlir @@ -0,0 +1,254 @@ +// These tests will be only enabled if circt-lec is built. +// REQUIRES: circt-lec + +hw.module @basic(in %in: i1, out out: i1) { + hw.output %in : i1 +} + +hw.module @not(in %in: i1, out out: i1) { + %true = hw.constant true + %out = comb.xor bin %in, %true : i1 + hw.output %out : i1 +} + +// comb.add +// RUN: circt-lec %s -c1=adder -c2=completeAdder -v=false | FileCheck %s --check-prefix=COMB_ADD +// COMB_ADD: c1 == c2 + +hw.module @adder(in %in1: i2, in %in2: i2, out out: i2) { + %sum = comb.add bin %in1, %in2 : i2 + hw.output %sum : i2 +} + +hw.module @halfAdder(in %in1: i1, in %in2: i1, out carry: i1, out sum: i1) { + %sum = comb.xor bin %in1, %in2 : i1 + %carry = comb.and bin %in1, %in2 : i1 + hw.output %carry, %sum: i1, i1 +} + +hw.module @completeAdder(in %in1: i2, in %in2 : i2, out out: i2) { + %in1_0 = comb.extract %in1 from 0 : (i2) -> i1 + %in1_1 = comb.extract %in1 from 1 : (i2) -> i1 + %in2_0 = comb.extract %in2 from 0 : (i2) -> i1 + %in2_1 = comb.extract %in2 from 1 : (i2) -> i1 + %c1, %s1 = hw.instance "h1" @halfAdder(in1: %in1_0: i1, in2: %in2_0: i1) -> (carry: i1, sum: i1) + %c2, %s2 = hw.instance "h2" @halfAdder(in1: %in1_1: i1, in2: %in2_1: i1) -> (carry: i1, sum: i1) + %c3, %s3 = hw.instance "h3" @halfAdder(in1: %s2: i1, in2: %c1: i1) -> (carry: i1, sum: i1) + %fullsum = comb.concat %s3, %s1 : i1, i1 + hw.output %fullsum : i2 +} + +// comb.and +// RUN: circt-lec %s -c1=and -c2=decomposedAnd -v=false | FileCheck %s --check-prefix=COMB_AND +// COMB_AND: c1 == c2 + +hw.module @and(in %in1: i1, in %in2: i1, out out: i1) { + %out = comb.and bin %in1, %in2 : i1 + hw.output %out : i1 +} + +hw.module @decomposedAnd(in %in1: i1, in %in2: i1, out out: i1) { + %not_in1 = hw.instance "n_in1" @not(in: %in1: i1) -> (out: i1) + %not_in2 = hw.instance "n_in2" @not(in: %in2: i1) -> (out: i1) + %not_and = comb.or bin %not_in1, %not_in2 : i1 + %and = hw.instance "and" @not(in: %not_and: i1) -> (out: i1) + hw.output %and : i1 +} + +// comb.concat +// TODO + +// comb.divs +// TODO + +// comb.divu +// TODO + +// comb.extract +// TODO + +// comb.icmp +// TODO + +// comb.mods +// TODO + +// comb.modu +// TODO + +// comb.mul +// RUN: circt-lec %s -c1=mulBy2 -c2=addTwice -v=false | FileCheck %s --check-prefix=COMB_MUL +// COMB_MUL: c1 == c2 + +hw.module @mulBy2(in %in: i2, out out: i2) { + %two = hw.constant 2 : i2 + %res = comb.mul bin %in, %two : i2 + hw.output %res : i2 +} + +hw.module @addTwice(in %in: i2, out out: i2) { + %res = comb.add bin %in, %in : i2 + hw.output %res : i2 +} + +// comb.mux +// RUN: circt-lec %s -c1=mux -c2=decomposedMux -v=false | FileCheck %s --check-prefix=COMB_MUX +// COMB_MUX: c1 == c2 + +hw.module @mux(in %cond: i1, in %tvalue: i8, in %fvalue: i8, out out: i8) { + %res = comb.mux bin %cond, %tvalue, %fvalue : i8 + hw.output %res : i8 +} + +hw.module @decomposedMux(in %cond: i1, in %tvalue: i8, in %fvalue: i8, out out: i8) { + %cond_bar = hw.instance "n" @not(in: %cond: i1) -> (out: i1) + %lead_0 = hw.constant 0 : i7 + %c_t = comb.concat %lead_0, %cond : i7, i1 + %c_f = comb.concat %lead_0, %cond_bar : i7, i1 + %t = comb.mul bin %tvalue, %c_t : i8 + %f = comb.mul bin %fvalue, %c_f : i8 + %res = comb.add bin %t, %f : i8 + hw.output %res : i8 +} + +// comb.or +// RUN: circt-lec %s -c1=or -c2=decomposedOr -v=false | FileCheck %s --check-prefix=COMB_OR +// COMB_OR: c1 == c2 + +hw.module @or(in %in1: i1, in %in2: i1, out out: i1) { + %out = comb.or bin %in1, %in2 : i1 + hw.output %out : i1 +} + +hw.module @decomposedOr(in %in1: i1, in %in2: i1, out out: i1) { + %not_in1 = hw.instance "n_in1" @not(in: %in1: i1) -> (out: i1) + %not_in2 = hw.instance "n_in2" @not(in: %in2: i1) -> (out: i1) + %not_or = comb.and bin %not_in1, %not_in2 : i1 + %or = hw.instance "or" @not(in: %not_or: i1) -> (out: i1) + hw.output %or : i1 +} + +// comb.parity +// RUN: circt-lec %s -c1=parity -c2=decomposedParity -v=false | FileCheck %s --check-prefix=COMB_PARITY +// COMB_PARITY: c1 == c2 + +hw.module @parity(in %in: i8, out out: i1) { + %res = comb.parity bin %in : i8 + hw.output %res : i1 +} + +hw.module @decomposedParity(in %in: i8, out out: i1) { + %b0 = comb.extract %in from 0 : (i8) -> i1 + %b1 = comb.extract %in from 1 : (i8) -> i1 + %b2 = comb.extract %in from 2 : (i8) -> i1 + %b3 = comb.extract %in from 3 : (i8) -> i1 + %b4 = comb.extract %in from 4 : (i8) -> i1 + %b5 = comb.extract %in from 5 : (i8) -> i1 + %b6 = comb.extract %in from 6 : (i8) -> i1 + %b7 = comb.extract %in from 7 : (i8) -> i1 + %res = comb.xor bin %b0, %b1, %b2, %b3, %b4, %b5, %b6, %b7 : i1 + hw.output %res : i1 +} + +// comb.replicate +// RUN: circt-lec %s -c1=replicate -c2=decomposedReplicate -v=false | FileCheck %s --check-prefix=COMB_REPLICATE +// COMB_REPLICATE: c1 == c2 + +hw.module @replicate(in %in: i2, out out: i8) { + %res = comb.replicate %in : (i2) -> i8 + hw.output %res : i8 +} + +hw.module @decomposedReplicate(in %in: i2, out out: i8) { + %res = comb.concat %in, %in, %in, %in : i2, i2, i2, i2 + hw.output %res : i8 +} + +// comb.shl +// RUN: circt-lec %s -c1=shl -c2=decomposedShl -v=false | FileCheck %s --check-prefix=COMB_SHL +// COMB_SHL: c1 == c2 + +hw.module @shl(in %in1: i2, in %in2: i2, out out: i2) { + %res = comb.shl bin %in1, %in2 : i2 + hw.output %res : i2 +} + +hw.module @decomposedShl(in %in1: i2, in %in2: i2, out out: i2) { + %zero = hw.constant 0 : i2 + %one = hw.constant 1 : i2 + %two = hw.constant 2 : i2 + // first possible shift + %cond1 = comb.icmp bin ugt %in2, %zero : i2 + %mul1 = comb.mux bin %cond1, %two, %one : i2 + %shl1 = comb.mul bin %in1, %mul1 : i2 + // avoid subtraction underflow + %cond1_1 = comb.icmp bin eq %in2, %zero : i2 + %sub1 = comb.mux bin %cond1_1, %zero, %one : i2 + %in2_2 = comb.sub bin %in2, %sub1 : i2 + // second possible shift + %cond2 = comb.icmp bin ugt %in2_2, %zero : i2 + %mul2 = comb.mux bin %cond2, %two, %one : i2 + %shl2 = comb.mul bin %shl1, %mul2 : i2 + hw.output %shl2 : i2 +} + +// comb.shrs +// TODO + +// comb.shru +// TODO + +// comb.sub +// RUN: circt-lec %s -c1=subtractor -c2=completeSubtractor -v=false | FileCheck %s --check-prefix=COMB_SUB +// COMB_SUB: c1 == c2 + +hw.module @subtractor(in %in1: i8, in %in2: i8, out out: i8) { + %diff = comb.sub bin %in1, %in2 : i8 + hw.output %diff : i8 +} + +hw.module @halfSubtractor(in %in1: i1, in %in2: i1, out borrow: i1, out diff: i1) { + %diff = comb.xor bin %in1, %in2 : i1 + %not_in1 = hw.instance "n_in1" @not(in: %in1: i1) -> (out: i1) + %borrow = comb.and bin %not_in1, %in2 : i1 + hw.output %borrow, %diff: i1, i1 +} + +hw.module @fullSubtractor(in %in1: i1, in %in2: i1, in %b_in: i1, out borrow: i1, out diff: i1) { + %b1, %d1 = hw.instance "s1" @halfSubtractor(in1: %in1: i1, in2: %in2: i1) -> (borrow: i1, diff: i1) + %b2, %d_out = hw.instance "s2" @halfSubtractor(in1: %d1: i1, in2: %b_in: i1) -> (borrow: i1, diff: i1) + %b_out = comb.or bin %b1, %b2 : i1 + hw.output %b_out, %d_out: i1, i1 +} + +hw.module @completeSubtractor(in %in1: i8, in %in2 : i8, out out: i8) { + %in1_0 = comb.extract %in1 from 0 : (i8) -> i1 + %in1_1 = comb.extract %in1 from 1 : (i8) -> i1 + %in1_2 = comb.extract %in1 from 2 : (i8) -> i1 + %in1_3 = comb.extract %in1 from 3 : (i8) -> i1 + %in1_4 = comb.extract %in1 from 4 : (i8) -> i1 + %in1_5 = comb.extract %in1 from 5 : (i8) -> i1 + %in1_6 = comb.extract %in1 from 6 : (i8) -> i1 + %in1_7 = comb.extract %in1 from 7 : (i8) -> i1 + %in2_0 = comb.extract %in2 from 0 : (i8) -> i1 + %in2_1 = comb.extract %in2 from 1 : (i8) -> i1 + %in2_2 = comb.extract %in2 from 2 : (i8) -> i1 + %in2_3 = comb.extract %in2 from 3 : (i8) -> i1 + %in2_4 = comb.extract %in2 from 4 : (i8) -> i1 + %in2_5 = comb.extract %in2 from 5 : (i8) -> i1 + %in2_6 = comb.extract %in2 from 6 : (i8) -> i1 + %in2_7 = comb.extract %in2 from 7 : (i8) -> i1 + %b0, %d0 = hw.instance "s0" @halfSubtractor(in1: %in1_0: i1, in2: %in2_0: i1) -> (borrow: i1, diff: i1) + %b1, %d1 = hw.instance "s1" @fullSubtractor(in1: %in1_1: i1, in2: %in2_1: i1, b_in: %b0: i1) -> (borrow: i1, diff: i1) + %b2, %d2 = hw.instance "s2" @fullSubtractor(in1: %in1_2: i1, in2: %in2_2: i1, b_in: %b1: i1) -> (borrow: i1, diff: i1) + %b3, %d3 = hw.instance "s3" @fullSubtractor(in1: %in1_3: i1, in2: %in2_3: i1, b_in: %b2: i1) -> (borrow: i1, diff: i1) + %b4, %d4 = hw.instance "s4" @fullSubtractor(in1: %in1_4: i1, in2: %in2_4: i1, b_in: %b3: i1) -> (borrow: i1, diff: i1) + %b5, %d5 = hw.instance "s5" @fullSubtractor(in1: %in1_5: i1, in2: %in2_5: i1, b_in: %b4: i1) -> (borrow: i1, diff: i1) + %b6, %d6 = hw.instance "s6" @fullSubtractor(in1: %in1_6: i1, in2: %in2_6: i1, b_in: %b5: i1) -> (borrow: i1, diff: i1) + %b7, %d7 = hw.instance "s7" @fullSubtractor(in1: %in1_7: i1, in2: %in2_7: i1, b_in: %b6: i1) -> (borrow: i1, diff: i1) + %diff = comb.concat %d7, %d6, %d5, %d4, %d3, %d2, %d1, %d0 : i1, i1, i1, i1, i1, i1, i1, i1 + hw.output %diff : i8 +} + +// comb.xor +// TODO diff --git a/integration_test/circt-lec/commandline.mlir b/integration_test/circt-lec/commandline.mlir new file mode 100644 index 000000000000..422d63dbd900 --- /dev/null +++ b/integration_test/circt-lec/commandline.mlir @@ -0,0 +1,18 @@ +// These tests will be only enabled if circt-lec is built. +// REQUIRES: circt-lec + +// RUN: split-file %s %t + +// Passing two input files +// RUN: circt-lec %t/first.mlir %t/second.mlir -v=false | FileCheck %s +// CHECK: c1 == c2 + +//--- first.mlir +hw.module @basic(in %in: i1, out out: i1) { + hw.output %in : i1 +} + +//--- second.mlir +hw.module @basic(in %in: i1, out out: i1) { + hw.output %in : i1 +} diff --git a/integration_test/circt-lec/hw.mlir b/integration_test/circt-lec/hw.mlir new file mode 100644 index 000000000000..c176b05824ca --- /dev/null +++ b/integration_test/circt-lec/hw.mlir @@ -0,0 +1,78 @@ +// These tests will be only enabled if circt-lec is built. +// REQUIRES: circt-lec + +hw.module @basic(in %in: i1, out out: i1) { + hw.output %in : i1 +} + +// hw.constant +// RUN: circt-lec %s -c1=basic -c2=notnot -v=false | FileCheck %s --check-prefix=HW_CONSTANT +// HW_CONSTANT: c1 == c2 + +hw.module @onePlusTwo(out out: i2) { + %one = hw.constant 1 : i2 + %two = hw.constant 2 : i2 + %three = comb.add bin %one, %two : i2 + hw.output %three : i2 +} + +hw.module @three(out out: i2) { + %three = hw.constant 3 : i2 + hw.output %three : i2 +} + +// hw.instance +// RUN: circt-lec %s -c1=basic -c2=notnot -v=false | FileCheck %s --check-prefix=HW_INSTANCE +// HW_INSTANCE: c1 == c2 + +hw.module @not(in %in: i1, out out: i1) { + %true = hw.constant true + %out = comb.xor bin %in, %true : i1 + hw.output %out : i1 +} + +hw.module @notnot(in %in: i1, out out: i1) { + %n = hw.instance "n" @not(in: %in: i1) -> (out: i1) + %nn = hw.instance "nn" @not(in: %n: i1) -> (out: i1) + hw.output %nn : i1 +} + +// hw.output +// RUN: circt-lec %s -c1=basic -c2=basic -v=false | FileCheck %s --check-prefix=HW_OUTPUT +// HW_OUTPUT: c1 == c2 + +hw.module @constZeroZero(in %in: i1, out o1: i1, out o2: i1) { + %zero = hw.constant 0 : i1 + hw.output %zero, %zero : i1, i1 +} + +hw.module @xorZeroZero(in %in: i1, out o1: i1, out o2: i1) { + %zero = comb.xor bin %in, %in : i1 + hw.output %zero, %zero : i1, i1 +} + +hw.module @constZeroOne(in %in: i1, out o1: i1, out o2: i1) { + %zero = hw.constant 0 : i1 + %one = hw.constant 1 : i1 + hw.output %zero, %one : i1, i1 +} + +// Equivalent modules with two outputs +// RUN: circt-lec %s -c1=constZeroZero -c2=xorZeroZero -v=false | FileCheck %s --check-prefix=TWOOUTPUTS +// TWOOUTPUTS: c1 == c2 + +// Modules with one equivalent and one non-equivalent output +// RUN: not circt-lec %s -c1=constZeroZero -c2=constZeroOne -v=false | FileCheck %s --check-prefix=TWOOUTPUTSFAIL +// TWOOUTPUTSFAIL: c1 != c2 + +hw.module @onePlusTwoNonSSA(out out: i2) { + %three = comb.add bin %one, %two : i2 + %one = hw.constant 1 : i2 + %two = hw.constant 2 : i2 + hw.output %three : i2 +} + +// hw.module graph region check +// RUN: circt-lec %s -c1=onePlusTwo -c2=onePlusTwoNonSSA -v=false | FileCheck %s --check-prefix=HW_MODULE_GRAPH +// HW_MODULE_GRAPH: c1 == c2 + diff --git a/integration_test/handshake-runner/call_bb.mlir b/integration_test/handshake-runner/call_bb.mlir index dbd166acede4..b8bbc650d7f9 100644 --- a/integration_test/handshake-runner/call_bb.mlir +++ b/integration_test/handshake-runner/call_bb.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 763 2996 module { func.func @muladd(%1:index, %2:index, %3:index) -> (index) { diff --git a/integration_test/handshake-runner/call_controlflow.mlir b/integration_test/handshake-runner/call_controlflow.mlir index 51c7234b772b..26f2d441f607 100644 --- a/integration_test/handshake-runner/call_controlflow.mlir +++ b/integration_test/handshake-runner/call_controlflow.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s 3 2 1 | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s > handshake.mlir +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s > handshake.mlir // RUN handshake-runner handshake.mlir 3 2 1 | FileCheck %s // CHECK: 5 diff --git a/integration_test/handshake-runner/cdiv-old-std.mlir b/integration_test/handshake-runner/cdiv-old-std.mlir index e29107fb07d0..d5950ef1cc37 100755 --- a/integration_test/handshake-runner/cdiv-old-std.mlir +++ b/integration_test/handshake-runner/cdiv-old-std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 1,0,1,0 | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner - 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 1,0,1,0 | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner - 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 1,0,1,0 | FileCheck %s // CHECK: 0 2,3,4,5 2,3,4,5 1,1431655763,3,858993455 3,4,5,6 2,3,4,5 2,3,4,5 2,3,4,5 2,3,4,5 0,-1,0,-1 module { diff --git a/integration_test/handshake-runner/cdiv-std.mlir b/integration_test/handshake-runner/cdiv-std.mlir index b1b1189d2c86..9506c4cc0452 100755 --- a/integration_test/handshake-runner/cdiv-std.mlir +++ b/integration_test/handshake-runner/cdiv-std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 0 module { diff --git a/integration_test/handshake-runner/complex_bb.mlir b/integration_test/handshake-runner/complex_bb.mlir index 7c84074696f9..53d2473c1861 100644 --- a/integration_test/handshake-runner/complex_bb.mlir +++ b/integration_test/handshake-runner/complex_bb.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 763 2996 module { func.func @main() -> (index, index) { diff --git a/integration_test/handshake-runner/floydwarshall-std.mlir b/integration_test/handshake-runner/floydwarshall-std.mlir index 690c27ecf7fb..0410b5c51dd2 100755 --- a/integration_test/handshake-runner/floydwarshall-std.mlir +++ b/integration_test/handshake-runner/floydwarshall-std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 0 module { diff --git a/integration_test/handshake-runner/histogram-std.mlir b/integration_test/handshake-runner/histogram-std.mlir index eddc986f81f0..327354e68738 100755 --- a/integration_test/handshake-runner/histogram-std.mlir +++ b/integration_test/handshake-runner/histogram-std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 0 module { diff --git a/integration_test/handshake-runner/loadstore.mlir b/integration_test/handshake-runner/loadstore.mlir index d0e236d0cecc..c92f3056e4ab 100644 --- a/integration_test/handshake-runner/loadstore.mlir +++ b/integration_test/handshake-runner/loadstore.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s 2 | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner - 2 | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner - 2 | FileCheck %s // CHECK: 1 module { diff --git a/integration_test/handshake-runner/loop-check-1-std.mlir b/integration_test/handshake-runner/loop-check-1-std.mlir index 691550155d50..23e3a2e550c6 100755 --- a/integration_test/handshake-runner/loop-check-1-std.mlir +++ b/integration_test/handshake-runner/loop-check-1-std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 10 module { diff --git a/integration_test/handshake-runner/loop-check-2-std.mlir b/integration_test/handshake-runner/loop-check-2-std.mlir index dc70338fdc37..55d36fa5656f 100755 --- a/integration_test/handshake-runner/loop-check-2-std.mlir +++ b/integration_test/handshake-runner/loop-check-2-std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 10 module { diff --git a/integration_test/handshake-runner/matmul-check-std.mlir b/integration_test/handshake-runner/matmul-check-std.mlir index 295b46762c38..e97cf988ebb2 100755 --- a/integration_test/handshake-runner/matmul-check-std.mlir +++ b/integration_test/handshake-runner/matmul-check-std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 200 diff --git a/integration_test/handshake-runner/matmul-std.mlir b/integration_test/handshake-runner/matmul-std.mlir index aebe37f8b8b0..8262b0b01920 100755 --- a/integration_test/handshake-runner/matmul-std.mlir +++ b/integration_test/handshake-runner/matmul-std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // CHECK: 0 module { diff --git a/integration_test/handshake-runner/memory_simple_2_std.mlir b/integration_test/handshake-runner/memory_simple_2_std.mlir index ae0c255bef5b..f9e82ffdd0e5 100755 --- a/integration_test/handshake-runner/memory_simple_2_std.mlir +++ b/integration_test/handshake-runner/memory_simple_2_std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s 2,3,4,5 | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner - 2,3,4,5 | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner - 2,3,4,5 | FileCheck %s // CHECK: 5 5,3,4,5 module { diff --git a/integration_test/handshake-runner/memory_simple_std.mlir b/integration_test/handshake-runner/memory_simple_std.mlir index 4ca6173fc676..ab9d430f6bc6 100755 --- a/integration_test/handshake-runner/memory_simple_std.mlir +++ b/integration_test/handshake-runner/memory_simple_std.mlir @@ -1,5 +1,5 @@ // RUN: handshake-runner %s 2,3,4,5 | FileCheck %s -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner - 2,3,4,5 | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner - 2,3,4,5 | FileCheck %s // CHECK: 2 2,3,4,5 module { diff --git a/integration_test/handshake-runner/simple_loop.mlir b/integration_test/handshake-runner/simple_loop.mlir index 0eed557bddb9..696559eedcb5 100644 --- a/integration_test/handshake-runner/simple_loop.mlir +++ b/integration_test/handshake-runner/simple_loop.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s | handshake-runner | FileCheck %s // RUN: handshake-runner %s | FileCheck %s // CHECK: 42 module { diff --git a/integration_test/handshake-runner/simple_loop_buffered.mlir b/integration_test/handshake-runner/simple_loop_buffered.mlir index fb1be50d5c6b..a0cd3348d68e 100644 --- a/integration_test/handshake-runner/simple_loop_buffered.mlir +++ b/integration_test/handshake-runner/simple_loop_buffered.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt -lower-std-to-handshake -handshake-materialize-forks-sinks %s \ +// RUN: circt-opt -lower-cf-to-handshake -handshake-materialize-forks-sinks %s \ // RUN: | circt-opt --handshake-insert-buffers="strategy=all" \ // RUN: | handshake-runner | FileCheck %s // CHECK: 42 diff --git a/integration_test/lit.cfg.py b/integration_test/lit.cfg.py index 1f7c9f4ef959..f2ffbf55e51b 100644 --- a/integration_test/lit.cfg.py +++ b/integration_test/lit.cfg.py @@ -38,8 +38,10 @@ config.substitutions.append( ('%BININC%', os.path.join(config.circt_obj_root, "include"))) config.substitutions.append( - ('%TCL_PATH%', config.circt_src_root + '/build/lib/Bindings/Tcl/')) + ('%TCL_PATH%', config.circt_src_root + '/build/lib/')) config.substitutions.append(('%CIRCT_SOURCE%', config.circt_src_root)) +config.substitutions.append( + ('%ESI_COLLATERAL_PATH%', config.esi_collateral_path)) llvm_config.with_system_environment(['HOME', 'INCLUDE', 'LIB', 'TMP', 'TEMP']) @@ -65,7 +67,6 @@ # Tweak the PATH to include the tools dir. llvm_config.with_environment('PATH', config.llvm_tools_dir, append_path=True) -# Substitute '%l' with the path to the build lib dir. # Tweak the PYTHONPATH to include the binary dir. if config.bindings_python_enabled: @@ -80,7 +81,8 @@ ] tools = [ 'circt-opt', 'circt-translate', 'firtool', 'circt-rtl-sim.py', - 'esi-cosim-runner.py', 'equiv-rtl.sh', 'handshake-runner', 'hlstool' + 'esi-cosim-runner.py', 'equiv-rtl.sh', 'handshake-runner', 'hlstool', + 'ibistool' ] # Enable python if its path was configured @@ -167,16 +169,20 @@ if ieee_sims and ieee_sims[-1][1] == config.iverilog_path: config.available_features.add('ieee-sim-iverilog') -# Enable ESI cosim tests if they have been built. -if config.esi_cosim_path != "": - config.available_features.add('esi-cosim') - config.substitutions.append( - ('%ESIINC%', f'{config.circt_include_dir}/circt/Dialect/ESI/')) - config.substitutions.append(('%ESICOSIM%', f'{config.esi_cosim_path}')) +# Enable ESI runtime tests. +if config.esi_runtime == "1": + config.available_features.add('esi-runtime') -# Enable ESI's Capnp tests if they're supported. -if config.esi_capnp != "": - config.available_features.add('capnp') + llvm_config.with_environment('PYTHONPATH', + [f"{config.esi_runtime_path}/python/"], + append_path=True) + + # Enable ESI cosim tests if they have been built. + if config.esi_cosim_path != "": + config.available_features.add('esi-cosim') + config.substitutions.append( + ('%ESIINC%', f'{config.circt_include_dir}/circt/Dialect/ESI/')) + config.substitutions.append(('%ESICOSIM%', f'{config.esi_cosim_path}')) # Enable Python bindings tests if they're supported. if config.bindings_python_enabled: @@ -194,6 +200,11 @@ if config.have_systemc != "": config.available_features.add('systemc') +# Enable circt-lec tests if it is built. +if config.lec_enabled != "": + config.available_features.add('circt-lec') + tools.append('circt-lec') + llvm_config.add_tool_substitutions(tools, tool_dirs) # cocotb availability diff --git a/integration_test/lit.site.cfg.py.in b/integration_test/lit.site.cfg.py.in index 6f5ccb3f54a3..65cbc489cb55 100644 --- a/integration_test/lit.site.cfg.py.in +++ b/integration_test/lit.site.cfg.py.in @@ -49,8 +49,12 @@ config.iverilog_path = "@IVERILOG_PATH@" config.clang_tidy_path = "@CLANG_TIDY_PATH@" config.have_systemc = "@HAVE_SYSTEMC@" config.esi_capnp = "@ESI_CAPNP@" +config.esi_runtime = "@ESI_RUNTIME@" +config.esi_runtime_path = "@ESIRuntimePath@" +config.esi_collateral_path = "@ESI_COLLATERAL_PATH@" config.bindings_python_enabled = @CIRCT_BINDINGS_PYTHON_ENABLED@ config.bindings_tcl_enabled = @CIRCT_BINDINGS_TCL_ENABLED@ +config.lec_enabled = "@CIRCT_LEC_ENABLED@" # Support substitution of the tools_dir with user parameters. This is # used when we can't determine the tool dir at configuration time. diff --git a/lib/Analysis/CMakeLists.txt b/lib/Analysis/CMakeLists.txt index 6f5379704193..832ee132c08f 100644 --- a/lib/Analysis/CMakeLists.txt +++ b/lib/Analysis/CMakeLists.txt @@ -1,16 +1,21 @@ set(LLVM_OPTIONAL_SOURCES - ControlFlowLoopAnalysis.cpp + DebugAnalysis.cpp + DebugInfo.cpp DependenceAnalysis.cpp SchedulingAnalysis.cpp TestPasses.cpp - ) +) -add_circt_library(CIRCTControlFlowLoopAnalysis - ControlFlowLoopAnalysis.cpp +add_circt_library(CIRCTDebugAnalysis + DebugAnalysis.cpp + DebugInfo.cpp LINK_LIBS PUBLIC + CIRCTComb + CIRCTDebug + CIRCTHW MLIRIR - ) +) add_circt_library(CIRCTDependenceAnalysis DependenceAnalysis.cpp @@ -19,7 +24,7 @@ add_circt_library(CIRCTDependenceAnalysis MLIRIR MLIRAffineUtils MLIRTransformUtils - ) +) add_circt_library(CIRCTSchedulingAnalysis SchedulingAnalysis.cpp @@ -27,18 +32,17 @@ add_circt_library(CIRCTSchedulingAnalysis LINK_LIBS PUBLIC MLIRAffineDialect MLIRIR - CIRCTControlFlowLoopAnalysis CIRCTDependenceAnalysis CIRCTScheduling - ) +) add_circt_library(CIRCTAnalysisTestPasses TestPasses.cpp LINK_LIBS PUBLIC - CIRCTControlFlowLoopAnalysis + CIRCTDebugAnalysis CIRCTDependenceAnalysis CIRCTSchedulingAnalysis CIRCTHW MLIRPass - ) +) diff --git a/lib/Analysis/ControlFlowLoopAnalysis.cpp b/lib/Analysis/ControlFlowLoopAnalysis.cpp deleted file mode 100644 index 5259f11d90dd..000000000000 --- a/lib/Analysis/ControlFlowLoopAnalysis.cpp +++ /dev/null @@ -1,181 +0,0 @@ -//===- ControlFlowLoopAnalysis.cpp - CF Loop Analysis ---------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file implements functions that perform loop analysis on structures -// expressed as a CFG. -// -//===----------------------------------------------------------------------===// - -#include "circt/Analysis/ControlFlowLoopAnalysis.h" -#include "mlir/IR/BuiltinOps.h" - -using namespace mlir; -using namespace circt::analysis; - -namespace { -// The BFS callback provides the current block as an argument and returns -// whether search should halt. -enum class BFSNextState { Halt, SkipSuccessors, Continue, Custom }; -using BFSCallbackExtended = llvm::function_ref &, SmallVector &)>; - -void blockBFS(Block *start, BFSCallbackExtended callback) { - DenseSet visited; - SmallVector queue = {start}; - while (!queue.empty()) { - Block *currBlock = queue.front(); - queue.erase(queue.begin()); - if (visited.contains(currBlock)) - continue; - visited.insert(currBlock); - - switch (callback(currBlock, visited, queue)) { - case BFSNextState::Halt: - return; - case BFSNextState::Continue: { - llvm::copy(currBlock->getSuccessors(), std::back_inserter(queue)); - break; - } - case BFSNextState::SkipSuccessors: - for (auto *succ : currBlock->getSuccessors()) - visited.insert(succ); - break; - case BFSNextState::Custom: - break; - } - } -} - -using BFSCallback = llvm::function_ref; -void blockBFS(Block *start, BFSCallback callback) { - blockBFS(start, [&](Block *block, DenseSet &, - SmallVector &) { return callback(block); }); -} - -/// Performs a BFS to determine whether there exists a path between 'from' and -/// 'to'. -static bool isReachable(Block *from, Block *to) { - bool isReachable = false; - blockBFS(from, [&](Block *currBlock) { - if (currBlock == to) { - isReachable = true; - return BFSNextState::Halt; - } - return BFSNextState::Continue; - }); - return isReachable; -} - -} // namespace - -/// Helper that checks if entry is a loop header. If it is, it collects -/// additional information about the loop for further processing. -LogicalResult ControlFlowLoopAnalysis::collectLoopInfo(Block *entry, - LoopInfo &loopInfo) { - loopInfo.loopHeader = entry; - - for (auto *backedge : entry->getPredecessors()) { - bool dominates = domInfo.dominates(entry, backedge); - bool formsLoop = isReachable(entry, backedge); - if (formsLoop) { - if (dominates) - loopInfo.loopLatches.insert(backedge); - else - return entry->getParentOp()->emitError() - << "Non-canonical loop structures detected; a potential " - "loop header has backedges not dominated by the loop " - "header. This indicates that the loop has multiple entry " - "points."; - } - } - - // Exit blocks are the blocks that control is transfered to after exiting - // the loop. This is essentially determining the strongly connected - // components with the loop header. We perform a BFS from the loop header, - // and if the loop header is reachable from the block, it is within the - // loop. - blockBFS(entry, [&](Block *currBlock) { - if (isReachable(currBlock, entry)) { - loopInfo.inLoop.insert(currBlock); - return BFSNextState::Continue; - } - loopInfo.exitBlocks.insert(currBlock); - return BFSNextState::SkipSuccessors; - }); - - assert(loopInfo.inLoop.size() >= 2 && "A loop must have at least 2 blocks"); - assert(loopInfo.exitBlocks.size() != 0 && - "A loop must have an exit block...?"); - - return success(); -} - -ControlFlowLoopAnalysis::ControlFlowLoopAnalysis(Region ®ion) - : region(region), domInfo(region.getParentOp()) {} - -bool ControlFlowLoopAnalysis::hasBackedge(Block *block) { - return llvm::any_of(block->getPredecessors(), - [&](Block *pred) { return isReachable(block, pred); }); -} - -LogicalResult ControlFlowLoopAnalysis::analyzeRegion() { - Block *entry = ®ion.front(); - LogicalResult result = success(); - blockBFS(entry, [&](Block *currBlock, DenseSet &visited, - SmallVector &queue) { - if (!hasBackedge(currBlock)) - return BFSNextState::Continue; - - LoopInfo newInfo; - if (failed(collectLoopInfo(currBlock, newInfo))) { - result = failure(); - return BFSNextState::Halt; - } - - // Adjusting the BFS state to jump over the loop. - for (Block *loopBlock : newInfo.inLoop) - visited.insert(loopBlock); - llvm::copy(newInfo.exitBlocks, std::back_inserter(queue)); - - topLevelLoops.emplace_back(std::move(newInfo)); - - return BFSNextState::Custom; - }); - - return result; -} - -bool ControlFlowLoopAnalysis::isLoopHeader(Block *b) { - for (auto &info : topLevelLoops) - if (info.loopHeader == b) - return true; - return false; -} - -bool ControlFlowLoopAnalysis::isLoopElement(Block *b) { - for (auto &info : topLevelLoops) - if (info.inLoop.contains(b)) - return true; - return false; -} - -LoopInfo *ControlFlowLoopAnalysis::getLoopInfoForHeader(Block *b) { - for (auto &info : topLevelLoops) - if (info.loopHeader == b) - return &info; - - return nullptr; -} - -LoopInfo *ControlFlowLoopAnalysis::getLoopInfo(Block *b) { - for (auto &info : topLevelLoops) - if (info.inLoop.contains(b)) - return &info; - - return nullptr; -} diff --git a/lib/Analysis/DebugAnalysis.cpp b/lib/Analysis/DebugAnalysis.cpp new file mode 100644 index 000000000000..0725ea1a13a0 --- /dev/null +++ b/lib/Analysis/DebugAnalysis.cpp @@ -0,0 +1,128 @@ +//===- DebugAnalysis.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt/Analysis/DebugAnalysis.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/Debug/DebugOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/Support/Debug.h" + +using namespace circt; +using namespace debug; +using namespace mlir; + +namespace { +struct DebugAnalysisBuilder { + DebugAnalysisBuilder(Operation *rootOp) : rootOp(rootOp) {} + void run(); + void addDebugOp(Operation *op); + void addDebugValue(Value value); + void addDebugOperand(OpOperand *operand); + void maybeDebugOp(Operation *op); + + Operation *rootOp; + SetVector worklist; + + DenseSet debugOps; + DenseSet debugValues; + DenseSet debugOperands; +}; +} // namespace + +void DebugAnalysisBuilder::run() { + // Find all debug ops nested under the root op and mark them as debug-only + // to kickstart the analysis. + rootOp->walk([&](Operation *op) { + if (isa(op->getDialect())) { + addDebugOp(op); + return; + } + for (auto ®ion : op->getRegions()) + for (auto &block : region) + for (auto arg : block.getArguments()) + if (isa(arg.getType().getDialect())) + addDebugValue(arg); + for (auto result : op->getResults()) + if (isa(result.getType().getDialect())) + addDebugValue(result); + }); + + // Visit operations and check if all their operands or all their uses are + // marked as debug-only. If they are, mark the op itself as debug-only. + while (!worklist.empty()) { + auto *op = worklist.pop_back_val(); + if (debugOps.contains(op)) + continue; + + // Do not propagate through stateful elements. This should probably be + // configurable, since certain forms of debug info extraction would be able + // to pull entire state machines out of the design. For now this just + // represents the common denominator across all debug infos. + if (!isa(op->getDialect())) + continue; + if (op->hasAttr("name")) + continue; + + if (op->getNumResults() > 0) { + auto allUsesDebug = llvm::all_of(op->getUses(), [&](auto &use) { + return debugOperands.contains(&use); + }); + if (allUsesDebug) { + addDebugOp(op); + continue; + } + } + + if (op->getNumOperands() > 0) { + auto allOperandsDebug = + llvm::all_of(op->getOperands(), [&](auto operand) { + return debugValues.contains(operand); + }); + if (allOperandsDebug) { + addDebugOp(op); + continue; + } + } + } +} + +void DebugAnalysisBuilder::addDebugOp(Operation *op) { + if (debugOps.insert(op).second) { + for (auto &operand : op->getOpOperands()) + addDebugOperand(&operand); + for (auto result : op->getResults()) + addDebugValue(result); + } +} + +void DebugAnalysisBuilder::addDebugValue(Value value) { + if (debugValues.insert(value).second) { + for (auto *user : value.getUsers()) + maybeDebugOp(user); + } +} + +void DebugAnalysisBuilder::addDebugOperand(OpOperand *operand) { + if (debugOperands.insert(operand).second) + maybeDebugOp(operand->get().getDefiningOp()); +} + +void DebugAnalysisBuilder::maybeDebugOp(Operation *op) { + if (!op || debugOps.contains(op)) + return; + worklist.insert(op); +} + +DebugAnalysis::DebugAnalysis(Operation *op) { + DebugAnalysisBuilder builder(op); + builder.run(); + debugOps = std::move(builder.debugOps); + debugValues = std::move(builder.debugValues); + debugOperands = std::move(builder.debugOperands); +} diff --git a/lib/Analysis/DebugInfo.cpp b/lib/Analysis/DebugInfo.cpp new file mode 100644 index 000000000000..c69fa5b83ffb --- /dev/null +++ b/lib/Analysis/DebugInfo.cpp @@ -0,0 +1,162 @@ +//===- DebugInfo.cpp - Debug info analysis --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt/Analysis/DebugInfo.h" +#include "circt/Dialect/Debug/DebugOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/IR/BuiltinOps.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "di" + +using namespace mlir; +using namespace circt; + +namespace circt { +namespace detail { + +/// Helper to populate a `DebugInfo` with nodes. +struct DebugInfoBuilder { + DebugInfoBuilder(DebugInfo &di) : di(di) {} + DebugInfo &di; + + void visitRoot(Operation *op); + void visitModule(hw::HWModuleOp moduleOp, DIModule &module); + + DIModule *createModule() { + return new (di.moduleAllocator.Allocate()) DIModule; + } + + DIInstance *createInstance() { + return new (di.instanceAllocator.Allocate()) DIInstance; + } + + DIVariable *createVariable() { + return new (di.variableAllocator.Allocate()) DIVariable; + } + + DIModule &getOrCreateModule(StringAttr moduleName) { + auto &slot = di.moduleNodes[moduleName]; + if (!slot) { + slot = createModule(); + slot->name = moduleName; + } + return *slot; + } +}; + +void DebugInfoBuilder::visitRoot(Operation *op) { + op->walk([&](Operation *op) { + if (auto moduleOp = dyn_cast(op)) { + LLVM_DEBUG(llvm::dbgs() + << "Collect DI for module " << moduleOp.getNameAttr() << "\n"); + auto &module = getOrCreateModule(moduleOp.getNameAttr()); + module.op = op; + visitModule(moduleOp, module); + return WalkResult::skip(); + } + + if (auto moduleOp = dyn_cast(op)) { + LLVM_DEBUG(llvm::dbgs() << "Collect DI for extern module " + << moduleOp.getNameAttr() << "\n"); + auto &module = getOrCreateModule(moduleOp.getNameAttr()); + module.op = op; + module.isExtern = true; + + // Add variables for each of the ports. + for (auto &port : moduleOp.getPortList()) { + auto *var = createVariable(); + var->name = port.name; + var->loc = port.loc; + module.variables.push_back(var); + } + + return WalkResult::skip(); + } + + return WalkResult::advance(); + }); +} + +void DebugInfoBuilder::visitModule(hw::HWModuleOp moduleOp, DIModule &module) { + // Try to gather debug info from debug ops in the module. If we find any, + // return. Otherwise collect ports, instances, and variables as a + // fallback. + + // Check what kind of DI is present in the module. + bool hasVariables = false; + bool hasInstances = false; + moduleOp.walk([&](Operation *op) { + if (isa(op)) + hasVariables = true; + }); + + // If the module has no DI for variables, add variables for each of the ports + // as a fallback. + if (!hasVariables) { + auto inputValues = moduleOp.getBody().getArguments(); + auto outputValues = moduleOp.getBodyBlock()->getTerminator()->getOperands(); + for (auto &port : moduleOp.getPortList()) { + auto value = port.isOutput() ? outputValues[port.argNum] + : inputValues[port.argNum]; + auto *var = createVariable(); + var->name = port.name; + var->loc = port.loc; + var->value = value; + module.variables.push_back(var); + } + } + + // Fill in any missing DI as a fallback. + moduleOp->walk([&](Operation *op) { + if (auto varOp = dyn_cast(op)) { + auto *var = createVariable(); + var->name = varOp.getNameAttr(); + var->loc = varOp.getLoc(); + var->value = varOp.getValue(); + module.variables.push_back(var); + return; + } + + // Fallback if the module has no DI for its instances. + if (!hasInstances) { + if (auto instOp = dyn_cast(op)) { + auto &childModule = + getOrCreateModule(instOp.getModuleNameAttr().getAttr()); + auto *instance = createInstance(); + instance->name = instOp.getInstanceNameAttr(); + instance->op = instOp; + instance->module = &childModule; + module.instances.push_back(instance); + + // TODO: What do we do with the port assignments? These should be + // tracked somewhere. + return; + } + } + + // Fallback if the module has no DI for its variables. + if (!hasVariables) { + if (auto wireOp = dyn_cast(op)) { + auto *var = createVariable(); + var->name = wireOp.getNameAttr(); + var->loc = wireOp.getLoc(); + var->value = wireOp; + module.variables.push_back(var); + return; + } + } + }); +} + +} // namespace detail +} // namespace circt + +DebugInfo::DebugInfo(Operation *op) : operation(op) { + detail::DebugInfoBuilder(*this).visitRoot(op); +} diff --git a/lib/Analysis/DependenceAnalysis.cpp b/lib/Analysis/DependenceAnalysis.cpp index 83b657adb47f..8482f9a6d209 100644 --- a/lib/Analysis/DependenceAnalysis.cpp +++ b/lib/Analysis/DependenceAnalysis.cpp @@ -22,6 +22,7 @@ #include "mlir/IR/BuiltinOps.h" using namespace mlir; +using namespace mlir::affine; using namespace circt::analysis; /// Helper to iterate through memory operation pairs and check for dependences @@ -43,6 +44,12 @@ static void checkMemrefDependence(SmallVectorImpl &memoryOps, MemRefAccess dst(destination); FlatAffineValueConstraints dependenceConstraints; SmallVector depComps; + + // Requested depth might not be a valid comparison if they do not belong + // to the same loop nest + if (depth > getInnermostCommonLoopDepth({source, destination})) + continue; + DependenceResult result = checkMemrefAccessDependence( src, dst, depth, &dependenceConstraints, &depComps, true); @@ -129,7 +136,7 @@ circt::analysis::MemoryDependenceAnalysis::MemoryDependenceAnalysis( // Collect affine loops grouped by nesting depth. std::vector> depthToLoops; - mlir::gatherLoops(funcOp, depthToLoops); + mlir::affine::gatherLoops(funcOp, depthToLoops); // Collect load and store operations to check. SmallVector memoryOps; diff --git a/lib/Analysis/SchedulingAnalysis.cpp b/lib/Analysis/SchedulingAnalysis.cpp index 37963edf1928..3ca43cfb65e6 100644 --- a/lib/Analysis/SchedulingAnalysis.cpp +++ b/lib/Analysis/SchedulingAnalysis.cpp @@ -23,6 +23,7 @@ #include using namespace mlir; +using namespace mlir::affine; /// CyclicSchedulingAnalysis constructs a CyclicProblem for each AffineForOp by /// performing a memory dependence analysis and inserting dependences into the @@ -117,7 +118,7 @@ void circt::analysis::CyclicSchedulingAnalysis::analyzeForOp( // terminator to ensure the problem schedules them before the terminator. auto *anchor = forOp.getBody()->getTerminator(); forOp.getBody()->walk([&](Operation *op) { - if (!isa(op)) + if (!isa(op)) return; Problem::Dependence dep(op, anchor); auto depInserted = problem.insertDependence(dep); diff --git a/lib/Analysis/TestPasses.cpp b/lib/Analysis/TestPasses.cpp index 3fad7d9228a5..9aa447d423fb 100644 --- a/lib/Analysis/TestPasses.cpp +++ b/lib/Analysis/TestPasses.cpp @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -#include "circt/Analysis/ControlFlowLoopAnalysis.h" +#include "circt/Analysis/DebugAnalysis.h" #include "circt/Analysis/DependenceAnalysis.h" #include "circt/Analysis/SchedulingAnalysis.h" #include "circt/Dialect/HW/HWInstanceGraph.h" @@ -18,14 +18,43 @@ #include "mlir/Dialect/Affine/IR/AffineMemoryOpInterfaces.h" #include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/Value.h" #include "mlir/Pass/Pass.h" #include "llvm/Support/Debug.h" using namespace mlir; +using namespace mlir::affine; +using namespace circt; using namespace circt::analysis; //===----------------------------------------------------------------------===// -// DependenceAnalysis passes. +// DebugAnalysis +//===----------------------------------------------------------------------===// + +namespace { +struct TestDebugAnalysisPass + : public PassWrapper> { + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestDebugAnalysisPass) + + void runOnOperation() override; + StringRef getArgument() const override { return "test-debug-analysis"; } + StringRef getDescription() const override { + return "Perform debug analysis and emit results as attributes"; + } +}; +} // namespace + +void TestDebugAnalysisPass::runOnOperation() { + auto *context = &getContext(); + auto &analysis = getAnalysis(); + for (auto *op : analysis.debugOps) { + op->setAttr("debug.only", UnitAttr::get(context)); + } +} + +//===----------------------------------------------------------------------===// +// DependenceAnalysis //===----------------------------------------------------------------------===// namespace { @@ -54,7 +83,7 @@ void TestDependenceAnalysisPass::runOnOperation() { SmallVector deps; for (auto dep : analysis.getDependences(op)) { - if (dep.dependenceType != mlir::DependenceResult::HasDependence) + if (dep.dependenceType != DependenceResult::HasDependence) continue; SmallVector comps; @@ -76,7 +105,7 @@ void TestDependenceAnalysisPass::runOnOperation() { } //===----------------------------------------------------------------------===// -// DependenceAnalysis passes. +// SchedulingAnalysis //===----------------------------------------------------------------------===// namespace { @@ -113,65 +142,7 @@ void TestSchedulingAnalysisPass::runOnOperation() { } //===----------------------------------------------------------------------===// -// ControlFlowLoopAnalysis passes. -//===----------------------------------------------------------------------===// - -namespace { -struct TestControlFlowLoopAnalysisPass - : public PassWrapper> { - MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestControlFlowLoopAnalysisPass) - - void runOnOperation() override; - StringRef getArgument() const override { return "test-cf-loop-analysis"; } - StringRef getDescription() const override { - return "Perform cf loop analysis and emit results as attributes"; - } -}; -} // namespace - -static SmallVector & -lookupOrInsert(DenseMap> &map, Block *key) { - if (map.count(key) == 0) { - map.try_emplace(key, SmallVector()); - } - return map.find(key)->getSecond(); -} - -void TestControlFlowLoopAnalysisPass::runOnOperation() { - Region &r = getOperation().getRegion(); - ControlFlowLoopAnalysis analysis(r); - if (failed(analysis.analyzeRegion())) { - signalPassFailure(); - return; - } - OpBuilder builder(r); - DenseMap> blockMap; - for (const LoopInfo &info : analysis.topLevelLoops) { - Block *header = info.loopHeader; - lookupOrInsert(blockMap, header).push_back(builder.getStringAttr("header")); - - for (auto *latch : info.loopLatches) - lookupOrInsert(blockMap, latch).push_back(builder.getStringAttr("latch")); - - for (auto *inLoop : info.inLoop) - lookupOrInsert(blockMap, inLoop) - .push_back(builder.getStringAttr("inLoop")); - - for (auto *exit : info.exitBlocks) - lookupOrInsert(blockMap, exit).push_back(builder.getStringAttr("exit")); - } - - for (auto it : blockMap) { - OperationState opState(builder.getUnknownLoc(), "block.info"); - opState.addAttribute("loopInfo", builder.getArrayAttr(it.getSecond())); - builder.setInsertionPointToStart(it.getFirst()); - builder.create(opState); - } -} - -//===----------------------------------------------------------------------===// -// InferTopModule passes. +// InstanceGraph //===----------------------------------------------------------------------===// namespace { @@ -198,7 +169,7 @@ void InferTopModulePass::runOnOperation() { llvm::SmallVector attrs; for (auto *node : *res) - attrs.push_back(node->getModule().moduleNameAttr()); + attrs.push_back(node->getModule().getModuleNameAttr()); analysis.getParent()->setAttr("test.top", ArrayAttr::get(&getContext(), attrs)); @@ -211,16 +182,16 @@ void InferTopModulePass::runOnOperation() { namespace circt { namespace test { void registerAnalysisTestPasses() { - mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { + registerPass([]() -> std::unique_ptr { return std::make_unique(); }); - mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { + registerPass([]() -> std::unique_ptr { return std::make_unique(); }); - mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { - return std::make_unique(); + registerPass([]() -> std::unique_ptr { + return std::make_unique(); }); - mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { + registerPass([]() -> std::unique_ptr { return std::make_unique(); }); } diff --git a/lib/Bindings/Python/CIRCTModule.cpp b/lib/Bindings/Python/CIRCTModule.cpp index dbc2f16b7e9c..4abcae682ff9 100644 --- a/lib/Bindings/Python/CIRCTModule.cpp +++ b/lib/Bindings/Python/CIRCTModule.cpp @@ -8,15 +8,19 @@ #include "DialectModules.h" +#include "circt-c/Conversion.h" #include "circt-c/Dialect/Comb.h" #include "circt-c/Dialect/ESI.h" #include "circt-c/Dialect/FSM.h" #include "circt-c/Dialect/HW.h" #include "circt-c/Dialect/HWArith.h" #include "circt-c/Dialect/Handshake.h" +#include "circt-c/Dialect/LTL.h" #include "circt-c/Dialect/MSFT.h" +#include "circt-c/Dialect/OM.h" #include "circt-c/Dialect/SV.h" #include "circt-c/Dialect/Seq.h" +#include "circt-c/Dialect/Verif.h" #include "circt-c/ExportVerilog.h" #include "mlir-c/Bindings/Python/Interop.h" #include "mlir-c/IR.h" @@ -36,6 +40,7 @@ static void registerPasses() { registerFSMPasses(); registerHWArithPasses(); registerHandshakePasses(); + mlirRegisterConversionPasses(); mlirRegisterTransformsPasses(); } @@ -73,6 +78,10 @@ PYBIND11_MODULE(_circt, m) { mlirDialectHandleRegisterDialect(hwarith, context); mlirDialectHandleLoadDialect(hwarith, context); + MlirDialectHandle om = mlirGetDialectHandle__om__(); + mlirDialectHandleRegisterDialect(om, context); + mlirDialectHandleLoadDialect(om, context); + MlirDialectHandle seq = mlirGetDialectHandle__seq__(); mlirDialectHandleRegisterDialect(seq, context); mlirDialectHandleLoadDialect(seq, context); @@ -88,6 +97,14 @@ PYBIND11_MODULE(_circt, m) { MlirDialectHandle handshake = mlirGetDialectHandle__handshake__(); mlirDialectHandleRegisterDialect(handshake, context); mlirDialectHandleLoadDialect(handshake, context); + + MlirDialectHandle ltl = mlirGetDialectHandle__ltl__(); + mlirDialectHandleRegisterDialect(ltl, context); + mlirDialectHandleLoadDialect(ltl, context); + + MlirDialectHandle verif = mlirGetDialectHandle__verif__(); + mlirDialectHandleRegisterDialect(verif, context); + mlirDialectHandleLoadDialect(verif, context); }, "Register CIRCT dialects on a PyMlirContext."); @@ -108,6 +125,10 @@ PYBIND11_MODULE(_circt, m) { circt::python::populateDialectMSFTSubmodule(msft); py::module hw = m.def_submodule("_hw", "HW API"); circt::python::populateDialectHWSubmodule(hw); + py::module seq = m.def_submodule("_seq", "Seq API"); + circt::python::populateDialectSeqSubmodule(seq); + py::module om = m.def_submodule("_om", "OM API"); + circt::python::populateDialectOMSubmodule(om); py::module sv = m.def_submodule("_sv", "SV API"); circt::python::populateDialectSVSubmodule(sv); } diff --git a/lib/Bindings/Python/CMakeLists.txt b/lib/Bindings/Python/CMakeLists.txt index 92ad0ebef6a0..5a4649c1cbaa 100644 --- a/lib/Bindings/Python/CMakeLists.txt +++ b/lib/Bindings/Python/CMakeLists.txt @@ -17,7 +17,9 @@ declare_mlir_python_extension(CIRCTBindingsPythonExtension CIRCTModule.cpp ESIModule.cpp HWModule.cpp + OMModule.cpp MSFTModule.cpp + SeqModule.cpp SVModule.cpp EMBED_CAPI_LINK_LIBS CIRCTCAPIComb @@ -25,16 +27,22 @@ declare_mlir_python_extension(CIRCTBindingsPythonExtension CIRCTCAPIMSFT CIRCTCAPIHW CIRCTCAPIHWArith + CIRCTCAPIOM CIRCTCAPISeq CIRCTCAPISV CIRCTCAPIExportVerilog CIRCTCAPIFSM CIRCTCAPIHandshake + CIRCTCAPILTL + CIRCTCAPIVerif + CIRCTCAPIConversion MLIRCAPITransforms PRIVATE_LINK_LIBS LLVMSupport ) +add_dependencies(CIRCTBindingsPythonExtension circt-headers) + ################################################################################ # Declare Python sources ################################################################################ @@ -63,7 +71,6 @@ declare_mlir_dialect_python_bindings( TD_FILE dialects/CombOps.td SOURCES dialects/comb.py - dialects/_comb_ops_ext.py DIALECT_NAME comb) declare_mlir_dialect_python_bindings( @@ -72,7 +79,6 @@ declare_mlir_dialect_python_bindings( TD_FILE dialects/ESIOps.td SOURCES dialects/esi.py - dialects/_esi_ops_ext.py DIALECT_NAME esi) declare_mlir_dialect_python_bindings( @@ -81,7 +87,6 @@ declare_mlir_dialect_python_bindings( TD_FILE dialects/HWOps.td SOURCES dialects/hw.py - dialects/_hw_ops_ext.py DIALECT_NAME hw) declare_mlir_dialect_python_bindings( @@ -90,16 +95,22 @@ declare_mlir_dialect_python_bindings( TD_FILE dialects/MSFTOps.td SOURCES dialects/msft.py - dialects/_msft_ops_ext.py DIALECT_NAME msft) +declare_mlir_dialect_python_bindings( + ADD_TO_PARENT CIRCTBindingsPythonSources.Dialects + ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + TD_FILE dialects/OMOps.td + SOURCES + dialects/om.py + DIALECT_NAME om) + declare_mlir_dialect_python_bindings( ADD_TO_PARENT CIRCTBindingsPythonSources.Dialects ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" TD_FILE dialects/SeqOps.td SOURCES dialects/seq.py - dialects/_seq_ops_ext.py DIALECT_NAME seq) declare_mlir_dialect_python_bindings( @@ -108,7 +119,6 @@ declare_mlir_dialect_python_bindings( TD_FILE dialects/SVOps.td SOURCES dialects/sv.py - dialects/_sv_ops_ext.py DIALECT_NAME sv) declare_mlir_dialect_python_bindings( @@ -117,7 +127,6 @@ declare_mlir_dialect_python_bindings( TD_FILE dialects/FSMOps.td SOURCES dialects/fsm.py - dialects/_fsm_ops_ext.py DIALECT_NAME fsm) declare_mlir_dialect_python_bindings( @@ -126,9 +135,24 @@ declare_mlir_dialect_python_bindings( TD_FILE dialects/HWArithOps.td SOURCES dialects/hwarith.py - dialects/_hwarith_ops_ext.py DIALECT_NAME hwarith) +declare_mlir_dialect_python_bindings( + ADD_TO_PARENT CIRCTBindingsPythonSources.Dialects + ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + TD_FILE dialects/LTLOps.td + SOURCES + dialects/ltl.py + DIALECT_NAME ltl) + +declare_mlir_dialect_python_bindings( + ADD_TO_PARENT CIRCTBindingsPythonSources.Dialects + ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}" + TD_FILE dialects/VerifOps.td + SOURCES + dialects/verif.py + DIALECT_NAME verif) + ################################################################################ # Build composite binaries ################################################################################ diff --git a/lib/Bindings/Python/DialectModules.h b/lib/Bindings/Python/DialectModules.h index 849967380250..933aea340f8e 100644 --- a/lib/Bindings/Python/DialectModules.h +++ b/lib/Bindings/Python/DialectModules.h @@ -21,6 +21,8 @@ namespace python { void populateDialectESISubmodule(pybind11::module &m); void populateDialectHWSubmodule(pybind11::module &m); void populateDialectMSFTSubmodule(pybind11::module &m); +void populateDialectOMSubmodule(pybind11::module &m); +void populateDialectSeqSubmodule(pybind11::module &m); void populateDialectSVSubmodule(pybind11::module &m); } // namespace python diff --git a/lib/Bindings/Python/ESIModule.cpp b/lib/Bindings/Python/ESIModule.cpp index 457380fa7b4e..706f05dd0fdc 100644 --- a/lib/Bindings/Python/ESIModule.cpp +++ b/lib/Bindings/Python/ESIModule.cpp @@ -8,6 +8,8 @@ #include "DialectModules.h" +#include "circt/Dialect/ESI/ESIDialect.h" + #include "circt-c/Dialect/ESI.h" #include "mlir-c/Bindings/Python/Interop.h" @@ -23,6 +25,8 @@ #include namespace py = pybind11; +using namespace circt::esi; + //===----------------------------------------------------------------------===// // The main entry point into the ESI Assembly API. //===----------------------------------------------------------------------===// @@ -47,24 +51,35 @@ void registerServiceGenerator(std::string name, py::object genFunc) { circtESIRegisterGlobalServiceGenerator(wrap(*n), serviceGenFunc, n); } +class PyAppIDIndex { +public: + PyAppIDIndex(MlirOperation root) { index = circtESIAppIDIndexGet(root); } + PyAppIDIndex(const PyAppIDIndex &) = delete; + ~PyAppIDIndex() { circtESIAppIDIndexFree(index); } + + MlirAttribute getChildAppIDsOf(MlirOperation op) const { + return circtESIAppIDIndexGetChildAppIDsOf(index, op); + } + + py::object getAppIDPathAttr(MlirOperation fromMod, MlirAttribute appid, + MlirLocation loc) const { + MlirAttribute path = + circtESIAppIDIndexGetAppIDPath(index, fromMod, appid, loc); + if (path.ptr == nullptr) + return py::none(); + return py::cast(path); + } + +private: + CirctESIAppIDIndex index; +}; + using namespace mlir::python::adaptors; void circt::python::populateDialectESISubmodule(py::module &m) { m.doc() = "ESI Python Native Extension"; ::registerESIPasses(); - m.def( - "buildWrapper", - [](MlirOperation cModOp, std::vector cPortNames) { - llvm::SmallVector portNames; - for (auto portName : cPortNames) - portNames.push_back({portName.c_str(), portName.length()}); - return circtESIWrapModule(cModOp, portNames.size(), portNames.data()); - }, - "Construct an ESI wrapper around HW module 'op' given a list of " - "latency-insensitive ports.", - py::arg("op"), py::arg("name_list")); - m.def("registerServiceGenerator", registerServiceGenerator, "Register a service generator for a given service name.", py::arg("impl_type"), py::arg("generator")); @@ -91,4 +106,102 @@ void circt::python::populateDialectESISubmodule(py::module &m) { return cls(circtESIAnyTypeGet(ctxt)); }, py::arg("self"), py::arg("ctxt") = nullptr); + + mlir_type_subclass(m, "ListType", circtESITypeIsAListType) + .def_classmethod( + "get", + [](py::object cls, MlirType inner) { + return cls(circtESIListTypeGet(inner)); + }, + py::arg("cls"), py::arg("inner")) + .def_property_readonly("element_type", [](MlirType self) { + return circtESIListTypeGetElementType(self); + }); + + py::enum_(m, "ChannelDirection") + .value("TO", ChannelDirection::to) + .value("FROM", ChannelDirection::from); + mlir_type_subclass(m, "BundleType", circtESITypeIsABundleType) + .def_classmethod( + "get", + [](py::object cls, std::vector channelTuples, + bool resettable, MlirContext ctxt) { + llvm::SmallVector channels( + llvm::map_range(channelTuples, [ctxt](py::tuple t) { + std::string name = py::cast(t[0]); + return CirctESIBundleTypeBundleChannel{ + mlirIdentifierGet(ctxt, mlirStringRefCreate( + name.data(), name.length())), + (uint32_t)py::cast(t[1]), + py::cast(t[2])}; + })); + return cls(circtESIBundleTypeGet(ctxt, channels.size(), + channels.data(), resettable)); + }, + py::arg("cls"), py::arg("channels"), py::arg("resettable"), + py::arg("ctxt") = nullptr) + .def_property_readonly("resettable", &circtESIBundleTypeGetResettable) + .def_property_readonly("channels", [](MlirType bundleType) { + std::vector channels; + size_t numChannels = circtESIBundleTypeGetNumChannels(bundleType); + for (size_t i = 0; i < numChannels; ++i) { + CirctESIBundleTypeBundleChannel channel = + circtESIBundleTypeGetChannel(bundleType, i); + MlirStringRef name = mlirIdentifierStr(channel.name); + channels.push_back(py::make_tuple(py::str(name.data, name.length), + (ChannelDirection)channel.direction, + channel.channelType)); + } + return channels; + }); + + mlir_attribute_subclass(m, "AppIDAttr", circtESIAttributeIsAnAppIDAttr) + .def_classmethod( + "get", + [](py::object cls, std::string name, uint64_t index, + MlirContext ctxt) { + return cls(circtESIAppIDAttrGet(ctxt, wrap(name), index)); + }, + "Create an AppID attribute", py::arg("cls"), py::arg("name"), + py::arg("index"), py::arg("context") = py::none()) + .def_property_readonly("name", + [](MlirAttribute self) { + llvm::StringRef name = + unwrap(circtESIAppIDAttrGetName(self)); + return std::string(name.data(), name.size()); + }) + .def_property_readonly("index", [](MlirAttribute self) -> py::object { + uint64_t index; + if (circtESIAppIDAttrGetIndex(self, &index)) + return py::cast(index); + return py::none(); + }); + + mlir_attribute_subclass(m, "AppIDPathAttr", + circtESIAttributeIsAnAppIDPathAttr) + .def_classmethod( + "get", + [](py::object cls, MlirAttribute root, + std::vector path, MlirContext ctxt) { + return cls( + circtESIAppIDAttrPathGet(ctxt, root, path.size(), path.data())); + }, + "Create an AppIDPath attribute", py::arg("cls"), py::arg("root"), + py::arg("path"), py::arg("context") = py::none()) + .def_property_readonly("root", &circtESIAppIDAttrPathGetRoot) + .def("__len__", &circtESIAppIDAttrPathGetNumComponents) + .def("__getitem__", &circtESIAppIDAttrPathGetComponent); + + py::class_(m, "AppIDIndex") + .def(py::init(), py::arg("root")) + .def("get_child_appids_of", &PyAppIDIndex::getChildAppIDsOf, + "Return a dictionary of AppIDAttrs to ArrayAttr of InnerRefAttrs " + "containing the relative paths to the leaf of the particular " + "AppIDAttr. Argument MUST be HWModuleLike.", + py::arg("mod")) + .def("get_appid_path", &PyAppIDIndex::getAppIDPathAttr, + "Return an array of InnerNameRefAttrs representing the relative " + "path to 'appid' from 'fromMod'.", + py::arg("from_mod"), py::arg("appid"), + py::arg("query_site") = py::none()); } diff --git a/lib/Bindings/Python/HWModule.cpp b/lib/Bindings/Python/HWModule.cpp index 8808b3558f55..1db80da6bff2 100644 --- a/lib/Bindings/Python/HWModule.cpp +++ b/lib/Bindings/Python/HWModule.cpp @@ -51,6 +51,66 @@ void circt::python::populateDialectHWSubmodule(py::module &m) { .def_property_readonly( "size", [](MlirType self) { return hwArrayTypeGetSize(self); }); + py::enum_(m, "ModulePortDirection") + .value("INPUT", HWModulePortDirection::Input) + .value("OUTPUT", HWModulePortDirection::Output) + .value("INOUT", HWModulePortDirection::InOut) + .export_values(); + + py::class_(m, "ModulePort") + .def(py::init()); + + mlir_type_subclass(m, "ModuleType", hwTypeIsAModuleType) + .def_classmethod( + "get", + [](py::object cls, py::list pyModulePorts, MlirContext ctx) { + std::vector modulePorts; + for (auto pyModulePort : pyModulePorts) + modulePorts.push_back(pyModulePort.cast()); + + return cls( + hwModuleTypeGet(ctx, modulePorts.size(), modulePorts.data())); + }, + py::arg("cls"), py::arg("ports"), py::arg("context") = py::none()) + .def_property_readonly( + "input_types", + [](MlirType self) { + py::list inputTypes; + intptr_t numInputs = hwModuleTypeGetNumInputs(self); + for (intptr_t i = 0; i < numInputs; ++i) + inputTypes.append(hwModuleTypeGetInputType(self, i)); + return inputTypes; + }) + .def_property_readonly( + "input_names", + [](MlirType self) { + std::vector inputNames; + intptr_t numInputs = hwModuleTypeGetNumInputs(self); + for (intptr_t i = 0; i < numInputs; ++i) { + auto name = hwModuleTypeGetInputName(self, i); + inputNames.emplace_back(name.data, name.length); + } + return inputNames; + }) + .def_property_readonly( + "output_types", + [](MlirType self) { + py::list outputTypes; + intptr_t numOutputs = hwModuleTypeGetNumOutputs(self); + for (intptr_t i = 0; i < numOutputs; ++i) + outputTypes.append(hwModuleTypeGetOutputType(self, i)); + return outputTypes; + }) + .def_property_readonly("output_names", [](MlirType self) { + std::vector outputNames; + intptr_t numOutputs = hwModuleTypeGetNumOutputs(self); + for (intptr_t i = 0; i < numOutputs; ++i) { + auto name = hwModuleTypeGetOutputName(self, i); + outputNames.emplace_back(name.data, name.length); + } + return outputNames; + }); + mlir_type_subclass(m, "ParamIntType", hwTypeIsAIntType) .def_classmethod( "get_from_param", @@ -89,6 +149,11 @@ void circt::python::populateDialectHWSubmodule(py::module &m) { return hwStructTypeGetField( self, mlirStringRefCreateFromCString(fieldName.c_str())); }) + .def("get_field_index", + [](MlirType self, const std::string &fieldName) { + return hwStructTypeGetFieldIndex( + self, mlirStringRefCreateFromCString(fieldName.c_str())); + }) .def("get_fields", [](MlirType self) { intptr_t num_fields = hwStructTypeGetNumFields(self); py::list fields; @@ -172,6 +237,24 @@ void circt::python::populateDialectHWSubmodule(py::module &m) { return cls(hwParamVerbatimAttrGet(text)); }); + mlir_attribute_subclass(m, "OutputFileAttr", hwAttrIsAOutputFileAttr) + .def_classmethod("get_from_filename", [](py::object cls, + MlirAttribute fileName, + bool excludeFromFileList, + bool includeReplicatedOp) { + return cls(hwOutputFileGetFromFileName(fileName, excludeFromFileList, + includeReplicatedOp)); + }); + + mlir_attribute_subclass(m, "InnerSymAttr", hwAttrIsAInnerSymAttr) + .def_classmethod("get", + [](py::object cls, MlirAttribute symName) { + return cls(hwInnerSymAttrGet(symName)); + }) + .def_property_readonly("symName", [](MlirAttribute self) { + return hwInnerSymAttrGetSymName(self); + }); + mlir_attribute_subclass(m, "InnerRefAttr", hwAttrIsAInnerRefAttr) .def_classmethod( "get", @@ -184,9 +267,4 @@ void circt::python::populateDialectHWSubmodule(py::module &m) { .def_property_readonly("name", [](MlirAttribute self) { return hwInnerRefAttrGetName(self); }); - - mlir_attribute_subclass(m, "GlobalRefAttr", hwAttrIsAGlobalRefAttr) - .def_classmethod("get", [](py::object cls, MlirAttribute symName) { - return cls(hwGlobalRefAttrGet(symName)); - }); } diff --git a/lib/Bindings/Python/MSFTModule.cpp b/lib/Bindings/Python/MSFTModule.cpp index 23b885ef5c99..9691d5b95807 100644 --- a/lib/Bindings/Python/MSFTModule.cpp +++ b/lib/Bindings/Python/MSFTModule.cpp @@ -9,8 +9,7 @@ #include "DialectModules.h" #include "circt-c/Dialect/MSFT.h" -#include "circt/Dialect/MSFT/MSFTAttributes.h" -#include "circt/Support/LLVM.h" +#include "circt/Dialect/MSFT/MSFTDialect.h" #include "mlir/Bindings/Python/PybindAdaptors.h" #include "mlir/CAPI/IR.h" @@ -22,8 +21,8 @@ #include namespace py = pybind11; -using namespace circt; -using namespace circt::msft; +// using namespace circt; +// using namespace circt::msft; using namespace mlir::python::adaptors; static py::handle getPhysLocationAttr(MlirAttribute attr) { @@ -147,6 +146,7 @@ class PyLocationVecIterator { MlirAttribute attr; intptr_t nextIndex = 0; }; + /// Populate the msft python module. void circt::python::populateDialectMSFTSubmodule(py::module &m) { mlirMSFTRegisterPasses(); @@ -278,23 +278,4 @@ void circt::python::populateDialectMSFTSubmodule(py::module &m) { .def(py::init(), py::arg("columns") = CirctMSFTDirection::NONE, py::arg("rows") = CirctMSFTDirection::NONE); - - mlir_attribute_subclass(m, "AppIDAttr", circtMSFTAttributeIsAnAppIDAttr) - .def_classmethod( - "get", - [](py::object cls, std::string name, uint64_t index, - MlirContext ctxt) { - return cls(circtMSFTAppIDAttrGet(ctxt, wrap(name), index)); - }, - "Create an AppID attribute", py::arg("cls"), py::arg("name"), - py::arg("index"), py::arg("context") = py::none()) - .def_property_readonly("name", - [](MlirAttribute self) { - StringRef name = - unwrap(circtMSFTAppIDAttrGetName(self)); - return std::string(name.data(), name.size()); - }) - .def_property_readonly("index", [](MlirAttribute self) { - return circtMSFTAppIDAttrGetIndex(self); - }); } diff --git a/lib/Bindings/Python/OMModule.cpp b/lib/Bindings/Python/OMModule.cpp new file mode 100644 index 000000000000..33b106c1afcc --- /dev/null +++ b/lib/Bindings/Python/OMModule.cpp @@ -0,0 +1,507 @@ +//===- OMModule.cpp - OM API pybind module --------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "DialectModules.h" +#include "circt-c/Dialect/OM.h" +#include "mlir-c/BuiltinAttributes.h" +#include "mlir-c/BuiltinTypes.h" +#include "mlir-c/IR.h" +#include "mlir/Bindings/Python/PybindAdaptors.h" +#include +#include +namespace py = pybind11; + +using namespace mlir; +using namespace mlir::python; +using namespace mlir::python::adaptors; + +namespace { + +struct List; +struct Object; +struct Tuple; +struct Map; +struct BasePath; +struct Path; + +/// None is used to by pybind when default initializing a PythonValue. The order +/// of types in the variant matters here, and we want pybind to try casting to +/// the Python classes defined in this file first, before MlirAttribute and the +/// upstream MLIR type casters. If the MlirAttribute is tried first, then we +/// can hit an assert inside the MLIR codebase. +struct None {}; +using PythonValue = + std::variant; + +/// Map an opaque OMEvaluatorValue into a python value. +PythonValue omEvaluatorValueToPythonValue(OMEvaluatorValue result); +OMEvaluatorValue pythonValueToOMEvaluatorValue(PythonValue result); + +/// Provides a List class by simply wrapping the OMObject CAPI. +struct List { + // Instantiate a List with a reference to the underlying OMEvaluatorValue. + List(OMEvaluatorValue value) : value(value) {} + + /// Return the number of elements. + intptr_t getNumElements() { return omEvaluatorListGetNumElements(value); } + + PythonValue getElement(intptr_t i); + OMEvaluatorValue getValue() const { return value; } + +private: + // The underlying CAPI value. + OMEvaluatorValue value; +}; + +struct Tuple { + // Instantiate a Tuple with a reference to the underlying OMEvaluatorValue. + Tuple(OMEvaluatorValue value) : value(value) {} + + /// Return the number of elements. + intptr_t getNumElements() { return omEvaluatorTupleGetNumElements(value); } + + PythonValue getElement(intptr_t i); + OMEvaluatorValue getValue() const { return value; } + +private: + // The underlying CAPI value. + OMEvaluatorValue value; +}; + +/// Provides a Map class by simply wrapping the OMObject CAPI. +struct Map { + // Instantiate a Map with a reference to the underlying OMEvaluatorValue. + Map(OMEvaluatorValue value) : value(value) {} + + /// Return the keys. + std::vector getKeys() { + auto attr = omEvaluatorMapGetKeys(value); + intptr_t numFieldNames = mlirArrayAttrGetNumElements(attr); + + std::vector pyFieldNames; + for (intptr_t i = 0; i < numFieldNames; ++i) + pyFieldNames.emplace_back(mlirArrayAttrGetElement(attr, i)); + + return pyFieldNames; + } + + /// Look up the value. A key is an integer, string or attribute. + PythonValue dunderGetItemAttr(MlirAttribute key); + PythonValue dunderGetItemNamed(const std::string &key); + PythonValue dunderGetItemIndexed(intptr_t key); + PythonValue + dunderGetItem(std::variant key); + + /// Return a context from an underlying value. + MlirContext getContext() const { return omEvaluatorValueGetContext(value); } + + OMEvaluatorValue getValue() const { return value; } + MlirType getType() { return omEvaluatorMapGetType(value); } + +private: + // The underlying CAPI value. + OMEvaluatorValue value; +}; + +/// Provides a BasePath class by simply wrapping the OMObject CAPI. +struct BasePath { + /// Instantiate a BasePath with a reference to the underlying + /// OMEvaluatorValue. + BasePath(OMEvaluatorValue value) : value(value) {} + + static BasePath getEmpty(MlirContext context) { + return BasePath(omEvaluatorBasePathGetEmpty(context)); + } + + /// Return a context from an underlying value. + MlirContext getContext() const { return omEvaluatorValueGetContext(value); } + + OMEvaluatorValue getValue() const { return value; } + +private: + // The underlying CAPI value. + OMEvaluatorValue value; +}; + +/// Provides a Path class by simply wrapping the OMObject CAPI. +struct Path { + /// Instantiate a Path with a reference to the underlying OMEvaluatorValue. + Path(OMEvaluatorValue value) : value(value) {} + + /// Return a context from an underlying value. + MlirContext getContext() const { return omEvaluatorValueGetContext(value); } + + OMEvaluatorValue getValue() const { return value; } + + std::string dunderStr() { + auto ref = mlirStringAttrGetValue(omEvaluatorPathGetAsString(getValue())); + return std::string(ref.data, ref.length); + } + +private: + // The underlying CAPI value. + OMEvaluatorValue value; +}; + +/// Provides an Object class by simply wrapping the OMObject CAPI. +struct Object { + // Instantiate an Object with a reference to the underlying OMObject. + Object(OMEvaluatorValue value) : value(value) {} + + /// Get the Type from an Object, which will be a ClassType. + MlirType getType() { return omEvaluatorObjectGetType(value); } + + // Get the field location info. + MlirLocation getFieldLoc(const std::string &name) { + // Wrap the requested field name in an attribute. + MlirContext context = mlirTypeGetContext(omEvaluatorObjectGetType(value)); + MlirStringRef cName = mlirStringRefCreateFromCString(name.c_str()); + MlirAttribute nameAttr = mlirStringAttrGet(context, cName); + + // Get the field's ObjectValue via the CAPI. + OMEvaluatorValue result = omEvaluatorObjectGetField(value, nameAttr); + + return omEvaluatorValueGetLoc(result); + } + + // Get a field from the Object, using pybind's support for variant to return a + // Python object that is either an Object or Attribute. + PythonValue getField(const std::string &name) { + // Wrap the requested field name in an attribute. + MlirContext context = mlirTypeGetContext(omEvaluatorObjectGetType(value)); + MlirStringRef cName = mlirStringRefCreateFromCString(name.c_str()); + MlirAttribute nameAttr = mlirStringAttrGet(context, cName); + + // Get the field's ObjectValue via the CAPI. + OMEvaluatorValue result = omEvaluatorObjectGetField(value, nameAttr); + + return omEvaluatorValueToPythonValue(result); + } + + // Get a list with the names of all the fields in the Object. + std::vector getFieldNames() { + MlirAttribute fieldNames = omEvaluatorObjectGetFieldNames(value); + intptr_t numFieldNames = mlirArrayAttrGetNumElements(fieldNames); + + std::vector pyFieldNames; + for (intptr_t i = 0; i < numFieldNames; ++i) { + MlirAttribute fieldName = mlirArrayAttrGetElement(fieldNames, i); + MlirStringRef fieldNameStr = mlirStringAttrGetValue(fieldName); + pyFieldNames.emplace_back(fieldNameStr.data, fieldNameStr.length); + } + + return pyFieldNames; + } + + // Get the hash of the object + unsigned getHash() { return omEvaluatorObjectGetHash(value); } + + // Check the equality of the underlying values. + bool eq(Object &other) { return omEvaluatorObjectIsEq(value, other.value); } + + OMEvaluatorValue getValue() const { return value; } + +private: + // The underlying CAPI OMObject. + OMEvaluatorValue value; +}; + +/// Provides an Evaluator class by simply wrapping the OMEvaluator CAPI. +struct Evaluator { + // Instantiate an Evaluator with a reference to the underlying OMEvaluator. + Evaluator(MlirModule mod) : evaluator(omEvaluatorNew(mod)) {} + + // Instantiate an Object. + Object instantiate(MlirAttribute className, + std::vector actualParams) { + std::vector values; + for (auto ¶m : actualParams) + values.push_back(pythonValueToOMEvaluatorValue(param)); + + // Instantiate the Object via the CAPI. + OMEvaluatorValue result = omEvaluatorInstantiate( + evaluator, className, values.size(), values.data()); + + // If the Object is null, something failed. Diagnostic handling is + // implemented in pure Python, so nothing to do here besides throwing an + // error to halt execution. + if (omEvaluatorObjectIsNull(result)) + throw py::value_error( + "unable to instantiate object, see previous error(s)"); + + // Return a new Object. + return Object(result); + } + + // Get the Module the Evaluator is built from. + MlirModule getModule() { return omEvaluatorGetModule(evaluator); } + +private: + // The underlying CAPI OMEvaluator. + OMEvaluator evaluator; +}; + +class PyListAttrIterator { +public: + PyListAttrIterator(MlirAttribute attr) : attr(std::move(attr)) {} + + PyListAttrIterator &dunderIter() { return *this; } + + MlirAttribute dunderNext() { + if (nextIndex >= omListAttrGetNumElements(attr)) + throw py::stop_iteration(); + return omListAttrGetElement(attr, nextIndex++); + } + + static void bind(py::module &m) { + py::class_(m, "ListAttributeIterator", + py::module_local()) + .def("__iter__", &PyListAttrIterator::dunderIter) + .def("__next__", &PyListAttrIterator::dunderNext); + } + +private: + MlirAttribute attr; + intptr_t nextIndex = 0; +}; + +PythonValue List::getElement(intptr_t i) { + return omEvaluatorValueToPythonValue(omEvaluatorListGetElement(value, i)); +} + +class PyMapAttrIterator { +public: + PyMapAttrIterator(MlirAttribute attr) : attr(std::move(attr)) {} + + PyMapAttrIterator &dunderIter() { return *this; } + + py::tuple dunderNext() { + if (nextIndex >= omMapAttrGetNumElements(attr)) + throw py::stop_iteration(); + + MlirIdentifier key = omMapAttrGetElementKey(attr, nextIndex); + MlirAttribute value = omMapAttrGetElementValue(attr, nextIndex); + nextIndex++; + + auto keyName = mlirIdentifierStr(key); + std::string keyStr(keyName.data, keyName.length); + return py::make_tuple(keyStr, value); + } + + static void bind(py::module &m) { + py::class_(m, "MapAttributeIterator", py::module_local()) + .def("__iter__", &PyMapAttrIterator::dunderIter) + .def("__next__", &PyMapAttrIterator::dunderNext); + } + +private: + MlirAttribute attr; + intptr_t nextIndex = 0; +}; + +PythonValue Tuple::getElement(intptr_t i) { + if (i < 0 || i >= omEvaluatorTupleGetNumElements(value)) + throw std::out_of_range("tuple index out of range"); + + return omEvaluatorValueToPythonValue(omEvaluatorTupleGetElement(value, i)); +} + +PythonValue Map::dunderGetItemNamed(const std::string &key) { + MlirType type = omMapTypeGetKeyType(omEvaluatorMapGetType(value)); + if (!omTypeIsAStringType(type)) + throw pybind11::key_error("key is not string"); + MlirAttribute attr = + mlirStringAttrTypedGet(type, mlirStringRefCreateFromCString(key.c_str())); + return dunderGetItemAttr(attr); +} + +PythonValue Map::dunderGetItemIndexed(intptr_t i) { + MlirType type = omMapTypeGetKeyType(omEvaluatorMapGetType(value)); + if (!mlirTypeIsAInteger(type)) + throw pybind11::key_error("key is not integer"); + MlirAttribute attr = mlirIntegerAttrGet(type, i); + return dunderGetItemAttr(attr); +} + +PythonValue Map::dunderGetItemAttr(MlirAttribute key) { + OMEvaluatorValue result = omEvaluatorMapGetElement(value, key); + + if (omEvaluatorValueIsNull(result)) + throw pybind11::key_error("key not found"); + + return omEvaluatorValueToPythonValue(result); +} + +PythonValue +Map::dunderGetItem(std::variant key) { + if (auto *i = std::get_if(&key)) + return dunderGetItemIndexed(*i); + else if (auto *str = std::get_if(&key)) + return dunderGetItemNamed(*str); + return dunderGetItemAttr(std::get(key)); +} + +PythonValue omEvaluatorValueToPythonValue(OMEvaluatorValue result) { + // If the result is null, something failed. Diagnostic handling is + // implemented in pure Python, so nothing to do here besides throwing an + // error to halt execution. + if (omEvaluatorValueIsNull(result)) + throw py::value_error("unable to get field, see previous error(s)"); + + // If the field was an Object, return a new Object. + if (omEvaluatorValueIsAObject(result)) + return Object(result); + + // If the field was a list, return a new List. + if (omEvaluatorValueIsAList(result)) + return List(result); + + // If the field was a tuple, return a new Tuple. + if (omEvaluatorValueIsATuple(result)) + return Tuple(result); + + // If the field was a map, return a new Map. + if (omEvaluatorValueIsAMap(result)) + return Map(result); + + // If the field was a base path, return a new BasePath. + if (omEvaluatorValueIsABasePath(result)) + return BasePath(result); + + // If the field was a path, return a new Path. + if (omEvaluatorValueIsAPath(result)) + return Path(result); + + // If the field was a primitive, return the Attribute. + assert(omEvaluatorValueIsAPrimitive(result)); + return omEvaluatorValueGetPrimitive(result); +} + +OMEvaluatorValue pythonValueToOMEvaluatorValue(PythonValue result) { + if (auto *attr = std::get_if(&result)) + return omEvaluatorValueFromPrimitive(*attr); + + if (auto *list = std::get_if(&result)) + return list->getValue(); + + if (auto *tuple = std::get_if(&result)) + return tuple->getValue(); + + if (auto *map = std::get_if(&result)) + return map->getValue(); + + if (auto *basePath = std::get_if(&result)) + return basePath->getValue(); + + if (auto *path = std::get_if(&result)) + return path->getValue(); + + return std::get(result).getValue(); +} + +} // namespace + +/// Populate the OM Python module. +void circt::python::populateDialectOMSubmodule(py::module &m) { + m.doc() = "OM dialect Python native extension"; + + // Add the Evaluator class definition. + py::class_(m, "Evaluator") + .def(py::init(), py::arg("module")) + .def("instantiate", &Evaluator::instantiate, "Instantiate an Object", + py::arg("class_name"), py::arg("actual_params")) + .def_property_readonly("module", &Evaluator::getModule, + "The Module the Evaluator is built from"); + + // Add the List class definition. + py::class_(m, "List") + .def(py::init(), py::arg("list")) + .def("__getitem__", &List::getElement) + .def("__len__", &List::getNumElements); + + py::class_(m, "Tuple") + .def(py::init(), py::arg("tuple")) + .def("__getitem__", &Tuple::getElement) + .def("__len__", &Tuple::getNumElements); + + // Add the Map class definition. + py::class_(m, "Map") + .def(py::init(), py::arg("map")) + .def("__getitem__", &Map::dunderGetItem) + .def("keys", &Map::getKeys) + .def_property_readonly("type", &Map::getType, "The Type of the Map"); + + // Add the BasePath class definition. + py::class_(m, "BasePath") + .def(py::init(), py::arg("basepath")) + .def_static("get_empty", &BasePath::getEmpty, + py::arg("context") = py::none()); + + // Add the Path class definition. + py::class_(m, "Path") + .def(py::init(), py::arg("path")) + .def("__str__", &Path::dunderStr); + + // Add the Object class definition. + py::class_(m, "Object") + .def(py::init(), py::arg("object")) + .def("__getattr__", &Object::getField, "Get a field from an Object", + py::arg("name")) + .def("get_field_loc", &Object::getFieldLoc, + "Get the location of a field from an Object", py::arg("name")) + .def_property_readonly("field_names", &Object::getFieldNames, + "Get field names from an Object") + .def_property_readonly("type", &Object::getType, "The Type of the Object") + .def("__hash__", &Object::getHash, "Get object hash") + .def("__eq__", &Object::eq, "Check if two objects are same"); + + // Add the ReferenceAttr definition + mlir_attribute_subclass(m, "ReferenceAttr", omAttrIsAReferenceAttr) + .def_property_readonly("inner_ref", [](MlirAttribute self) { + return omReferenceAttrGetInnerRef(self); + }); + + // Add the IntegerAttr definition + mlir_attribute_subclass(m, "OMIntegerAttr", omAttrIsAIntegerAttr) + .def_classmethod("get", + [](py::object cls, MlirAttribute intVal) { + return cls(omIntegerAttrGet(intVal)); + }) + .def_property_readonly("integer", [](MlirAttribute self) { + return omIntegerAttrGetInt(self); + }); + + // Add the OMListAttr definition + mlir_attribute_subclass(m, "ListAttr", omAttrIsAListAttr) + .def("__getitem__", &omListAttrGetElement) + .def("__len__", &omListAttrGetNumElements) + .def("__iter__", + [](MlirAttribute arr) { return PyListAttrIterator(arr); }); + PyListAttrIterator::bind(m); + + // Add the MapAttr definition + mlir_attribute_subclass(m, "MapAttr", omAttrIsAMapAttr) + .def("__iter__", [](MlirAttribute arr) { return PyMapAttrIterator(arr); }) + .def("__len__", &omMapAttrGetNumElements); + PyMapAttrIterator::bind(m); + + // Add the ClassType class definition. + mlir_type_subclass(m, "ClassType", omTypeIsAClassType, omClassTypeGetTypeID) + .def_property_readonly("name", [](MlirType type) { + MlirStringRef name = mlirIdentifierStr(omClassTypeGetName(type)); + return std::string(name.data, name.length); + }); + + // Add the BasePathType class definition. + mlir_type_subclass(m, "BasePathType", omTypeIsAFrozenBasePathType, + omFrozenBasePathTypeGetTypeID); + + // Add the PathType class definition. + mlir_type_subclass(m, "PathType", omTypeIsAFrozenPathType, + omFrozenPathTypeGetTypeID); +} diff --git a/lib/Bindings/Python/SeqModule.cpp b/lib/Bindings/Python/SeqModule.cpp new file mode 100644 index 000000000000..d9ccf730e5f2 --- /dev/null +++ b/lib/Bindings/Python/SeqModule.cpp @@ -0,0 +1,36 @@ +//===- SeqModule.cpp - Seq API pybind module ------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "DialectModules.h" + +#include "circt-c/Dialect/Seq.h" +#include "mlir/Bindings/Python/PybindAdaptors.h" + +#include "PybindUtils.h" +#include "mlir-c/Support.h" +#include +#include +#include + +namespace py = pybind11; + +using namespace circt; +using namespace mlir::python::adaptors; + +/// Populate the seq python module. +void circt::python::populateDialectSeqSubmodule(py::module &m) { + m.doc() = "Seq dialect Python native extension"; + + mlir_type_subclass(m, "ClockType", seqTypeIsAClock) + .def_classmethod( + "get", + [](py::object cls, MlirContext ctx) { + return cls(seqClockTypeGet(ctx)); + }, + py::arg("cls"), py::arg("context") = py::none()); +} diff --git a/lib/Bindings/Python/dialects/CombOps.td b/lib/Bindings/Python/dialects/CombOps.td index 58ea1f0e462b..46db72fed9ff 100644 --- a/lib/Bindings/Python/dialects/CombOps.td +++ b/lib/Bindings/Python/dialects/CombOps.td @@ -9,7 +9,6 @@ #ifndef BINDINGS_PYTHON_COMB_OPS #define BINDINGS_PYTHON_COMB_OPS -include "mlir/Bindings/Python/Attributes.td" include "circt/Dialect/Comb/Comb.td" #endif diff --git a/lib/Bindings/Python/dialects/ESIOps.td b/lib/Bindings/Python/dialects/ESIOps.td index d2c658031e44..a4a538f81703 100644 --- a/lib/Bindings/Python/dialects/ESIOps.td +++ b/lib/Bindings/Python/dialects/ESIOps.td @@ -9,7 +9,6 @@ #ifndef BINDINGS_PYTHON_ESI_OPS #define BINDINGS_PYTHON_ESI_OPS -include "mlir/Bindings/Python/Attributes.td" include "circt/Dialect/ESI/ESI.td" #endif diff --git a/lib/Bindings/Python/dialects/FSMOps.td b/lib/Bindings/Python/dialects/FSMOps.td index aef46b192bb7..b869cfebe71b 100644 --- a/lib/Bindings/Python/dialects/FSMOps.td +++ b/lib/Bindings/Python/dialects/FSMOps.td @@ -9,7 +9,6 @@ #ifndef BINDINGS_PYTHON_FSM_OPS #define BINDINGS_PYTHON_FSM_OPS -include "mlir/Bindings/Python/Attributes.td" include "circt/Dialect/FSM/FSM.td" #endif diff --git a/lib/Bindings/Python/dialects/HWArithOps.td b/lib/Bindings/Python/dialects/HWArithOps.td index e6004416b775..6dc3c1890fdb 100644 --- a/lib/Bindings/Python/dialects/HWArithOps.td +++ b/lib/Bindings/Python/dialects/HWArithOps.td @@ -9,7 +9,6 @@ #ifndef BINDINGS_PYTHON_HWARITH_OPS #define BINDINGS_PYTHON_HWARITH_OPS -include "mlir/Bindings/Python/Attributes.td" include "circt/Dialect/HWArith/HWArith.td" #endif diff --git a/lib/Bindings/Python/dialects/HWOps.td b/lib/Bindings/Python/dialects/HWOps.td index e8c8e27efec7..4ca2afafd88a 100644 --- a/lib/Bindings/Python/dialects/HWOps.td +++ b/lib/Bindings/Python/dialects/HWOps.td @@ -9,7 +9,6 @@ #ifndef BINDINGS_PYTHON_HW_OPS #define BINDINGS_PYTHON_HW_OPS -include "mlir/Bindings/Python/Attributes.td" include "circt/Dialect/HW/HW.td" #endif diff --git a/lib/Bindings/Python/dialects/LTLOps.td b/lib/Bindings/Python/dialects/LTLOps.td new file mode 100644 index 000000000000..f0309c95df0b --- /dev/null +++ b/lib/Bindings/Python/dialects/LTLOps.td @@ -0,0 +1,14 @@ +//===-- HWOps.td - Entry point for HWOps bindings ----------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef BINDINGS_PYTHON_LTL_OPS +#define BINDINGS_PYTHON_LTL_OPS + +include "circt/Dialect/LTL/LTLOps.td" + +#endif diff --git a/lib/Bindings/Python/dialects/MSFTOps.td b/lib/Bindings/Python/dialects/MSFTOps.td index a1cc6b8cc13c..087679fea6c0 100644 --- a/lib/Bindings/Python/dialects/MSFTOps.td +++ b/lib/Bindings/Python/dialects/MSFTOps.td @@ -9,7 +9,6 @@ #ifndef BINDINGS_PYTHON_MSFT_OPS #define BINDINGS_PYTHON_MSFT_OPS -include "mlir/Bindings/Python/Attributes.td" include "circt/Dialect/MSFT/MSFT.td" #endif diff --git a/lib/Bindings/Python/dialects/OMOps.td b/lib/Bindings/Python/dialects/OMOps.td new file mode 100644 index 000000000000..9085004fd54b --- /dev/null +++ b/lib/Bindings/Python/dialects/OMOps.td @@ -0,0 +1,14 @@ +//===-- OMOps.td - Entry point for OM bindings ---------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef BINDINGS_PYTHON_OM_OPS +#define BINDINGS_PYTHON_OM_OPS + +include "circt/Dialect/OM/OM.td" + +#endif diff --git a/lib/Bindings/Python/dialects/SVOps.td b/lib/Bindings/Python/dialects/SVOps.td index 76a5cb08bdb2..47595c477fda 100644 --- a/lib/Bindings/Python/dialects/SVOps.td +++ b/lib/Bindings/Python/dialects/SVOps.td @@ -9,7 +9,6 @@ #ifndef BINDINGS_PYTHON_SV_OPS #define BINDINGS_PYTHON_SV_OPS -include "mlir/Bindings/Python/Attributes.td" include "circt/Dialect/SV/SV.td" #endif diff --git a/lib/Bindings/Python/dialects/SeqOps.td b/lib/Bindings/Python/dialects/SeqOps.td index a33d19d74c85..afcffe82e0cb 100644 --- a/lib/Bindings/Python/dialects/SeqOps.td +++ b/lib/Bindings/Python/dialects/SeqOps.td @@ -9,7 +9,6 @@ #ifndef BINDINGS_PYTHON_SEQ_OPS #define BINDINGS_PYTHON_SEQ_OPS -include "mlir/Bindings/Python/Attributes.td" include "circt/Dialect/Seq/Seq.td" #endif diff --git a/lib/Bindings/Python/dialects/VerifOps.td b/lib/Bindings/Python/dialects/VerifOps.td new file mode 100644 index 000000000000..30fae0f4c3b6 --- /dev/null +++ b/lib/Bindings/Python/dialects/VerifOps.td @@ -0,0 +1,14 @@ +//===-- HWOps.td - Entry point for HWOps bindings ----------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef BINDINGS_PYTHON_VERIF_OPS +#define BINDINGS_PYTHON_VERIF_OPS + +include "circt/Dialect/Verif/Verif.td" + +#endif diff --git a/lib/Bindings/Python/dialects/_comb_ops_ext.py b/lib/Bindings/Python/dialects/_comb_ops_ext.py deleted file mode 100644 index 3020deaece18..000000000000 --- a/lib/Bindings/Python/dialects/_comb_ops_ext.py +++ /dev/null @@ -1,178 +0,0 @@ -from . import comb -from ..support import NamedValueOpView, get_value -from ..ir import IntegerAttr, IntegerType - - -# Builder base classes for non-variadic unary and binary ops. -class UnaryOpBuilder(NamedValueOpView): - - def operand_names(self): - return ["input"] - - def result_names(self): - return ["result"] - - -def UnaryOp(base): - - class _Class(base): - - @classmethod - def create(cls, input=None, result_type=None): - mapping = {"input": input} if input else {} - return UnaryOpBuilder(cls, result_type, mapping) - - return _Class - - -class ExtractOpBuilder(UnaryOpBuilder): - - def __init__(self, low_bit, data_type, input_port_mapping={}, **kwargs): - low_bit = IntegerAttr.get(IntegerType.get_signless(32), low_bit) - super().__init__(comb.ExtractOp, data_type, input_port_mapping, [], - [low_bit], **kwargs) - - -class BinaryOpBuilder(NamedValueOpView): - - def operand_names(self): - return ["lhs", "rhs"] - - def result_names(self): - return ["result"] - - -def BinaryOp(base): - - class _Class(base): - - @classmethod - def create(cls, lhs=None, rhs=None, result_type=None): - mapping = {} - if lhs: - mapping["lhs"] = lhs - if rhs: - mapping["rhs"] = rhs - return BinaryOpBuilder(cls, result_type, mapping) - - return _Class - - -# Base classes for the variadic ops. -def VariadicOp(base): - - class _Class(base): - - @classmethod - def create(cls, *args): - return cls([get_value(a) for a in args]) - - return _Class - - -# Base class for miscellaneous ops that can't be abstracted but should provide a -# create method for uniformity. -def CreatableOp(base): - - class _Class(base): - - @classmethod - def create(cls, *args, **kwargs): - return cls(*args, **kwargs) - - return _Class - - -# Sugar classes for the various non-variadic unary ops. -class ExtractOp: - - @staticmethod - def create(low_bit, result_type, input=None): - mapping = {"input": input} if input else {} - return ExtractOpBuilder(low_bit, - result_type, - mapping, - needs_result_type=True) - - -@UnaryOp -class ParityOp: - pass - - -# Sugar classes for the various non-variadic binary ops. -@BinaryOp -class DivSOp: - pass - - -@BinaryOp -class DivUOp: - pass - - -@BinaryOp -class ModSOp: - pass - - -@BinaryOp -class ModUOp: - pass - - -@BinaryOp -class ShlOp: - pass - - -@BinaryOp -class ShrSOp: - pass - - -@BinaryOp -class ShrUOp: - pass - - -@BinaryOp -class SubOp: - pass - - -# Sugar classes for the variadic ops. -@VariadicOp -class AddOp: - pass - - -@VariadicOp -class MulOp: - pass - - -@VariadicOp -class AndOp: - pass - - -@VariadicOp -class OrOp: - pass - - -@VariadicOp -class XorOp: - pass - - -@VariadicOp -class ConcatOp: - pass - - -# Sugar classes for miscellaneous ops. -@CreatableOp -class MuxOp: - pass diff --git a/lib/Bindings/Python/dialects/_esi_ops_ext.py b/lib/Bindings/Python/dialects/_esi_ops_ext.py deleted file mode 100644 index 4ab88eb43687..000000000000 --- a/lib/Bindings/Python/dialects/_esi_ops_ext.py +++ /dev/null @@ -1,44 +0,0 @@ -from __future__ import annotations - -from typing import Dict, List, Optional, Sequence, Type - -from . import esi -from .. import support - -from .. import ir - - -class RequestToServerConnectionOp: - - @property - def clientNamePath(self) -> List[str]: - return [ - ir.StringAttr(x).value - for x in ir.ArrayAttr(self.attributes["clientNamePath"]) - ] - - -class RequestToClientConnectionOp: - - @property - def clientNamePath(self) -> List[str]: - return [ - ir.StringAttr(x).value - for x in ir.ArrayAttr(self.attributes["clientNamePath"]) - ] - - -class RandomAccessMemoryDeclOp: - - @property - def innerType(self): - return ir.TypeAttr(self.attributes["innerType"]) - - -class ESIPureModuleOp: - - def add_entry_block(self): - if len(self.body.blocks) > 0: - raise IndexError('The module already has an entry block') - self.body.blocks.append() - return self.body.blocks[0] diff --git a/lib/Bindings/Python/dialects/_fsm_ops_ext.py b/lib/Bindings/Python/dialects/_fsm_ops_ext.py deleted file mode 100644 index 12dad2d041b0..000000000000 --- a/lib/Bindings/Python/dialects/_fsm_ops_ext.py +++ /dev/null @@ -1,167 +0,0 @@ -# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -# See https://llvm.org/LICENSE.txt for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -from . import fsm as fsm -from .. import support -from ..ir import * - - -def _get_or_add_single_block(region, args=[]): - if len(region.blocks) == 0: - region.blocks.append(*args) - return region.blocks[0] - - -class MachineOp: - - def __init__(self, - name, - initial_state, - input_ports, - output_ports, - *, - attributes={}, - loc=None, - ip=None): - attributes["sym_name"] = StringAttr.get(name) - attributes["initialState"] = StringAttr.get(initial_state) - - input_types = [] - output_types = [] - for (i, (_, port_type)) in enumerate(input_ports): - input_types.append(port_type) - - for (i, (_, port_type)) in enumerate(output_ports): - output_types.append(port_type) - - attributes["function_type"] = TypeAttr.get( - FunctionType.get(inputs=input_types, results=output_types)) - - OpView.__init__( - self, - self.build_generic(attributes=attributes, - results=[], - operands=[], - successors=None, - regions=1, - loc=loc, - ip=ip)) - - _get_or_add_single_block(self.body, self.type.inputs) - - @property - def type(self): - return FunctionType(TypeAttr(self.attributes["function_type"]).value) - - def instantiate(self, name: str, loc=None, ip=None, **kwargs): - """ FSM Instantiation function""" - in_names = support.attribute_to_var(self.attributes['in_names']) - inputs = [kwargs[port].value for port in in_names] - - # Clock and resets are not part of the input ports of the FSM, but - # it is at the point of `fsm.hw_instance` instantiation that they - # must be connected. - # Attach backedges to these, and associate these backedges to the operation. - # They can then be accessed at the point of instantiation and assigned. - clock = support.BackedgeBuilder().create( - IntegerType.get_signed(1), - StringAttr(self.attributes['clock_name']).value, self) - reset = support.BackedgeBuilder().create( - IntegerType.get_signed(1), - StringAttr(self.attributes['reset_name']).value, self) - - op = fsm.HWInstanceOp(outputs=self.type.results, - inputs=inputs, - sym_name=StringAttr.get(name), - machine=FlatSymbolRefAttr.get(self.sym_name.value), - clock=clock.result, - reset=reset.result if reset else None, - loc=loc, - ip=ip) - op.backedges = {} - - def set_OpOperand(name, backedge): - index = None - for i, operand in enumerate(op.operands): - if operand == backedge.result: - index = i - break - assert index is not None - op_operand = support.OpOperand(op, index, op.operands[index], op) - setattr(op, f'_{name}_backedge', op_operand) - op.backedges[i] = backedge - - set_OpOperand('clock', clock) - if reset: - set_OpOperand('reset', reset) - - return op - - -class TransitionOp: - - def __init__(self, next_state, *, loc=None, ip=None): - attributes = { - "nextState": FlatSymbolRefAttr.get(next_state), - } - super().__init__( - self.build_generic(attributes=attributes, - results=[], - operands=[], - successors=None, - regions=2, - loc=loc, - ip=ip)) - - @staticmethod - def create(to_state): - op = fsm.TransitionOp(to_state) - return op - - def set_guard(self, guard_fn): - """Executes a function to generate a guard for the transition. - The function is executed within the guard region of this operation.""" - guard_block = _get_or_add_single_block(self.guard) - with InsertionPoint(guard_block): - guard = guard_fn() - guard_type = support.type_to_pytype(guard.type) - if guard_type.width != 1: - raise ValueError('The guard must be a single bit') - fsm.ReturnOp(operand=guard.value) - - -class StateOp: - - def __init__(self, name, *, loc=None, ip=None): - attributes = {} - attributes["sym_name"] = StringAttr.get(name) - - OpView.__init__( - self, - self.build_generic(attributes=attributes, - results=[], - operands=[], - successors=None, - regions=2, - loc=loc, - ip=ip)) - - @staticmethod - def create(name): - return fsm.StateOp(name) - - @property - def output(self): - return _get_or_add_single_block(super().output) - - @property - def transitions(self): - return _get_or_add_single_block(super().transitions) - - -class OutputOp: - - @staticmethod - def create(*operands): - return fsm.OutputOp(operands) diff --git a/lib/Bindings/Python/dialects/_hw_ops_ext.py b/lib/Bindings/Python/dialects/_hw_ops_ext.py deleted file mode 100644 index 1a681255130f..000000000000 --- a/lib/Bindings/Python/dialects/_hw_ops_ext.py +++ /dev/null @@ -1,531 +0,0 @@ -from __future__ import annotations - -from typing import Dict, Type - -from . import hw -from .. import support -from ..ir import * - - -def create_parameters(parameters: dict[str, _ir.Attribute], module: ModuleLike): - # Compute mapping from parameter name to index, and initialize array. - mod_param_decls = module.parameters - mod_param_decls_idxs = { - decl.name: idx for (idx, decl) in enumerate(mod_param_decls) - } - inst_param_array = [None] * len(module.parameters) - - # Fill in all the parameters specified. - if isinstance(parameters, DictAttr): - parameters = {i.name: i.attr for i in parameters} - for (pname, pval) in parameters.items(): - if pname not in mod_param_decls_idxs: - raise ValueError( - f"Could not find parameter '{pname}' in module parameter decls") - idx = mod_param_decls_idxs[pname] - param_decl = mod_param_decls[idx] - inst_param_array[idx] = hw.ParamDeclAttr.get(pname, param_decl.param_type, - pval) - - # Fill in the defaults from the module param decl. - for (idx, pval) in enumerate(inst_param_array): - if pval is not None: - continue - inst_param_array[idx] = mod_param_decls[idx] - - return inst_param_array - - -class InstanceBuilder(support.NamedValueOpView): - """Helper class to incrementally construct an instance of a module.""" - - def __init__(self, - module, - name, - input_port_mapping, - *, - results=None, - parameters={}, - sym_name=None, - loc=None, - ip=None): - self.module = module - instance_name = StringAttr.get(name) - module_name = FlatSymbolRefAttr.get(StringAttr(module.name).value) - inst_param_array = create_parameters(parameters, module) - if sym_name: - sym_name = StringAttr.get(sym_name) - pre_args = [instance_name, module_name] - post_args = [ - ArrayAttr.get([StringAttr.get(x) for x in self.operand_names()]), - ArrayAttr.get([StringAttr.get(x) for x in self.result_names()]), - ArrayAttr.get(inst_param_array) - ] - if results is None: - results = module.type.results - - if not isinstance(module, hw.HWModuleExternOp): - input_name_type_lookup = { - name: support.type_to_pytype(ty) - for name, ty in zip(self.operand_names(), module.type.inputs) - } - for input_name, input_value in input_port_mapping.items(): - if input_name not in input_name_type_lookup: - continue # This error gets caught and raised later. - mod_input_type = input_name_type_lookup[input_name] - if support.type_to_pytype(input_value.type) != mod_input_type: - raise TypeError(f"Input '{input_name}' has type '{input_value.type}' " - f"but expected '{mod_input_type}'") - - super().__init__(hw.InstanceOp, - results, - input_port_mapping, - pre_args, - post_args, - needs_result_type=True, - inner_sym=sym_name, - loc=loc, - ip=ip) - - def create_default_value(self, index, data_type, arg_name): - type = self.module.type.inputs[index] - return support.BackedgeBuilder.create(type, - arg_name, - self, - instance_of=self.module) - - def operand_names(self): - arg_names = ArrayAttr(self.module.attributes["argNames"]) - arg_name_attrs = map(StringAttr, arg_names) - return list(map(lambda s: s.value, arg_name_attrs)) - - def result_names(self): - arg_names = ArrayAttr(self.module.attributes["resultNames"]) - arg_name_attrs = map(StringAttr, arg_names) - return list(map(lambda s: s.value, arg_name_attrs)) - - -class ModuleLike: - """Custom Python base class for module-like operations.""" - - def __init__( - self, - name, - input_ports=[], - output_ports=[], - *, - parameters=[], - attributes={}, - body_builder=None, - loc=None, - ip=None, - ): - """ - Create a module-like with the provided `name`, `input_ports`, and - `output_ports`. - - `name` is a string representing the module name. - - `input_ports` is a list of pairs of string names and mlir.ir types. - - `output_ports` is a list of pairs of string names and mlir.ir types. - - `body_builder` is an optional callback, when provided a new entry block - is created and the callback is invoked with the new op as argument within - an InsertionPoint context already set for the block. The callback is - expected to insert a terminator in the block. - """ - # Copy the mutable default arguments. 'Cause python. - input_ports = list(input_ports) - output_ports = list(output_ports) - parameters = list(parameters) - attributes = dict(attributes) - - operands = [] - results = [] - attributes["sym_name"] = StringAttr.get(str(name)) - - input_types = [] - input_names = [] - input_locs = [] - unknownLoc = Location.unknown().attr - for (i, (port_name, port_type)) in enumerate(input_ports): - input_types.append(port_type) - input_names.append(StringAttr.get(str(port_name))) - input_locs.append(unknownLoc) - attributes["argNames"] = ArrayAttr.get(input_names) - attributes["argLocs"] = ArrayAttr.get(input_locs) - - output_types = [] - output_names = [] - output_locs = [] - for (i, (port_name, port_type)) in enumerate(output_ports): - output_types.append(port_type) - output_names.append(StringAttr.get(str(port_name))) - output_locs.append(unknownLoc) - attributes["resultNames"] = ArrayAttr.get(output_names) - attributes["resultLocs"] = ArrayAttr.get(output_locs) - - if len(parameters) > 0 or "parameters" not in attributes: - attributes["parameters"] = ArrayAttr.get(parameters) - - attributes["function_type"] = TypeAttr.get( - FunctionType.get(inputs=input_types, results=output_types)) - - super().__init__( - self.build_generic(attributes=attributes, - results=results, - operands=operands, - loc=loc, - ip=ip)) - - if body_builder: - entry_block = self.add_entry_block() - - with InsertionPoint(entry_block): - with support.BackedgeBuilder(): - outputs = body_builder(self) - _create_output_op(name, output_ports, entry_block, outputs) - - @property - def type(self): - return FunctionType(TypeAttr(self.attributes["function_type"]).value) - - @property - def name(self): - return self.attributes["sym_name"] - - @property - def is_external(self): - return len(self.regions[0].blocks) == 0 - - @property - def parameters(self) -> list[ParamDeclAttr]: - return [ - hw.ParamDeclAttr(a) for a in ArrayAttr(self.attributes["parameters"]) - ] - - def instantiate(self, - name: str, - parameters: Dict[str, object] = {}, - results=None, - loc=None, - ip=None, - **kwargs): - return InstanceBuilder(self, - name, - kwargs, - parameters=parameters, - results=results, - loc=loc, - ip=ip) - - -def _create_output_op(cls_name, output_ports, entry_block, bb_ret): - """Create the hw.OutputOp from the body_builder return.""" - - # Determine if the body already has an output op. - block_len = len(entry_block.operations) - if block_len > 0: - last_op = entry_block.operations[block_len - 1] - if isinstance(last_op, hw.OutputOp): - # If it does, the return from body_builder must be None. - if bb_ret is not None and bb_ret != last_op: - raise support.ConnectionError( - f"In {cls_name}, cannot return value from body_builder and " - "create hw.OutputOp") - return - - # If builder didn't create an output op and didn't return anything, this op - # mustn't have any outputs. - if bb_ret is None: - if len(output_ports) == 0: - hw.OutputOp([]) - return - raise support.ConnectionError( - f"In {cls_name}, must return module output values") - - # Now create the output op depending on the object type returned - outputs: list[Value] = list() - - # Only acceptable return is a dict of port, value mappings. - if not isinstance(bb_ret, dict): - raise support.ConnectionError( - f"In {cls_name}, can only return a dict of port, value mappings " - "from body_builder.") - - # A dict of `OutputPortName` -> ValueLike must be converted to a list in port - # order. - unconnected_ports = [] - for (name, port_type) in output_ports: - if name not in bb_ret: - unconnected_ports.append(name) - outputs.append(None) - else: - val = support.get_value(bb_ret[name]) - if val is None: - field_type = type(bb_ret[name]) - raise TypeError( - f"In {cls_name}, body_builder return doesn't support type " - f"'{field_type}'") - if val.type != port_type: - if isinstance(port_type, hw.TypeAliasType) and \ - port_type.inner_type == val.type: - val = hw.BitcastOp.create(port_type, val).result - else: - raise TypeError( - f"In {cls_name}, output port '{name}' type ({val.type}) doesn't " - f"match declared type ({port_type})") - outputs.append(val) - bb_ret.pop(name) - if len(unconnected_ports) > 0: - raise support.UnconnectedSignalError(cls_name, unconnected_ports) - if len(bb_ret) > 0: - raise support.ConnectionError( - f"Could not map the following to output ports in {cls_name}: " + - ",".join(bb_ret.keys())) - - hw.OutputOp(outputs) - - -class HWModuleOp(ModuleLike): - """Specialization for the HW module op class.""" - - def __init__( - self, - name, - input_ports=[], - output_ports=[], - *, - parameters=[], - attributes={}, - body_builder=None, - loc=None, - ip=None, - ): - if "comment" not in attributes: - attributes["comment"] = StringAttr.get("") - super().__init__(name, - input_ports, - output_ports, - parameters=parameters, - attributes=attributes, - body_builder=body_builder, - loc=loc, - ip=ip) - - @property - def body(self): - return self.regions[0] - - @property - def entry_block(self): - return self.regions[0].blocks[0] - - @property - def input_indices(self): - indices: dict[int, str] = {} - op_names = ArrayAttr(self.attributes["argNames"]) - for idx, name in enumerate(op_names): - str_name = StringAttr(name).value - indices[str_name] = idx - return indices - - # Support attribute access to block arguments by name - def __getattr__(self, name): - if name in self.input_indices: - index = self.input_indices[name] - return self.entry_block.arguments[index] - raise AttributeError(f"unknown input port name {name}") - - def inputs(self) -> dict[str:Value]: - ret = {} - for (name, idx) in self.input_indices.items(): - ret[name] = self.entry_block.arguments[idx] - return ret - - def outputs(self) -> dict[str:Type]: - result_names = [ - StringAttr(name).value - for name in ArrayAttr(self.attributes["resultNames"]) - ] - result_types = self.type.results - return dict(zip(result_names, result_types)) - - def add_entry_block(self): - if not self.is_external: - raise IndexError('The module already has an entry block') - self.body.blocks.append(*self.type.inputs) - return self.body.blocks[0] - - -class HWModuleExternOp(ModuleLike): - """Specialization for the HW module op class.""" - - def __init__( - self, - name, - input_ports=[], - output_ports=[], - *, - parameters=[], - attributes={}, - body_builder=None, - loc=None, - ip=None, - ): - if "comment" not in attributes: - attributes["comment"] = StringAttr.get("") - super().__init__(name, - input_ports, - output_ports, - parameters=parameters, - attributes=attributes, - body_builder=body_builder, - loc=loc, - ip=ip) - - -class ConstantOp: - - @staticmethod - def create(data_type, value): - return hw.ConstantOp(IntegerAttr.get(data_type, value)) - - -class BitcastOp: - - @staticmethod - def create(data_type, value): - value = support.get_value(value) - return hw.BitcastOp(data_type, value) - - -class ArrayGetOp: - - @staticmethod - def create(array_value, idx): - array_value = support.get_value(array_value) - array_type = support.get_self_or_inner(array_value.type) - if isinstance(idx, int): - idx_width = (array_type.size - 1).bit_length() - idx_val = ConstantOp.create(IntegerType.get_signless(idx_width), - idx).result - else: - idx_val = support.get_value(idx) - return hw.ArrayGetOp(array_value, idx_val) - - -class ArraySliceOp: - - @staticmethod - def create(array_value, low_index, ret_type): - array_value = support.get_value(array_value) - array_type = support.get_self_or_inner(array_value.type) - if isinstance(low_index, int): - idx_width = (array_type.size - 1).bit_length() - idx_width = max(1, idx_width) # hw.constant cannot produce i0. - idx_val = ConstantOp.create(IntegerType.get_signless(idx_width), - low_index).result - else: - idx_val = support.get_value(low_index) - return hw.ArraySliceOp(ret_type, array_value, idx_val) - - -class ArrayCreateOp: - - @staticmethod - def create(elements): - if not elements: - raise ValueError("Cannot 'create' an array of length zero") - vals = [] - type = None - for i, arg in enumerate(elements): - arg_val = support.get_value(arg) - vals.append(arg_val) - if type is None: - type = arg_val.type - elif type != arg_val.type: - raise TypeError( - f"Argument {i} has a different element type ({arg_val.type}) than the element type of the array ({type})" - ) - return hw.ArrayCreateOp(hw.ArrayType.get(type, len(vals)), vals) - - -class ArrayConcatOp: - - @staticmethod - def create(*sub_arrays): - vals = [] - types = [] - element_type = None - for i, array in enumerate(sub_arrays): - array_value = support.get_value(array) - array_type = support.type_to_pytype(array_value.type) - if array_value is None or not isinstance(array_type, hw.ArrayType): - raise TypeError(f"Cannot concatenate {array_value}") - if element_type is None: - element_type = array_type.element_type - elif element_type != array_type.element_type: - raise TypeError( - f"Argument {i} has a different element type ({element_type}) than the element type of the array ({array_type.element_type})" - ) - - vals.append(array_value) - types.append(array_type) - - size = sum(t.size for t in types) - combined_type = hw.ArrayType.get(element_type, size) - return hw.ArrayConcatOp(combined_type, vals) - - -class StructCreateOp: - - @staticmethod - def create(elements, result_type: Type = None): - elem_name_values = [ - (name, support.get_value(value)) for (name, value) in elements - ] - struct_fields = [(name, value.type) for (name, value) in elem_name_values] - struct_type = hw.StructType.get(struct_fields) - - if result_type is None: - result_type = struct_type - else: - result_type_inner = support.get_self_or_inner(result_type) - if result_type_inner != struct_type: - raise TypeError( - f"result type:\n\t{result_type_inner}\nmust match generated struct type:\n\t{struct_type}" - ) - - return hw.StructCreateOp(result_type, - [value for (_, value) in elem_name_values]) - - -class StructExtractOp: - - @staticmethod - def create(struct_value, field_name: str): - struct_value = support.get_value(struct_value) - struct_type = support.get_self_or_inner(struct_value.type) - field_type = struct_type.get_field(field_name) - return hw.StructExtractOp(field_type, struct_value, - StringAttr.get(field_name)) - - -class TypedeclOp: - - @staticmethod - def create(sym_name: str, type: Type, verilog_name: str = None): - return hw.TypedeclOp(StringAttr.get(sym_name), - TypeAttr.get(type), - verilogName=verilog_name) - - -class TypeScopeOp: - - @staticmethod - def create(sym_name: str): - op = hw.TypeScopeOp(StringAttr.get(sym_name)) - op.regions[0].blocks.append() - return op - - @property - def body(self): - return self.regions[0].blocks[0] diff --git a/lib/Bindings/Python/dialects/_hwarith_ops_ext.py b/lib/Bindings/Python/dialects/_hwarith_ops_ext.py deleted file mode 100644 index 503d3e6fe5da..000000000000 --- a/lib/Bindings/Python/dialects/_hwarith_ops_ext.py +++ /dev/null @@ -1,76 +0,0 @@ -from ..support import NamedValueOpView, get_value -from ..ir import IntegerAttr, IntegerType - - -class BinaryOpBuilder(NamedValueOpView): - - def operand_names(self): - return ["lhs", "rhs"] - - def result_names(self): - return ["result"] - - -def BinaryOp(base): - - class _Class(base): - - @classmethod - def create(cls, lhs=None, rhs=None, result_type=None): - return cls([get_value(lhs), get_value(rhs)]) - - return _Class - - -@BinaryOp -class DivOp: - pass - - -@BinaryOp -class SubOp: - pass - - -@BinaryOp -class AddOp: - pass - - -@BinaryOp -class MulOp: - pass - - -class CastOp: - - @classmethod - def create(cls, value, result_type): - return cls(result_type, value) - - -class ICmpOp: - # Predicate constants. - - # `==` and `!=` - PRED_EQ = 0b000 - PRED_NE = 0b001 - # `<` and `>=` - PRED_LT = 0b010 - PRED_GE = 0b011 - # `<=` and `>` - PRED_LE = 0b100 - PRED_GT = 0b101 - - @classmethod - def create(cls, pred, a, b): - if isinstance(pred, int): - pred = IntegerAttr.get(IntegerType.get_signless(64), pred) - return cls(pred, a, b) - - -class ConstantOp: - - @classmethod - def create(cls, data_type, value): - return cls(IntegerAttr.get(data_type, value)) diff --git a/lib/Bindings/Python/dialects/_msft_ops_ext.py b/lib/Bindings/Python/dialects/_msft_ops_ext.py deleted file mode 100644 index f793d2c6cce1..000000000000 --- a/lib/Bindings/Python/dialects/_msft_ops_ext.py +++ /dev/null @@ -1,211 +0,0 @@ -# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -# See https://llvm.org/LICENSE.txt for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -from . import hw, msft as _msft -from . import _hw_ops_ext as _hw_ext -from .. import support - -from .. import ir as _ir - - -class InstanceBuilder(support.NamedValueOpView): - """Helper class to incrementally construct an instance of a module.""" - - def __init__(self, - module, - name, - input_port_mapping, - *, - sym_name=None, - parameters=None, - target_design_partition=None, - loc=None, - ip=None): - self.module = module - instance_name = _ir.StringAttr.get(name) - module_name = _ir.FlatSymbolRefAttr.get(_ir.StringAttr(module.name).value) - if sym_name: - sym_name = _ir.StringAttr.get(sym_name) - pre_args = [instance_name, module_name] - if parameters is not None: - parameters = _hw_ext.create_parameters(parameters, module) - else: - parameters = [] - post_args = [] - results = module.type.results - - super().__init__( - _msft.InstanceOp, - results, - input_port_mapping, - pre_args, - post_args, - parameters=_ir.ArrayAttr.get(parameters), - targetDesignPartition=target_design_partition, - loc=loc, - ip=ip, - ) - - def create_default_value(self, index, data_type, arg_name): - type = self.module.type.inputs[index] - return support.BackedgeBuilder.create(type, - arg_name, - self, - instance_of=self.module) - - def operand_names(self): - arg_names = _ir.ArrayAttr(self.module.attributes["argNames"]) - arg_name_attrs = map(_ir.StringAttr, arg_names) - return list(map(lambda s: s.value, arg_name_attrs)) - - def result_names(self): - arg_names = _ir.ArrayAttr(self.module.attributes["resultNames"]) - arg_name_attrs = map(_ir.StringAttr, arg_names) - return list(map(lambda s: s.value, arg_name_attrs)) - - -class MSFTModuleOp(_hw_ext.ModuleLike): - - def __init__( - self, - name, - input_ports=[], - output_ports=[], - parameters: _ir.DictAttr = None, - file_name: str = None, - attributes=None, - loc=None, - ip=None, - ): - if attributes is None: - attributes = {} - if parameters is not None: - attributes["parameters"] = parameters - else: - attributes["parameters"] = _ir.DictAttr.get({}) - if file_name is not None: - attributes["fileName"] = _ir.StringAttr.get(file_name) - super().__init__(name, - input_ports, - output_ports, - attributes=attributes, - loc=loc, - ip=ip) - - def instantiate(self, name: str, loc=None, ip=None, **kwargs): - return InstanceBuilder(self, name, kwargs, loc=loc, ip=ip) - - def add_entry_block(self): - self.body.blocks.append(*self.type.inputs) - return self.body.blocks[0] - - @property - def body(self): - return self.regions[0] - - @property - def entry_block(self): - return self.regions[0].blocks[0] - - @property - def parameters(self): - return [ - hw.ParamDeclAttr.get(p.name, p.attr.type, p.attr) - for p in _ir.DictAttr(self.attributes["parameters"]) - ] - - @property - def childAppIDBases(self): - if "childAppIDBases" not in self.attributes: - return None - bases = self.attributes["childAppIDBases"] - if bases is None: - return None - return [_ir.StringAttr(n) for n in _ir.ArrayAttr(bases)] - - -class MSFTModuleExternOp(_hw_ext.ModuleLike): - - def instantiate(self, - name: str, - parameters=None, - results=None, - loc=None, - ip=None, - **kwargs): - return InstanceBuilder(self, - name, - kwargs, - sym_name=name, - parameters=parameters, - loc=loc, - ip=ip) - - -class PhysicalRegionOp: - - def add_bounds(self, bounds): - existing_bounds = [b for b in _ir.ArrayAttr(self.attributes["bounds"])] - existing_bounds.append(bounds) - new_bounds = _ir.ArrayAttr.get(existing_bounds) - self.attributes["bounds"] = new_bounds - - -class InstanceOp: - - @property - def moduleName(self): - return _ir.FlatSymbolRefAttr(self.attributes["moduleName"]) - - -class EntityExternOp: - - @staticmethod - def create(symbol, metadata=""): - symbol_attr = support.var_to_attribute(symbol) - metadata_attr = support.var_to_attribute(metadata) - return _msft.EntityExternOp(symbol_attr, metadata_attr) - - -class InstanceHierarchyOp: - - @staticmethod - def create(root_mod, instance_name=None): - hier = _msft.InstanceHierarchyOp(root_mod, instName=instance_name) - hier.body.blocks.append() - return hier - - @property - def top_module_ref(self): - return self.attributes["topModuleRef"] - - -class DynamicInstanceOp: - - @staticmethod - def create(name_ref): - inst = _msft.DynamicInstanceOp(name_ref) - inst.body.blocks.append() - return inst - - @property - def instance_path(self): - path = [] - next = self - while isinstance(next, DynamicInstanceOp): - path.append(next.attributes["instanceRef"]) - next = next.operation.parent.opview - path.reverse() - return _ir.ArrayAttr.get(path) - - @property - def instanceRef(self): - return self.attributes["instanceRef"] - - -class PDPhysLocationOp: - - @property - def loc(self): - return _msft.PhysLocationAttr(self.attributes["loc"]) diff --git a/lib/Bindings/Python/dialects/_seq_ops_ext.py b/lib/Bindings/Python/dialects/_seq_ops_ext.py deleted file mode 100644 index a6094d569179..000000000000 --- a/lib/Bindings/Python/dialects/_seq_ops_ext.py +++ /dev/null @@ -1,119 +0,0 @@ -from ..support import BackedgeBuilder, NamedValueOpView -from ..ir import IntegerType, OpView, StringAttr - - -class CompRegLikeBuilder(NamedValueOpView): - - def result_names(self): - return ["data"] - - def create_initial_value(self, index, data_type, arg_name): - if arg_name == "input": - operand_type = data_type - else: - operand_type = IntegerType.get_signless(1) - return BackedgeBuilder.create(operand_type, arg_name, self) - - -class CompRegLike: - - def __init__(self, - data_type, - input, - clk, - clockEnable=None, - *, - reset=None, - reset_value=None, - name=None, - sym_name=None, - loc=None, - ip=None): - operands = [] - results = [] - attributes = {} - results.append(data_type) - operands.append(input) - operands.append(clk) - if isinstance(self, CompRegOp): - if clockEnable is not None: - raise Exception("Clock enable not supported on compreg") - elif isinstance(self, CompRegClockEnabledOp): - if clockEnable is None: - raise Exception("Clock enable required on compreg.ce") - operands.append(clockEnable) - else: - assert False, "Class not recognized" - if reset is not None: - operands.append(reset) - if reset_value is not None: - operands.append(reset_value) - if name is None: - attributes["name"] = StringAttr.get("") - else: - attributes["name"] = StringAttr.get(name) - if sym_name is not None: - attributes["sym_name"] = StringAttr.get(sym_name) - - OpView.__init__( - self, - self.build_generic( - attributes=attributes, - results=results, - operands=operands, - loc=loc, - ip=ip, - ), - ) - - -class CompRegBuilder(CompRegLikeBuilder): - - def operand_names(self): - return ["input", "clk"] - - -class CompRegOp(CompRegLike): - - @classmethod - def create(cls, - result_type, - reset=None, - reset_value=None, - name=None, - sym_name=None, - **kwargs): - return CompRegBuilder(cls, - result_type, - kwargs, - reset=reset, - reset_value=reset_value, - name=name, - sym_name=sym_name, - needs_result_type=True) - - -class CompRegClockEnabledBuilder(CompRegLikeBuilder): - - def operand_names(self): - return ["input", "clk", "clockEnable"] - - -class CompRegClockEnabledOp(CompRegLike): - - @classmethod - def create(cls, - result_type, - reset=None, - reset_value=None, - name=None, - sym_name=None, - **kwargs): - return CompRegClockEnabledBuilder(cls, - result_type, - kwargs, - reset=reset, - reset_value=reset_value, - name=name, - sym_name=sym_name, - needs_result_type=True) diff --git a/lib/Bindings/Python/dialects/_sv_ops_ext.py b/lib/Bindings/Python/dialects/_sv_ops_ext.py deleted file mode 100644 index 6240ab33cf8b..000000000000 --- a/lib/Bindings/Python/dialects/_sv_ops_ext.py +++ /dev/null @@ -1,100 +0,0 @@ -# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -# See https://llvm.org/LICENSE.txt for license information. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -from ..ir import ArrayAttr, Attribute, FlatSymbolRefAttr, OpView, StringAttr -from . import sv, hw -from .. import support - - -class IfDefOp: - - def __init__(self, cond: Attribute, *, loc=None, ip=None): - operands = [] - results = [] - attributes = {"cond": cond} - regions = 2 - super().__init__( - self.build_generic(attributes=attributes, - results=results, - operands=operands, - successors=None, - regions=regions, - loc=loc, - ip=ip)) - self.regions[0].blocks.append() - self.regions[1].blocks.append() - - -class WireOp: - - def __init__(self, - data_type, - name, - *, - inner_sym=None, - svAttributes=None, - loc=None, - ip=None): - attributes = {"name": StringAttr.get(name)} - if inner_sym is not None: - attributes["inner_sym"] = StringAttr.get(inner_sym) - if svAttributes is not None: - attributes["svAttributes"] = ArrayAttr.get(svAttributes) - OpView.__init__( - self, - self.build_generic(attributes=attributes, - results=[data_type], - operands=[], - successors=None, - regions=0, - loc=loc, - ip=ip)) - - @staticmethod - def create(data_type, name=None, inner_sym=None): - if not isinstance(data_type, hw.InOutType): - data_type = hw.InOutType.get(data_type) - return sv.WireOp(data_type, name, inner_sym=inner_sym) - - -class RegOp: - - def __init__(self, - data_type, - name, - *, - inner_sym=None, - svAttributes=None, - loc=None, - ip=None): - attributes = {"name": StringAttr.get(name)} - if inner_sym is not None: - attributes["inner_sym"] = FlatSymbolRefAttr.get(inner_sym) - if svAttributes is not None: - attributes["svAttributes"] = ArrayAttr.get(svAttributes) - OpView.__init__( - self, - self.build_generic(attributes=attributes, - results=[data_type], - operands=[], - successors=None, - regions=0, - loc=loc, - ip=ip)) - - -class AssignOp: - - @staticmethod - def create(dest, src): - return sv.AssignOp(dest=dest, src=src) - - -class ReadInOutOp: - - @staticmethod - def create(value): - value = support.get_value(value) - type = support.get_self_or_inner(value.type).element_type - return sv.ReadInOutOp(value) diff --git a/lib/Bindings/Python/dialects/comb.py b/lib/Bindings/Python/dialects/comb.py index 5ef1e31a17be..a0df29f6d463 100644 --- a/lib/Bindings/Python/dialects/comb.py +++ b/lib/Bindings/Python/dialects/comb.py @@ -2,11 +2,12 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -from ._comb_ops_gen import * - -from ..support import NamedValueOpView - +from . import comb +from ..dialects._ods_common import _cext as _ods_cext from ..ir import IntegerAttr, IntegerType, OpView +from ..support import NamedValueOpView, get_value +from ._comb_ops_gen import * +from ._comb_ops_gen import _Dialect # Sugar classes for the various possible verions of ICmpOp. @@ -96,3 +97,195 @@ class GtUOp: @CompareOp(9) class GeUOp: pass + + +# Builder base classes for non-variadic unary and binary ops. +class UnaryOpBuilder(NamedValueOpView): + + def operand_names(self): + return ["input"] + + def result_names(self): + return ["result"] + + +def UnaryOp(base): + + class _Class(base): + + @classmethod + def create(cls, input=None, result_type=None): + mapping = {"input": input} if input else {} + return UnaryOpBuilder(cls, result_type, mapping) + + return _Class + + +class ExtractOpBuilder(UnaryOpBuilder): + + def __init__(self, low_bit, data_type, input_port_mapping={}, **kwargs): + low_bit = IntegerAttr.get(IntegerType.get_signless(32), low_bit) + super().__init__(comb.ExtractOp, data_type, input_port_mapping, [], + [low_bit], **kwargs) + + +class BinaryOpBuilder(NamedValueOpView): + + def operand_names(self): + return ["lhs", "rhs"] + + def result_names(self): + return ["result"] + + +def BinaryOp(base): + + class _Class(base): + + @classmethod + def create(cls, lhs=None, rhs=None, result_type=None): + mapping = {} + if lhs: + mapping["lhs"] = lhs + if rhs: + mapping["rhs"] = rhs + return BinaryOpBuilder(cls, result_type, mapping) + + return _Class + + +# Base classes for the variadic ops. +def VariadicOp(base): + + class _Class(base): + + @classmethod + def create(cls, *args): + return cls([get_value(a) for a in args]) + + return _Class + + +# Base class for miscellaneous ops that can't be abstracted but should provide a +# create method for uniformity. +def CreatableOp(base): + + class _Class(base): + + @classmethod + def create(cls, *args, **kwargs): + return cls(*args, **kwargs) + + return _Class + + +# Sugar classes for the various non-variadic unary ops. +@_ods_cext.register_operation(_Dialect, replace=True) +class ExtractOp(ExtractOp): + + @staticmethod + def create(low_bit, result_type, input=None): + mapping = {"input": input} if input else {} + return ExtractOpBuilder(low_bit, + result_type, + mapping, + needs_result_type=True) + + +@UnaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class ParityOp(ParityOp): + pass + + +# Sugar classes for the various non-variadic binary ops. +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class DivSOp(DivSOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class DivUOp(DivUOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class ModSOp(ModSOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class ModUOp(ModUOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class ShlOp(ShlOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class ShrSOp(ShrSOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class ShrUOp(ShrUOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class SubOp(SubOp): + pass + + +# Sugar classes for the variadic ops. +@VariadicOp +@_ods_cext.register_operation(_Dialect, replace=True) +class AddOp(AddOp): + pass + + +@VariadicOp +@_ods_cext.register_operation(_Dialect, replace=True) +class MulOp(MulOp): + pass + + +@VariadicOp +@_ods_cext.register_operation(_Dialect, replace=True) +class AndOp(AndOp): + pass + + +@VariadicOp +@_ods_cext.register_operation(_Dialect, replace=True) +class OrOp(OrOp): + pass + + +@VariadicOp +@_ods_cext.register_operation(_Dialect, replace=True) +class XorOp(XorOp): + pass + + +@VariadicOp +@_ods_cext.register_operation(_Dialect, replace=True) +class ConcatOp(ConcatOp): + pass + + +# Sugar classes for miscellaneous ops. +@CreatableOp +@_ods_cext.register_operation(_Dialect, replace=True) +class MuxOp(MuxOp): + pass diff --git a/lib/Bindings/Python/dialects/esi.py b/lib/Bindings/Python/dialects/esi.py index 9ba54c8ecd80..255c5ae4e037 100644 --- a/lib/Bindings/Python/dialects/esi.py +++ b/lib/Bindings/Python/dialects/esi.py @@ -2,10 +2,58 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -from ._esi_ops_gen import * +from __future__ import annotations + +from . import esi +from .. import ir +from .. import support from .._mlir_libs._circt._esi import * +from ..dialects._ods_common import _cext as _ods_cext +from ._esi_ops_gen import * +from ._esi_ops_gen import _Dialect +from typing import Dict, List, Optional, Sequence, Type class ChannelSignaling: - ValidReady: int = 0 - FIFO0: int = 1 + ValidReady = 0 + FIFO0 = 1 + + +@_ods_cext.register_operation(_Dialect, replace=True) +class RequestToServerConnectionOp(RequestToServerConnectionOp): + + @property + def clientNamePath(self) -> List[str]: + return [ + ir.StringAttr(x).value + for x in ir.ArrayAttr(self.attributes["clientNamePath"]) + ] + + +@_ods_cext.register_operation(_Dialect, replace=True) +class RequestToClientConnectionOp(RequestToClientConnectionOp): + + @property + def clientNamePath(self) -> List[str]: + return [ + ir.StringAttr(x).value + for x in ir.ArrayAttr(self.attributes["clientNamePath"]) + ] + + +@_ods_cext.register_operation(_Dialect, replace=True) +class RandomAccessMemoryDeclOp(RandomAccessMemoryDeclOp): + + @property + def innerType(self): + return ir.TypeAttr(self.attributes["innerType"]) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ESIPureModuleOp(ESIPureModuleOp): + + def add_entry_block(self): + if len(self.body.blocks) > 0: + raise IndexError('The module already has an entry block') + self.body.blocks.append() + return self.body.blocks[0] diff --git a/lib/Bindings/Python/dialects/fsm.py b/lib/Bindings/Python/dialects/fsm.py index 72fd4571a457..a7db46360a25 100644 --- a/lib/Bindings/Python/dialects/fsm.py +++ b/lib/Bindings/Python/dialects/fsm.py @@ -2,4 +2,173 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +from . import fsm as fsm +from .. import support +from ..dialects._ods_common import _cext as _ods_cext +from ..ir import * from ._fsm_ops_gen import * +from ._fsm_ops_gen import _Dialect + + +def _get_or_add_single_block(region, args=[]): + if len(region.blocks) == 0: + region.blocks.append(*args) + return region.blocks[0] + + +@_ods_cext.register_operation(_Dialect, replace=True) +class MachineOp(MachineOp): + + def __init__(self, + name, + initial_state, + input_ports, + output_ports, + *, + attributes={}, + loc=None, + ip=None): + attributes["sym_name"] = StringAttr.get(name) + attributes["initialState"] = StringAttr.get(initial_state) + + input_types = [] + output_types = [] + for (i, (_, port_type)) in enumerate(input_ports): + input_types.append(port_type) + + for (i, (_, port_type)) in enumerate(output_ports): + output_types.append(port_type) + + attributes["function_type"] = TypeAttr.get( + FunctionType.get(inputs=input_types, results=output_types)) + + OpView.__init__( + self, + self.build_generic(attributes=attributes, + results=[], + operands=[], + successors=None, + regions=1, + loc=loc, + ip=ip)) + + _get_or_add_single_block(self.body, self.type.inputs) + + @property + def type(self): + return FunctionType(TypeAttr(self.attributes["function_type"]).value) + + def instantiate(self, name: str, loc=None, ip=None, **kwargs): + """ FSM Instantiation function""" + in_names = support.attribute_to_var(self.attributes['in_names']) + inputs = [kwargs[port].value for port in in_names] + + # Clock and resets are not part of the input ports of the FSM, but + # it is at the point of `fsm.hw_instance` instantiation that they + # must be connected. + # Attach backedges to these, and associate these backedges to the operation. + # They can then be accessed at the point of instantiation and assigned. + clock = support.BackedgeBuilder().create( + IntegerType.get_signed(1), + StringAttr(self.attributes['clock_name']).value, self) + reset = support.BackedgeBuilder().create( + IntegerType.get_signed(1), + StringAttr(self.attributes['reset_name']).value, self) + + op = fsm.HWInstanceOp(outputs=self.type.results, + inputs=inputs, + sym_name=StringAttr.get(name), + machine=FlatSymbolRefAttr.get(self.sym_name.value), + clock=clock.result, + reset=reset.result if reset else None, + loc=loc, + ip=ip) + op.backedges = {} + + def set_OpOperand(name, backedge): + index = None + for i, operand in enumerate(op.operands): + if operand == backedge.result: + index = i + break + assert index is not None + op_operand = support.OpOperand(op, index, op.operands[index], op) + setattr(op, f'_{name}_backedge', op_operand) + op.backedges[i] = backedge + + set_OpOperand('clock', clock) + if reset: + set_OpOperand('reset', reset) + + return op + + +@_ods_cext.register_operation(_Dialect, replace=True) +class TransitionOp(TransitionOp): + + def __init__(self, next_state, *, loc=None, ip=None): + attributes = { + "nextState": FlatSymbolRefAttr.get(next_state), + } + super().__init__( + self.build_generic(attributes=attributes, + results=[], + operands=[], + successors=None, + regions=2, + loc=loc, + ip=ip)) + + @staticmethod + def create(to_state): + op = fsm.TransitionOp(to_state) + return op + + def set_guard(self, guard_fn): + """Executes a function to generate a guard for the transition. + The function is executed within the guard region of this operation.""" + guard_block = _get_or_add_single_block(self.guard) + with InsertionPoint(guard_block): + guard = guard_fn() + guard_type = support.type_to_pytype(guard.type) + if guard_type.width != 1: + raise ValueError('The guard must be a single bit') + fsm.ReturnOp(operand=guard) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class StateOp(StateOp): + + def __init__(self, name, *, loc=None, ip=None): + attributes = {} + attributes["sym_name"] = StringAttr.get(name) + + OpView.__init__( + self, + self.build_generic(attributes=attributes, + results=[], + operands=[], + successors=None, + regions=2, + loc=loc, + ip=ip)) + + @staticmethod + def create(name): + return fsm.StateOp(name) + + @property + def output(self): + return _get_or_add_single_block(super().output) + + @property + def transitions(self): + return _get_or_add_single_block(super().transitions) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class OutputOp(OutputOp): + + @staticmethod + def create(*operands): + return fsm.OutputOp(operands) diff --git a/lib/Bindings/Python/dialects/hw.py b/lib/Bindings/Python/dialects/hw.py index 4f17ee432acd..826b9c1f4deb 100644 --- a/lib/Bindings/Python/dialects/hw.py +++ b/lib/Bindings/Python/dialects/hw.py @@ -2,5 +2,551 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -from ._hw_ops_gen import * +from __future__ import annotations + +from . import hw +from .. import support from .._mlir_libs._circt._hw import * +from ..dialects._ods_common import _cext as _ods_cext +from ..ir import * +from ._hw_ops_gen import * +from ._hw_ops_gen import _Dialect +from typing import Dict, Type + + +def create_parameters(parameters: dict[str, Attribute], module: ModuleLike): + # Compute mapping from parameter name to index, and initialize array. + mod_param_decls = module.parameters + mod_param_decls_idxs = { + decl.name: idx for (idx, decl) in enumerate(mod_param_decls) + } + inst_param_array = [None] * len(module.parameters) + + # Fill in all the parameters specified. + if isinstance(parameters, DictAttr): + parameters = {i.name: i.attr for i in parameters} + for (pname, pval) in parameters.items(): + if pname not in mod_param_decls_idxs: + raise ValueError( + f"Could not find parameter '{pname}' in module parameter decls") + idx = mod_param_decls_idxs[pname] + param_decl = mod_param_decls[idx] + inst_param_array[idx] = hw.ParamDeclAttr.get(pname, param_decl.param_type, + pval) + + # Fill in the defaults from the module param decl. + for (idx, pval) in enumerate(inst_param_array): + if pval is not None: + continue + inst_param_array[idx] = mod_param_decls[idx] + + return inst_param_array + + +class InstanceBuilder(support.NamedValueOpView): + """Helper class to incrementally construct an instance of a module.""" + + def __init__(self, + module, + name, + input_port_mapping, + *, + results=None, + parameters={}, + sym_name=None, + loc=None, + ip=None): + self.module = module + instance_name = StringAttr.get(name) + module_name = FlatSymbolRefAttr.get(StringAttr(module.name).value) + inst_param_array = create_parameters(parameters, module) + if sym_name: + inner_sym = hw.InnerSymAttr.get(StringAttr.get(sym_name)) + else: + inner_sym = None + pre_args = [instance_name, module_name] + post_args = [ + ArrayAttr.get([StringAttr.get(x) for x in self.operand_names()]), + ArrayAttr.get([StringAttr.get(x) for x in self.result_names()]), + ArrayAttr.get(inst_param_array) + ] + if results is None: + results = module.type.output_types + + if not isinstance(module, hw.HWModuleExternOp): + input_name_type_lookup = { + name: support.type_to_pytype(ty) + for name, ty in zip(self.operand_names(), module.type.input_types) + } + for input_name, input_value in input_port_mapping.items(): + if input_name not in input_name_type_lookup: + continue # This error gets caught and raised later. + mod_input_type = input_name_type_lookup[input_name] + if support.type_to_pytype(input_value.type) != mod_input_type: + raise TypeError(f"Input '{input_name}' has type '{input_value.type}' " + f"but expected '{mod_input_type}'") + + super().__init__(hw.InstanceOp, + results, + input_port_mapping, + pre_args, + post_args, + needs_result_type=True, + inner_sym=inner_sym, + loc=loc, + ip=ip) + + def create_default_value(self, index, data_type, arg_name): + type = self.module.type.input_types[index] + return support.BackedgeBuilder.create(type, + arg_name, + self, + instance_of=self.module) + + def operand_names(self): + return self.module.type.input_names + + def result_names(self): + return self.module.type.output_names + + +class ModuleLike: + """Custom Python base class for module-like operations.""" + + def __init__( + self, + name, + input_ports=[], + output_ports=[], + *, + parameters=[], + attributes={}, + body_builder=None, + loc=None, + ip=None, + ): + """ + Create a module-like with the provided `name`, `input_ports`, and + `output_ports`. + - `name` is a string representing the module name. + - `input_ports` is a list of pairs of string names and mlir.ir types. + - `output_ports` is a list of pairs of string names and mlir.ir types. + - `body_builder` is an optional callback, when provided a new entry block + is created and the callback is invoked with the new op as argument within + an InsertionPoint context already set for the block. The callback is + expected to insert a terminator in the block. + """ + # Copy the mutable default arguments. 'Cause python. + input_ports = list(input_ports) + output_ports = list(output_ports) + parameters = list(parameters) + attributes = dict(attributes) + + operands = [] + results = [] + attributes["sym_name"] = StringAttr.get(str(name)) + + module_ports = [] + input_names = [] + port_locs = [] + unknownLoc = Location.unknown().attr + for (i, (port_name, port_type)) in enumerate(input_ports): + input_name = StringAttr.get(str(port_name)) + input_dir = hw.ModulePortDirection.INPUT + input_port = hw.ModulePort(input_name, port_type, input_dir) + module_ports.append(input_port) + input_names.append(input_name) + port_locs.append(unknownLoc) + + output_types = [] + output_names = [] + for (i, (port_name, port_type)) in enumerate(output_ports): + output_name = StringAttr.get(str(port_name)) + output_dir = hw.ModulePortDirection.OUTPUT + output_port = hw.ModulePort(output_name, port_type, output_dir) + module_ports.append(output_port) + output_names.append(output_name) + port_locs.append(unknownLoc) + attributes["port_locs"] = ArrayAttr.get(port_locs) + attributes["per_port_attrs"] = ArrayAttr.get([]) + + if len(parameters) > 0 or "parameters" not in attributes: + attributes["parameters"] = ArrayAttr.get(parameters) + + attributes["module_type"] = TypeAttr.get(hw.ModuleType.get(module_ports)) + + _ods_cext.ir.OpView.__init__( + self, + self.build_generic(attributes=attributes, + results=results, + operands=operands, + loc=loc, + ip=ip)) + + if body_builder: + entry_block = self.add_entry_block() + + with InsertionPoint(entry_block): + with support.BackedgeBuilder(): + outputs = body_builder(self) + _create_output_op(name, output_ports, entry_block, outputs) + + @property + def type(self): + return hw.ModuleType(TypeAttr(self.attributes["module_type"]).value) + + @property + def name(self): + return self.attributes["sym_name"] + + @property + def is_external(self): + return len(self.regions[0].blocks) == 0 + + @property + def parameters(self) -> list[ParamDeclAttr]: + return [ + hw.ParamDeclAttr(a) for a in ArrayAttr(self.attributes["parameters"]) + ] + + def instantiate(self, + name: str, + parameters: Dict[str, object] = {}, + results=None, + sym_name=None, + loc=None, + ip=None, + **kwargs): + return InstanceBuilder(self, + name, + kwargs, + parameters=parameters, + results=results, + sym_name=sym_name, + loc=loc, + ip=ip) + + +def _create_output_op(cls_name, output_ports, entry_block, bb_ret): + """Create the hw.OutputOp from the body_builder return.""" + + # Determine if the body already has an output op. + block_len = len(entry_block.operations) + if block_len > 0: + last_op = entry_block.operations[block_len - 1] + if isinstance(last_op, hw.OutputOp): + # If it does, the return from body_builder must be None. + if bb_ret is not None and bb_ret != last_op: + raise support.ConnectionError( + f"In {cls_name}, cannot return value from body_builder and " + "create hw.OutputOp") + return + + # If builder didn't create an output op and didn't return anything, this op + # mustn't have any outputs. + if bb_ret is None: + if len(output_ports) == 0: + hw.OutputOp([]) + return + raise support.ConnectionError( + f"In {cls_name}, must return module output values") + + # Now create the output op depending on the object type returned + outputs: list[Value] = list() + + # Only acceptable return is a dict of port, value mappings. + if not isinstance(bb_ret, dict): + raise support.ConnectionError( + f"In {cls_name}, can only return a dict of port, value mappings " + "from body_builder.") + + # A dict of `OutputPortName` -> ValueLike must be converted to a list in port + # order. + unconnected_ports = [] + for (name, port_type) in output_ports: + if name not in bb_ret: + unconnected_ports.append(name) + outputs.append(None) + else: + val = support.get_value(bb_ret[name]) + if val is None: + field_type = type(bb_ret[name]) + raise TypeError( + f"In {cls_name}, body_builder return doesn't support type " + f"'{field_type}'") + if val.type != port_type: + if isinstance(port_type, hw.TypeAliasType) and \ + port_type.inner_type == val.type: + val = hw.BitcastOp.create(port_type, val).result + else: + raise TypeError( + f"In {cls_name}, output port '{name}' type ({val.type}) doesn't " + f"match declared type ({port_type})") + outputs.append(val) + bb_ret.pop(name) + if len(unconnected_ports) > 0: + raise support.UnconnectedSignalError(cls_name, unconnected_ports) + if len(bb_ret) > 0: + raise support.ConnectionError( + f"Could not map the following to output ports in {cls_name}: " + + ",".join(bb_ret.keys())) + + hw.OutputOp(outputs) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class HWModuleOp(ModuleLike, HWModuleOp): + """Specialization for the HW module op class.""" + + def __init__( + self, + name, + input_ports=[], + output_ports=[], + *, + parameters=[], + attributes={}, + body_builder=None, + loc=None, + ip=None, + ): + if "comment" not in attributes: + attributes["comment"] = StringAttr.get("") + super().__init__(name, + input_ports, + output_ports, + parameters=parameters, + attributes=attributes, + body_builder=body_builder, + loc=loc, + ip=ip) + + @property + def body(self): + return self.regions[0] + + @property + def entry_block(self): + return self.regions[0].blocks[0] + + @property + def input_indices(self): + indices: dict[int, str] = {} + op_names = self.type.input_names + for idx, name in enumerate(op_names): + indices[name] = idx + return indices + + # Support attribute access to block arguments by name + def __getattr__(self, name): + if name in self.input_indices: + index = self.input_indices[name] + return self.entry_block.arguments[index] + raise AttributeError(f"unknown input port name {name}") + + def inputs(self) -> dict[str:Value]: + ret = {} + for (name, idx) in self.input_indices.items(): + ret[name] = self.entry_block.arguments[idx] + return ret + + def outputs(self) -> dict[str:Type]: + result_names = self.type.output_names + result_types = self.type.output_types + return dict(zip(result_names, result_types)) + + def add_entry_block(self): + if not self.is_external: + raise IndexError('The module already has an entry block') + self.body.blocks.append(*self.type.input_types) + return self.body.blocks[0] + + +@_ods_cext.register_operation(_Dialect, replace=True) +class HWModuleExternOp(ModuleLike, HWModuleExternOp): + """Specialization for the HW module op class.""" + + def __init__( + self, + name, + input_ports=[], + output_ports=[], + *, + parameters=[], + attributes={}, + body_builder=None, + loc=None, + ip=None, + ): + if "comment" not in attributes: + attributes["comment"] = StringAttr.get("") + super().__init__(name, + input_ports, + output_ports, + parameters=parameters, + attributes=attributes, + body_builder=body_builder, + loc=loc, + ip=ip) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ConstantOp(ConstantOp): + + @staticmethod + def create(data_type, value): + return hw.ConstantOp(IntegerAttr.get(data_type, value)) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class BitcastOp(BitcastOp): + + @staticmethod + def create(data_type, value): + value = support.get_value(value) + return hw.BitcastOp(data_type, value) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ArrayGetOp(ArrayGetOp): + + @staticmethod + def create(array_value, idx): + array_value = support.get_value(array_value) + array_type = support.get_self_or_inner(array_value.type) + if isinstance(idx, int): + idx_width = (array_type.size - 1).bit_length() + idx_val = ConstantOp.create(IntegerType.get_signless(idx_width), + idx).result + else: + idx_val = support.get_value(idx) + return hw.ArrayGetOp(array_value, idx_val) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ArraySliceOp(ArraySliceOp): + + @staticmethod + def create(array_value, low_index, ret_type): + array_value = support.get_value(array_value) + array_type = support.get_self_or_inner(array_value.type) + if isinstance(low_index, int): + idx_width = (array_type.size - 1).bit_length() + idx_width = max(1, idx_width) # hw.constant cannot produce i0. + idx_val = ConstantOp.create(IntegerType.get_signless(idx_width), + low_index).result + else: + idx_val = support.get_value(low_index) + return hw.ArraySliceOp(ret_type, array_value, idx_val) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ArrayCreateOp(ArrayCreateOp): + + @staticmethod + def create(elements): + if not elements: + raise ValueError("Cannot 'create' an array of length zero") + vals = [] + type = None + for i, arg in enumerate(elements): + arg_val = support.get_value(arg) + vals.append(arg_val) + if type is None: + type = arg_val.type + elif type != arg_val.type: + raise TypeError( + f"Argument {i} has a different element type ({arg_val.type}) than the element type of the array ({type})" + ) + return hw.ArrayCreateOp(hw.ArrayType.get(type, len(vals)), vals) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ArrayConcatOp(ArrayConcatOp): + + @staticmethod + def create(*sub_arrays): + vals = [] + types = [] + element_type = None + for i, array in enumerate(sub_arrays): + array_value = support.get_value(array) + array_type = support.type_to_pytype(array_value.type) + if array_value is None or not isinstance(array_type, hw.ArrayType): + raise TypeError(f"Cannot concatenate {array_value}") + if element_type is None: + element_type = array_type.element_type + elif element_type != array_type.element_type: + raise TypeError( + f"Argument {i} has a different element type ({element_type}) than the element type of the array ({array_type.element_type})" + ) + + vals.append(array_value) + types.append(array_type) + + size = sum(t.size for t in types) + combined_type = hw.ArrayType.get(element_type, size) + return hw.ArrayConcatOp(combined_type, vals) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class StructCreateOp(StructCreateOp): + + @staticmethod + def create(elements, result_type: Type = None): + elem_name_values = [ + (name, support.get_value(value)) for (name, value) in elements + ] + struct_fields = [(name, value.type) for (name, value) in elem_name_values] + struct_type = hw.StructType.get(struct_fields) + + if result_type is None: + result_type = struct_type + else: + result_type_inner = support.get_self_or_inner(result_type) + if result_type_inner != struct_type: + raise TypeError( + f"result type:\n\t{result_type_inner}\nmust match generated struct type:\n\t{struct_type}" + ) + + return hw.StructCreateOp(result_type, + [value for (_, value) in elem_name_values]) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class StructExtractOp(StructExtractOp): + + @staticmethod + def create(struct_value, field_name: str): + struct_value = support.get_value(struct_value) + struct_type = support.get_self_or_inner(struct_value.type) + field_type = struct_type.get_field(field_name) + field_index = struct_type.get_field_index(field_name) + if field_index == UnitAttr.get(): + raise TypeError( + f"field '{field_name}' not element of struct type {struct_type}") + return hw.StructExtractOp(field_type, struct_value, field_index) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class TypedeclOp(TypedeclOp): + + @staticmethod + def create(sym_name: str, type: Type, verilog_name: str = None): + return hw.TypedeclOp(StringAttr.get(sym_name), + TypeAttr.get(type), + verilogName=verilog_name) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class TypeScopeOp(TypeScopeOp): + + @staticmethod + def create(sym_name: str): + op = hw.TypeScopeOp(StringAttr.get(sym_name)) + op.regions[0].blocks.append() + return op + + @property + def body(self): + return self.regions[0].blocks[0] diff --git a/lib/Bindings/Python/dialects/hwarith.py b/lib/Bindings/Python/dialects/hwarith.py index 9a2c914a00fa..019145a2a5ba 100644 --- a/lib/Bindings/Python/dialects/hwarith.py +++ b/lib/Bindings/Python/dialects/hwarith.py @@ -2,4 +2,89 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +from ..dialects._ods_common import _cext as _ods_cext +from ..ir import IntegerAttr, IntegerType +from ..support import NamedValueOpView, get_value from ._hwarith_ops_gen import * +from ._hwarith_ops_gen import _Dialect + + +class BinaryOpBuilder(NamedValueOpView): + + def operand_names(self): + return ["lhs", "rhs"] + + def result_names(self): + return ["result"] + + +def BinaryOp(base): + + class _Class(base): + + @classmethod + def create(cls, lhs=None, rhs=None, result_type=None): + return cls([get_value(lhs), get_value(rhs)]) + + return _Class + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class DivOp(DivOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class SubOp(SubOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class AddOp(AddOp): + pass + + +@BinaryOp +@_ods_cext.register_operation(_Dialect, replace=True) +class MulOp(MulOp): + pass + + +@_ods_cext.register_operation(_Dialect, replace=True) +class CastOp(CastOp): + + @classmethod + def create(cls, value, result_type): + return cls(result_type, value) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ICmpOp(ICmpOp): + # Predicate constants. + + # `==` and `!=` + PRED_EQ = 0b000 + PRED_NE = 0b001 + # `<` and `>=` + PRED_LT = 0b010 + PRED_GE = 0b011 + # `<=` and `>` + PRED_LE = 0b100 + PRED_GT = 0b101 + + @classmethod + def create(cls, pred, a, b): + if isinstance(pred, int): + pred = IntegerAttr.get(IntegerType.get_signless(64), pred) + return cls(pred, a, b) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ConstantOp(ConstantOp): + + @classmethod + def create(cls, data_type, value): + return cls(IntegerAttr.get(data_type, value)) diff --git a/lib/Bindings/Python/dialects/ltl.py b/lib/Bindings/Python/dialects/ltl.py new file mode 100644 index 000000000000..6fe4ce3bce75 --- /dev/null +++ b/lib/Bindings/Python/dialects/ltl.py @@ -0,0 +1,5 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ._ltl_ops_gen import * diff --git a/lib/Bindings/Python/dialects/msft.py b/lib/Bindings/Python/dialects/msft.py index 654b99f4d297..401ee7a12113 100644 --- a/lib/Bindings/Python/dialects/msft.py +++ b/lib/Bindings/Python/dialects/msft.py @@ -2,5 +2,67 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -from ._msft_ops_gen import * +from . import hw, msft +from .. import support from .._mlir_libs._circt._msft import * +from ..dialects._ods_common import _cext as _ods_cext +from ..ir import ArrayAttr +from ._msft_ops_gen import * +from ._msft_ops_gen import _Dialect +from typing import Dict, List, Type + + +@_ods_cext.register_operation(_Dialect, replace=True) +class DeclPhysicalRegionOp(DeclPhysicalRegionOp): + + def add_bounds(self, bounds): + existing_bounds = [b for b in ArrayAttr(self.attributes["bounds"])] + existing_bounds.append(bounds) + new_bounds = ArrayAttr.get(existing_bounds) + self.attributes["bounds"] = new_bounds + + +@_ods_cext.register_operation(_Dialect, replace=True) +class InstanceHierarchyOp(InstanceHierarchyOp): + + @staticmethod + def create(root_mod, instance_name=None): + hier = msft.InstanceHierarchyOp(root_mod, instName=instance_name) + hier.body.blocks.append() + return hier + + @property + def top_module_ref(self): + return self.attributes["topModuleRef"] + + +@_ods_cext.register_operation(_Dialect, replace=True) +class DynamicInstanceOp(DynamicInstanceOp): + + @staticmethod + def create(name_ref): + inst = msft.DynamicInstanceOp(name_ref) + inst.body.blocks.append() + return inst + + @property + def instance_path(self): + path = [] + next = self + while isinstance(next, DynamicInstanceOp): + path.append(next.attributes["instanceRef"]) + next = next.operation.parent.opview + path.reverse() + return ArrayAttr.get(path) + + @property + def instanceRef(self): + return self.attributes["instanceRef"] + + +@_ods_cext.register_operation(_Dialect, replace=True) +class PDPhysLocationOp(PDPhysLocationOp): + + @property + def loc(self): + return msft.PhysLocationAttr(self.attributes["loc"]) diff --git a/lib/Bindings/Python/dialects/om.py b/lib/Bindings/Python/dialects/om.py new file mode 100644 index 000000000000..2abeaffe0e28 --- /dev/null +++ b/lib/Bindings/Python/dialects/om.py @@ -0,0 +1,225 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from __future__ import annotations + +from ._om_ops_gen import * +from .._mlir_libs._circt._om import Evaluator as BaseEvaluator, Object as BaseObject, List as BaseList, Tuple as BaseTuple, Map as BaseMap, BasePath as BaseBasePath, BasePathType, Path, PathType, ClassType, ReferenceAttr, ListAttr, MapAttr, OMIntegerAttr + +from ..ir import Attribute, Diagnostic, DiagnosticSeverity, Module, StringAttr, IntegerAttr, IntegerType +from ..support import attribute_to_var, var_to_attribute + +import sys +import logging +from dataclasses import fields +from typing import TYPE_CHECKING, Any, Sequence, TypeVar + +if TYPE_CHECKING: + from _typeshed.stdlib.dataclass import DataclassInstance + + +# Wrap a base mlir object with high-level object. +def wrap_mlir_object(value): + # For primitives, return a Python value. + if isinstance(value, Attribute): + return attribute_to_var(value) + + if isinstance(value, BaseList): + return List(value) + + if isinstance(value, BaseTuple): + return Tuple(value) + + if isinstance(value, BaseMap): + return Map(value) + + if isinstance(value, BaseBasePath): + return BasePath(value) + + if isinstance(value, Path): + return value + + # For objects, return an Object, wrapping the base implementation. + assert isinstance(value, BaseObject) + return Object(value) + + +def om_var_to_attribute(obj, none_on_fail: bool = False) -> ir.Attrbute: + if isinstance(obj, int): + return OMIntegerAttr.get(IntegerAttr.get(IntegerType.get_signless(64), obj)) + return var_to_attribute(obj, none_on_fail) + + +def unwrap_python_object(value): + # Check if the value is a Primitive. + try: + return om_var_to_attribute(value) + except: + pass + + if isinstance(value, List): + return BaseList(value) + + if isinstance(value, Tuple): + return BaseTuple(value) + + if isinstance(value, Map): + return BaseMap(value) + + if isinstance(value, BasePath): + return BaseBasePath(value) + + if isinstance(value, Path): + return value + + # Otherwise, it must be an Object. Cast to the mlir object. + assert isinstance(value, Object) + return BaseObject(value) + + +class List(BaseList): + + def __init__(self, obj: BaseList) -> None: + super().__init__(obj) + + def __getitem__(self, i): + val = super().__getitem__(i) + return wrap_mlir_object(val) + + # Support iterating over a List by yielding its elements. + def __iter__(self): + for i in range(0, self.__len__()): + yield self.__getitem__(i) + + +class Tuple(BaseTuple): + + def __init__(self, obj: BaseTuple) -> None: + super().__init__(obj) + + def __getitem__(self, i): + val = super().__getitem__(i) + return wrap_mlir_object(val) + + # Support iterating over a Tuple by yielding its elements. + def __iter__(self): + for i in range(0, self.__len__()): + yield self.__getitem__(i) + + +class Map(BaseMap): + + def __init__(self, obj: BaseMap) -> None: + super().__init__(obj) + + def __getitem__(self, key): + val = super().__getitem__(key) + return wrap_mlir_object(val) + + def keys(self): + return [wrap_mlir_object(arg) for arg in super().keys()] + + def items(self): + for i in self: + yield i + + def values(self): + for (_, v) in self: + yield v + + # Support iterating over a Map + def __iter__(self): + for i in super().keys(): + yield (wrap_mlir_object(i), self.__getitem__(i)) + + +class BasePath(BaseBasePath): + + @staticmethod + def get_empty(context=None) -> "BasePath": + return BasePath(BaseBasePath.get_empty(context)) + + +# Define the Object class by inheriting from the base implementation in C++. +class Object(BaseObject): + + def __init__(self, obj: BaseObject) -> None: + super().__init__(obj) + + def __getattr__(self, name: str): + # Call the base method to get a field. + field = super().__getattr__(name) + return wrap_mlir_object(field) + + def get_field_loc(self, name: str): + # Call the base method to get the loc. + loc = super().get_field_loc(name) + return loc + + # Support iterating over an Object by yielding its fields. + def __iter__(self): + for name in self.field_names: + yield (name, getattr(self, name)) + + +# Define the Evaluator class by inheriting from the base implementation in C++. +class Evaluator(BaseEvaluator): + + def __init__(self, mod: Module) -> None: + """Instantiate an Evaluator with a Module.""" + + # Call the base constructor. + super().__init__(mod) + + # Set up logging for diagnostics. + logging.basicConfig( + format="[%(asctime)s] %(name)s (%(levelname)s) %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + level=logging.INFO, + stream=sys.stdout, + ) + self._logger = logging.getLogger("Evaluator") + + # Attach our Diagnostic handler. + mod.context.attach_diagnostic_handler(self._handle_diagnostic) + + def instantiate(self, cls: str, *args: Any) -> Object: + """Instantiate an Object with a class name and actual parameters.""" + + # Convert the class name and actual parameters to Attributes within the + # Evaluator's context. + with self.module.context: + # Get the class name from the class name. + class_name = StringAttr.get(cls) + + # Get the actual parameter Values from the supplied variadic + # arguments. + actual_params = [unwrap_python_object(arg) for arg in args] + + # Call the base instantiate method. + obj = super().instantiate(class_name, actual_params) + + # Return the Object, wrapping the base implementation. + return Object(obj) + + def _handle_diagnostic(self, diagnostic: Diagnostic) -> bool: + """Handle MLIR Diagnostics by logging them.""" + + # Log the diagnostic message at the appropriate level. + if diagnostic.severity == DiagnosticSeverity.ERROR: + self._logger.error(diagnostic.message) + elif diagnostic.severity == DiagnosticSeverity.WARNING: + self._logger.warning(diagnostic.message) + else: + self._logger.info(diagnostic.message) + + # Log any diagnostic notes at the info level. + for note in diagnostic.notes: + self._logger.info(str(note)) + + # Flush the stdout stream to ensure logs appear when expected. + sys.stdout.flush() + + # Return True, indicating this diagnostic has been fully handled. + return True diff --git a/lib/Bindings/Python/dialects/seq.py b/lib/Bindings/Python/dialects/seq.py index 31a5e6de337f..35eba447898d 100644 --- a/lib/Bindings/Python/dialects/seq.py +++ b/lib/Bindings/Python/dialects/seq.py @@ -2,8 +2,13 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +from . import hw +from .._mlir_libs._circt._seq import * +from ..dialects._ods_common import _cext as _ods_cext +from ..ir import IntegerType, OpView, StringAttr +from ..support import BackedgeBuilder, NamedValueOpView from ._seq_ops_gen import * - +from ._seq_ops_gen import _Dialect from .seq import CompRegOp @@ -12,7 +17,7 @@ # signal. If a reset value is provided, the register will reset to that, # otherwise it will reset to zero. If name is provided, the register will be # named. -def reg(value, clock, reset=None, reset_value=None, name=None): +def reg(value, clock, reset=None, reset_value=None, name=None, sym_name=None): from . import hw from ..ir import IntegerAttr value_type = value.type @@ -25,7 +30,143 @@ def reg(value, clock, reset=None, reset_value=None, name=None): clk=clock, reset=reset, reset_value=reset_value, - name=name).data.value + name=name, + sym_name=sym_name).data.value else: - return CompRegOp.create(value_type, input=value, clk=clock, - name=name).data.value + return CompRegOp.create(value_type, + input=value, + clk=clock, + name=name, + sym_name=sym_name).data.value + + +class CompRegLikeBuilder(NamedValueOpView): + + def result_names(self): + return ["data"] + + def create_initial_value(self, index, data_type, arg_name): + if arg_name == "input": + operand_type = data_type + else: + operand_type = IntegerType.get_signless(1) + return BackedgeBuilder.create(operand_type, arg_name, self) + + +class CompRegLike: + + def __init__(self, + data_type, + input, + clk, + clockEnable=None, + *, + reset=None, + reset_value=None, + power_on_value=None, + name=None, + sym_name=None, + loc=None, + ip=None): + operands = [input, clk] + results = [] + attributes = {} + results.append(data_type) + operand_segment_sizes = [1, 1] + if isinstance(self, CompRegOp): + if clockEnable is not None: + raise Exception("Clock enable not supported on compreg") + elif isinstance(self, CompRegClockEnabledOp): + if clockEnable is None: + raise Exception("Clock enable required on compreg.ce") + operands.append(clockEnable) + operand_segment_sizes.append(1) + else: + assert False, "Class not recognized" + if reset is not None and reset_value is not None: + operands.append(reset) + operands.append(reset_value) + operand_segment_sizes += [1, 1] + else: + operand_segment_sizes += [0, 0] + operands += [None, None] + + if power_on_value is not None: + operands.append(power_on_value) + operand_segment_sizes.append(1) + else: + operands.append(None) + operand_segment_sizes.append(0) + if name is None: + attributes["name"] = StringAttr.get("") + else: + attributes["name"] = StringAttr.get(name) + if sym_name is not None: + attributes["inner_sym"] = hw.InnerSymAttr.get(StringAttr.get(sym_name)) + + self._ODS_OPERAND_SEGMENTS = operand_segment_sizes + + OpView.__init__( + self, + self.build_generic( + attributes=attributes, + results=results, + operands=operands, + loc=loc, + ip=ip, + ), + ) + + +class CompRegBuilder(CompRegLikeBuilder): + + def operand_names(self): + return ["input", "clk"] + + +@_ods_cext.register_operation(_Dialect, replace=True) +class CompRegOp(CompRegLike, CompRegOp): + + @classmethod + def create(cls, + result_type, + reset=None, + reset_value=None, + name=None, + sym_name=None, + **kwargs): + return CompRegBuilder(cls, + result_type, + kwargs, + reset=reset, + reset_value=reset_value, + name=name, + sym_name=sym_name, + needs_result_type=True) + + +class CompRegClockEnabledBuilder(CompRegLikeBuilder): + + def operand_names(self): + return ["input", "clk", "clockEnable"] + + +@_ods_cext.register_operation(_Dialect, replace=True) +class CompRegClockEnabledOp(CompRegLike, CompRegClockEnabledOp): + + @classmethod + def create(cls, + result_type, + reset=None, + reset_value=None, + name=None, + sym_name=None, + **kwargs): + return CompRegClockEnabledBuilder(cls, + result_type, + kwargs, + reset=reset, + reset_value=reset_value, + name=name, + sym_name=sym_name, + needs_result_type=True) diff --git a/lib/Bindings/Python/dialects/sv.py b/lib/Bindings/Python/dialects/sv.py index 8c544079dca9..eac42f8cb1ba 100644 --- a/lib/Bindings/Python/dialects/sv.py +++ b/lib/Bindings/Python/dialects/sv.py @@ -2,5 +2,108 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -from ._sv_ops_gen import * +from . import sv, hw +from .. import support from .._mlir_libs._circt._sv import * +from ..dialects._ods_common import _cext as _ods_cext +from ..ir import ArrayAttr, Attribute, FlatSymbolRefAttr, OpView, StringAttr +from ._sv_ops_gen import * +from ._sv_ops_gen import _Dialect + + +@_ods_cext.register_operation(_Dialect, replace=True) +class IfDefOp(IfDefOp): + + def __init__(self, cond: Attribute, *, loc=None, ip=None): + operands = [] + results = [] + attributes = {"cond": cond} + regions = 2 + super().__init__( + self.build_generic(attributes=attributes, + results=results, + operands=operands, + successors=None, + regions=regions, + loc=loc, + ip=ip)) + self.regions[0].blocks.append() + self.regions[1].blocks.append() + + +@_ods_cext.register_operation(_Dialect, replace=True) +class WireOp(WireOp): + + def __init__(self, + data_type, + name, + *, + sym_name=None, + svAttributes=None, + loc=None, + ip=None): + attributes = {"name": StringAttr.get(name)} + if sym_name is not None: + attributes["inner_sym"] = hw.InnerSymAttr.get(StringAttr.get(sym_name)) + if svAttributes is not None: + attributes["svAttributes"] = ArrayAttr.get(svAttributes) + OpView.__init__( + self, + self.build_generic(attributes=attributes, + results=[data_type], + operands=[], + successors=None, + regions=0, + loc=loc, + ip=ip)) + + @staticmethod + def create(data_type, name=None, sym_name=None): + if not isinstance(data_type, hw.InOutType): + data_type = hw.InOutType.get(data_type) + return sv.WireOp(data_type, name, sym_name=sym_name) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class RegOp(RegOp): + + def __init__(self, + data_type, + name, + *, + sym_name=None, + svAttributes=None, + loc=None, + ip=None): + attributes = {"name": StringAttr.get(name)} + if sym_name is not None: + attributes["inner_sym"] = hw.InnerSymAttr.get(StringAttr.get(sym_name)) + if svAttributes is not None: + attributes["svAttributes"] = ArrayAttr.get(svAttributes) + OpView.__init__( + self, + self.build_generic(attributes=attributes, + results=[data_type], + operands=[], + successors=None, + regions=0, + loc=loc, + ip=ip)) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class AssignOp(AssignOp): + + @staticmethod + def create(dest, src): + return sv.AssignOp(dest=dest, src=src) + + +@_ods_cext.register_operation(_Dialect, replace=True) +class ReadInOutOp(ReadInOutOp): + + @staticmethod + def create(value): + value = support.get_value(value) + type = support.get_self_or_inner(value.type).element_type + return sv.ReadInOutOp(value) diff --git a/lib/Bindings/Python/dialects/verif.py b/lib/Bindings/Python/dialects/verif.py new file mode 100644 index 000000000000..8acd8788b8cb --- /dev/null +++ b/lib/Bindings/Python/dialects/verif.py @@ -0,0 +1,5 @@ +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +from ._verif_ops_gen import * diff --git a/lib/Bindings/Python/pyproject.toml b/lib/Bindings/Python/pyproject.toml index 2697ebafee04..9d7a40deebdb 100644 --- a/lib/Bindings/Python/pyproject.toml +++ b/lib/Bindings/Python/pyproject.toml @@ -1,12 +1,12 @@ [build-system] requires = [ "setuptools>=42", - "setuptools_scm>=6.2", + "setuptools_scm==7.1.0", "wheel", "cmake>=3.12", # MLIR build depends. "numpy", - "pybind11>=2.7.1", + "pybind11>=2.9", "PyYAML", ] build-backend = "setuptools.build_meta" diff --git a/lib/Bindings/Python/setup.py b/lib/Bindings/Python/setup.py index 54c1446d6a17..5150b6f260a5 100644 --- a/lib/Bindings/Python/setup.py +++ b/lib/Bindings/Python/setup.py @@ -64,9 +64,18 @@ def run(self): llvm_dir = os.getenv("CIRCT_LLVM_DIR") if not llvm_dir: llvm_dir = os.path.join(circt_dir, "llvm", "llvm") + + # Use Ninja if available. + exist_ninja = shutil.which("ninja") is not None + cmake_generator = ["-G", "Ninja"] if exist_ninja else [] + + # Use lld if available. + exist_lld = shutil.which("lld") is not None + cmake_linker = ["-DLLVM_USE_LINKER=lld"] if exist_lld else [] cmake_args = [ "-DCMAKE_BUILD_TYPE=Release", # not used on MSVC, but no harm "-DCMAKE_INSTALL_PREFIX={}".format(os.path.abspath(cmake_install_dir)), + "-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14", # on OSX, min target for C++17 "-DPython3_EXECUTABLE={}".format(sys.executable.replace("\\", "/")), "-DLLVM_ENABLE_PROJECTS=mlir", "-DLLVM_EXTERNAL_PROJECTS=circt", @@ -74,7 +83,9 @@ def run(self): "-DLLVM_TARGETS_TO_BUILD=host", "-DMLIR_ENABLE_BINDINGS_PYTHON=ON", "-DCIRCT_BINDINGS_PYTHON_ENABLED=ON", - ] + "-DCIRCT_RELEASE_TAG_ENABLED=ON", + "-DCIRCT_RELEASE_TAG=firtool" + ] + cmake_linker + cmake_generator # HACK: CMake fails to auto-detect static linked Python installations, which # happens to be what exists on manylinux. We detect this and give it a dummy diff --git a/lib/Bindings/Python/support.py b/lib/Bindings/Python/support.py index 7b33071c1002..50465a2abb1c 100644 --- a/lib/Bindings/Python/support.py +++ b/lib/Bindings/Python/support.py @@ -86,7 +86,7 @@ def type_to_pytype(t) -> ir.Type: if t.__class__ != ir.Type: return t - from .dialects import esi, hw + from .dialects import esi, hw, seq try: return ir.IntegerType(t) except ValueError: @@ -111,10 +111,18 @@ def type_to_pytype(t) -> ir.Type: return hw.InOutType(t) except ValueError: pass + try: + return seq.ClockType(t) + except ValueError: + pass try: return esi.ChannelType(t) except ValueError: pass + try: + return esi.BundleType(t) + except ValueError: + pass raise TypeError(f"Cannot convert {repr(t)} to python type") @@ -131,9 +139,10 @@ def attribute_to_var(attr): # If it's not the root type, assume it's already been downcasted and don't do # the expensive probing below. - if attr.__class__ != ir.Attribute: - return attr + if attr.__class__ != ir.Attribute and hasattr(attr, "value"): + return attr.value + from .dialects import hw, om try: return ir.BoolAttr(attr).value except ValueError: @@ -142,6 +151,10 @@ def attribute_to_var(attr): return ir.IntegerAttr(attr).value except ValueError: pass + try: + return ir.StringAttr(hw.InnerSymAttr(attr).symName).value + except ValueError: + pass try: return ir.StringAttr(attr).value except ValueError: @@ -164,6 +177,31 @@ def attribute_to_var(attr): return {i.name: attribute_to_var(i.attr) for i in dict} except ValueError: pass + try: + return attribute_to_var(om.ReferenceAttr(attr).inner_ref) + except ValueError: + pass + try: + ref = hw.InnerRefAttr(attr) + return (ir.StringAttr(ref.module).value, ir.StringAttr(ref.name).value) + except ValueError: + pass + try: + return list(map(attribute_to_var, om.ListAttr(attr))) + except ValueError: + pass + try: + return {name: attribute_to_var(value) for name, value in om.MapAttr(attr)} + except ValueError: + pass + try: + return attribute_to_var(om.OMIntegerAttr(attr).integer) + except ValueError: + pass + try: + return om.PathAttr(attr).value + except ValueError: + pass raise TypeError(f"Cannot convert {repr(attr)} to python value") diff --git a/lib/Bindings/Tcl/circt_tcl.cpp b/lib/Bindings/Tcl/circt_tcl.cpp index fed854a84adb..ab86e6c9e351 100644 --- a/lib/Bindings/Tcl/circt_tcl.cpp +++ b/lib/Bindings/Tcl/circt_tcl.cpp @@ -60,7 +60,7 @@ static int loadFirMlirFile(mlir::MLIRContext *context, Tcl_Interp *interp, MlirOperation module; if (!strcmp(Tcl_GetString(objv[1]), "MLIR")) - module = wrap(mlir::parseSourceFile(sourceMgr, context) + module = wrap(mlir::parseSourceFile(sourceMgr, context) .release() .getOperation()); else if (!strcmp(Tcl_GetString(objv[1]), "FIR")) diff --git a/lib/CAPI/CMakeLists.txt b/lib/CAPI/CMakeLists.txt index e1aef61ad35e..5c7865400325 100644 --- a/lib/CAPI/CMakeLists.txt +++ b/lib/CAPI/CMakeLists.txt @@ -1,2 +1,5 @@ +add_subdirectory(Conversion) +add_subdirectory(ExportFIRRTL) add_subdirectory(ExportVerilog) add_subdirectory(Dialect) +add_subdirectory(Firtool) diff --git a/lib/CAPI/Conversion/CMakeLists.txt b/lib/CAPI/Conversion/CMakeLists.txt new file mode 100644 index 000000000000..816594ff4593 --- /dev/null +++ b/lib/CAPI/Conversion/CMakeLists.txt @@ -0,0 +1,34 @@ +add_mlir_public_c_api_library(CIRCTCAPIConversion + Passes.cpp + + LINK_LIBS PUBLIC + CIRCTAffineToLoopSchedule + CIRCTArcToLLVM + CIRCTCalyxToFSM + CIRCTCalyxToHW + CIRCTCalyxNative + CIRCTCombToArith + CIRCTCombToLLVM + CIRCTConvertToArcs + CIRCTDCToHW + CIRCTExportChiselInterface + CIRCTExportVerilog + CIRCTFIRRTLToHW + CIRCTFSMToSV + CIRCTHandshakeToDC + CIRCTHandshakeToHW + CIRCTHWArithToHW + CIRCTHWToLLHD + CIRCTHWToLLVM + CIRCTHWToSV + CIRCTHWToSystemC + CIRCTLLHDToLLVM + CIRCTLoopScheduleToCalyx + CIRCTMooreToCore + CIRCTPipelineToHW + CIRCTSCFToCalyx + CIRCTSeqToSV + CIRCTCFToHandshake + CIRCTVerifToSV +) + diff --git a/lib/CAPI/Conversion/Passes.cpp b/lib/CAPI/Conversion/Passes.cpp new file mode 100644 index 000000000000..ccc5a3b6f318 --- /dev/null +++ b/lib/CAPI/Conversion/Passes.cpp @@ -0,0 +1,26 @@ +//===- Conversion.cpp - C API for Conversion Passes -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/Passes.h" +#include "mlir/CAPI/Pass.h" +#include "mlir/Pass/Pass.h" + +// Must include the declarations as they carry important visibility attributes. +#include "circt/Conversion/Conversion.capi.h.inc" + +using namespace circt; + +#ifdef __cplusplus +extern "C" { +#endif + +#include "circt/Conversion/Conversion.capi.cpp.inc" + +#ifdef __cplusplus +} +#endif diff --git a/lib/CAPI/Dialect/CHIRRTL.cpp b/lib/CAPI/Dialect/CHIRRTL.cpp new file mode 100644 index 000000000000..7381bbcf5844 --- /dev/null +++ b/lib/CAPI/Dialect/CHIRRTL.cpp @@ -0,0 +1,35 @@ +//===- CHIRRTL.cpp - C Interface for the CHIRRTL Dialect ------------------===// +// +//===----------------------------------------------------------------------===// + +#include "circt-c/Dialect/CHIRRTL.h" +#include "circt/Dialect/FIRRTL/CHIRRTLDialect.h" +#include "mlir/CAPI/IR.h" +#include "mlir/CAPI/Registration.h" +#include "mlir/CAPI/Support.h" + +using namespace circt; +using namespace chirrtl; + +//===----------------------------------------------------------------------===// +// Dialect API. +//===----------------------------------------------------------------------===// + +MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(CHIRRTL, chirrtl, + circt::chirrtl::CHIRRTLDialect) + +//===----------------------------------------------------------------------===// +// Type API. +//===----------------------------------------------------------------------===// + +MlirType chirrtlTypeGetCMemory(MlirContext ctx, MlirType elementType, + uint64_t numElements) { + auto baseType = unwrap(elementType).cast(); + assert(baseType && "element must be base type"); + + return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements)); +} + +MlirType chirrtlTypeGetCMemoryPort(MlirContext ctx) { + return wrap(CMemoryPortType::get(unwrap(ctx))); +} diff --git a/lib/CAPI/Dialect/CMakeLists.txt b/lib/CAPI/Dialect/CMakeLists.txt index 7b97422e89b6..61e0e7104d51 100644 --- a/lib/CAPI/Dialect/CMakeLists.txt +++ b/lib/CAPI/Dialect/CMakeLists.txt @@ -3,15 +3,19 @@ set(LLVM_OPTIONAL_SOURCES Comb.cpp ESI.cpp FIRRTL.cpp + CHIRRTL.cpp MSFT.cpp HW.cpp HWArith.cpp LLHD.cpp + LTL.cpp Moore.cpp + OM.cpp Seq.cpp SV.cpp FSM.cpp Handshake.cpp + Verif.cpp ) add_mlir_public_c_api_library(CIRCTCAPIComb @@ -38,6 +42,14 @@ add_mlir_public_c_api_library(CIRCTCAPIFIRRTL CIRCTFIRRTL ) +add_mlir_public_c_api_library(CIRCTCAPICHIRRTL + CHIRRTL.cpp + + LINK_LIBS PUBLIC + MLIRCAPIIR + CIRCTFIRRTL +) + add_mlir_public_c_api_library(CIRCTCAPIMSFT MSFT.cpp @@ -75,6 +87,15 @@ add_mlir_public_c_api_library(CIRCTCAPIMoore CIRCTMoore ) +add_mlir_public_c_api_library(CIRCTCAPIOM + OM.cpp + + LINK_LIBS PUBLIC + MLIRCAPIIR + CIRCTOM + CIRCTOMEvaluator +) + add_mlir_public_c_api_library(CIRCTCAPISeq Seq.cpp @@ -111,7 +132,7 @@ add_mlir_public_c_api_library(CIRCTCAPIHandshake CIRCTHandshake CIRCTHandshakeTransforms CIRCTHandshakeToHW - CIRCTStandardToHandshake + CIRCTCFToHandshake ) add_mlir_public_c_api_library(CIRCTCAPIHWArith @@ -122,3 +143,20 @@ add_mlir_public_c_api_library(CIRCTCAPIHWArith CIRCTHWArith CIRCTHWArithToHW ) + +add_mlir_public_c_api_library(CIRCTCAPIVerif + Verif.cpp + + LINK_LIBS PUBLIC + MLIRCAPIIR + CIRCTVerif + CIRCTVerifToSV +) + +add_mlir_public_c_api_library(CIRCTCAPILTL + LTL.cpp + + LINK_LIBS PUBLIC + MLIRCAPIIR + CIRCTLTL +) diff --git a/lib/CAPI/Dialect/ESI.cpp b/lib/CAPI/Dialect/ESI.cpp index c682008170b3..2e59fa3ed421 100644 --- a/lib/CAPI/Dialect/ESI.cpp +++ b/lib/CAPI/Dialect/ESI.cpp @@ -3,6 +3,7 @@ //===----------------------------------------------------------------------===// #include "circt-c/Dialect/ESI.h" +#include "circt/Dialect/ESI/AppID.h" #include "circt/Dialect/ESI/ESIServices.h" #include "circt/Dialect/ESI/ESITypes.h" #include "mlir/CAPI/IR.h" @@ -14,20 +15,13 @@ #include "mlir/Parser/Parser.h" #include "mlir/Support/FileUtilities.h" +using namespace circt; using namespace circt::esi; -using namespace mlir; MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(ESI, esi, circt::esi::ESIDialect) void registerESIPasses() { circt::esi::registerESIPasses(); } -MlirLogicalResult circtESIExportCosimSchema(MlirModule module, - MlirStringCallback callback, - void *userData) { - mlir::detail::CallbackOstream stream(callback, userData); - return wrap(circt::esi::exportCosimSchema(unwrap(module), stream)); -} - bool circtESITypeIsAChannelType(MlirType type) { return unwrap(type).isa(); } @@ -54,23 +48,23 @@ MlirType circtESIAnyTypeGet(MlirContext ctxt) { return wrap(AnyType::get(unwrap(ctxt))); } -MlirOperation circtESIWrapModule(MlirOperation cModOp, long numPorts, - const MlirStringRef *ports) { - mlir::Operation *modOp = unwrap(cModOp); - llvm::SmallVector portNamesRefs; - for (long i = 0; i < numPorts; ++i) - portNamesRefs.push_back(ports[i].data); - llvm::SmallVector portTriples; - resolvePortNames(modOp, portNamesRefs, portTriples); - mlir::OpBuilder b(modOp); - mlir::Operation *wrapper = buildESIWrapper(b, modOp, portTriples); - return wrap(wrapper); +bool circtESITypeIsAListType(MlirType type) { + return unwrap(type).isa(); +} + +MlirType circtESIListTypeGet(MlirType inner) { + auto cppInner = unwrap(inner); + return wrap(ListType::get(cppInner.getContext(), cppInner)); +} + +MlirType circtESIListTypeGetElementType(MlirType list) { + return wrap(unwrap(list).cast().getElementType()); } void circtESIAppendMlirFile(MlirModule cMod, MlirStringRef filename) { ModuleOp modOp = unwrap(cMod); auto loadedMod = - parseSourceFile(unwrap(filename), modOp.getContext()); + mlir::parseSourceFile(unwrap(filename), modOp.getContext()); Block *loadedBlock = loadedMod->getBody(); assert(!modOp->getRegions().empty()); if (modOp.getBodyRegion().empty()) { @@ -93,3 +87,121 @@ void circtESIRegisterGlobalServiceGenerator( return unwrap(genFunc(wrap(req), wrap(decl.getOperation()), userData)); }); } +//===----------------------------------------------------------------------===// +// Channel bundles +//===----------------------------------------------------------------------===// + +bool circtESITypeIsABundleType(MlirType type) { + return isa(unwrap(type)); +} +MlirType circtESIBundleTypeGet(MlirContext cctxt, size_t numChannels, + const CirctESIBundleTypeBundleChannel *channels, + bool resettable) { + MLIRContext *ctxt = unwrap(cctxt); + SmallVector channelsVector(llvm::map_range( + ArrayRef(channels, numChannels), + [](auto channel) { + return BundledChannel{cast(unwrap(channel.name)), + (ChannelDirection)channel.direction, + cast(unwrap(channel.channelType))}; + })); + return wrap(ChannelBundleType::get( + ctxt, channelsVector, resettable ? UnitAttr::get(ctxt) : UnitAttr())); +} +bool circtESIBundleTypeGetResettable(MlirType bundle) { + return cast(unwrap(bundle)).getResettable() != UnitAttr(); +} +size_t circtESIBundleTypeGetNumChannels(MlirType bundle) { + return cast(unwrap(bundle)).getChannels().size(); +} +CirctESIBundleTypeBundleChannel circtESIBundleTypeGetChannel(MlirType bundle, + size_t idx) { + BundledChannel channel = + cast(unwrap(bundle)).getChannels()[idx]; + return CirctESIBundleTypeBundleChannel{ + wrap(channel.name), (unsigned)channel.direction, wrap(channel.type)}; +} + +//===----------------------------------------------------------------------===// +// AppID +//===----------------------------------------------------------------------===// + +bool circtESIAttributeIsAnAppIDAttr(MlirAttribute attr) { + return unwrap(attr).isa(); +} + +MlirAttribute circtESIAppIDAttrGet(MlirContext ctxt, MlirStringRef name, + uint64_t index) { + return wrap(AppIDAttr::get( + unwrap(ctxt), StringAttr::get(unwrap(ctxt), unwrap(name)), index)); +} +MlirStringRef circtESIAppIDAttrGetName(MlirAttribute attr) { + return wrap(unwrap(attr).cast().getName().getValue()); +} +bool circtESIAppIDAttrGetIndex(MlirAttribute attr, uint64_t *indexOut) { + std::optional index = unwrap(attr).cast().getIndex(); + if (!index) + return false; + *indexOut = index.value(); + return true; +} + +bool circtESIAttributeIsAnAppIDPathAttr(MlirAttribute attr) { + return isa(unwrap(attr)); +} + +MlirAttribute circtESIAppIDAttrPathGet(MlirContext ctxt, MlirAttribute root, + intptr_t numElements, + MlirAttribute const *cElements) { + SmallVector elements; + for (intptr_t i = 0; i < numElements; ++i) + elements.push_back(cast(unwrap(cElements[i]))); + return wrap(AppIDPathAttr::get( + unwrap(ctxt), cast(unwrap(root)), elements)); +} +MlirAttribute circtESIAppIDAttrPathGetRoot(MlirAttribute attr) { + return wrap(cast(unwrap(attr)).getRoot()); +} +uint64_t circtESIAppIDAttrPathGetNumComponents(MlirAttribute attr) { + return cast(unwrap(attr)).getPath().size(); +} +MlirAttribute circtESIAppIDAttrPathGetComponent(MlirAttribute attr, + uint64_t index) { + return wrap(cast(unwrap(attr)).getPath()[index]); +} + +DEFINE_C_API_PTR_METHODS(CirctESIAppIDIndex, circt::esi::AppIDIndex) + +/// Create an index of appids through which to do appid lookups efficiently. +MLIR_CAPI_EXPORTED CirctESIAppIDIndex +circtESIAppIDIndexGet(MlirOperation root) { + auto *idx = new AppIDIndex(unwrap(root)); + if (idx->isValid()) + return wrap(idx); + return CirctESIAppIDIndex{nullptr}; +} + +/// Free an AppIDIndex. +MLIR_CAPI_EXPORTED void circtESIAppIDIndexFree(CirctESIAppIDIndex index) { + delete unwrap(index); +} + +MLIR_CAPI_EXPORTED MlirAttribute +circtESIAppIDIndexGetChildAppIDsOf(CirctESIAppIDIndex idx, MlirOperation op) { + auto mod = cast(unwrap(op)); + return wrap(unwrap(idx)->getChildAppIDsOf(mod)); +} + +MLIR_CAPI_EXPORTED +MlirAttribute circtESIAppIDIndexGetAppIDPath(CirctESIAppIDIndex idx, + MlirOperation fromMod, + MlirAttribute appid, + MlirLocation loc) { + auto mod = cast(unwrap(fromMod)); + auto path = cast(unwrap(appid)); + FailureOr instPath = + unwrap(idx)->getAppIDPathAttr(mod, path, unwrap(loc)); + if (failed(instPath)) + return MlirAttribute{nullptr}; + return wrap(*instPath); +} diff --git a/lib/CAPI/Dialect/FIRRTL.cpp b/lib/CAPI/Dialect/FIRRTL.cpp index de69955859bc..36573e6aad7d 100644 --- a/lib/CAPI/Dialect/FIRRTL.cpp +++ b/lib/CAPI/Dialect/FIRRTL.cpp @@ -3,10 +3,188 @@ //===----------------------------------------------------------------------===// #include "circt-c/Dialect/FIRRTL.h" +#include "circt/Dialect/FIRRTL/FIRRTLAttributes.h" #include "circt/Dialect/FIRRTL/FIRRTLDialect.h" +#include "circt/Dialect/FIRRTL/FIRRTLTypes.h" #include "mlir/CAPI/IR.h" #include "mlir/CAPI/Registration.h" #include "mlir/CAPI/Support.h" +using namespace circt; +using namespace firrtl; + +//===----------------------------------------------------------------------===// +// Dialect API. +//===----------------------------------------------------------------------===// + MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(FIRRTL, firrtl, circt::firrtl::FIRRTLDialect) + +//===----------------------------------------------------------------------===// +// Type API. +//===----------------------------------------------------------------------===// + +MlirType firrtlTypeGetUInt(MlirContext ctx, int32_t width) { + return wrap(UIntType::get(unwrap(ctx), width)); +} + +MlirType firrtlTypeGetSInt(MlirContext ctx, int32_t width) { + return wrap(SIntType::get(unwrap(ctx), width)); +} + +MlirType firrtlTypeGetClock(MlirContext ctx) { + return wrap(ClockType::get(unwrap(ctx))); +} + +MlirType firrtlTypeGetReset(MlirContext ctx) { + return wrap(ResetType::get(unwrap(ctx))); +} + +MlirType firrtlTypeGetAsyncReset(MlirContext ctx) { + return wrap(AsyncResetType::get(unwrap(ctx))); +} + +MlirType firrtlTypeGetAnalog(MlirContext ctx, int32_t width) { + return wrap(AnalogType::get(unwrap(ctx), width)); +} + +MlirType firrtlTypeGetVector(MlirContext ctx, MlirType element, size_t count) { + auto baseType = unwrap(element).cast(); + assert(baseType && "element must be base type"); + + return wrap(FVectorType::get(baseType, count)); +} + +MlirType firrtlTypeGetBundle(MlirContext ctx, size_t count, + const FIRRTLBundleField *fields) { + SmallVector bundleFields; + bundleFields.reserve(count); + + for (size_t i = 0; i < count; i++) { + auto field = fields[i]; + + auto baseType = unwrap(field.type).dyn_cast(); + assert(baseType && "field must be base type"); + + bundleFields.emplace_back(unwrap(field.name), field.isFlip, baseType); + } + return wrap(BundleType::get(unwrap(ctx), bundleFields)); +} + +//===----------------------------------------------------------------------===// +// Attribute API. +//===----------------------------------------------------------------------===// + +MlirAttribute firrtlAttrGetConvention(MlirContext ctx, + FIRRTLConvention convention) { + Convention value; + + switch (convention) { + case FIRRTL_CONVENTION_INTERNAL: + value = Convention::Internal; + break; + case FIRRTL_CONVENTION_SCALARIZED: + value = Convention::Scalarized; + break; + } + + return wrap(ConventionAttr::get(unwrap(ctx), value)); +} + +MlirAttribute firrtlAttrGetPortDirs(MlirContext ctx, size_t count, + const FIRRTLPortDir *dirs) { + static_assert(FIRRTLPortDir::FIRRTL_PORT_DIR_INPUT == + static_cast>(Direction::In)); + static_assert(FIRRTLPortDir::FIRRTL_PORT_DIR_OUTPUT == + static_cast>(Direction::Out)); + + // FIXME: The `reinterpret_cast` here may voilate strict aliasing rule. Is + // there a better way? + return wrap(direction::packAttribute( + unwrap(ctx), ArrayRef(reinterpret_cast(dirs), count))); +} + +MlirAttribute firrtlAttrGetParamDecl(MlirContext ctx, MlirIdentifier name, + MlirType type, MlirAttribute value) { + return wrap(ParamDeclAttr::get(unwrap(ctx), unwrap(name), unwrap(type), + unwrap(value))); +} + +MlirAttribute firrtlAttrGetNameKind(MlirContext ctx, FIRRTLNameKind nameKind) { + NameKindEnum value; + + switch (nameKind) { + case FIRRTL_NAME_KIND_DROPPABLE_NAME: + value = NameKindEnum::DroppableName; + break; + case FIRRTL_NAME_KIND_INTERESTING_NAME: + value = NameKindEnum::InterestingName; + break; + } + + return wrap(NameKindEnumAttr::get(unwrap(ctx), value)); +} + +MlirAttribute firrtlAttrGetRUW(MlirContext ctx, FIRRTLRUW ruw) { + RUWAttr value; + + switch (ruw) { + case FIRRTL_RUW_UNDEFINED: + value = RUWAttr::Undefined; + break; + case FIRRTL_RUW_OLD: + value = RUWAttr::Old; + break; + case FIRRTL_RUW_NEW: + value = RUWAttr::New; + break; + } + + return wrap(RUWAttrAttr::get(unwrap(ctx), value)); +} + +MlirAttribute firrtlAttrGetMemInit(MlirContext ctx, MlirIdentifier filename, + bool isBinary, bool isInline) { + return wrap( + MemoryInitAttr::get(unwrap(ctx), unwrap(filename), isBinary, isInline)); +} + +MlirAttribute firrtlAttrGetMemDir(MlirContext ctx, FIRRTLMemDir dir) { + MemDirAttr value; + + switch (dir) { + case FIRRTL_MEM_DIR_INFER: + value = MemDirAttr::Infer; + break; + case FIRRTL_MEM_DIR_READ: + value = MemDirAttr::Read; + break; + case FIRRTL_MEM_DIR_WRITE: + value = MemDirAttr::Write; + break; + case FIRRTL_MEM_DIR_READ_WRITE: + value = MemDirAttr::ReadWrite; + break; + } + + return wrap(MemDirAttrAttr::get(unwrap(ctx), value)); +} + +MlirAttribute firrtlAttrGetEventControl(MlirContext ctx, + FIRRTLEventControl eventControl) { + EventControl value; + + switch (eventControl) { + case FIRRTL_EVENT_CONTROL_AT_POS_EDGE: + value = EventControl::AtPosEdge; + break; + case FIRRTL_EVENT_CONTROL_AT_NEG_EDGE: + value = EventControl::AtNegEdge; + break; + case FIRRTL_EVENT_CONTROL_AT_EDGE: + value = EventControl::AtEdge; + break; + } + + return wrap(EventControlAttr::get(unwrap(ctx), value)); +} diff --git a/lib/CAPI/Dialect/HW.cpp b/lib/CAPI/Dialect/HW.cpp index c9358b77927e..3cdaf8836602 100644 --- a/lib/CAPI/Dialect/HW.cpp +++ b/lib/CAPI/Dialect/HW.cpp @@ -47,13 +47,13 @@ MlirType hwArrayTypeGetElementType(MlirType type) { } intptr_t hwArrayTypeGetSize(MlirType type) { - return unwrap(type).cast().getSize(); + return unwrap(type).cast().getNumElements(); } bool hwTypeIsAIntType(MlirType type) { return unwrap(type).isa(); } MlirType hwParamIntTypeGet(MlirAttribute parameter) { - return wrap(IntType::get(unwrap(parameter))); + return wrap(IntType::get(unwrap(parameter).cast())); } MlirAttribute hwParamIntTypeGetWidthAttr(MlirType type) { @@ -70,6 +70,62 @@ MlirType hwInOutTypeGetElementType(MlirType type) { bool hwTypeIsAInOut(MlirType type) { return unwrap(type).isa(); } +bool hwTypeIsAModuleType(MlirType type) { + return isa(unwrap(type)); +} + +MlirType hwModuleTypeGet(MlirContext ctx, intptr_t numPorts, + HWModulePort const *ports) { + SmallVector modulePorts; + for (intptr_t i = 0; i < numPorts; ++i) { + HWModulePort port = ports[i]; + + ModulePort::Direction dir; + switch (port.dir) { + case HWModulePortDirection::Input: + dir = ModulePort::Direction::Input; + break; + case HWModulePortDirection::Output: + dir = ModulePort::Direction::Output; + break; + case HWModulePortDirection::InOut: + dir = ModulePort::Direction::InOut; + break; + } + + StringAttr name = cast(unwrap(port.name)); + Type type = unwrap(port.type); + + modulePorts.push_back(ModulePort{name, type, dir}); + } + + return wrap(ModuleType::get(unwrap(ctx), modulePorts)); +} + +intptr_t hwModuleTypeGetNumInputs(MlirType type) { + return cast(unwrap(type)).getNumInputs(); +} + +MlirType hwModuleTypeGetInputType(MlirType type, intptr_t index) { + return wrap(cast(unwrap(type)).getInputType(index)); +} + +MlirStringRef hwModuleTypeGetInputName(MlirType type, intptr_t index) { + return wrap(cast(unwrap(type)).getInputName(index)); +} + +intptr_t hwModuleTypeGetNumOutputs(MlirType type) { + return cast(unwrap(type)).getNumOutputs(); +} + +MlirType hwModuleTypeGetOutputType(MlirType type, intptr_t index) { + return wrap(cast(unwrap(type)).getOutputType(index)); +} + +MlirStringRef hwModuleTypeGetOutputName(MlirType type, intptr_t index) { + return wrap(cast(unwrap(type)).getOutputName(index)); +} + bool hwTypeIsAStructType(MlirType type) { return unwrap(type).isa(); } @@ -90,6 +146,14 @@ MlirType hwStructTypeGetField(MlirType structType, MlirStringRef fieldName) { return wrap(st.getFieldType(unwrap(fieldName))); } +MlirAttribute hwStructTypeGetFieldIndex(MlirType structType, + MlirStringRef fieldName) { + StructType st = unwrap(structType).cast(); + if (auto idx = st.getFieldIndex(unwrap(fieldName))) + return wrap(IntegerAttr::get(IntegerType::get(st.getContext(), 32), *idx)); + return wrap(UnitAttr::get(st.getContext())); +} + intptr_t hwStructTypeGetNumFields(MlirType structType) { StructType st = unwrap(structType).cast(); return st.getElements().size(); @@ -144,6 +208,19 @@ MlirStringRef hwTypeAliasTypeGetScope(MlirType typeAlias) { // Attribute API. //===----------------------------------------------------------------------===// +bool hwAttrIsAInnerSymAttr(MlirAttribute attr) { + return unwrap(attr).isa(); +} + +MlirAttribute hwInnerSymAttrGet(MlirAttribute symName) { + return wrap(InnerSymAttr::get(unwrap(symName).cast())); +} + +MlirAttribute hwInnerSymAttrGetSymName(MlirAttribute innerSymAttr) { + return wrap( + (Attribute)unwrap(innerSymAttr).cast().getSymName()); +} + bool hwAttrIsAInnerRefAttr(MlirAttribute attr) { return unwrap(attr).isa(); } @@ -163,15 +240,6 @@ MlirAttribute hwInnerRefAttrGetModule(MlirAttribute innerRefAttr) { return wrap((Attribute)unwrap(innerRefAttr).cast().getModule()); } -bool hwAttrIsAGlobalRefAttr(MlirAttribute attr) { - return unwrap(attr).isa(); -} - -MlirAttribute hwGlobalRefAttrGet(MlirAttribute symName) { - auto symbolRef = FlatSymbolRefAttr::get(unwrap(symName).cast()); - return wrap(GlobalRefAttr::get(symbolRef.getContext(), symbolRef)); -} - MLIR_CAPI_EXPORTED bool hwAttrIsAParamDeclAttr(MlirAttribute attr) { return unwrap(attr).isa(); } @@ -220,3 +288,15 @@ MLIR_CAPI_EXPORTED MlirAttribute hwParamVerbatimAttrGet(MlirAttribute text) { auto type = NoneType::get(ctx); return wrap(ParamVerbatimAttr::get(ctx, textAttr, type)); } + +MLIR_CAPI_EXPORTED bool hwAttrIsAOutputFileAttr(MlirAttribute attr) { + return unwrap(attr).isa(); +} +MLIR_CAPI_EXPORTED MlirAttribute +hwOutputFileGetFromFileName(MlirAttribute fileName, bool excludeFromFileList, + bool includeReplicatedOp) { + auto fileNameStrAttr = unwrap(fileName).cast(); + return wrap(OutputFileAttr::getFromFilename( + fileNameStrAttr.getContext(), fileNameStrAttr.getValue(), + excludeFromFileList, includeReplicatedOp)); +} diff --git a/lib/CAPI/Dialect/Handshake.cpp b/lib/CAPI/Dialect/Handshake.cpp index cf0aefedceb8..50a97556f9f0 100644 --- a/lib/CAPI/Dialect/Handshake.cpp +++ b/lib/CAPI/Dialect/Handshake.cpp @@ -15,7 +15,7 @@ void registerHandshakePasses() { circt::handshake::registerPasses(); - circt::registerStandardToHandshakePass(); + circt::registerCFToHandshakePass(); circt::registerHandshakeToHWPass(); } MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(Handshake, handshake, diff --git a/lib/CAPI/Dialect/LTL.cpp b/lib/CAPI/Dialect/LTL.cpp new file mode 100644 index 000000000000..ba801a939941 --- /dev/null +++ b/lib/CAPI/Dialect/LTL.cpp @@ -0,0 +1,14 @@ +//===- Ltl.cpp - C Interface for the Ltl Dialect -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt-c/Dialect/LTL.h" +#include "circt/Dialect/LTL/LTLDialect.h" + +#include "mlir/CAPI/Registration.h" + +MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(LTL, ltl, circt::ltl::LTLDialect) diff --git a/lib/CAPI/Dialect/MSFT.cpp b/lib/CAPI/Dialect/MSFT.cpp index c2949dce750f..d1d244a835ea 100644 --- a/lib/CAPI/Dialect/MSFT.cpp +++ b/lib/CAPI/Dialect/MSFT.cpp @@ -231,19 +231,3 @@ MlirAttribute circtMSFTLocationVectorAttrGetElement(MlirAttribute attr, intptr_t pos) { return wrap(unwrap(attr).cast().getLocs()[pos]); } - -bool circtMSFTAttributeIsAnAppIDAttr(MlirAttribute attr) { - return unwrap(attr).isa(); -} - -MlirAttribute circtMSFTAppIDAttrGet(MlirContext ctxt, MlirStringRef name, - uint64_t index) { - return wrap(AppIDAttr::get( - unwrap(ctxt), StringAttr::get(unwrap(ctxt), unwrap(name)), index)); -} -MlirStringRef circtMSFTAppIDAttrGetName(MlirAttribute attr) { - return wrap(unwrap(attr).cast().getName().getValue()); -} -uint64_t circtMSFTAppIDAttrGetIndex(MlirAttribute attr) { - return unwrap(attr).cast().getIndex(); -} diff --git a/lib/CAPI/Dialect/OM.cpp b/lib/CAPI/Dialect/OM.cpp new file mode 100644 index 000000000000..7ab0e893f890 --- /dev/null +++ b/lib/CAPI/Dialect/OM.cpp @@ -0,0 +1,402 @@ +//===- OM.cpp - C Interface for the OM Dialect ----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Implements a C Interface for the OM Dialect +// +//===----------------------------------------------------------------------===// + +#include "circt-c/Dialect/OM.h" +#include "circt/Dialect/OM/Evaluator/Evaluator.h" +#include "circt/Dialect/OM/OMAttributes.h" +#include "circt/Dialect/OM/OMDialect.h" +#include "mlir/CAPI/Registration.h" +#include "mlir/CAPI/Wrap.h" +#include "mlir/IR/Location.h" +#include "llvm/ADT/Hashing.h" +#include "llvm/Support/Casting.h" + +using namespace mlir; +using namespace circt::om; + +//===----------------------------------------------------------------------===// +// Dialect API. +//===----------------------------------------------------------------------===// + +MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(OM, om, OMDialect) + +//===----------------------------------------------------------------------===// +// Type API. +//===----------------------------------------------------------------------===// + +/// Is the Type a ClassType. +bool omTypeIsAClassType(MlirType type) { return unwrap(type).isa(); } + +/// Get the TypeID for a ClassType. +MlirTypeID omClassTypeGetTypeID() { return wrap(ClassType::getTypeID()); } + +/// Get the name for a ClassType. +MlirIdentifier omClassTypeGetName(MlirType type) { + return wrap(cast(unwrap(type)).getClassName().getAttr()); +} + +/// Is the Type a FrozenBasePathType. +bool omTypeIsAFrozenBasePathType(MlirType type) { + return isa(unwrap(type)); +} + +/// Get the TypeID for a FrozenBasePathType. +MlirTypeID omFrozenBasePathTypeGetTypeID(void) { + return wrap(FrozenBasePathType::getTypeID()); +} + +/// Is the Type a FrozenPathType. +bool omTypeIsAFrozenPathType(MlirType type) { + return isa(unwrap(type)); +} + +/// Get the TypeID for a FrozenPathType. +MlirTypeID omFrozenPathTypeGetTypeID(void) { + return wrap(FrozenPathType::getTypeID()); +} + +/// Is the Type a StringType. +bool omTypeIsAStringType(MlirType type) { + return unwrap(type).isa(); +} + +/// Get a StringType. +MlirType omStringTypeGet(MlirContext ctx) { + return wrap(StringType::get(unwrap(ctx))); +} + +/// Return a key type of a map. +MlirType omMapTypeGetKeyType(MlirType type) { + return wrap(unwrap(type).cast().getKeyType()); +} + +//===----------------------------------------------------------------------===// +// Evaluator data structures. +//===----------------------------------------------------------------------===// + +DEFINE_C_API_PTR_METHODS(OMEvaluator, circt::om::Evaluator) + +/// Define our own wrap and unwrap instead of using the usual macro. This is To +/// handle the std::shared_ptr reference counts appropriately. We want to always +/// create *new* shared pointers to the EvaluatorValue when we wrap it for C, to +/// increment the reference count. We want to use the shared_from_this +/// functionality to ensure it is unwrapped into C++ with the correct reference +/// count. + +static inline OMEvaluatorValue wrap(EvaluatorValuePtr object) { + return OMEvaluatorValue{ + static_cast((new EvaluatorValuePtr(std::move(object)))->get())}; +} + +static inline EvaluatorValuePtr unwrap(OMEvaluatorValue c) { + return static_cast(c.ptr)->shared_from_this(); +} + +//===----------------------------------------------------------------------===// +// Evaluator API. +//===----------------------------------------------------------------------===// + +/// Construct an Evaluator with an IR module. +OMEvaluator omEvaluatorNew(MlirModule mod) { + // Just allocate and wrap the Evaluator. + return wrap(new Evaluator(unwrap(mod))); +} + +/// Use the Evaluator to Instantiate an Object from its class name and actual +/// parameters. +OMEvaluatorValue omEvaluatorInstantiate(OMEvaluator evaluator, + MlirAttribute className, + intptr_t nActualParams, + OMEvaluatorValue *actualParams) { + // Unwrap the Evaluator. + Evaluator *cppEvaluator = unwrap(evaluator); + + // Unwrap the className, which the client must supply as a StringAttr. + StringAttr cppClassName = unwrap(className).cast(); + + // Unwrap the actual parameters. + SmallVector> cppActualParams; + for (unsigned i = 0; i < nActualParams; i++) + cppActualParams.push_back(unwrap(actualParams[i])); + + // Invoke the Evaluator to instantiate the Object. + auto result = cppEvaluator->instantiate(cppClassName, cppActualParams); + + // If instantiation failed, return a null Object. A Diagnostic will be emitted + // in this case. + if (failed(result)) + return OMEvaluatorValue(); + + // Wrap and return the Object. + return wrap(result.value()); +} + +/// Get the Module the Evaluator is built from. +MlirModule omEvaluatorGetModule(OMEvaluator evaluator) { + // Just unwrap the Evaluator, get the Module, and wrap it. + return wrap(unwrap(evaluator)->getModule()); +} + +//===----------------------------------------------------------------------===// +// Object API. +//===----------------------------------------------------------------------===// + +/// Query if the Object is null. +bool omEvaluatorObjectIsNull(OMEvaluatorValue object) { + // Just check if the Object shared pointer is null. + return !object.ptr; +} + +/// Get the Type from an Object, which will be a ClassType. +MlirType omEvaluatorObjectGetType(OMEvaluatorValue object) { + return wrap(llvm::cast(unwrap(object).get())->getType()); +} + +/// Get the hash for the object. +unsigned omEvaluatorObjectGetHash(OMEvaluatorValue object) { + return llvm::hash_value(llvm::cast(unwrap(object).get())); +} + +/// Check if two objects are same. +bool omEvaluatorObjectIsEq(OMEvaluatorValue object, OMEvaluatorValue other) { + return llvm::cast(unwrap(object).get()) == + llvm::cast(unwrap(other).get()); +} + +/// Get an ArrayAttr with the names of the fields in an Object. +MlirAttribute omEvaluatorObjectGetFieldNames(OMEvaluatorValue object) { + return wrap(llvm::cast(unwrap(object).get())->getFieldNames()); +} + +MlirType omEvaluatorMapGetType(OMEvaluatorValue value) { + return wrap(llvm::cast(unwrap(value).get())->getType()); +} + +/// Get an ArrayAttr with the keys in a Map. +MlirAttribute omEvaluatorMapGetKeys(OMEvaluatorValue object) { + return wrap(llvm::cast(unwrap(object).get())->getKeys()); +} + +/// Get a field from an Object, which must contain a field of that name. +OMEvaluatorValue omEvaluatorObjectGetField(OMEvaluatorValue object, + MlirAttribute name) { + // Unwrap the Object and get the field of the name, which the client must + // supply as a StringAttr. + FailureOr result = + llvm::cast(unwrap(object).get()) + ->getField(unwrap(name).cast()); + + // If getField failed, return a null EvaluatorValue. A Diagnostic will be + // emitted in this case. + if (failed(result)) + return OMEvaluatorValue(); + + return OMEvaluatorValue{wrap(result.value())}; +} + +//===----------------------------------------------------------------------===// +// EvaluatorValue API. +//===----------------------------------------------------------------------===// + +// Get a context from an EvaluatorValue. +MlirContext omEvaluatorValueGetContext(OMEvaluatorValue evaluatorValue) { + return wrap(unwrap(evaluatorValue)->getContext()); +} + +// Get location from an EvaluatorValue. +MlirLocation omEvaluatorValueGetLoc(OMEvaluatorValue evaluatorValue) { + return wrap(unwrap(evaluatorValue)->getLoc()); +} + +// Query if the EvaluatorValue is null. +bool omEvaluatorValueIsNull(OMEvaluatorValue evaluatorValue) { + // Check if the pointer is null. + return !evaluatorValue.ptr; +} + +/// Query if the EvaluatorValue is an Object. +bool omEvaluatorValueIsAObject(OMEvaluatorValue evaluatorValue) { + // Check if the Object is non-null. + return isa(unwrap(evaluatorValue).get()); +} + +/// Query if the EvaluatorValue is a Primitive. +bool omEvaluatorValueIsAPrimitive(OMEvaluatorValue evaluatorValue) { + // Check if the Attribute is non-null. + return isa(unwrap(evaluatorValue).get()); +} + +/// Get the Primitive from an EvaluatorValue, which must contain a Primitive. +MlirAttribute omEvaluatorValueGetPrimitive(OMEvaluatorValue evaluatorValue) { + // Assert the Attribute is non-null, and return it. + assert(omEvaluatorValueIsAPrimitive(evaluatorValue)); + return wrap( + llvm::cast(unwrap(evaluatorValue).get()) + ->getAttr()); +} + +/// Get the Primitive from an EvaluatorValue, which must contain a Primitive. +OMEvaluatorValue omEvaluatorValueFromPrimitive(MlirAttribute primitive) { + // Assert the Attribute is non-null, and return it. + return wrap(std::make_shared(unwrap(primitive))); +} + +/// Query if the EvaluatorValue is a List. +bool omEvaluatorValueIsAList(OMEvaluatorValue evaluatorValue) { + return isa(unwrap(evaluatorValue).get()); +} + +/// Get the List from an EvaluatorValue, which must contain a List. +/// TODO: This can be removed. +OMEvaluatorValue omEvaluatorValueGetList(OMEvaluatorValue evaluatorValue) { + // Assert the List is non-null, and return it. + assert(omEvaluatorValueIsAList(evaluatorValue)); + return evaluatorValue; +} + +/// Get the length of the List. +intptr_t omEvaluatorListGetNumElements(OMEvaluatorValue evaluatorValue) { + return cast(unwrap(evaluatorValue).get()) + ->getElements() + .size(); +} + +/// Get an element of the List. +OMEvaluatorValue omEvaluatorListGetElement(OMEvaluatorValue evaluatorValue, + intptr_t pos) { + return wrap(cast(unwrap(evaluatorValue).get()) + ->getElements()[pos]); +} + +/// Query if the EvaluatorValue is a Tuple. +bool omEvaluatorValueIsATuple(OMEvaluatorValue evaluatorValue) { + return isa(unwrap(evaluatorValue).get()); +} + +/// Get the length of the Tuple. +intptr_t omEvaluatorTupleGetNumElements(OMEvaluatorValue evaluatorValue) { + return cast(unwrap(evaluatorValue).get()) + ->getElements() + .size(); +} + +/// Get an element of the Tuple. +OMEvaluatorValue omEvaluatorTupleGetElement(OMEvaluatorValue evaluatorValue, + intptr_t pos) { + return wrap(cast(unwrap(evaluatorValue).get()) + ->getElements()[pos]); +} + +/// Get an element of the Map. +OMEvaluatorValue omEvaluatorMapGetElement(OMEvaluatorValue evaluatorValue, + MlirAttribute attr) { + const auto &elements = + cast(unwrap(evaluatorValue).get())->getElements(); + const auto &it = elements.find(unwrap(attr)); + if (it != elements.end()) + return wrap(it->second); + return OMEvaluatorValue{nullptr}; +} + +/// Query if the EvaluatorValue is a map. +bool omEvaluatorValueIsAMap(OMEvaluatorValue evaluatorValue) { + return isa(unwrap(evaluatorValue).get()); +} + +bool omEvaluatorValueIsABasePath(OMEvaluatorValue evaluatorValue) { + return isa(unwrap(evaluatorValue).get()); +} + +OMEvaluatorValue omEvaluatorBasePathGetEmpty(MlirContext context) { + return wrap(std::make_shared(unwrap(context))); +} + +bool omEvaluatorValueIsAPath(OMEvaluatorValue evaluatorValue) { + return isa(unwrap(evaluatorValue).get()); +} + +MlirAttribute omEvaluatorPathGetAsString(OMEvaluatorValue evaluatorValue) { + const auto *path = cast(unwrap(evaluatorValue).get()); + return wrap((Attribute)path->getAsString()); +} + +//===----------------------------------------------------------------------===// +// ReferenceAttr API. +//===----------------------------------------------------------------------===// + +bool omAttrIsAReferenceAttr(MlirAttribute attr) { + return unwrap(attr).isa(); +} + +MlirAttribute omReferenceAttrGetInnerRef(MlirAttribute referenceAttr) { + return wrap( + (Attribute)unwrap(referenceAttr).cast().getInnerRef()); +} + +//===----------------------------------------------------------------------===// +// IntegerAttr API. +//===----------------------------------------------------------------------===// + +bool omAttrIsAIntegerAttr(MlirAttribute attr) { + return unwrap(attr).isa(); +} + +MlirAttribute omIntegerAttrGetInt(MlirAttribute attr) { + return wrap(cast(unwrap(attr)).getValue()); +} + +MlirAttribute omIntegerAttrGet(MlirAttribute attr) { + auto integerAttr = cast(unwrap(attr)); + return wrap( + circt::om::IntegerAttr::get(integerAttr.getContext(), integerAttr)); +} + +//===----------------------------------------------------------------------===// +// ListAttr API. +//===----------------------------------------------------------------------===// + +bool omAttrIsAListAttr(MlirAttribute attr) { + return unwrap(attr).isa(); +} + +intptr_t omListAttrGetNumElements(MlirAttribute attr) { + auto listAttr = llvm::cast(unwrap(attr)); + return static_cast(listAttr.getElements().size()); +} + +MlirAttribute omListAttrGetElement(MlirAttribute attr, intptr_t pos) { + auto listAttr = llvm::cast(unwrap(attr)); + return wrap(listAttr.getElements()[pos]); +} + +//===----------------------------------------------------------------------===// +// MapAttr API. +//===----------------------------------------------------------------------===// + +bool omAttrIsAMapAttr(MlirAttribute attr) { + return unwrap(attr).isa(); +} + +intptr_t omMapAttrGetNumElements(MlirAttribute attr) { + auto mapAttr = llvm::cast(unwrap(attr)); + return static_cast(mapAttr.getElements().size()); +} + +MlirIdentifier omMapAttrGetElementKey(MlirAttribute attr, intptr_t pos) { + auto mapAttr = llvm::cast(unwrap(attr)); + return wrap(mapAttr.getElements().getValue()[pos].getName()); +} + +MlirAttribute omMapAttrGetElementValue(MlirAttribute attr, intptr_t pos) { + auto mapAttr = llvm::cast(unwrap(attr)); + return wrap(mapAttr.getElements().getValue()[pos].getValue()); +} diff --git a/lib/CAPI/Dialect/Seq.cpp b/lib/CAPI/Dialect/Seq.cpp index ea60f51f270a..d0b003c0cd3b 100644 --- a/lib/CAPI/Dialect/Seq.cpp +++ b/lib/CAPI/Dialect/Seq.cpp @@ -9,9 +9,18 @@ #include "circt-c/Dialect/Seq.h" #include "circt/Dialect/Seq/SeqDialect.h" #include "circt/Dialect/Seq/SeqPasses.h" +#include "circt/Dialect/Seq/SeqTypes.h" #include "mlir/CAPI/Registration.h" +using namespace circt::seq; + MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(Sequential, seq, circt::seq::SeqDialect) void registerSeqPasses() { circt::seq::registerPasses(); } + +bool seqTypeIsAClock(MlirType type) { return unwrap(type).isa(); } + +MlirType seqClockTypeGet(MlirContext ctx) { + return wrap(ClockType::get(unwrap(ctx))); +} \ No newline at end of file diff --git a/lib/CAPI/Dialect/Verif.cpp b/lib/CAPI/Dialect/Verif.cpp new file mode 100644 index 000000000000..3572f762b20f --- /dev/null +++ b/lib/CAPI/Dialect/Verif.cpp @@ -0,0 +1,14 @@ +//===- Verif.cpp - C Interface for the Verif Dialect ----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt-c/Dialect/Verif.h" +#include "circt/Dialect/Verif/VerifDialect.h" + +#include "mlir/CAPI/Registration.h" + +MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(Verif, verif, circt::verif::VerifDialect) diff --git a/lib/CAPI/ExportFIRRTL/CMakeLists.txt b/lib/CAPI/ExportFIRRTL/CMakeLists.txt new file mode 100644 index 000000000000..a2ada33eebd7 --- /dev/null +++ b/lib/CAPI/ExportFIRRTL/CMakeLists.txt @@ -0,0 +1,7 @@ +add_mlir_public_c_api_library(CIRCTCAPIExportFIRRTL + ExportFIRRTL.cpp + + LINK_LIBS PUBLIC + MLIRCAPIIR + CIRCTExportFIRRTL + ) diff --git a/lib/CAPI/ExportFIRRTL/ExportFIRRTL.cpp b/lib/CAPI/ExportFIRRTL/ExportFIRRTL.cpp new file mode 100644 index 000000000000..87233801afda --- /dev/null +++ b/lib/CAPI/ExportFIRRTL/ExportFIRRTL.cpp @@ -0,0 +1,24 @@ +//===- ExportFIRRTL.cpp - C Interface to ExportFIRRTL ---------------------===// +// +// Implements a C Interface for export FIRRTL. +// +//===----------------------------------------------------------------------===// + +#include "circt-c/ExportFIRRTL.h" + +#include "circt/Dialect/FIRRTL/FIREmitter.h" +#include "circt/Dialect/FIRRTL/FIRParser.h" +#include "mlir/CAPI/IR.h" +#include "mlir/CAPI/Support.h" +#include "mlir/CAPI/Utils.h" +#include "llvm/Support/raw_ostream.h" + +using namespace circt; +using namespace firrtl; + +MlirLogicalResult mlirExportFIRRTL(MlirModule module, + MlirStringCallback callback, + void *userData) { + mlir::detail::CallbackOstream stream(callback, userData); + return wrap(exportFIRFile(unwrap(module), stream, {}, exportFIRVersion)); +} diff --git a/lib/CAPI/Firtool/CMakeLists.txt b/lib/CAPI/Firtool/CMakeLists.txt new file mode 100644 index 000000000000..4fd30624fb88 --- /dev/null +++ b/lib/CAPI/Firtool/CMakeLists.txt @@ -0,0 +1,7 @@ +add_mlir_public_c_api_library(CIRCTCAPIFirtool + Firtool.cpp + + LINK_LIBS PUBLIC + MLIRCAPIIR + CIRCTFirtool + ) diff --git a/lib/CAPI/Firtool/Firtool.cpp b/lib/CAPI/Firtool/Firtool.cpp new file mode 100644 index 000000000000..f907cd8d881f --- /dev/null +++ b/lib/CAPI/Firtool/Firtool.cpp @@ -0,0 +1,286 @@ +//===- Firtool.cpp - C Interface to Firtool-lib ---------------------------===// +// +// Implements a C Interface for Firtool-lib. +// +//===----------------------------------------------------------------------===// + +#include "circt-c/Firtool/Firtool.h" + +#include "circt/Firtool/Firtool.h" +#include "mlir/CAPI/IR.h" +#include "mlir/CAPI/Pass.h" +#include "mlir/CAPI/Support.h" +#include "mlir/CAPI/Utils.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/raw_ostream.h" + +using namespace circt; + +//===----------------------------------------------------------------------===// +// Option API. +//===----------------------------------------------------------------------===// + +DEFINE_C_API_PTR_METHODS(FirtoolOptions, firtool::FirtoolOptions) + +FirtoolOptions firtoolOptionsCreateDefault() { + static auto category = llvm::cl::OptionCategory{"Firtool Options"}; + auto *options = new firtool::FirtoolOptions{category}; + return wrap(options); +} + +void firtoolOptionsDestroy(FirtoolOptions options) { delete unwrap(options); } + +#define DEFINE_FIRTOOL_OPTION_STRING(name, field) \ + void firtoolOptionsSet##name(FirtoolOptions options, MlirStringRef value) { \ + unwrap(options)->field = unwrap(value).str(); \ + } \ + MlirStringRef firtoolOptionsGet##name(FirtoolOptions options) { \ + return wrap(unwrap(options)->field.getValue()); \ + } + +#define DEFINE_FIRTOOL_OPTION_BOOL(name, field) \ + void firtoolOptionsSet##name(FirtoolOptions options, bool value) { \ + unwrap(options)->field = value; \ + } \ + bool firtoolOptionsGet##name(FirtoolOptions options) { \ + return unwrap(options)->field; \ + } + +#define DEFINE_FIRTOOL_OPTION_ENUM(name, field, enum_type, c_to_cpp, cpp_to_c) \ + void firtoolOptionsSet##name(FirtoolOptions options, enum_type value) { \ + unwrap(options)->field = c_to_cpp(value); \ + } \ + enum_type firtoolOptionsGet##name(FirtoolOptions options) { \ + return cpp_to_c(unwrap(options)->field); \ + } + +DEFINE_FIRTOOL_OPTION_STRING(OutputFilename, outputFilename) +DEFINE_FIRTOOL_OPTION_BOOL(DisableAnnotationsUnknown, disableAnnotationsUnknown) +DEFINE_FIRTOOL_OPTION_BOOL(DisableAnnotationsClassless, + disableAnnotationsClassless) +DEFINE_FIRTOOL_OPTION_BOOL(LowerAnnotationsNoRefTypePorts, + lowerAnnotationsNoRefTypePorts) +DEFINE_FIRTOOL_OPTION_ENUM( + PreserveAggregate, preserveAggregate, FirtoolPreserveAggregateMode, + [](FirtoolPreserveAggregateMode value) { + switch (value) { + case FIRTOOL_PRESERVE_AGGREGATE_MODE_NONE: + return firrtl::PreserveAggregate::None; + case FIRTOOL_PRESERVE_AGGREGATE_MODE_ONE_DIM_VEC: + return firrtl::PreserveAggregate::OneDimVec; + case FIRTOOL_PRESERVE_AGGREGATE_MODE_VEC: + return firrtl::PreserveAggregate::Vec; + case FIRTOOL_PRESERVE_AGGREGATE_MODE_ALL: + return firrtl::PreserveAggregate::All; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown preserve aggregate mode"); + } + }, + [](firrtl::PreserveAggregate::PreserveMode value) { + switch (value) { + case firrtl::PreserveAggregate::None: + return FIRTOOL_PRESERVE_AGGREGATE_MODE_NONE; + case firrtl::PreserveAggregate::OneDimVec: + return FIRTOOL_PRESERVE_AGGREGATE_MODE_ONE_DIM_VEC; + case firrtl::PreserveAggregate::Vec: + return FIRTOOL_PRESERVE_AGGREGATE_MODE_VEC; + case firrtl::PreserveAggregate::All: + return FIRTOOL_PRESERVE_AGGREGATE_MODE_ALL; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown preserve aggregate mode"); + } + }) +DEFINE_FIRTOOL_OPTION_ENUM( + PreserveValues, preserveMode, FirtoolPreserveValuesMode, + [](FirtoolPreserveValuesMode value) { + switch (value) { + case FIRTOOL_PRESERVE_VALUES_MODE_NONE: + return firrtl::PreserveValues::None; + case FIRTOOL_PRESERVE_VALUES_MODE_NAMED: + return firrtl::PreserveValues::Named; + case FIRTOOL_PRESERVE_VALUES_MODE_ALL: + return firrtl::PreserveValues::All; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown preserve values mode"); + } + }, + [](firrtl::PreserveValues::PreserveMode value) { + switch (value) { + case firrtl::PreserveValues::None: + return FIRTOOL_PRESERVE_VALUES_MODE_NONE; + case firrtl::PreserveValues::Named: + return FIRTOOL_PRESERVE_VALUES_MODE_NAMED; + case firrtl::PreserveValues::All: + return FIRTOOL_PRESERVE_VALUES_MODE_ALL; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown preserve values mode"); + } + }) +DEFINE_FIRTOOL_OPTION_ENUM( + BuildMode, buildMode, FirtoolBuildMode, + [](FirtoolBuildMode value) { + switch (value) { + case FIRTOOL_BUILD_MODE_DEBUG: + return firtool::FirtoolOptions::BuildModeDebug; + case FIRTOOL_BUILD_MODE_RELEASE: + return firtool::FirtoolOptions::BuildModeRelease; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown build mode"); + } + }, + [](firtool::FirtoolOptions::BuildMode value) { + switch (value) { + case firtool::FirtoolOptions::BuildModeDebug: + return FIRTOOL_BUILD_MODE_DEBUG; + case firtool::FirtoolOptions::BuildModeRelease: + return FIRTOOL_BUILD_MODE_RELEASE; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown build mode"); + } + }) +DEFINE_FIRTOOL_OPTION_BOOL(DisableOptimization, disableOptimization) +DEFINE_FIRTOOL_OPTION_BOOL(ExportChiselInterface, exportChiselInterface) +DEFINE_FIRTOOL_OPTION_STRING(ChiselInterfaceOutDirectory, + chiselInterfaceOutDirectory) +DEFINE_FIRTOOL_OPTION_BOOL(VbToBv, vbToBV) +DEFINE_FIRTOOL_OPTION_BOOL(Dedup, dedup) +DEFINE_FIRTOOL_OPTION_ENUM( + CompanionMode, companionMode, FirtoolCompanionMode, + [](FirtoolCompanionMode value) { + switch (value) { + case FIRTOOL_COMPANION_MODE_BIND: + return firrtl::CompanionMode::Bind; + case FIRTOOL_COMPANION_MODE_INSTANTIATE: + return firrtl::CompanionMode::Instantiate; + case FIRTOOL_COMPANION_MODE_DROP: + return firrtl::CompanionMode::Drop; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown build mode"); + } + }, + [](firrtl::CompanionMode value) { + switch (value) { + case firrtl::CompanionMode::Bind: + return FIRTOOL_COMPANION_MODE_BIND; + case firrtl::CompanionMode::Instantiate: + return FIRTOOL_COMPANION_MODE_INSTANTIATE; + case firrtl::CompanionMode::Drop: + return FIRTOOL_COMPANION_MODE_DROP; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown build mode"); + } + }) + +DEFINE_FIRTOOL_OPTION_BOOL(DisableAggressiveMergeConnections, + disableAggressiveMergeConnections) +DEFINE_FIRTOOL_OPTION_BOOL(EmitOMIR, emitOMIR) +DEFINE_FIRTOOL_OPTION_STRING(OMIROutFile, omirOutFile) +DEFINE_FIRTOOL_OPTION_BOOL(LowerMemories, lowerMemories) +DEFINE_FIRTOOL_OPTION_STRING(BlackBoxRootPath, blackBoxRootPath) +DEFINE_FIRTOOL_OPTION_BOOL(ReplSeqMem, replSeqMem) +DEFINE_FIRTOOL_OPTION_STRING(ReplSeqMemFile, replSeqMemFile) +DEFINE_FIRTOOL_OPTION_BOOL(ExtractTestCode, extractTestCode) +DEFINE_FIRTOOL_OPTION_BOOL(IgnoreReadEnableMem, ignoreReadEnableMem) +DEFINE_FIRTOOL_OPTION_ENUM( + DisableRandom, disableRandom, FirtoolRandomKind, + [](FirtoolRandomKind value) { + switch (value) { + case FIRTOOL_RANDOM_KIND_NONE: + return firtool::FirtoolOptions::RandomKind::None; + case FIRTOOL_RANDOM_KIND_MEM: + return firtool::FirtoolOptions::RandomKind::Mem; + case FIRTOOL_RANDOM_KIND_REG: + return firtool::FirtoolOptions::RandomKind::Reg; + case FIRTOOL_RANDOM_KIND_ALL: + return firtool::FirtoolOptions::RandomKind::All; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown random kind"); + } + }, + [](firtool::FirtoolOptions::RandomKind value) { + switch (value) { + case firtool::FirtoolOptions::RandomKind::None: + return FIRTOOL_RANDOM_KIND_NONE; + case firtool::FirtoolOptions::RandomKind::Mem: + return FIRTOOL_RANDOM_KIND_MEM; + case firtool::FirtoolOptions::RandomKind::Reg: + return FIRTOOL_RANDOM_KIND_REG; + case firtool::FirtoolOptions::RandomKind::All: + return FIRTOOL_RANDOM_KIND_ALL; + default: // NOLINT(clang-diagnostic-covered-switch-default) + llvm_unreachable("unknown random kind"); + } + }) +DEFINE_FIRTOOL_OPTION_STRING(OutputAnnotationFilename, outputAnnotationFilename) +DEFINE_FIRTOOL_OPTION_BOOL(EnableAnnotationWarning, enableAnnotationWarning) +DEFINE_FIRTOOL_OPTION_BOOL(AddMuxPragmas, addMuxPragmas) +DEFINE_FIRTOOL_OPTION_BOOL(EmitChiselAssertsAsSVA, emitChiselAssertsAsSVA) +DEFINE_FIRTOOL_OPTION_BOOL(EmitSeparateAlwaysBlocks, emitSeparateAlwaysBlocks) +DEFINE_FIRTOOL_OPTION_BOOL(EtcDisableInstanceExtraction, + etcDisableInstanceExtraction) +DEFINE_FIRTOOL_OPTION_BOOL(EtcDisableRegisterExtraction, + etcDisableRegisterExtraction) +DEFINE_FIRTOOL_OPTION_BOOL(EtcDisableModuleInlining, etcDisableModuleInlining) +DEFINE_FIRTOOL_OPTION_BOOL(AddVivadoRAMAddressConflictSynthesisBugWorkaround, + addVivadoRAMAddressConflictSynthesisBugWorkaround) +DEFINE_FIRTOOL_OPTION_STRING(CkgModuleName, ckgModuleName) +DEFINE_FIRTOOL_OPTION_STRING(CkgInputName, ckgInputName) +DEFINE_FIRTOOL_OPTION_STRING(CkgOutputName, ckgOutputName) +DEFINE_FIRTOOL_OPTION_STRING(CkgEnableName, ckgEnableName) +DEFINE_FIRTOOL_OPTION_STRING(CkgTestEnableName, ckgTestEnableName) +DEFINE_FIRTOOL_OPTION_BOOL(ExportModuleHierarchy, exportModuleHierarchy) +DEFINE_FIRTOOL_OPTION_BOOL(StripFirDebugInfo, stripFirDebugInfo) +DEFINE_FIRTOOL_OPTION_BOOL(StripDebugInfo, stripDebugInfo) + +#undef DEFINE_FIRTOOL_OPTION_STRING +#undef DEFINE_FIRTOOL_OPTION_BOOL +#undef DEFINE_FIRTOOL_OPTION_ENUM + +//===----------------------------------------------------------------------===// +// Populate API. +//===----------------------------------------------------------------------===// + +MlirLogicalResult firtoolPopulatePreprocessTransforms(MlirPassManager pm, + FirtoolOptions options) { + return wrap( + firtool::populatePreprocessTransforms(*unwrap(pm), *unwrap(options))); +} + +MlirLogicalResult +firtoolPopulateCHIRRTLToLowFIRRTL(MlirPassManager pm, FirtoolOptions options, + MlirStringRef inputFilename) { + return wrap(firtool::populateCHIRRTLToLowFIRRTL(*unwrap(pm), *unwrap(options), + unwrap(inputFilename))); +} + +MlirLogicalResult firtoolPopulateLowFIRRTLToHW(MlirPassManager pm, + FirtoolOptions options) { + return wrap(firtool::populateLowFIRRTLToHW(*unwrap(pm), *unwrap(options))); +} + +MlirLogicalResult firtoolPopulateHWToSV(MlirPassManager pm, + FirtoolOptions options) { + return wrap(firtool::populateHWToSV(*unwrap(pm), *unwrap(options))); +} + +MlirLogicalResult firtoolPopulateExportVerilog(MlirPassManager pm, + FirtoolOptions options, + MlirStringCallback callback, + void *userData) { + auto stream = + std::make_unique(callback, userData); + return wrap(firtool::populateExportVerilog(*unwrap(pm), *unwrap(options), + std::move(stream))); +} + +MlirLogicalResult firtoolPopulateExportSplitVerilog(MlirPassManager pm, + FirtoolOptions options, + MlirStringRef directory) { + return wrap(firtool::populateExportSplitVerilog(*unwrap(pm), *unwrap(options), + unwrap(directory))); +} + +MlirLogicalResult firtoolPopulateFinalizeIR(MlirPassManager pm, + FirtoolOptions options) { + return wrap(firtool::populateFinalizeIR(*unwrap(pm), *unwrap(options))); +} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 6773a79a9d2c..4378fa58942b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -7,6 +7,9 @@ add_subdirectory(Bindings) add_subdirectory(CAPI) add_subdirectory(Conversion) add_subdirectory(Dialect) +add_subdirectory(LogicalEquivalence) +add_subdirectory(Firtool) +add_subdirectory(Reduce) add_subdirectory(Scheduling) add_subdirectory(Support) add_subdirectory(Target) diff --git a/lib/Conversion/AffineToPipeline/AffineToPipeline.cpp b/lib/Conversion/AffineToLoopSchedule/AffineToLoopSchedule.cpp similarity index 93% rename from lib/Conversion/AffineToPipeline/AffineToPipeline.cpp rename to lib/Conversion/AffineToLoopSchedule/AffineToLoopSchedule.cpp index a63f15df2a43..df982d5c449d 100644 --- a/lib/Conversion/AffineToPipeline/AffineToPipeline.cpp +++ b/lib/Conversion/AffineToLoopSchedule/AffineToLoopSchedule.cpp @@ -1,4 +1,4 @@ -//===- AffineToStaticlogic.cpp --------------------------------------------===// +//===- AffineToLoopSchedule.cpp--------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,11 +6,11 @@ // //===----------------------------------------------------------------------===// -#include "circt/Conversion/AffineToPipeline.h" +#include "circt/Conversion/AffineToLoopSchedule.h" #include "../PassDetail.h" #include "circt/Analysis/DependenceAnalysis.h" #include "circt/Analysis/SchedulingAnalysis.h" -#include "circt/Dialect/Pipeline/Pipeline.h" +#include "circt/Dialect/LoopSchedule/LoopScheduleOps.h" #include "circt/Scheduling/Algorithms.h" #include "circt/Scheduling/Problems.h" #include "mlir/Conversion/AffineToStandard/AffineToStandard.h" @@ -36,21 +36,23 @@ #include #include -#define DEBUG_TYPE "affine-to-pipeline" +#define DEBUG_TYPE "affine-to-loopschedule" using namespace mlir; using namespace mlir::arith; using namespace mlir::memref; using namespace mlir::scf; using namespace mlir::func; +using namespace mlir::affine; using namespace circt; using namespace circt::analysis; using namespace circt::scheduling; -using namespace circt::pipeline; +using namespace circt::loopschedule; namespace { -struct AffineToPipeline : public AffineToPipelineBase { +struct AffineToLoopSchedule + : public AffineToLoopScheduleBase { void runOnOperation() override; private: @@ -61,15 +63,16 @@ struct AffineToPipeline : public AffineToPipelineBase { ModuloProblem &problem); LogicalResult solveSchedulingProblem(SmallVectorImpl &loopNest, ModuloProblem &problem); - LogicalResult createPipelinePipeline(SmallVectorImpl &loopNest, - ModuloProblem &problem); + LogicalResult + createLoopSchedulePipeline(SmallVectorImpl &loopNest, + ModuloProblem &problem); CyclicSchedulingAnalysis *schedulingAnalysis; }; } // namespace -ModuloProblem AffineToPipeline::getModuloProblem(CyclicProblem &prob) { +ModuloProblem AffineToLoopSchedule::getModuloProblem(CyclicProblem &prob) { auto modProb = ModuloProblem::get(prob.getContainingOp()); for (auto *op : prob.getOperations()) { auto opr = prob.getLinkedOperatorType(op); @@ -98,7 +101,7 @@ ModuloProblem AffineToPipeline::getModuloProblem(CyclicProblem &prob) { return modProb; } -void AffineToPipeline::runOnOperation() { +void AffineToLoopSchedule::runOnOperation() { // Get dependence analysis for the whole function. auto dependenceAnalysis = getAnalysis(); @@ -131,7 +134,7 @@ void AffineToPipeline::runOnOperation() { return signalPassFailure(); // Convert the IR. - if (failed(createPipelinePipeline(nestedLoops, moduloProblem))) + if (failed(createLoopSchedulePipeline(nestedLoops, moduloProblem))) return signalPassFailure(); } } @@ -247,7 +250,7 @@ static bool yieldOpLegalityCallback(AffineYieldOp op) { /// computations in the condition of ifs, or the addresses of loads and stores. /// The dependence analysis will be updated so the dependences from the affine /// loads and stores are now on the memref loads and stores. -LogicalResult AffineToPipeline::lowerAffineStructures( +LogicalResult AffineToLoopSchedule::lowerAffineStructures( MemoryDependenceAnalysis &dependenceAnalysis) { auto *context = &getContext(); auto op = getOperation(); @@ -275,9 +278,8 @@ LogicalResult AffineToPipeline::lowerAffineStructures( /// targetting. Right now, we assume Calyx, which has a standard library with /// well-defined operator latencies. Ultimately, we should move this to a /// dialect interface in the Scheduling dialect. -LogicalResult -AffineToPipeline::populateOperatorTypes(SmallVectorImpl &loopNest, - ModuloProblem &problem) { +LogicalResult AffineToLoopSchedule::populateOperatorTypes( + SmallVectorImpl &loopNest, ModuloProblem &problem) { // Scheduling analyis only considers the innermost loop nest for now. auto forOp = loopNest.back(); @@ -352,9 +354,8 @@ AffineToPipeline::populateOperatorTypes(SmallVectorImpl &loopNest, } /// Solve the pre-computed scheduling problem. -LogicalResult -AffineToPipeline::solveSchedulingProblem(SmallVectorImpl &loopNest, - ModuloProblem &problem) { +LogicalResult AffineToLoopSchedule::solveSchedulingProblem( + SmallVectorImpl &loopNest, ModuloProblem &problem) { // Scheduling analyis only considers the innermost loop nest for now. auto forOp = loopNest.back(); @@ -397,10 +398,9 @@ AffineToPipeline::solveSchedulingProblem(SmallVectorImpl &loopNest, return success(); } -/// Create the pipeline op for a loop nest. -LogicalResult -AffineToPipeline::createPipelinePipeline(SmallVectorImpl &loopNest, - ModuloProblem &problem) { +/// Create the loopschedule pipeline op for a loop nest. +LogicalResult AffineToLoopSchedule::createLoopSchedulePipeline( + SmallVectorImpl &loopNest, ModuloProblem &problem) { // Scheduling analyis only considers the innermost loop nest for now. auto forOp = loopNest.back(); @@ -411,7 +411,7 @@ AffineToPipeline::createPipelinePipeline(SmallVectorImpl &loopNest, // Create Values for the loop's lower and upper bounds. Value lowerBound = lowerAffineLowerBound(innerLoop, builder); Value upperBound = lowerAffineUpperBound(innerLoop, builder); - int64_t stepValue = innerLoop.getStep(); + int64_t stepValue = innerLoop.getStep().getSExtValue(); auto step = builder.create( IntegerAttr::get(builder.getIndexType(), stepValue)); @@ -423,8 +423,7 @@ AffineToPipeline::createPipelinePipeline(SmallVectorImpl &loopNest, SmallVector iterArgs; iterArgs.push_back(lowerBound); - iterArgs.append(innerLoop.getIterOperands().begin(), - innerLoop.getIterOperands().end()); + iterArgs.append(innerLoop.getInits().begin(), innerLoop.getInits().end()); // If possible, attach a constant trip count attribute. This could be // generalized to support non-constant trip counts by supporting an AffineMap. @@ -432,8 +431,8 @@ AffineToPipeline::createPipelinePipeline(SmallVectorImpl &loopNest, if (auto tripCount = getConstantTripCount(forOp)) tripCountAttr = builder.getI64IntegerAttr(*tripCount); - auto pipeline = - builder.create(resultTypes, ii, tripCountAttr, iterArgs); + auto pipeline = builder.create( + resultTypes, ii, tripCountAttr, iterArgs); // Create the condition, which currently just compares the induction variable // to the upper bound. @@ -562,7 +561,7 @@ AffineToPipeline::createPipelinePipeline(SmallVectorImpl &loopNest, auto startTimeAttr = builder.getIntegerAttr( builder.getIntegerType(64, /*isSigned=*/true), startTime); auto stage = - builder.create(stageTypes, startTimeAttr); + builder.create(stageTypes, startTimeAttr); auto &stageBlock = stage.getBodyBlock(); auto *stageTerminator = stageBlock.getTerminator(); builder.setInsertionPointToStart(&stageBlock); @@ -609,7 +608,7 @@ AffineToPipeline::createPipelinePipeline(SmallVectorImpl &loopNest, // Add the iter args and results to the terminator. auto stagesTerminator = - cast(stagesBlock.getTerminator()); + cast(stagesBlock.getTerminator()); // Collect iter args and results from the induction variable increment and any // mapped values that were originally yielded. @@ -644,6 +643,6 @@ AffineToPipeline::createPipelinePipeline(SmallVectorImpl &loopNest, return success(); } -std::unique_ptr circt::createAffineToPipeline() { - return std::make_unique(); +std::unique_ptr circt::createAffineToLoopSchedule() { + return std::make_unique(); } diff --git a/lib/Conversion/AffineToPipeline/CMakeLists.txt b/lib/Conversion/AffineToLoopSchedule/CMakeLists.txt similarity index 56% rename from lib/Conversion/AffineToPipeline/CMakeLists.txt rename to lib/Conversion/AffineToLoopSchedule/CMakeLists.txt index 3e1097c60e32..c3db43fef4d4 100644 --- a/lib/Conversion/AffineToPipeline/CMakeLists.txt +++ b/lib/Conversion/AffineToLoopSchedule/CMakeLists.txt @@ -1,13 +1,14 @@ -add_circt_library(CIRCTAffineToPipeline - AffineToPipeline.cpp +add_circt_library(CIRCTAffineToLoopSchedule + AffineToLoopSchedule.cpp DEPENDS CIRCTConversionPassIncGen + MLIRArithDialect LINK_LIBS PUBLIC MLIRAffineToStandard MLIRPass CIRCTScheduling CIRCTSchedulingAnalysis - CIRCTPipelineOps + CIRCTLoopSchedule ) diff --git a/lib/Conversion/ArcToLLVM/CMakeLists.txt b/lib/Conversion/ArcToLLVM/CMakeLists.txt new file mode 100644 index 000000000000..b4d595ba0442 --- /dev/null +++ b/lib/Conversion/ArcToLLVM/CMakeLists.txt @@ -0,0 +1,22 @@ +add_circt_conversion_library(CIRCTArcToLLVM + LowerArcToLLVM.cpp + + DEPENDS + CIRCTConversionPassIncGen + + LINK_COMPONENTS + Core + + LINK_LIBS PUBLIC + CIRCTArc + CIRCTComb + CIRCTSeq + CIRCTCombToLLVM + CIRCTHWToLLVM + MLIRArithToLLVM + MLIRControlFlowToLLVM + MLIRFuncToLLVM + MLIRLLVMCommonConversion + MLIRSCFToControlFlow + MLIRTransforms +) diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp new file mode 100644 index 000000000000..4e908196d6b5 --- /dev/null +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -0,0 +1,476 @@ +//===- LowerArcToLLVM.cpp -------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "../PassDetail.h" +#include "circt/Conversion/ArcToLLVM.h" +#include "circt/Conversion/CombToLLVM.h" +#include "circt/Conversion/HWToLLVM.h" +#include "circt/Dialect/Arc/ArcOps.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Support/Namespace.h" +#include "mlir/Conversion/ArithToLLVM/ArithToLLVM.h" +#include "mlir/Conversion/ControlFlowToLLVM/ControlFlowToLLVM.h" +#include "mlir/Conversion/FuncToLLVM/ConvertFuncToLLVM.h" +#include "mlir/Conversion/LLVMCommon/ConversionTarget.h" +#include "mlir/Conversion/LLVMCommon/TypeConverter.h" +#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/LLVMIR/LLVMAttrs.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/IR/BuiltinDialect.h" +#include "mlir/Transforms/DialectConversion.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "arc-lower-to-llvm" + +using namespace mlir; +using namespace circt; +using namespace arc; +using namespace hw; + +//===----------------------------------------------------------------------===// +// Lowering Patterns +//===----------------------------------------------------------------------===// + +namespace { + +struct ModelOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::ModelOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + { + IRRewriter::InsertionGuard guard(rewriter); + rewriter.setInsertionPointToEnd(&op.getBodyBlock()); + rewriter.create(op.getLoc()); + } + auto funcName = rewriter.getStringAttr(op.getName() + "_eval"); + auto funcType = + rewriter.getFunctionType(op.getBody().getArgumentTypes(), {}); + auto func = + rewriter.create(op.getLoc(), funcName, funcType); + rewriter.inlineRegionBefore(op.getRegion(), func.getBody(), func.end()); + rewriter.eraseOp(op); + return success(); + } +}; + +struct AllocStorageOpLowering + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::AllocStorageOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto type = typeConverter->convertType(op.getType()); + if (!op.getOffset().has_value()) + return failure(); + rewriter.replaceOpWithNewOp(op, type, adaptor.getInput(), + LLVM::GEPArg(*op.getOffset())); + return success(); + } +}; + +template +struct AllocStateLikeOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpConversionPattern::typeConverter; + using OpAdaptor = typename ConcreteOp::Adaptor; + + LogicalResult + matchAndRewrite(ConcreteOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + // Get a pointer to the correct offset in the storage. + auto offsetAttr = op->template getAttrOfType("offset"); + if (!offsetAttr) + return failure(); + Value ptr = rewriter.create( + op->getLoc(), adaptor.getStorage().getType(), adaptor.getStorage(), + LLVM::GEPArg(offsetAttr.getValue().getZExtValue())); + + // Cast the raw storage pointer to a pointer of the state's actual type. + auto type = typeConverter->convertType(op.getType()); + if (type != ptr.getType()) + ptr = rewriter.create(op->getLoc(), type, ptr); + + rewriter.replaceOp(op, ptr); + return success(); + } +}; + +struct StateReadOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::StateReadOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + rewriter.replaceOpWithNewOp(op, adaptor.getState()); + return success(); + } +}; + +struct StateWriteOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::StateWriteOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + if (adaptor.getCondition()) { + rewriter.replaceOpWithNewOp( + op, adaptor.getCondition(), [&](auto &builder, auto loc) { + builder.template create(loc, adaptor.getValue(), + adaptor.getState()); + builder.template create(loc); + }); + } else { + rewriter.replaceOpWithNewOp(op, adaptor.getValue(), + adaptor.getState()); + } + return success(); + } +}; + +struct AllocMemoryOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::AllocMemoryOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto offsetAttr = op->getAttrOfType("offset"); + if (!offsetAttr) + return failure(); + Value ptr = rewriter.create( + op.getLoc(), adaptor.getStorage().getType(), adaptor.getStorage(), + LLVM::GEPArg(offsetAttr.getValue().getZExtValue())); + + auto type = typeConverter->convertType(op.getType()); + if (type != ptr.getType()) + ptr = rewriter.create(op.getLoc(), type, ptr); + + rewriter.replaceOp(op, ptr); + return success(); + } +}; + +struct StorageGetOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::StorageGetOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + Value offset = rewriter.create( + op.getLoc(), rewriter.getI32Type(), op.getOffsetAttr()); + Value ptr = rewriter.create(op.getLoc(), + adaptor.getStorage().getType(), + adaptor.getStorage(), offset); + auto type = typeConverter->convertType(op.getType()); + if (type != ptr.getType()) + ptr = rewriter.create(op.getLoc(), type, ptr); + rewriter.replaceOp(op, ptr); + return success(); + } +}; + +struct MemoryAccess { + Value ptr; + Value withinBounds; +}; + +static MemoryAccess prepareMemoryAccess(Location loc, Value memory, + Value address, MemoryType type, + ConversionPatternRewriter &rewriter) { + auto zextAddrType = rewriter.getIntegerType( + address.getType().cast().getWidth() + 1); + Value addr = rewriter.create(loc, zextAddrType, address); + Value addrLimit = rewriter.create( + loc, zextAddrType, rewriter.getI32IntegerAttr(type.getNumWords())); + Value withinBounds = rewriter.create( + loc, LLVM::ICmpPredicate::ult, addr, addrLimit); + auto ptrType = LLVM::LLVMPointerType::get(type.getWordType()); + Value ptr = + rewriter.create(loc, ptrType, memory, ValueRange{addr}); + return {ptr, withinBounds}; +} + +struct MemoryReadOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::MemoryReadOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto type = typeConverter->convertType(op.getType()); + auto access = prepareMemoryAccess( + op.getLoc(), adaptor.getMemory(), adaptor.getAddress(), + op.getMemory().getType().cast(), rewriter); + + // Only attempt to read the memory if the address is within bounds, + // otherwise produce a zero value. + rewriter.replaceOpWithNewOp( + op, access.withinBounds, + [&](auto &builder, auto loc) { + Value loadOp = builder.template create(loc, access.ptr); + builder.template create(loc, loadOp); + }, + [&](auto &builder, auto loc) { + Value zeroValue = builder.template create( + loc, type, builder.getI64IntegerAttr(0)); + builder.template create(loc, zeroValue); + }); + return success(); + } +}; + +struct MemoryWriteOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::MemoryWriteOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto access = prepareMemoryAccess( + op.getLoc(), adaptor.getMemory(), adaptor.getAddress(), + op.getMemory().getType().cast(), rewriter); + auto enable = access.withinBounds; + if (adaptor.getEnable()) + enable = rewriter.create(op.getLoc(), adaptor.getEnable(), + enable); + + // Only attempt to write the memory if the address is within bounds. + rewriter.replaceOpWithNewOp( + op, enable, [&](auto &builder, auto loc) { + builder.template create(loc, adaptor.getData(), + access.ptr); + builder.template create(loc); + }); + return success(); + } +}; + +/// A dummy lowering for clock gates to an AND gate. +struct ClockGateOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::ClockGateOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), + adaptor.getEnable(), true); + return success(); + } +}; + +struct ReturnOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(func::ReturnOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); + return success(); + } +}; + +struct FuncCallOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(func::CallOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + SmallVector newResultTypes; + if (failed( + typeConverter->convertTypes(op->getResultTypes(), newResultTypes))) + return failure(); + rewriter.replaceOpWithNewOp( + op, op.getCalleeAttr(), newResultTypes, adaptor.getOperands()); + return success(); + } +}; + +struct ZeroCountOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(arc::ZeroCountOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Use poison when input is zero. + IntegerAttr isZeroPoison = rewriter.getBoolAttr(true); + + if (op.getPredicate() == arc::ZeroCountPredicate::leading) { + rewriter.replaceOpWithNewOp( + op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison); + return success(); + } + + rewriter.replaceOpWithNewOp( + op, adaptor.getInput().getType(), adaptor.getInput(), isZeroPoison); + return success(); + } +}; + +template +struct ReplaceOpWithInputPattern : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename OpTy::Adaptor; + LogicalResult + matchAndRewrite(OpTy op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOp(op, adaptor.getInput()); + return success(); + } +}; + +} // namespace + +static bool isArcType(Type type) { + return type.isa() || type.isa() || + type.isa() || type.isa(); +} + +static bool hasArcType(TypeRange types) { + return llvm::any_of(types, isArcType); +} + +static bool hasArcType(ValueRange values) { + return hasArcType(values.getTypes()); +} + +template +static void addGenericLegality(ConversionTarget &target) { + target.addDynamicallyLegalOp([](Op op) { + return !hasArcType(op->getOperands()) && !hasArcType(op->getResults()); + }); +} + +static void populateLegality(ConversionTarget &target) { + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addIllegalDialect(); + + target.addIllegalOp(); + target.addIllegalOp(); + target.addIllegalOp(); + target.addIllegalOp(); + target.addIllegalOp(); + + target.addDynamicallyLegalOp([](func::FuncOp op) { + auto argsConverted = llvm::none_of(op.getBlocks(), [](auto &block) { + return hasArcType(block.getArguments()); + }); + auto resultsConverted = !hasArcType(op.getResultTypes()); + return argsConverted && resultsConverted; + }); + addGenericLegality(target); + addGenericLegality(target); +} + +static void populateTypeConversion(TypeConverter &typeConverter) { + typeConverter.addConversion([&](seq::ClockType type) { + return IntegerType::get(type.getContext(), 1); + }); + typeConverter.addConversion([&](StorageType type) { + return LLVM::LLVMPointerType::get(IntegerType::get(type.getContext(), 8)); + }); + typeConverter.addConversion([&](MemoryType type) { + return LLVM::LLVMPointerType::get( + IntegerType::get(type.getContext(), type.getStride() * 8)); + }); + typeConverter.addConversion([&](StateType type) { + return LLVM::LLVMPointerType::get( + typeConverter.convertType(type.getType())); + }); + typeConverter.addConversion([](hw::ArrayType type) { return type; }); + typeConverter.addConversion([](mlir::IntegerType type) { return type; }); +} + +static void populateOpConversion(RewritePatternSet &patterns, + TypeConverter &typeConverter) { + auto *context = patterns.getContext(); + // clang-format off + patterns.add< + AllocMemoryOpLowering, + AllocStateLikeOpLowering, + AllocStateLikeOpLowering, + AllocStateLikeOpLowering, + AllocStorageOpLowering, + ClockGateOpLowering, + FuncCallOpLowering, + MemoryReadOpLowering, + MemoryWriteOpLowering, + ModelOpLowering, + ReplaceOpWithInputPattern, + ReplaceOpWithInputPattern, + ReturnOpLowering, + StateReadOpLowering, + StateWriteOpLowering, + StorageGetOpLowering, + ZeroCountOpLowering + >(typeConverter, context); + // clang-format on + + mlir::populateFunctionOpInterfaceTypeConversionPattern( + patterns, typeConverter); +} + +//===----------------------------------------------------------------------===// +// Pass Implementation +//===----------------------------------------------------------------------===// + +namespace { +struct LowerArcToLLVMPass : public LowerArcToLLVMBase { + void runOnOperation() override; + LogicalResult lowerToMLIR(); + LogicalResult lowerArcToLLVM(); +}; +} // namespace + +void LowerArcToLLVMPass::runOnOperation() { + if (failed(lowerToMLIR())) + return signalPassFailure(); + + if (failed(lowerArcToLLVM())) + return signalPassFailure(); +} + +/// Perform the lowering to Func and SCF. +LogicalResult LowerArcToLLVMPass::lowerToMLIR() { + LLVM_DEBUG(llvm::dbgs() << "Lowering arcs to Func/SCF dialects\n"); + ConversionTarget target(getContext()); + TypeConverter converter; + RewritePatternSet patterns(&getContext()); + populateLegality(target); + populateTypeConversion(converter); + populateOpConversion(patterns, converter); + return applyPartialConversion(getOperation(), target, std::move(patterns)); +} + +/// Perform lowering to LLVM. +LogicalResult LowerArcToLLVMPass::lowerArcToLLVM() { + LLVM_DEBUG(llvm::dbgs() << "Lowering to LLVM dialect\n"); + + Namespace globals; + SymbolCache cache; + cache.addDefinitions(getOperation()); + globals.add(cache); + + LLVMConversionTarget target(getContext()); + LLVMTypeConverter converter(&getContext()); + RewritePatternSet patterns(&getContext()); + target.addLegalOp(); + target.addIllegalOp(); + populateSCFToControlFlowConversionPatterns(patterns); + populateFuncToLLVMConversionPatterns(converter, patterns); + cf::populateControlFlowToLLVMConversionPatterns(converter, patterns); + + DenseMap, LLVM::GlobalOp> constAggregateGlobalsMap; + populateHWToLLVMConversionPatterns(converter, patterns, globals, + constAggregateGlobalsMap); + populateHWToLLVMTypeConversions(converter); + populateCombToLLVMConversionPatterns(converter, patterns); + arith::populateArithToLLVMConversionPatterns(converter, patterns); + + return applyFullConversion(getOperation(), target, std::move(patterns)); +} + +std::unique_ptr> circt::createLowerArcToLLVMPass() { + return std::make_unique(); +} diff --git a/lib/Conversion/StandardToHandshake/StandardToHandshake.cpp b/lib/Conversion/CFToHandshake/CFToHandshake.cpp similarity index 90% rename from lib/Conversion/StandardToHandshake/StandardToHandshake.cpp rename to lib/Conversion/CFToHandshake/CFToHandshake.cpp index c4f7808ce1a0..26ece7897a87 100644 --- a/lib/Conversion/StandardToHandshake/StandardToHandshake.cpp +++ b/lib/Conversion/CFToHandshake/CFToHandshake.cpp @@ -1,4 +1,4 @@ -//===- StandardToHandshake.cpp - Convert standard MLIR into dataflow IR ---===// +//===- CFToHandshake.cpp - Convert standard MLIR into dataflow IR ---------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -9,13 +9,12 @@ // //===----------------------------------------------------------------------===// -#include "circt/Conversion/StandardToHandshake.h" +#include "circt/Conversion/CFToHandshake.h" #include "../PassDetail.h" -#include "circt/Analysis/ControlFlowLoopAnalysis.h" #include "circt/Dialect/Handshake/HandshakeOps.h" #include "circt/Dialect/Handshake/HandshakePasses.h" -#include "circt/Dialect/Pipeline/Pipeline.h" #include "circt/Support/BackedgeBuilder.h" +#include "mlir/Analysis/CFGLoopInfo.h" #include "mlir/Conversion/AffineToStandard/AffineToStandard.h" #include "mlir/Dialect/Affine/Analysis/AffineAnalysis.h" #include "mlir/Dialect/Affine/Analysis/AffineStructures.h" @@ -48,9 +47,9 @@ using namespace mlir; using namespace mlir::func; +using namespace mlir::affine; using namespace circt; using namespace circt::handshake; -using namespace circt::analysis; using namespace std; // ============================================================================ @@ -67,7 +66,7 @@ class LowerOpTarget : public ConversionTarget { addLegalDialect(); addLegalDialect(); addIllegalDialect(); - addIllegalDialect(); + addIllegalDialect(); /// The root operation to be replaced is marked dynamically legal /// based on the lowering status of the given operation, see @@ -220,23 +219,13 @@ void HandshakeLowering::setBlockEntryControl(Block *block, Value v) { } void handshake::removeBasicBlocks(Region &r) { - auto &entryBlock = r.front().getOperations(); - - // Now that basic blocks are going to be removed, we can erase all cf-dialect - // branches, and move ReturnOp to the entry block's end - for (auto &block : r) { - Operation &termOp = block.back(); - if (isa(termOp)) - termOp.erase(); - else if (isa(termOp)) - entryBlock.splice(entryBlock.end(), block.getOperations(), termOp); - } + Block *entryBlock = &r.front(); + auto &entryBlockOps = entryBlock->getOperations(); // Move all operations to entry block and erase other blocks. - for (auto &block : llvm::make_early_inc_range(llvm::drop_begin(r, 1))) { - entryBlock.splice(--entryBlock.end(), block.getOperations()); - } - for (auto &block : llvm::make_early_inc_range(llvm::drop_begin(r, 1))) { + for (Block &block : llvm::make_early_inc_range(llvm::drop_begin(r, 1))) { + entryBlockOps.splice(entryBlockOps.end(), block.getOperations()); + block.clear(); block.dropAllDefinedValueUses(); for (size_t i = 0; i < block.getNumArguments(); i++) { @@ -244,6 +233,21 @@ void handshake::removeBasicBlocks(Region &r) { } block.erase(); } + + // Remove any control flow operations, and move the non-control flow + // terminator op to the end of the entry block. + for (Operation &terminatorLike : llvm::make_early_inc_range(*entryBlock)) { + if (!terminatorLike.hasTrait()) + continue; + + if (isa(terminatorLike)) { + terminatorLike.erase(); + continue; + } + + // Else, assume that this is a return-like terminator op. + terminatorLike.moveBefore(entryBlock, entryBlock->end()); + } } void removeBasicBlocks(handshake::FuncOp funcOp) { @@ -541,7 +545,8 @@ class FeedForwardNetworkRewriter { FeedForwardNetworkRewriter(HandshakeLowering &hl, ConversionPatternRewriter &rewriter) : hl(hl), rewriter(rewriter), postDomInfo(hl.getRegion().getParentOp()), - domInfo(hl.getRegion().getParentOp()), loopAnalysis(hl.getRegion()) {} + domInfo(hl.getRegion().getParentOp()), + loopInfo(domInfo.getDomTree(&hl.getRegion())) {} LogicalResult apply(); private: @@ -549,7 +554,7 @@ class FeedForwardNetworkRewriter { ConversionPatternRewriter &rewriter; PostDominanceInfo postDomInfo; DominanceInfo domInfo; - ControlFlowLoopAnalysis loopAnalysis; + CFGLoopInfo loopInfo; using BlockPair = std::pair; using BlockPairs = SmallVector; @@ -557,8 +562,8 @@ class FeedForwardNetworkRewriter { BufferOp buildSplitNetwork(Block *splitBlock, handshake::ConditionalBranchOp &ctrlBr); - void buildMergeNetwork(Block *mergeBlock, BufferOp buf, - handshake::ConditionalBranchOp &ctrlBr); + LogicalResult buildMergeNetwork(Block *mergeBlock, BufferOp buf, + handshake::ConditionalBranchOp &ctrlBr); // Determines if the cmerge inpus match the cond_br output order. bool requiresOperandFlip(ControlMergeOp &ctrlMerge, @@ -569,22 +574,25 @@ class FeedForwardNetworkRewriter { LogicalResult HandshakeLowering::feedForwardRewriting(ConversionPatternRewriter &rewriter) { + // Nothing to do on a single block region. + if (this->getRegion().hasOneBlock()) + return success(); return FeedForwardNetworkRewriter(*this, rewriter).apply(); } -static bool loopsHaveSingleExit(ControlFlowLoopAnalysis &loopAnalysis) { - for (LoopInfo &info : loopAnalysis.topLevelLoops) - if (info.exitBlocks.size() > 1) +[[maybe_unused]] static bool loopsHaveSingleExit(CFGLoopInfo &loopInfo) { + for (CFGLoop *loop : loopInfo.getTopLevelLoops()) + if (!loop->getExitBlock()) return false; return true; } bool FeedForwardNetworkRewriter::formsIrreducibleCF(Block *splitBlock, Block *mergeBlock) { - LoopInfo *info = loopAnalysis.getLoopInfoForHeader(mergeBlock); - for (auto mergePred : mergeBlock->getPredecessors()) { + CFGLoop *loop = loopInfo.getLoopFor(mergeBlock); + for (auto *mergePred : mergeBlock->getPredecessors()) { // Skip loop predecessors - if (info && info->inLoop.contains(mergePred)) + if (loop && loop->contains(mergePred)) continue; // A DAG-CFG is irreducible, iff a merge block has a predecessor that can be @@ -617,7 +625,7 @@ FeedForwardNetworkRewriter::findBlockPairs(BlockPairs &blockPairs) { // Assumes that each loop has only one exit block. Such an error should // already be reported by the loop rewriting. - assert(loopsHaveSingleExit(loopAnalysis) && + assert(loopsHaveSingleExit(loopInfo) && "expected loop to only have one exit block."); for (Block &b : r) { @@ -625,7 +633,7 @@ FeedForwardNetworkRewriter::findBlockPairs(BlockPairs &blockPairs) { continue; // Loop headers cannot be merge blocks. - if (loopAnalysis.isLoopElement(&b)) + if (loopInfo.getLoopFor(&b)) continue; assert(b.getNumSuccessors() == 2); @@ -645,9 +653,9 @@ FeedForwardNetworkRewriter::findBlockPairs(BlockPairs &blockPairs) { } unsigned nonLoopPreds = 0; - LoopInfo *info = loopAnalysis.getLoopInfoForHeader(mergeBlock); - for (auto pred : mergeBlock->getPredecessors()) { - if (info && info->inLoop.contains(pred)) + CFGLoop *loop = loopInfo.getLoopFor(mergeBlock); + for (auto *pred : mergeBlock->getPredecessors()) { + if (loop && loop->contains(pred)) continue; nonLoopPreds++; } @@ -665,17 +673,16 @@ FeedForwardNetworkRewriter::findBlockPairs(BlockPairs &blockPairs) { } LogicalResult FeedForwardNetworkRewriter::apply() { - if (failed(loopAnalysis.analyzeRegion())) - return failure(); - BlockPairs pairs; + if (failed(findBlockPairs(pairs))) return failure(); for (auto [splitBlock, mergeBlock] : pairs) { handshake::ConditionalBranchOp ctrlBr; BufferOp buffer = buildSplitNetwork(splitBlock, ctrlBr); - buildMergeNetwork(mergeBlock, buffer, ctrlBr); + if (failed(buildMergeNetwork(mergeBlock, buffer, ctrlBr))) + return failure(); } return success(); @@ -712,13 +719,16 @@ BufferOp FeedForwardNetworkRewriter::buildSplitNetwork( BufferTypeEnum::fifo); } -void FeedForwardNetworkRewriter::buildMergeNetwork( +LogicalResult FeedForwardNetworkRewriter::buildMergeNetwork( Block *mergeBlock, BufferOp buf, handshake::ConditionalBranchOp &ctrlBr) { // Replace control merge with mux auto ctrlMerges = mergeBlock->getOps(); assert(std::distance(ctrlMerges.begin(), ctrlMerges.end()) == 1); handshake::ControlMergeOp ctrlMerge = *ctrlMerges.begin(); + // This input might contain irreducible loops that we cannot handle. + if (ctrlMerge.getNumOperands() != 2) + return ctrlMerge.emitError("expected cmerges to have two operands"); rewriter.setInsertionPointAfter(ctrlMerge); Location loc = ctrlMerge->getLoc(); @@ -753,6 +763,7 @@ void FeedForwardNetworkRewriter::buildMergeNetwork( // Replace with new ctrl value from mux and the index rewriter.replaceOp(ctrlMerge, {newCtrl, condAsIndex}); + return success(); } bool FeedForwardNetworkRewriter::requiresOperandFlip( @@ -795,7 +806,7 @@ class LoopNetworkRewriter { // An exit pair is a pair of that // indicates where control leaves a loop. using ExitPair = std::pair; - LogicalResult processOuterLoop(Location loc, LoopInfo &loopInfo); + LogicalResult processOuterLoop(Location loc, CFGLoop *loop); // Builds the loop continue network in between the loop header and its loop // latch. The loop continuation network will replace the existing control @@ -829,17 +840,18 @@ HandshakeLowering::loopNetworkRewriting(ConversionPatternRewriter &rewriter) { LogicalResult LoopNetworkRewriter::processRegion(Region &r, ConversionPatternRewriter &rewriter) { + // Nothing to do on a single block region. + if (r.hasOneBlock()) + return success(); this->rewriter = &rewriter; Operation *op = r.getParentOp(); - ControlFlowLoopAnalysis loopAnalysis(r); - if (failed(loopAnalysis.analyzeRegion())) { - return failure(); - } + DominanceInfo domInfo(op); + CFGLoopInfo loopInfo(domInfo.getDomTree(&r)); - for (LoopInfo &loopInfo : loopAnalysis.topLevelLoops) { - if (loopInfo.loopLatches.size() > 1) + for (CFGLoop *loop : loopInfo.getTopLevelLoops()) { + if (!loop->getLoopLatch()) return emitError(op->getLoc()) << "Multiple loop latches detected " "(backedges from within the loop " "to the loop header). Loop task " @@ -847,7 +859,7 @@ LoopNetworkRewriter::processRegion(Region &r, "loops with unified loop latches."; // This is the start of an outer loop - go process! - if (failed(processOuterLoop(op->getLoc(), loopInfo))) + if (failed(processOuterLoop(op->getLoc(), loop))) return failure(); } @@ -1041,14 +1053,16 @@ void LoopNetworkRewriter::buildExitNetwork( } LogicalResult LoopNetworkRewriter::processOuterLoop(Location loc, - LoopInfo &loopInfo) { + CFGLoop *loop) { // We determine the exit pairs of the loop; this is the in-loop nodes // which branch off to the exit nodes. llvm::SmallSet exitPairs; - for (auto *exitNode : loopInfo.exitBlocks) { + SmallVector exitBlocks; + loop->getExitBlocks(exitBlocks); + for (auto *exitNode : exitBlocks) { for (auto *pred : exitNode->getPredecessors()) { // is the predecessor inside the loop? - if (!loopInfo.inLoop.contains(pred)) + if (!loop->contains(pred)) continue; ExitPair condPair = {pred, exitNode}; @@ -1057,7 +1071,7 @@ LogicalResult LoopNetworkRewriter::processOuterLoop(Location loc, exitPairs.insert(condPair); } } - assert(exitPairs.size() > 0 && "No exits from loop?"); + assert(!exitPairs.empty() && "No exits from loop?"); // The first precondition to our loop transformation is that only a single // exit pair exists in the loop. @@ -1066,18 +1080,18 @@ LogicalResult LoopNetworkRewriter::processOuterLoop(Location loc, << "Multiple exits detected within a loop. Loop task pipelining is " "only supported for loops with unified loop exit blocks."; - BackedgeBuilder bebuilder(*rewriter, loopInfo.loopHeader->front().getLoc()); + Block *header = loop->getHeader(); + BackedgeBuilder bebuilder(*rewriter, header->front().getLoc()); // Build the loop continue network. Loop continuation is triggered solely by // backedges to the header. auto loopPrimingRegisterInput = bebuilder.get(rewriter->getI1Type()); - auto loopPrimingRegister = - buildContinueNetwork(loopInfo.loopHeader, *loopInfo.loopLatches.begin(), - loopPrimingRegisterInput); + auto loopPrimingRegister = buildContinueNetwork(header, loop->getLoopLatch(), + loopPrimingRegisterInput); // Build the loop exit network. Loop exiting is driven solely by exit pairs // from the loop. - buildExitNetwork(loopInfo.loopHeader, exitPairs, loopPrimingRegister, + buildExitNetwork(header, exitPairs, loopPrimingRegister, loopPrimingRegisterInput); return success(); @@ -1227,7 +1241,7 @@ static LogicalResult getOpMemRef(Operation *op, Value &out) { out = memOp.getMemRef(); else if (auto memOp = dyn_cast(op)) out = memOp.getMemRef(); - else if (isa(op)) { + else if (isa(op)) { MemRefAccess access(op); out = access.memref; } @@ -1237,8 +1251,8 @@ static LogicalResult getOpMemRef(Operation *op, Value &out) { } static bool isMemoryOp(Operation *op) { - return isa(op); + return isa(op); } LogicalResult @@ -1290,41 +1304,40 @@ HandshakeLowering::replaceMemoryOps(ConversionPatternRewriter &rewriter, newOp = rewriter.create( op.getLoc(), storeOp.getValueToStore(), operands); }) - .Case( - [&](auto) { - // Get essential memref access inforamtion. - MemRefAccess access(&op); - // The address of an affine load/store operation can be a result - // of an affine map, which is a linear combination of constants - // and parameters. Therefore, we should extract the affine map of - // each address and expand it into proper expressions that - // calculate the result. - mlir::AffineMap map; - if (auto loadOp = dyn_cast(op)) - map = loadOp.getAffineMap(); - else - map = dyn_cast(op).getAffineMap(); - - // The returned object from expandAffineMap is an optional list of - // the expansion results from the given affine map, which are the - // actual address indices that can be used as operands for - // handshake LoadOp/StoreOp. The following processing requires it - // to be a valid result. - auto operands = - expandAffineMap(rewriter, op.getLoc(), map, access.indices); - assert(operands && "Address operands of affine memref access " - "cannot be reduced."); - - if (isa(op)) { - auto loadOp = rewriter.create( - op.getLoc(), access.memref, *operands); - newOp = loadOp; - op.getResult(0).replaceAllUsesWith(loadOp.getDataResult()); - } else { - newOp = rewriter.create( - op.getLoc(), op.getOperand(0), *operands); - } - }) + .Case([&](auto) { + // Get essential memref access inforamtion. + MemRefAccess access(&op); + // The address of an affine load/store operation can be a result + // of an affine map, which is a linear combination of constants + // and parameters. Therefore, we should extract the affine map of + // each address and expand it into proper expressions that + // calculate the result. + AffineMap map; + if (auto loadOp = dyn_cast(op)) + map = loadOp.getAffineMap(); + else + map = dyn_cast(op).getAffineMap(); + + // The returned object from expandAffineMap is an optional list of + // the expansion results from the given affine map, which are the + // actual address indices that can be used as operands for + // handshake LoadOp/StoreOp. The following processing requires it + // to be a valid result. + auto operands = + expandAffineMap(rewriter, op.getLoc(), map, access.indices); + assert(operands && "Address operands of affine memref access " + "cannot be reduced."); + + if (isa(op)) { + auto loadOp = rewriter.create( + op.getLoc(), access.memref, *operands); + newOp = loadOp; + op.getResult(0).replaceAllUsesWith(loadOp.getDataResult()); + } else { + newOp = rewriter.create( + op.getLoc(), op.getOperand(0), *operands); + } + }) .Default([&](auto) { op.emitOpError("Load/store operation cannot be handled."); }); @@ -1627,7 +1640,7 @@ HandshakeLowering::replaceCallOps(ConversionPatternRewriter &rewriter) { } namespace { -/// Strategy class for SSA maximization during std-to-handshake conversion. +/// Strategy class for SSA maximization during cf-to-handshake conversion. /// Block arguments of type MemRefType and allocation operations are not /// considered for SSA maximization. class HandshakeLoweringSSAStrategy : public SSAMaximizationStrategy { @@ -1686,8 +1699,11 @@ static LogicalResult lowerFuncOp(func::FuncOp funcOp, MLIRContext *ctx, funcOp.getLoc(), funcOp.getName(), func_type, attributes); rewriter.inlineRegionBefore(funcOp.getBody(), newFuncOp.getBody(), newFuncOp.end()); - if (!newFuncOp.isExternal()) + if (!newFuncOp.isExternal()) { + newFuncOp.getBodyBlock()->addArgument(rewriter.getNoneType(), + funcOp.getLoc()); newFuncOp.resolveArgAndResNames(); + } rewriter.eraseOp(funcOp); return success(); }, @@ -1698,38 +1714,17 @@ static LogicalResult lowerFuncOp(func::FuncOp funcOp, MLIRContext *ctx, partiallyLowerRegion(maximizeSSANoMem, ctx, newFuncOp.getBody())); if (!newFuncOp.isExternal()) { + Block *bodyBlock = newFuncOp.getBodyBlock(); + Value entryCtrl = bodyBlock->getArguments().back(); HandshakeLowering fol(newFuncOp.getBody()); - returnOnError(lowerRegion(fol, sourceConstants, - disableTaskPipelining)); + if (failed(lowerRegion( + fol, sourceConstants, disableTaskPipelining, entryCtrl))) + return failure(); } return success(); } -namespace { -struct ConvertSelectOps : public OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - LogicalResult - matchAndRewrite(mlir::arith::SelectOp op, OpAdaptor adaptor, - ConversionPatternRewriter &rewriter) const override { - rewriter.replaceOpWithNewOp(op, adaptor.getCondition(), - adaptor.getFalseValue(), - adaptor.getTrueValue()); - return success(); - }; -}; -} // namespace - -LogicalResult handshake::postDataflowConvert(Operation *op) { - MLIRContext *context = op->getContext(); - ConversionTarget target(*context); - target.addLegalDialect(); - target.addIllegalOp(); - RewritePatternSet patterns(context); - patterns.insert(context); - return applyPartialConversion(op, target, std::move(patterns)); -} - namespace { struct HandshakeRemoveBlockPass @@ -1737,9 +1732,8 @@ struct HandshakeRemoveBlockPass void runOnOperation() override { removeBasicBlocks(getOperation()); } }; -struct StandardToHandshakePass - : public StandardToHandshakeBase { - StandardToHandshakePass(bool sourceConstants, bool disableTaskPipelining) { +struct CFToHandshakePass : public CFToHandshakeBase { + CFToHandshakePass(bool sourceConstants, bool disableTaskPipelining) { this->sourceConstants = sourceConstants; this->disableTaskPipelining = disableTaskPipelining; } @@ -1753,24 +1747,16 @@ struct StandardToHandshakePass return; } } - - // Legalize the resulting regions, removing basic blocks and performing - // any simple conversions. - for (auto func : m.getOps()) { - removeBasicBlocks(func); - if (failed(postDataflowConvert(func))) - return signalPassFailure(); - } } }; } // namespace std::unique_ptr> -circt::createStandardToHandshakePass(bool sourceConstants, - bool disableTaskPipelining) { - return std::make_unique(sourceConstants, - disableTaskPipelining); +circt::createCFToHandshakePass(bool sourceConstants, + bool disableTaskPipelining) { + return std::make_unique(sourceConstants, + disableTaskPipelining); } std::unique_ptr> diff --git a/lib/Conversion/StandardToHandshake/CMakeLists.txt b/lib/Conversion/CFToHandshake/CMakeLists.txt similarity index 53% rename from lib/Conversion/StandardToHandshake/CMakeLists.txt rename to lib/Conversion/CFToHandshake/CMakeLists.txt index 6b6db960e6f0..cc903c273cd1 100644 --- a/lib/Conversion/StandardToHandshake/CMakeLists.txt +++ b/lib/Conversion/CFToHandshake/CMakeLists.txt @@ -1,16 +1,17 @@ -add_circt_library(CIRCTStandardToHandshake - StandardToHandshake.cpp - InsertMergeBlocks.cpp - MaximizeSSA.cpp +add_circt_library(CIRCTCFToHandshake + CFToHandshake.cpp ADDITIONAL_HEADER_DIRS - ${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/StandardToHandshake + ${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/CFToHandshake + + DEPENDS + CIRCTConversionPassIncGen + MLIRArithDialect LINK_LIBS PUBLIC CIRCTHandshake CIRCTHandshakeTransforms CIRCTSupport - CIRCTControlFlowLoopAnalysis MLIRIR MLIRPass MLIRArithDialect @@ -19,4 +20,4 @@ add_circt_library(CIRCTStandardToHandshake MLIRSupport MLIRTransforms MLIRAffineToStandard - ) +) diff --git a/lib/Conversion/CMakeLists.txt b/lib/Conversion/CMakeLists.txt index 2dd66ec83b8c..5bbaf7612193 100644 --- a/lib/Conversion/CMakeLists.txt +++ b/lib/Conversion/CMakeLists.txt @@ -1,22 +1,28 @@ -add_subdirectory(AffineToPipeline) +add_subdirectory(AffineToLoopSchedule) +add_subdirectory(ArcToLLVM) add_subdirectory(CalyxToFSM) -add_subdirectory(ConvertToArcs) add_subdirectory(CalyxToHW) +add_subdirectory(CombToArith) +add_subdirectory(CombToLLVM) +add_subdirectory(ConvertToArcs) +add_subdirectory(DCToHW) add_subdirectory(ExportChiselInterface) add_subdirectory(ExportVerilog) add_subdirectory(FIRRTLToHW) +add_subdirectory(FSMToSV) +add_subdirectory(HandshakeToDC) +add_subdirectory(HandshakeToHW) add_subdirectory(HWArithToHW) add_subdirectory(HWToLLHD) +add_subdirectory(HWToLLVM) +add_subdirectory(HWToSV) add_subdirectory(HWToSystemC) -add_subdirectory(HandshakeToFIRRTL) -add_subdirectory(HandshakeToHW) add_subdirectory(LLHDToLLVM) -add_subdirectory(HWToLLVM) -add_subdirectory(CombToArith) -add_subdirectory(CombToLLVM) +add_subdirectory(LoopScheduleToCalyx) add_subdirectory(MooreToCore) -add_subdirectory(SCFToCalyx) -add_subdirectory(PipelineToCalyx) -add_subdirectory(StandardToHandshake) -add_subdirectory(FSMToSV) add_subdirectory(PipelineToHW) +add_subdirectory(SCFToCalyx) +add_subdirectory(SeqToSV) +add_subdirectory(CFToHandshake) +add_subdirectory(VerifToSV) +add_subdirectory(CalyxNative) \ No newline at end of file diff --git a/lib/Conversion/CalyxNative/CMakeLists.txt b/lib/Conversion/CalyxNative/CMakeLists.txt new file mode 100644 index 000000000000..488022b984c4 --- /dev/null +++ b/lib/Conversion/CalyxNative/CMakeLists.txt @@ -0,0 +1,19 @@ +add_circt_library(CIRCTCalyxNative + CalyxNative.cpp + + ADDITIONAL_HEADER_DIRS + ${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/CalyxNative + + DEPENDS + CIRCTConversionPassIncGen + + LINK_COMPONENTS + Core + + LINK_LIBS PUBLIC + CIRCTCalyx + CIRCTExportCalyx + MLIRIR + MLIRPass + MLIRSupport + ) diff --git a/lib/Conversion/CalyxNative/CalyxNative.cpp b/lib/Conversion/CalyxNative/CalyxNative.cpp new file mode 100644 index 000000000000..643d4079bd2e --- /dev/null +++ b/lib/Conversion/CalyxNative/CalyxNative.cpp @@ -0,0 +1,165 @@ +//===- CalyxNative.cpp - Invoke the native Calyx compiler +//----------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//===----------------------------------------------------------------------===// +// +// Calls out to the native, Rust-based Calyx compiler using the `calyx` binary +// to run passes. +// +//===----------------------------------------------------------------------===// + +#include "../PassDetail.h" + +#include "circt/Conversion/CalyxNative.h" +#include "circt/Dialect/Calyx/CalyxEmitter.h" +#include "mlir/Parser/Parser.h" +#include "mlir/Support/FileUtilities.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/ToolOutputFile.h" + +using namespace mlir; +using namespace circt; + +/// ConversionPatterns. + +/// Pass entrypoint. + +namespace { +class CalyxNativePass : public CalyxNativeBase { +public: + void runOnOperation() override; + +private: + LogicalResult runOnModule(ModuleOp root); +}; +} // end anonymous namespace + +void CalyxNativePass::runOnOperation() { + ModuleOp mod = getOperation(); + if (failed(runOnModule(mod))) + return signalPassFailure(); +} + +LogicalResult CalyxNativePass::runOnModule(ModuleOp root) { + SmallString<32> execName = llvm::sys::path::filename("calyx"); + llvm::ErrorOr exeMb = llvm::sys::findProgramByName(execName); + + // If cannot find the executable, then nothing to do, return. + if (!exeMb) { + root.emitError() << "cannot find the `calyx` executable in PATH. " + << "Consider installing `calyx` using `cargo install " + "calyx` or reading the instructions here: " + "https://docs.calyxir.org/#compiler-installation"; + return failure(); + } + StringRef calyxExe = exeMb.get(); + + std::string errMsg; + SmallString<32> nativeInputFileName; + std::error_code errCode = llvm::sys::fs::getPotentiallyUniqueTempFileName( + "calyxNativeTemp", /*suffix=*/"", nativeInputFileName); + + if (std::error_code ok; errCode != ok) { + root.emitError( + "cannot generate a unique temporary file for input to Calyx compiler"); + return failure(); + } + + // Emit the current program into a file so the native compiler can operate + // over it. + std::unique_ptr inputFile = + mlir::openOutputFile(nativeInputFileName, &errMsg); + if (inputFile == nullptr) { + root.emitError(errMsg); + return failure(); + } + + auto res = circt::calyx::exportCalyx(root, inputFile->os()); + if (failed(res)) + return failure(); + inputFile->os().flush(); + + // Create a file for the native compiler to write the results into + SmallString<32> nativeOutputFileName; + errCode = llvm::sys::fs::getPotentiallyUniqueTempFileName( + "calyxNativeOutTemp", /*suffix=*/"", nativeOutputFileName); + if (std::error_code ok; errCode != ok) { + root.emitError( + "cannot generate a unique temporary file name to store output"); + return failure(); + } + + llvm::SmallVector calyxArgs = { + calyxExe, nativeInputFileName, "-o", nativeOutputFileName, "-b", "mlir"}; + + // Configure the native pass pipeline. If passPipeline is provided, we expect + // it to be a comma separated list of passes to run. + if (!passPipeline.empty()) { + llvm::SmallVector passArgs; + llvm::StringRef ppRef = passPipeline; + ppRef.split(passArgs, ","); + for (auto pass : passArgs) { + if (pass.empty()) + continue; + calyxArgs.push_back("-p"); + calyxArgs.push_back(pass); + } + } else { + // If no arguments are specified, use the default pipeline. + calyxArgs.push_back("-p"); + calyxArgs.push_back("all"); + } + + // We always need to run the lower-guards pass in the native compiler to emit + // valid MLIR + calyxArgs.push_back("-p"); + calyxArgs.push_back("lower-guards"); + + std::optional redirects[] = {/*stdin=*/std::nullopt, + /*stdout=*/nativeOutputFileName, + /*stderr=*/std::nullopt}; + + int result = llvm::sys::ExecuteAndWait( + calyxExe, calyxArgs, /*Env=*/std::nullopt, + /*Redirects=*/redirects, + /*SecondsToWait=*/0, /*MemoryLimit=*/0, &errMsg); + + if (result != 0) { + root.emitError() << errMsg; + return failure(); + } + + // Parse the output buffer into a Calyx operation so that we can insert it + // back into the program. + auto bufferRead = llvm::MemoryBuffer::getFile(nativeInputFileName); + if (!bufferRead || !*bufferRead) { + root.emitError("execution of '" + calyxExe + + "' did not produce any output file named '" + + nativeInputFileName + "'"); + return failure(); + } + + // Load the output from the native compiler as a ModuleOp + auto loadedMod = + parseSourceFile(nativeOutputFileName.str(), root.getContext()); + auto *loadedBlock = loadedMod->getBody(); + + // XXX(rachitnigam): This is quite baroque. We insert the new block before the + // previous one and then remove the old block. A better thing to do would be + // to replace the moduleOp completely but I couldn't figure out how to do + // that. + auto *oldBlock = root.getBody(); + loadedBlock->moveBefore(oldBlock); + oldBlock->erase(); + return success(); +} + +std::unique_ptr circt::createCalyxNativePass() { + return std::make_unique(); +} diff --git a/lib/Conversion/CalyxToFSM/CalyxToFSM.cpp b/lib/Conversion/CalyxToFSM/CalyxToFSM.cpp index c3acb2dc5382..30bf32256594 100644 --- a/lib/Conversion/CalyxToFSM/CalyxToFSM.cpp +++ b/lib/Conversion/CalyxToFSM/CalyxToFSM.cpp @@ -268,6 +268,81 @@ LogicalResult CompileFSMVisitor::visit(StateOp currentState, EnableOp enableOp, return success(); } +// CompileInvoke is used to convert invoke operations to group operations and +// enable operations. +class CompileInvoke { +public: + CompileInvoke(ComponentOp component, OpBuilder builder) + : component(component), builder(builder) {} + void compile(); + +private: + void lowerInvokeOp(InvokeOp invokeOp); + std::string getTransitionName(InvokeOp invokeOp); + ComponentOp component; + OpBuilder builder; + // Part of the group name. It is used to generate unique group names, the + // unique counter is reused across multiple calls to lowerInvokeOp, so the + // loop that's checking for name uniqueness usually finds a unique name on the + // first try. + size_t transitionNameTail = 0; +}; + +// Access all invokeOp. +void CompileInvoke::compile() { + llvm::SmallVector invokeOps = + component.getControlOp().getInvokeOps(); + for (InvokeOp op : invokeOps) + lowerInvokeOp(op); +} + +// Get the name of the generation group. +std::string CompileInvoke::getTransitionName(InvokeOp invokeOp) { + llvm::StringRef callee = invokeOp.getCallee(); + std::string transitionNameHead = "invoke_" + callee.str() + "_"; + std::string transitionName; + + // The following loop is used to check if the transitionName already exists. + // If it does, the loop regenerates the transitionName. + do { + transitionName = transitionNameHead + std::to_string(transitionNameTail++); + } while (component.getWiresOp().lookupSymbol(transitionName)); + return transitionName; +} + +// Convert an invoke operation to a group operation and an enable operation. +void CompileInvoke::lowerInvokeOp(InvokeOp invokeOp) { + // Create a ConstantOp to assign a value to the go port. + Operation *prevNode = component.getWiresOp().getOperation()->getPrevNode(); + builder.setInsertionPointAfter(prevNode); + hw::ConstantOp constantOp = builder.create( + prevNode->getLoc(), builder.getI1Type(), 1); + Location loc = component.getWiresOp().getLoc(); + + // Set the insertion point at the end of the wires block. + builder.setInsertionPointToEnd(component.getWiresOp().getBodyBlock()); + std::string transitionName = getTransitionName(invokeOp); + GroupOp groupOp = builder.create(loc, transitionName); + builder.setInsertionPointToStart(groupOp.getBodyBlock()); + Value go = invokeOp.getInstGoValue(); + + // Assign a value to the go port. + builder.create(loc, go, constantOp); + auto ports = invokeOp.getPorts(); + auto inputs = invokeOp.getInputs(); + + // Generate a series of assignment operations from a list of parameters. + for (auto [port, input] : llvm::zip(ports, inputs)) + builder.create(loc, port, input); + Value done = invokeOp.getInstDoneValue(); + + // Generate a group_done operation with the instance's done port. + builder.create(loc, done); + builder.setInsertionPointAfter(invokeOp.getOperation()); + builder.create(invokeOp.getLoc(), transitionName); + invokeOp.erase(); +} + class CalyxToFSMPass : public CalyxToFSMBase { public: void runOnOperation() override; @@ -279,6 +354,8 @@ void CalyxToFSMPass::runOnOperation() { auto ctrlOp = component.getControlOp(); assert(ctrlOp.getBodyBlock()->getOperations().size() == 1 && "Expected a single top-level operation in the schedule"); + CompileInvoke compileInvoke(component, builder); + compileInvoke.compile(); Operation &topLevelCtrlOp = ctrlOp.getBodyBlock()->front(); builder.setInsertionPoint(&topLevelCtrlOp); @@ -286,9 +363,10 @@ void CalyxToFSMPass::runOnOperation() { // refer to the symbols and SSA values defined in the regions of the // ComponentOp. This makes for an intermediate step, which allows for // outlining the FSM (materializing FSM I/O) at a later point. + auto machineName = ("control_" + component.getName()).str(); auto funcType = FunctionType::get(&getContext(), {}, {}); auto machine = - builder.create(ctrlOp.getLoc(), /*name=*/"control", + builder.create(ctrlOp.getLoc(), machineName, /*initialState=*/"fsm_entry", funcType); auto graph = FSMGraph(machine); diff --git a/lib/Conversion/CalyxToFSM/RemoveGroupsFromFSM.cpp b/lib/Conversion/CalyxToFSM/RemoveGroupsFromFSM.cpp index f61562b134d9..23141e7cc905 100644 --- a/lib/Conversion/CalyxToFSM/RemoveGroupsFromFSM.cpp +++ b/lib/Conversion/CalyxToFSM/RemoveGroupsFromFSM.cpp @@ -15,6 +15,7 @@ #include "circt/Dialect/Calyx/CalyxHelpers.h" #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/FSM/FSMGraph.h" +#include "circt/Dialect/Seq/SeqOps.h" #include "circt/Support/BackedgeBuilder.h" #include "circt/Support/LLVM.h" #include "mlir/IR/BuiltinTypes.h" @@ -256,10 +257,11 @@ LogicalResult CalyxRemoveGroupsFromFSM::outlineMachine() { } // Instantiate the FSM. + auto clkPort = componentOp.getClkPort(); + auto clk = b->create(clkPort.getLoc(), clkPort); auto fsmInstance = b->create( machineOp.getLoc(), machineOutputTypes, b->getStringAttr("controller"), - machineOp.getSymNameAttr(), fsmInputs, componentOp.getClkPort(), - componentOp.getResetPort()); + machineOp.getSymNameAttr(), fsmInputs, clk, componentOp.getResetPort()); // Record the FSM output group go signals. for (auto namedAttr : groupGoOutputsAttr.getValue()) { diff --git a/lib/Conversion/CalyxToHW/CalyxToHW.cpp b/lib/Conversion/CalyxToHW/CalyxToHW.cpp index ace3f89c151a..1d11e4457f02 100644 --- a/lib/Conversion/CalyxToHW/CalyxToHW.cpp +++ b/lib/Conversion/CalyxToHW/CalyxToHW.cpp @@ -44,7 +44,7 @@ struct ConvertComponentOp : public OpConversionPattern { SmallVector hwInputInfo; auto portInfo = component.getPortInfo(); for (auto [name, type, direction, _] : portInfo) - hwInputInfo.push_back({name, hwDirection(direction), type}); + hwInputInfo.push_back({{name, type, hwDirection(direction)}}); ModulePortInfo hwPortInfo(hwInputInfo); SmallVector argValues; @@ -77,12 +77,12 @@ struct ConvertComponentOp : public OpConversionPattern { } private: - hw::PortDirection hwDirection(calyx::Direction dir) const { + hw::ModulePort::Direction hwDirection(calyx::Direction dir) const { switch (dir) { case calyx::Direction::Input: - return hw::PortDirection::INPUT; + return hw::ModulePort::Direction::Input; case calyx::Direction::Output: - return hw::PortDirection::OUTPUT; + return hw::ModulePort::Direction::Output; } llvm_unreachable("unknown direction"); } @@ -237,6 +237,20 @@ struct ConvertCellOp : public OpInterfaceConversionPattern { .Case([&](XorLibOp op) { convertArithBinaryOp(op, wires, b); }) + .Case([&](MuxLibOp op) { + auto sel = wireIn(op.getCond(), op.instanceName(), + op.portName(op.getCond()), b); + auto tru = wireIn(op.getTru(), op.instanceName(), + op.portName(op.getTru()), b); + auto fal = wireIn(op.getFal(), op.instanceName(), + op.portName(op.getFal()), b); + + auto mux = b.create(sel, tru, fal); + + auto out = + wireOut(mux, op.instanceName(), op.portName(op.getOut()), b); + wires.append({sel.getInput(), tru.getInput(), fal.getInput(), out}); + }) // Pipelined arithmetic operations. .Case([&](MultPipeLibOp op) { convertPipelineOp(op, wires, b); @@ -263,13 +277,14 @@ struct ConvertCellOp : public OpInterfaceConversionPattern { op.portName(op.getClk()), b); auto reset = wireIn(op.getReset(), op.instanceName(), op.portName(op.getReset()), b); + auto seqClk = b.create(clk); auto doneReg = - reg(writeEn, clk, reset, op.instanceName() + "_done_reg", b); + reg(writeEn, seqClk, reset, op.instanceName() + "_done_reg", b); auto done = wireOut(doneReg, op.instanceName(), op.portName(op.getDone()), b); auto clockEn = b.create(writeEn, createOrFoldNot(done, b)); auto outReg = - regCe(in, clk, clockEn, reset, op.instanceName() + "_reg", b); + regCe(in, seqClk, clockEn, reset, op.instanceName() + "_reg", b); auto out = wireOut(outReg, op.instanceName(), "", b); wires.append({in.getInput(), writeEn.getInput(), clk.getInput(), reset.getInput(), out, done}); @@ -289,18 +304,21 @@ struct ConvertCellOp : public OpInterfaceConversionPattern { .Case([&](NotLibOp op) { auto in = wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b); - auto one = b.create(op.getIn().getType(), 0); - auto xorOp = b.create(in, one, false); + auto notOp = comb::createOrFoldNot(in, b); auto out = - wireOut(xorOp, op.instanceName(), op.portName(op.getOut()), b); + wireOut(notOp, op.instanceName(), op.portName(op.getOut()), b); wires.append({in.getInput(), out}); }) .Case([&](WireLibOp op) { auto wire = wireIn(op.getIn(), op.instanceName(), "", b); wires.append({wire.getInput(), wire}); }) + .Case([&](UndefLibOp op) { + auto undef = b.create(op.getType()); + wires.append({undef}); + }) .Case([&](PadLibOp op) { auto in = wireIn(op.getIn(), op.instanceName(), op.portName(op.getIn()), b); @@ -400,20 +418,18 @@ struct ConvertCellOp : public OpInterfaceConversionPattern { return b.create(wire); } - CompRegOp reg(Value source, Value clock, Value reset, Twine name, + CompRegOp reg(Value source, Value clock, Value reset, const Twine &name, ImplicitLocOpBuilder &b) const { auto resetValue = b.create(source.getType(), 0); - auto regName = b.getStringAttr(name); - return b.create(source.getType(), source, clock, regName, reset, - resetValue, regName); + return b.create(source, clock, reset, resetValue, name.str()); } CompRegClockEnabledOp regCe(Value source, Value clock, Value ce, Value reset, - Twine name, ImplicitLocOpBuilder &b) const { + const Twine &name, + ImplicitLocOpBuilder &b) const { auto resetValue = b.create(source.getType(), 0); - auto regName = b.getStringAttr(name); - return b.create(source.getType(), source, clock, ce, - regName, reset, resetValue, regName); + return b.create(source, clock, ce, reset, resetValue, + name.str()); } std::string createName(StringRef instanceName, StringRef portName) const { diff --git a/lib/Conversion/CombToArith/CMakeLists.txt b/lib/Conversion/CombToArith/CMakeLists.txt index 9d86c0810f0d..c581474c00fb 100644 --- a/lib/Conversion/CombToArith/CMakeLists.txt +++ b/lib/Conversion/CombToArith/CMakeLists.txt @@ -3,6 +3,7 @@ add_circt_conversion_library(CIRCTCombToArith DEPENDS CIRCTConversionPassIncGen + MLIRArithDialect LINK_COMPONENTS Core diff --git a/lib/Conversion/CombToArith/CombToArith.cpp b/lib/Conversion/CombToArith/CombToArith.cpp index 9ce8d8fe5669..51ab642aab29 100644 --- a/lib/Conversion/CombToArith/CombToArith.cpp +++ b/lib/Conversion/CombToArith/CombToArith.cpp @@ -198,6 +198,62 @@ struct VariadicOpConversion : OpConversionPattern { return success(); } }; + +// Shifts greater than or equal to the width of the lhs are currently +// unspecified in arith and produce poison in LLVM IR. To prevent undefined +// behaviour we handle this case explicitly. + +/// Lower the logical shift SourceOp to the logical shift TargetOp +/// Ensure to produce zero for shift amounts greater than or equal to the width +/// of the lhs +template +struct LogicalShiftConversion : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename SourceOp::Adaptor; + + LogicalResult + matchAndRewrite(SourceOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + unsigned shifteeWidth = + hw::type_cast(adaptor.getLhs().getType()) + .getIntOrFloatBitWidth(); + auto zeroConstOp = rewriter.create( + op.getLoc(), IntegerAttr::get(adaptor.getLhs().getType(), 0)); + auto maxShamtConstOp = rewriter.create( + op.getLoc(), + IntegerAttr::get(adaptor.getLhs().getType(), shifteeWidth)); + auto shiftOp = rewriter.createOrFold( + op.getLoc(), adaptor.getLhs(), adaptor.getRhs()); + auto isAllZeroOp = rewriter.createOrFold( + op.getLoc(), CmpIPredicate::uge, adaptor.getRhs(), + maxShamtConstOp.getResult()); + rewriter.replaceOpWithNewOp(op, isAllZeroOp, zeroConstOp, + shiftOp); + return success(); + } +}; + +/// Lower a comb::ShrSOp operation to a (saturating) arith::ShRSIOp +struct ShrSOpConversion : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ShrSOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + unsigned shifteeWidth = + hw::type_cast(adaptor.getLhs().getType()) + .getIntOrFloatBitWidth(); + // Clamp the shift amount to shifteeWidth - 1 + auto maxShamtMinusOneConstOp = rewriter.create( + op.getLoc(), + IntegerAttr::get(adaptor.getLhs().getType(), shifteeWidth - 1)); + auto shamtOp = rewriter.createOrFold(op.getLoc(), adaptor.getRhs(), + maxShamtMinusOneConstOp); + rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), shamtOp); + return success(); + } +}; + } // namespace //===----------------------------------------------------------------------===// @@ -211,25 +267,36 @@ struct ConvertCombToArithPass }; } // namespace +void circt::populateCombToArithConversionPatterns( + TypeConverter &converter, mlir::RewritePatternSet &patterns) { + patterns.add< + CombReplicateOpConversion, HWConstantOpConversion, IcmpOpConversion, + ExtractOpConversion, ConcatOpConversion, ShrSOpConversion, + LogicalShiftConversion, + LogicalShiftConversion, + BinaryOpConversion, BinaryOpConversion, + BinaryOpConversion, BinaryOpConversion, + BinaryOpConversion, BinaryOpConversion, + VariadicOpConversion, VariadicOpConversion, + VariadicOpConversion, VariadicOpConversion, + VariadicOpConversion>(converter, patterns.getContext()); +} + void ConvertCombToArithPass::runOnOperation() { ConversionTarget target(getContext()); target.addIllegalDialect(); target.addIllegalOp(); target.addLegalDialect(); + // Arith does not have an operation equivalent to comb.parity. A lowering + // would result in undesirably complex logic, therefore, we mark it legal + // here. + target.addLegalOp(); RewritePatternSet patterns(&getContext()); + TypeConverter converter; + converter.addConversion([](Type type) { return type; }); // TODO: a pattern for comb.parity - patterns.add< - CombReplicateOpConversion, HWConstantOpConversion, IcmpOpConversion, - ExtractOpConversion, ConcatOpConversion, - BinaryOpConversion, BinaryOpConversion, - BinaryOpConversion, BinaryOpConversion, - BinaryOpConversion, BinaryOpConversion, - BinaryOpConversion, BinaryOpConversion, - BinaryOpConversion, VariadicOpConversion, - VariadicOpConversion, VariadicOpConversion, - VariadicOpConversion, VariadicOpConversion>( - &getContext()); + populateCombToArithConversionPatterns(converter, patterns); if (failed(mlir::applyPartialConversion(getOperation(), target, std::move(patterns)))) diff --git a/lib/Conversion/CombToLLVM/CombToLLVM.cpp b/lib/Conversion/CombToLLVM/CombToLLVM.cpp index a61b68da32f4..36afcae67ef5 100644 --- a/lib/Conversion/CombToLLVM/CombToLLVM.cpp +++ b/lib/Conversion/CombToLLVM/CombToLLVM.cpp @@ -23,178 +23,6 @@ using namespace mlir; using namespace circt; -//===----------------------------------------------------------------------===// -// Extraction operation conversions -//===----------------------------------------------------------------------===// - -namespace { -/// Convert a comb::ExtractOp to LLVM dialect. -struct CombExtractOpConversion : public ConvertToLLVMPattern { - explicit CombExtractOpConversion(MLIRContext *ctx, - LLVMTypeConverter &typeConverter) - : ConvertToLLVMPattern(comb::ExtractOp::getOperationName(), ctx, - typeConverter) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const override { - auto extractOp = cast(op); - mlir::Value valueToTrunc = extractOp.getInput(); - mlir::Type type = extractOp.getInput().getType(); - - if (extractOp.getLowBit() != 0) { - mlir::Value amt = rewriter.create( - op->getLoc(), type, extractOp.getLowBitAttr()); - valueToTrunc = rewriter.create(op->getLoc(), type, - extractOp.getInput(), amt); - } - - rewriter.replaceOpWithNewOp( - op, extractOp.getResult().getType(), valueToTrunc); - return success(); - } -}; -} // namespace - -//===----------------------------------------------------------------------===// -// Concat operations conversion -//===----------------------------------------------------------------------===// - -namespace { -/// Convert a comb::ConcatOp to the LLVM dialect. -struct CombConcatOpConversion : public ConvertToLLVMPattern { - explicit CombConcatOpConversion(MLIRContext *ctx, - LLVMTypeConverter &typeConverter) - : ConvertToLLVMPattern(comb::ConcatOp::getOperationName(), ctx, - typeConverter) {} - - LogicalResult - matchAndRewrite(Operation *op, ArrayRef operands, - ConversionPatternRewriter &rewriter) const override { - auto concatOp = cast(op); - auto numOperands = concatOp->getNumOperands(); - mlir::Type type = concatOp.getResult().getType(); - - unsigned nextInsertion = type.getIntOrFloatBitWidth(); - auto aggregate = rewriter - .create(op->getLoc(), type, - IntegerAttr::get(type, 0)) - .getRes(); - - for (unsigned i = 0; i < numOperands; i++) { - nextInsertion -= - concatOp->getOperand(i).getType().getIntOrFloatBitWidth(); - - auto nextInsValue = rewriter.create( - op->getLoc(), type, IntegerAttr::get(type, nextInsertion)); - auto extended = rewriter.create(op->getLoc(), type, - concatOp->getOperand(i)); - auto shifted = rewriter.create(op->getLoc(), type, extended, - nextInsValue); - aggregate = - rewriter.create(op->getLoc(), type, aggregate, shifted) - .getRes(); - } - - rewriter.replaceOp(op, aggregate); - return success(); - } -}; -} // namespace - -namespace { -/// Lower a comb::ReplicateOp operation to the LLVM dialect. -struct CombReplicateOpConversion - : public ConvertOpToLLVMPattern { - using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern; - - LogicalResult - matchAndRewrite(comb::ReplicateOp op, OpAdaptor adaptor, - ConversionPatternRewriter &rewriter) const override { - - std::vector inputs(op.getMultiple(), op.getInput()); - rewriter.replaceOpWithNewOp(op, inputs); - return success(); - } -}; -} // namespace - -//===----------------------------------------------------------------------===// -// Bitwise conversions -//===----------------------------------------------------------------------===// - -namespace { -template -class VariadicOpConversion : public ConvertOpToLLVMPattern { -public: - using OpAdaptor = typename SourceOp::Adaptor; - using ConvertOpToLLVMPattern::ConvertOpToLLVMPattern; - using Super = VariadicOpConversion; - - LogicalResult - matchAndRewrite(SourceOp op, OpAdaptor adaptor, - ConversionPatternRewriter &rewriter) const override { - - size_t numOperands = op.getOperands().size(); - // All operands have the same type. - Type type = op.getOperandTypes().front(); - auto replacement = op.getOperand(0); - - for (unsigned i = 1; i < numOperands; i++) { - replacement = rewriter.create(op.getLoc(), type, replacement, - op.getOperand(i)); - } - - rewriter.replaceOp(op, replacement); - - return success(); - } -}; - -using AndOpConversion = VariadicOpConversion; -using OrOpConversion = VariadicOpConversion; -using XorOpConversion = VariadicOpConversion; - -using CombShlOpConversion = - OneToOneConvertToLLVMPattern; -using CombShrUOpConversion = - OneToOneConvertToLLVMPattern; -using CombShrSOpConversion = - OneToOneConvertToLLVMPattern; - -} // namespace - -//===----------------------------------------------------------------------===// -// Arithmetic conversions -//===----------------------------------------------------------------------===// - -namespace { - -using CombAddOpConversion = VariadicOpConversion; -using CombMulOpConversion = VariadicOpConversion; -using CombSubOpConversion = - OneToOneConvertToLLVMPattern; - -using CombDivUOpConversion = - OneToOneConvertToLLVMPattern; -using CombDivSOpConversion = - OneToOneConvertToLLVMPattern; - -using CombModUOpConversion = - OneToOneConvertToLLVMPattern; -using CombModSOpConversion = - OneToOneConvertToLLVMPattern; - -using CombICmpOpConversion = - OneToOneConvertToLLVMPattern; - -// comb.mux supports any type thus this conversion relies on the type converter -// to be able to convert the type of the operands and result to an LLVM_Type -using CombMuxOpConversion = - OneToOneConvertToLLVMPattern; - -} // namespace - namespace { /// Convert a comb::ParityOp to the LLVM dialect. struct CombParityOpConversion : public ConvertToLLVMPattern { @@ -216,61 +44,9 @@ struct CombParityOpConversion : public ConvertToLLVMPattern { return success(); } }; - -} // namespace - -//===----------------------------------------------------------------------===// -// Pass initialization -//===----------------------------------------------------------------------===// - -namespace { -struct CombToLLVMLoweringPass - : public ConvertCombToLLVMBase { - void runOnOperation() override; -}; } // namespace void circt::populateCombToLLVMConversionPatterns(LLVMTypeConverter &converter, RewritePatternSet &patterns) { - MLIRContext *ctx = converter.getDialect()->getContext(); - - // Extract conversion patterns. - patterns.add(ctx, converter); - - // Bitwise conversion patterns. - patterns.add(ctx, converter); - patterns.add(converter); - patterns.add( - converter); - - // Arithmetic conversion patterns. - patterns.add(converter); -} - -void CombToLLVMLoweringPass::runOnOperation() { - - RewritePatternSet patterns(&getContext()); - auto converter = mlir::LLVMTypeConverter(&getContext()); - - LLVMConversionTarget target(getContext()); - target.addLegalOp(); - target.addLegalOp(); - target.addLegalDialect(); - target.addIllegalDialect(); - - // Setup the conversion. - populateCombToLLVMConversionPatterns(converter, patterns); - - // Apply a partial conversion. - if (failed( - applyPartialConversion(getOperation(), target, std::move(patterns)))) - signalPassFailure(); -} - -/// Create an Comb to LLVM conversion pass. -std::unique_ptr> circt::createConvertCombToLLVMPass() { - return std::make_unique(); + patterns.add(patterns.getContext(), converter); } diff --git a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp index fb18c68e3de9..10c3ea6682b1 100644 --- a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp +++ b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp @@ -8,7 +8,7 @@ #include "circt/Conversion/ConvertToArcs.h" #include "../PassDetail.h" -#include "circt/Dialect/Arc/Ops.h" +#include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" #include "circt/Support/Namespace.h" @@ -23,8 +23,8 @@ using llvm::MapVector; static bool isArcBreakingOp(Operation *op) { return op->hasTrait() || - isa(op) || + isa(op) || op->getNumResults() > 1; } @@ -38,7 +38,7 @@ struct Converter { LogicalResult runOnModule(HWModuleOp module); LogicalResult analyzeFanIn(); void extractArcs(HWModuleOp module); - void absorbRegs(HWModuleOp module); + LogicalResult absorbRegs(HWModuleOp module); /// The global namespace used to create unique definition names. Namespace globalNamespace; @@ -89,7 +89,7 @@ LogicalResult Converter::runOnModule(HWModuleOp module) { if (module.getBodyBlock()->without_terminator().empty() && isa(module.getBodyBlock()->getTerminator())) return success(); - LLVM_DEBUG(llvm::dbgs() << "Analyzing " << module.moduleNameAttr() << " (" + LLVM_DEBUG(llvm::dbgs() << "Analyzing " << module.getModuleNameAttr() << " (" << arcBreakers.size() << " breakers)\n"); // For each operation, figure out the set of breaker ops it contributes to, @@ -101,7 +101,8 @@ LogicalResult Converter::runOnModule(HWModuleOp module) { // Extract the fanin mask groups into separate combinational arcs and // combine them with the registers in the design. extractArcs(module); - absorbRegs(module); + if (failed(absorbRegs(module))) + return failure(); return success(); } @@ -236,7 +237,7 @@ void Converter::extractArcs(HWModuleOp module) { auto defOp = builder.create( lastOp->getLoc(), builder.getStringAttr( - globalNamespace.newName(module.moduleName() + "_arc")), + globalNamespace.newName(module.getModuleName() + "_arc")), builder.getFunctionType(inputTypes, outputTypes)); defOp.getBody().push_back(block.release()); @@ -251,13 +252,14 @@ void Converter::extractArcs(HWModuleOp module) { } } -void Converter::absorbRegs(HWModuleOp module) { +LogicalResult Converter::absorbRegs(HWModuleOp module) { // Handle the trivial cases where all of an arc's results are used by // exactly one register each. unsigned outIdx = 0; unsigned numTrivialRegs = 0; for (auto &arc : arcUses) { Value clock = arc.getClock(); + Value reset; SmallVector absorbedRegs; SmallVector absorbedNames(arc.getNumResults(), {}); if (auto names = arc->getAttrOfType("names")) @@ -278,7 +280,27 @@ void Converter::absorbRegs(HWModuleOp module) { isTrivial = false; break; } + clock = regOp.getClk(); + reset = regOp.getReset(); + + // Check that if the register has a reset, it is to a constant zero + if (reset) { + Value resetValue = regOp.getResetValue(); + Operation *op = resetValue.getDefiningOp(); + if (!op) + return regOp->emitOpError( + "is reset by an input; not supported by ConvertToArcs"); + if (auto constant = dyn_cast(op)) { + if (constant.getValue() != 0) + return regOp->emitOpError("is reset to a constant non-zero value; " + "not supported by ConvertToArcs"); + } else { + return regOp->emitOpError("is reset to a value that is not clearly " + "constant; not supported by ConvertToArcs"); + } + } + absorbedRegs.push_back(regOp); // If we absorb a register into the arc, the arc effectively produces that // register's value. So if the register had a name, ensure that we assign @@ -294,10 +316,17 @@ void Converter::absorbRegs(HWModuleOp module) { ++numTrivialRegs; // Set the arc's clock to the clock of the registers we've absorbed, bump - // the latency up by one to account for the registers, and update the output - // names. Then replace the registers. + // the latency up by one to account for the registers, add the reset if + // present and update the output names. Then replace the registers. arc.getClockMutable().assign(clock); arc.setLatency(arc.getLatency() + 1); + if (reset) { + if (arc.getReset()) + return arc.emitError( + "StateOp tried to infer reset from CompReg, but already " + "had a reset."); + arc.getResetMutable().assign(reset); + } if (llvm::any_of(absorbedNames, [](auto name) { return !name.template cast().getValue().empty(); })) @@ -315,18 +344,20 @@ void Converter::absorbRegs(HWModuleOp module) { << " regs to arcs\n"); arcUses.truncate(outIdx); - // Group the remaining registers by the operation they use as input. This - // will allow us to generally collapse registers derived from the same arc - // into one shuffling arc. - MapVector, SmallVector> + // Group the remaining registers by their clock, their reset and the operation + // they use as input. This will allow us to generally collapse registers + // derived from the same arc into one shuffling arc. + MapVector, SmallVector> regsByInput; for (auto *op : arcBreakers) - if (auto regOp = dyn_cast_or_null(op)) - regsByInput[{regOp.getClk(), regOp.getInput().getDefiningOp()}].push_back( - regOp); + if (auto regOp = dyn_cast_or_null(op)) { + regsByInput[{regOp.getClk(), regOp.getReset(), + regOp.getInput().getDefiningOp()}] + .push_back(regOp); + } unsigned numMappedRegs = 0; - for (auto [clockAndOp, regOps] : regsByInput) { + for (auto [clockAndResetAndOp, regOps] : regsByInput) { numMappedRegs += regOps.size(); OpBuilder builder(module); auto block = std::make_unique(); @@ -357,13 +388,17 @@ void Converter::absorbRegs(HWModuleOp module) { auto defOp = builder.create(loc, builder.getStringAttr(globalNamespace.newName( - module.moduleName() + "_arc")), + module.getModuleName() + "_arc")), builder.getFunctionType(types, types)); defOp.getBody().push_back(block.release()); builder.setInsertionPoint(module.getBodyBlock()->getTerminator()); - auto arcOp = builder.create(loc, defOp, clockAndOp.first, Value{}, - 1, inputs); + auto arcOp = + builder.create(loc, defOp, std::get<0>(clockAndResetAndOp), + /*enable=*/Value{}, 1, inputs); + auto reset = std::get<1>(clockAndResetAndOp); + if (reset) + arcOp.getResetMutable().assign(reset); if (llvm::any_of(names, [](auto name) { return !name.template cast().getValue().empty(); })) @@ -377,6 +412,8 @@ void Converter::absorbRegs(HWModuleOp module) { if (numMappedRegs > 0) LLVM_DEBUG(llvm::dbgs() << "- Mapped " << numMappedRegs << " regs to " << regsByInput.size() << " shuffling arcs\n"); + + return success(); } //===----------------------------------------------------------------------===// diff --git a/lib/Conversion/DCToHW/CMakeLists.txt b/lib/Conversion/DCToHW/CMakeLists.txt new file mode 100644 index 000000000000..cf0a017ea345 --- /dev/null +++ b/lib/Conversion/DCToHW/CMakeLists.txt @@ -0,0 +1,18 @@ +add_circt_library(CIRCTDCToHW + DCToHW.cpp + + ADDITIONAL_HEADER_DIRS + ${MLIR_MAIN_INCLUDE_DIR}/mlir/Conversion/DCToHW + + LINK_LIBS PUBLIC + CIRCTHW + CIRCTDC + CIRCTESI + CIRCTDC + CIRCTDCTransforms + MLIRIR + MLIRPass + MLIRFuncDialect + MLIRSupport + MLIRTransforms +) diff --git a/lib/Conversion/DCToHW/DCToHW.cpp b/lib/Conversion/DCToHW/DCToHW.cpp new file mode 100644 index 000000000000..0d7fb54047ee --- /dev/null +++ b/lib/Conversion/DCToHW/DCToHW.cpp @@ -0,0 +1,867 @@ +//===- DCToHW.cpp - Translate DC into HW ----------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +//===----------------------------------------------------------------------===// +// +// This is the main DC to HW Conversion Pass Implementation. +// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/DCToHW.h" +#include "../PassDetail.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/DC/DCDialect.h" +#include "circt/Dialect/DC/DCOps.h" +#include "circt/Dialect/DC/DCPasses.h" +#include "circt/Dialect/ESI/ESIOps.h" +#include "circt/Dialect/ESI/ESITypes.h" +#include "circt/Dialect/HW/ConversionPatterns.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/HW/HWTypes.h" +#include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Support/BackedgeBuilder.h" +#include "circt/Support/ValueMapper.h" +#include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Transforms/DialectConversion.h" +#include "llvm/ADT/TypeSwitch.h" +#include "llvm/Support/MathExtras.h" + +#include + +using namespace mlir; +using namespace circt; +using namespace circt::dc; +using namespace circt::hw; + +using NameUniquer = std::function; + +// NOLINTNEXTLINE(misc-no-recursion) +static Type tupleToStruct(TupleType tuple) { + auto *ctx = tuple.getContext(); + mlir::SmallVector hwfields; + for (auto [i, innerType] : llvm::enumerate(tuple)) { + Type convertedInnerType = innerType; + if (auto tupleInnerType = innerType.dyn_cast()) + convertedInnerType = tupleToStruct(tupleInnerType); + hwfields.push_back( + {StringAttr::get(ctx, "field" + Twine(i)), convertedInnerType}); + } + + return hw::StructType::get(ctx, hwfields); +} + +/// Converts any type 't' into a `hw`-compatible type. +/// tuple -> hw.struct +/// none -> i0 +/// (tuple[...] | hw.struct)[...] -> (tuple | hw.struct)[toHwType(...)] +// NOLINTNEXTLINE(misc-no-recursion) +static Type toHWType(Type t) { + return TypeSwitch(t) + .Case([](TupleType tt) { return toHWType(tupleToStruct(tt)); }) + .Case([](hw::StructType st) { + llvm::SmallVector structFields( + st.getElements()); + for (auto &field : structFields) + field.type = toHWType(field.type); + return hw::StructType::get(st.getContext(), structFields); + }) + .Case([](NoneType nt) { return IntegerType::get(nt.getContext(), 0); }) + .Default([](Type t) { return t; }); +} + +static Type toESIHWType(Type t) { + Type outType = + llvm::TypeSwitch(t) + .Case([](ValueType vt) { + return esi::ChannelType::get(vt.getContext(), + toHWType(vt.getInnerType())); + }) + .Case([](TokenType tt) { + return esi::ChannelType::get(tt.getContext(), + IntegerType::get(tt.getContext(), 0)); + }) + .Default([](auto t) { return toHWType(t); }); + + return outType; +} + +namespace { + +/// Shared state used by various functions; captured in a struct to reduce the +/// number of arguments that we have to pass around. +struct DCLoweringState { + ModuleOp parentModule; + NameUniquer nameUniquer; +}; + +/// A type converter is needed to perform the in-flight materialization of "raw" +/// (non-ESI channel) types to their ESI channel correspondents. This comes into +/// effect when backedges exist in the input IR. +class ESITypeConverter : public TypeConverter { +public: + ESITypeConverter() { + addConversion([](Type type) -> Type { return toESIHWType(type); }); + addConversion([](esi::ChannelType t) -> Type { return t; }); + addTargetMaterialization( + [](mlir::OpBuilder &builder, mlir::Type resultType, + mlir::ValueRange inputs, + mlir::Location loc) -> std::optional { + if (inputs.size() != 1) + return std::nullopt; + + return inputs[0]; + }); + + addSourceMaterialization( + [](mlir::OpBuilder &builder, mlir::Type resultType, + mlir::ValueRange inputs, + mlir::Location loc) -> std::optional { + if (inputs.size() != 1) + return std::nullopt; + + return inputs[0]; + }); + } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// HW Sub-module Related Functions +//===----------------------------------------------------------------------===// + +namespace { + +/// Input handshakes contain a resolved valid and (optional )data signal, and +/// a to-be-assigned ready signal. +struct InputHandshake { + Value channel; + Value valid; + std::optional ready; + Value data; +}; + +/// Output handshakes contain a resolved ready, and to-be-assigned valid and +/// (optional) data signals. +struct OutputHandshake { + Value channel; + std::optional valid; + Value ready; + std::optional data; +}; + +/// Directly connect an input handshake to an output handshake +static void connect(InputHandshake &input, OutputHandshake &output) { + output.valid->setValue(input.valid); + input.ready->setValue(output.ready); +} + +template +llvm::SmallVector extractValues(llvm::SmallVector &container, + llvm::function_ref extractor) { + llvm::SmallVector result; + llvm::transform(container, std::back_inserter(result), extractor); + return result; +} + +// Wraps a set of input and output handshakes with an API that provides +// access to collections of the underlying values. +struct UnwrappedIO { + llvm::SmallVector inputs; + llvm::SmallVector outputs; + + llvm::SmallVector getInputValids() { + return extractValues( + inputs, [](auto &hs) { return hs.valid; }); + } + llvm::SmallVector> getInputReadys() { + return extractValues, InputHandshake>( + inputs, [](auto &hs) { return hs.ready; }); + } + llvm::SmallVector> getOutputValids() { + return extractValues, OutputHandshake>( + outputs, [](auto &hs) { return hs.valid; }); + } + llvm::SmallVector getInputDatas() { + return extractValues( + inputs, [](auto &hs) { return hs.data; }); + } + llvm::SmallVector getOutputReadys() { + return extractValues( + outputs, [](auto &hs) { return hs.ready; }); + } + + llvm::SmallVector getOutputChannels() { + return extractValues( + outputs, [](auto &hs) { return hs.channel; }); + } + llvm::SmallVector> getOutputDatas() { + return extractValues, OutputHandshake>( + outputs, [](auto &hs) { return hs.data; }); + } +}; + +/// A class containing a bunch of syntactic sugar to reduce builder function +/// verbosity. +/// @todo: should be moved to support. +struct RTLBuilder { + RTLBuilder(Location loc, OpBuilder &builder, Value clk = Value(), + Value rst = Value()) + : b(builder), loc(loc), clk(clk), rst(rst) {} + + Value constant(const APInt &apv, StringRef name = {}) { + // Cannot use zero-width APInt's in DenseMap's, see + // https://github.com/llvm/llvm-project/issues/58013 + bool isZeroWidth = apv.getBitWidth() == 0; + if (!isZeroWidth) { + auto it = constants.find(apv); + if (it != constants.end()) + return it->second; + } + + auto cval = b.create(loc, apv); + if (!isZeroWidth) + constants[apv] = cval; + return cval; + } + + Value constant(unsigned width, int64_t value, StringRef name = {}) { + return constant(APInt(width, value)); + } + std::pair wrap(Value data, Value valid, StringRef name = {}) { + auto wrapOp = b.create(loc, data, valid); + return {wrapOp.getResult(0), wrapOp.getResult(1)}; + } + std::pair unwrap(Value channel, Value ready, + StringRef name = {}) { + auto unwrapOp = b.create(loc, channel, ready); + return {unwrapOp.getResult(0), unwrapOp.getResult(1)}; + } + + /// Various syntactic sugar functions. + Value reg(StringRef name, Value in, Value rstValue, Value clk = Value(), + Value rst = Value()) { + Value resolvedClk = clk ? clk : this->clk; + Value resolvedRst = rst ? rst : this->rst; + assert(resolvedClk && + "No global clock provided to this RTLBuilder - a clock " + "signal must be provided to the reg(...) function."); + assert(resolvedRst && + "No global reset provided to this RTLBuilder - a reset " + "signal must be provided to the reg(...) function."); + + return b.create(loc, in, resolvedClk, resolvedRst, rstValue, + name); + } + + Value cmp(Value lhs, Value rhs, comb::ICmpPredicate predicate, + StringRef name = {}) { + return b.create(loc, predicate, lhs, rhs); + } + + Value buildNamedOp(llvm::function_ref f, StringRef name) { + Value v = f(); + StringAttr nameAttr; + Operation *op = v.getDefiningOp(); + if (!name.empty()) { + op->setAttr("sv.namehint", b.getStringAttr(name)); + nameAttr = b.getStringAttr(name); + } + return v; + } + + /// Bitwise 'and'. + Value bitAnd(ValueRange values, StringRef name = {}) { + return buildNamedOp( + [&]() { return b.create(loc, values, false); }, name); + } + + // Bitwise 'or'. + Value bitOr(ValueRange values, StringRef name = {}) { + return buildNamedOp( + [&]() { return b.create(loc, values, false); }, name); + } + + /// Bitwise 'not'. + Value bitNot(Value value, StringRef name = {}) { + auto allOnes = constant(value.getType().getIntOrFloatBitWidth(), -1); + std::string inferedName; + if (!name.empty()) { + // Try to create a name from the input value. + if (auto valueName = + value.getDefiningOp()->getAttrOfType("sv.namehint")) { + inferedName = ("not_" + valueName.getValue()).str(); + name = inferedName; + } + } + + return buildNamedOp( + [&]() { return b.create(loc, value, allOnes); }, name); + } + + Value shl(Value value, Value shift, StringRef name = {}) { + return buildNamedOp( + [&]() { return b.create(loc, value, shift); }, name); + } + + Value concat(ValueRange values, StringRef name = {}) { + return buildNamedOp([&]() { return b.create(loc, values); }, + name); + } + + llvm::SmallVector extractBits(Value v, StringRef name = {}) { + llvm::SmallVector bits; + for (unsigned i = 0, e = v.getType().getIntOrFloatBitWidth(); i != e; ++i) + bits.push_back(b.create(loc, v, i, /*bitWidth=*/1)); + return bits; + } + + /// OR-reduction of the bits in 'v'. + Value reduceOr(Value v, StringRef name = {}) { + return buildNamedOp([&]() { return bitOr(extractBits(v)); }, name); + } + + /// Extract bits v[hi:lo] (inclusive). + Value extract(Value v, unsigned lo, unsigned hi, StringRef name = {}) { + unsigned width = hi - lo + 1; + return buildNamedOp( + [&]() { return b.create(loc, v, lo, width); }, name); + } + + /// Truncates 'value' to its lower 'width' bits. + Value truncate(Value value, unsigned width, StringRef name = {}) { + return extract(value, 0, width - 1, name); + } + + Value zext(Value value, unsigned outWidth, StringRef name = {}) { + unsigned inWidth = value.getType().getIntOrFloatBitWidth(); + assert(inWidth <= outWidth && "zext: input width must be <= output width."); + if (inWidth == outWidth) + return value; + auto c0 = constant(outWidth - inWidth, 0); + return concat({c0, value}, name); + } + + Value sext(Value value, unsigned outWidth, StringRef name = {}) { + return comb::createOrFoldSExt(loc, value, b.getIntegerType(outWidth), b); + } + + /// Extracts a single bit v[bit]. + Value bit(Value v, unsigned index, StringRef name = {}) { + return extract(v, index, index, name); + } + + /// Creates a hw.array of the given values. + Value arrayCreate(ValueRange values, StringRef name = {}) { + return buildNamedOp( + [&]() { return b.create(loc, values); }, name); + } + + /// Extract the 'index'th value from the input array. + Value arrayGet(Value array, Value index, StringRef name = {}) { + return buildNamedOp( + [&]() { return b.create(loc, array, index); }, name); + } + + /// Muxes a range of values. + /// The select signal is expected to be a decimal value which selects + /// starting from the lowest index of value. + Value mux(Value index, ValueRange values, StringRef name = {}) { + if (values.size() == 2) { + return buildNamedOp( + [&]() { + return b.create(loc, index, values[1], values[0]); + }, + name); + } + return arrayGet(arrayCreate(values), index, name); + } + + /// Muxes a range of values. The select signal is expected to be a 1-hot + /// encoded value. + Value oneHotMux(Value index, ValueRange inputs) { + // Confirm the select input can be a one-hot encoding for the inputs. + unsigned numInputs = inputs.size(); + assert(numInputs == index.getType().getIntOrFloatBitWidth() && + "mismatch between width of one-hot select input and the number of " + "inputs to be selected"); + + // Start the mux tree with zero value. + auto dataType = inputs[0].getType(); + unsigned width = + dataType.isa() ? 0 : dataType.getIntOrFloatBitWidth(); + Value muxValue = constant(width, 0); + + // Iteratively chain together muxes from the high bit to the low bit. + for (size_t i = numInputs - 1; i != 0; --i) { + Value input = inputs[i]; + Value selectBit = bit(index, i); + muxValue = mux(selectBit, {muxValue, input}); + } + + return muxValue; + } + + OpBuilder &b; + Location loc; + Value clk, rst; + DenseMap constants; +}; + +static bool isZeroWidthType(Type type) { + if (auto intType = type.dyn_cast()) + return intType.getWidth() == 0; + return type.isa(); +} + +static UnwrappedIO unwrapIO(Location loc, ValueRange operands, + TypeRange results, + ConversionPatternRewriter &rewriter, + BackedgeBuilder &bb) { + RTLBuilder rtlb(loc, rewriter); + UnwrappedIO unwrapped; + for (auto in : operands) { + assert(isa(in.getType())); + auto ready = bb.get(rtlb.b.getI1Type()); + auto [data, valid] = rtlb.unwrap(in, ready); + unwrapped.inputs.push_back(InputHandshake{in, valid, ready, data}); + } + for (auto outputType : results) { + outputType = toESIHWType(outputType); + esi::ChannelType channelType = cast(outputType); + OutputHandshake hs; + Type innerType = channelType.getInner(); + Value data; + if (isZeroWidthType(innerType)) { + // Feed the ESI wrap with an i0 constant. + data = + rewriter.create(loc, rewriter.getIntegerType(0), 0); + } else { + // Create a backedge for the unresolved data. + auto dataBackedge = bb.get(innerType); + hs.data = dataBackedge; + data = dataBackedge; + } + auto valid = bb.get(rewriter.getI1Type()); + auto [dataCh, ready] = rtlb.wrap(data, valid); + hs.valid = valid; + hs.ready = ready; + hs.channel = dataCh; + unwrapped.outputs.push_back(hs); + } + return unwrapped; +} + +static UnwrappedIO unwrapIO(Operation *op, ValueRange operands, + ConversionPatternRewriter &rewriter, + BackedgeBuilder &bb) { + return unwrapIO(op->getLoc(), operands, op->getResultTypes(), rewriter, bb); +} + +/// Locate the clock and reset values from the parent operation based on +/// attributes assigned to the arguments. +static FailureOr> getClockAndReset(Operation *op) { + auto *parent = op->getParentOp(); + auto parentFuncOp = dyn_cast(parent); + if (!parentFuncOp) + return parent->emitOpError("parent op does not implement HWModuleLike"); + + auto argAttrs = parentFuncOp.getAllInputAttrs(); + + std::optional clockIdx, resetIdx; + + for (auto [idx, battrs] : llvm::enumerate(argAttrs)) { + auto attrs = cast(battrs); + if (attrs.get("dc.clock")) { + if (clockIdx) + return parent->emitOpError( + "multiple arguments contains a 'dc.clock' attribute"); + clockIdx = idx; + } + + if (attrs.get("dc.reset")) { + if (resetIdx) + return parent->emitOpError( + "multiple arguments contains a 'dc.reset' attribute"); + resetIdx = idx; + } + } + + if (!clockIdx) + return parent->emitOpError("no argument contains a 'dc.clock' attribute"); + + if (!resetIdx) + return parent->emitOpError("no argument contains a 'dc.reset' attribute"); + + return {std::make_pair(parentFuncOp.getArgumentForInput(*clockIdx), + parentFuncOp.getArgumentForInput(*resetIdx))}; +} + +class ForkConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(ForkOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto bb = BackedgeBuilder(rewriter, op.getLoc()); + auto crRes = getClockAndReset(op); + if (failed(crRes)) + return failure(); + auto [clock, reset] = *crRes; + RTLBuilder rtlb(op.getLoc(), rewriter, clock, reset); + UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb); + + auto &input = io.inputs[0]; + + Value c0I1 = rtlb.constant(1, 0); + llvm::SmallVector doneWires; + for (auto [i, output] : llvm::enumerate(io.outputs)) { + Backedge doneBE = bb.get(rtlb.b.getI1Type()); + Value emitted = rtlb.bitAnd({doneBE, rtlb.bitNot(*input.ready)}); + Value emittedReg = + rtlb.reg("emitted_" + std::to_string(i), emitted, c0I1); + Value outValid = rtlb.bitAnd({rtlb.bitNot(emittedReg), input.valid}); + output.valid->setValue(outValid); + Value validReady = rtlb.bitAnd({output.ready, outValid}); + Value done = + rtlb.bitOr({validReady, emittedReg}, "done" + std::to_string(i)); + doneBE.setValue(done); + doneWires.push_back(done); + } + input.ready->setValue(rtlb.bitAnd(doneWires, "allDone")); + + rewriter.replaceOp(op, io.getOutputChannels()); + return success(); + } +}; + +class JoinConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(JoinOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto bb = BackedgeBuilder(rewriter, op.getLoc()); + UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb); + RTLBuilder rtlb(op.getLoc(), rewriter); + auto &output = io.outputs[0]; + + Value allValid = rtlb.bitAnd(io.getInputValids()); + output.valid->setValue(allValid); + + auto validAndReady = rtlb.bitAnd({output.ready, allValid}); + for (auto &input : io.inputs) + input.ready->setValue(validAndReady); + + rewriter.replaceOp(op, io.outputs[0].channel); + return success(); + } +}; + +class SelectConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(SelectOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto bb = BackedgeBuilder(rewriter, op.getLoc()); + UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb); + RTLBuilder rtlb(op.getLoc(), rewriter); + + // Extract select signal from the unwrapped IO. + auto select = io.inputs[0]; + io.inputs.erase(io.inputs.begin()); + buildMuxLogic(rtlb, io, select); + + rewriter.replaceOp(op, io.outputs[0].channel); + return success(); + } + + // Builds mux logic for the given inputs and outputs. + // Note: it is assumed that the caller has removed the 'select' signal from + // the 'unwrapped' inputs and provide it as a separate argument. + void buildMuxLogic(RTLBuilder &rtlb, UnwrappedIO &unwrapped, + InputHandshake &select) const { + + // ============================= Control logic ============================= + size_t numInputs = unwrapped.inputs.size(); + size_t selectWidth = llvm::Log2_64_Ceil(numInputs); + Value truncatedSelect = + select.data.getType().getIntOrFloatBitWidth() > selectWidth + ? rtlb.truncate(select.data, selectWidth) + : select.data; + + // Decimal-to-1-hot decoder. 'shl' operands must be identical in size. + auto selectZext = rtlb.zext(truncatedSelect, numInputs); + auto select1h = rtlb.shl(rtlb.constant(numInputs, 1), selectZext); + auto &res = unwrapped.outputs[0]; + + // Mux input valid signals. + auto selectedInputValid = + rtlb.mux(truncatedSelect, unwrapped.getInputValids()); + // Result is valid when the selected input and the select input is valid. + auto selAndInputValid = rtlb.bitAnd({selectedInputValid, select.valid}); + res.valid->setValue(selAndInputValid); + auto resValidAndReady = rtlb.bitAnd({selAndInputValid, res.ready}); + + // Select is ready when result is valid and ready (result transacting). + select.ready->setValue(resValidAndReady); + + // Assign each input ready signal if it is currently selected. + for (auto [inIdx, in] : llvm::enumerate(unwrapped.inputs)) { + // Extract the selection bit for this input. + auto isSelected = rtlb.bit(select1h, inIdx); + + // '&' that with the result valid and ready, and assign to the input + // ready signal. + auto activeAndResultValidAndReady = + rtlb.bitAnd({isSelected, resValidAndReady}); + in.ready->setValue(activeAndResultValidAndReady); + } + } +}; + +class BranchConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(BranchOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto bb = BackedgeBuilder(rewriter, op.getLoc()); + UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb); + RTLBuilder rtlb(op.getLoc(), rewriter); + auto cond = io.inputs[0]; + auto trueRes = io.outputs[0]; + auto falseRes = io.outputs[1]; + + // Connect valid signal of both results. + trueRes.valid->setValue(rtlb.bitAnd({cond.data, cond.valid})); + falseRes.valid->setValue(rtlb.bitAnd({rtlb.bitNot(cond.data), cond.valid})); + + // Connect ready signal of condition. + Value selectedResultReady = + rtlb.mux(cond.data, {falseRes.ready, trueRes.ready}); + Value condReady = rtlb.bitAnd({selectedResultReady, cond.valid}); + cond.ready->setValue(condReady); + + rewriter.replaceOp(op, + SmallVector{trueRes.channel, falseRes.channel}); + return success(); + } +}; + +class ToESIConversionPattern : public OpConversionPattern { + // Essentially a no-op, seeing as the type converter does the heavy + // lifting here. +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ToESIOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOp(op, operands.getOperands()); + return success(); + } +}; + +class FromESIConversionPattern : public OpConversionPattern { + // Essentially a no-op, seeing as the type converter does the heavy + // lifting here. +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(FromESIOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOp(op, operands.getOperands()); + return success(); + } +}; + +class SinkConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(SinkOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto bb = BackedgeBuilder(rewriter, op.getLoc()); + UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb); + io.inputs[0].ready->setValue( + RTLBuilder(op.getLoc(), rewriter).constant(1, 1)); + rewriter.eraseOp(op); + return success(); + } +}; + +class SourceConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(SourceOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto bb = BackedgeBuilder(rewriter, op.getLoc()); + UnwrappedIO io = unwrapIO(op, operands.getOperands(), rewriter, bb); + RTLBuilder rtlb(op.getLoc(), rewriter); + io.outputs[0].valid->setValue(rtlb.constant(1, 1)); + rewriter.replaceOp(op, io.outputs[0].channel); + return success(); + } +}; + +class PackConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(PackOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto bb = BackedgeBuilder(rewriter, op.getLoc()); + UnwrappedIO io = unwrapIO(op, llvm::SmallVector{operands.getToken()}, + rewriter, bb); + RTLBuilder rtlb(op.getLoc(), rewriter); + auto &input = io.inputs[0]; + auto &output = io.outputs[0]; + output.data->setValue(operands.getInput()); + connect(input, output); + rewriter.replaceOp(op, output.channel); + return success(); + } +}; + +class UnpackConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(UnpackOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto bb = BackedgeBuilder(rewriter, op.getLoc()); + UnwrappedIO io = unwrapIO( + op.getLoc(), llvm::SmallVector{operands.getInput()}, + // Only generate an output channel for the token typed output. + llvm::SmallVector{op.getToken().getType()}, rewriter, bb); + RTLBuilder rtlb(op.getLoc(), rewriter); + auto &input = io.inputs[0]; + auto &output = io.outputs[0]; + + llvm::SmallVector unpackedValues; + unpackedValues.push_back(input.data); + + connect(input, output); + llvm::SmallVector outputs; + outputs.push_back(output.channel); + outputs.append(unpackedValues.begin(), unpackedValues.end()); + rewriter.replaceOp(op, outputs); + return success(); + } +}; + +class BufferConversionPattern : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(BufferOp op, OpAdaptor operands, + ConversionPatternRewriter &rewriter) const override { + auto crRes = getClockAndReset(op); + if (failed(crRes)) + return failure(); + auto [clock, reset] = *crRes; + + // ... esi.buffer should in theory provide a correct (latency-insensitive) + // implementation... + Type channelType = operands.getInput().getType(); + rewriter.replaceOpWithNewOp( + op, channelType, clock, reset, operands.getInput(), op.getSizeAttr(), + nullptr); + return success(); + }; +}; + +} // namespace + +static bool isDCType(Type type) { return type.isa(); } + +/// Returns true if the given `op` is considered as legal - i.e. it does not +/// contain any dc-typed values. +static bool isLegalOp(Operation *op) { + if (auto funcOp = dyn_cast(op)) { + return llvm::none_of(funcOp.getPortTypes(), isDCType) && + llvm::none_of(funcOp.getBodyBlock()->getArgumentTypes(), isDCType); + } + + bool operandsOK = llvm::none_of(op->getOperandTypes(), isDCType); + bool resultsOK = llvm::none_of(op->getResultTypes(), isDCType); + return operandsOK && resultsOK; +} + +//===----------------------------------------------------------------------===// +// HW Top-module Related Functions +//===----------------------------------------------------------------------===// + +namespace { +class DCToHWPass : public DCToHWBase { +public: + void runOnOperation() override { + Operation *parent = getOperation(); + + // Lowering to HW requires that every DC-typed value is used exactly once. + // Check whether this precondition is met, and if not, exit. + auto walkRes = parent->walk([&](Operation *op) { + for (auto res : op->getResults()) { + if (res.getType().isa()) { + if (res.use_empty()) { + op->emitOpError() << "DCToHW: value " << res << " is unused."; + return WalkResult::interrupt(); + } + if (!res.hasOneUse()) { + op->emitOpError() + << "DCToHW: value " << res << " has multiple uses."; + return WalkResult::interrupt(); + } + } + } + return WalkResult::advance(); + }); + + if (walkRes.wasInterrupted()) { + parent->emitOpError() + << "DCToHW: failed to verify that all values " + "are used exactly once. Remember to run the " + "fork/sink materialization pass before HW lowering."; + signalPassFailure(); + return; + } + + ESITypeConverter typeConverter; + ConversionTarget target(getContext()); + target.markUnknownOpDynamicallyLegal(isLegalOp); + + // All top-level logic of a handshake module will be the interconnectivity + // between instantiated modules. + target.addIllegalDialect(); + + RewritePatternSet patterns(parent->getContext()); + + patterns.insert( + typeConverter, parent->getContext()); + + if (failed(applyPartialConversion(parent, target, std::move(patterns)))) + signalPassFailure(); + } +}; +} // namespace + +std::unique_ptr circt::createDCToHWPass() { + return std::make_unique(); +} diff --git a/lib/Conversion/ExportChiselInterface/ExportChiselInterface.cpp b/lib/Conversion/ExportChiselInterface/ExportChiselInterface.cpp index 08505e3532e6..123ba91f620d 100644 --- a/lib/Conversion/ExportChiselInterface/ExportChiselInterface.cpp +++ b/lib/Conversion/ExportChiselInterface/ExportChiselInterface.cpp @@ -30,148 +30,189 @@ using namespace firrtl; static const unsigned int indentIncrement = 2; -/// Emits type construction expression for the port type, recursing into -/// aggregate types as needed. -static LogicalResult emitPortType(Location location, FIRRTLBaseType type, - Direction direction, llvm::raw_ostream &os, - unsigned int indent, - bool hasEmittedDirection = false) { - auto emitTypeWithArguments = - [&](StringRef name, - // A lambda of type (bool hasEmittedDirection) -> LogicalResult. - auto emitArguments, - // Indicates whether parentheses around type arguments should be used. - bool emitParentheses = true) -> LogicalResult { - // Include the direction if the type is not composed of flips and analog - // signals and we haven't already emitted the direction before recursing to - // this field. - bool emitDirection = - type.isPassive() && !type.containsAnalog() && !hasEmittedDirection; - if (emitDirection) { - switch (direction) { - case Direction::In: - os << "Input("; - break; - case Direction::Out: - os << "Output("; - break; - } +namespace { +class Emitter { +public: + Emitter(llvm::raw_ostream &os) : os(os) {} + + bool hasEmittedProbeType() { return hasEmittedProbe; } + + /// Emits an `ExtModule` class with port declarations for `module`. + LogicalResult emitModule(FModuleLike module) { + os << "class " << module.getModuleName() << " extends ExtModule {\n"; + + for (const auto &port : module.getPorts()) { + if (failed(emitPort(port))) + return failure(); } - os << name; + os << "}\n"; - if (emitParentheses) - os << "("; + return success(); + } - if (failed(emitArguments(hasEmittedDirection || emitDirection))) +private: + /// Emits an `IO` for the `port`. + LogicalResult emitPort(const PortInfo &port) { + os.indent(indentIncrement) << "val " << port.getName() << " = IO("; + if (failed( + emitPortType(port.loc, port.type, port.direction, indentIncrement))) return failure(); + os << ")\n"; - if (emitParentheses) - os << ')'; + return success(); + } - if (emitDirection) - os << ')'; + /// Emits type construction expression for the port type, recursing into + /// aggregate types as needed. + LogicalResult emitPortType(Location location, Type type, Direction direction, + unsigned int indent, + bool hasEmittedDirection = false) { + auto emitTypeWithArguments = + [&]( // This is provided if the type is a base type, otherwise this is + // null + FIRRTLBaseType baseType, StringRef name, + // A lambda of type (bool hasEmittedDirection) -> LogicalResult. + auto emitArguments, + // Indicates whether parentheses around type arguments should be + // used. + bool emitParentheses = true) -> LogicalResult { + // Include the direction if the type is not a base (i.e. hardware) type or + // is not composed of flips and analog signals and we haven't already + // emitted the direction before recursing to this field. + // Chisel direction functions override any internal directions. In other + // words, Output(new Bundle {...}) erases all direction information inside + // the bundle. Because of this, directions are placed on the outermost + // passive members of a hardware type. + bool emitDirection = + !baseType || (!hasEmittedDirection && baseType.isPassive() && + !baseType.containsAnalog()); + if (emitDirection) { + switch (direction) { + case Direction::In: + os << "Input("; + break; + case Direction::Out: + os << "Output("; + break; + } + } - return success(); - }; - - // Emits a type that does not require arguments. - auto emitType = [&](StringRef name) -> LogicalResult { - return emitTypeWithArguments(name, [](bool) { return success(); }); - }; - - // Emits a type that requires a known width argument. - auto emitWidthQualifiedType = [&](auto type, - StringRef name) -> LogicalResult { - auto width = type.getWidth(); - if (!width.has_value()) { - return LogicalResult(emitError( - location, "Expected width to be inferred for exported port")); - } - return emitTypeWithArguments(name, [&](bool) { - os << *width << ".W"; - return success(); - }); - }; - - return TypeSwitch(type) - .Case([&](ClockType) { return emitType("Clock"); }) - .Case( - [&](AsyncResetType) { return emitType("AsyncReset"); }) - .Case([&](ResetType) { - return emitError( - location, "Expected reset type to be inferred for exported port"); - }) - .Case([&](UIntType uIntType) { - return emitWidthQualifiedType(uIntType, "UInt"); - }) - .Case([&](SIntType sIntType) { - return emitWidthQualifiedType(sIntType, "SInt"); - }) - .Case([&](AnalogType analogType) { - return emitWidthQualifiedType(analogType, "Analog"); - }) - .Case([&](BundleType bundleType) { - // Emit an anonymous bundle, emitting a `val` for each field. - return emitTypeWithArguments( - "new Bundle ", - [&](bool hasEmittedDirection) { - os << "{\n"; - unsigned int nestedIndent = indent + indentIncrement; - for (const auto &element : bundleType.getElements()) { - os.indent(nestedIndent) - << "val " << element.name.getValue() << " = "; - auto elementResult = emitPortType( - location, element.type, - element.isFlip ? direction::flip(direction) : direction, os, - nestedIndent, hasEmittedDirection); - if (failed(elementResult)) - return failure(); - os << '\n'; - } - os.indent(indent) << "}"; - return success(); - }, - false); - }) - .Case([&](FVectorType vectorType) { - // Emit a vector type, emitting the type of its element as an argument. - return emitTypeWithArguments("Vec", [&](bool hasEmittedDirection) { - os << vectorType.getNumElements() << ", "; - return emitPortType(location, vectorType.getElementType(), direction, - os, indent, hasEmittedDirection); - }); - }) - .Default([](FIRRTLBaseType) { - llvm_unreachable("unknown FIRRTL type"); + bool emitConst = baseType && baseType.isConst(); + if (emitConst) + os << "Const("; + + os << name; + + if (emitParentheses) + os << "("; + + if (failed(emitArguments(hasEmittedDirection || emitDirection))) return failure(); - }); -} -/// Emits an `IO` for the `port`. -static LogicalResult emitPort(const PortInfo &port, llvm::raw_ostream &os) { - os.indent(indentIncrement) << "val " << port.getName() << " = IO("; - if (failed(emitPortType(port.loc, port.type.cast(), - port.direction, os, indentIncrement))) - return failure(); - os << ")\n"; + if (emitParentheses) + os << ')'; - return success(); -} + if (emitConst) + os << ')'; -/// Emits an `ExtModule` class with port declarations for `module`. -static LogicalResult emitModule(FModuleLike module, llvm::raw_ostream &os) { - os << "class " << module.moduleName() << " extends ExtModule {\n"; + if (emitDirection) + os << ')'; - for (const auto &port : module.getPorts()) { - if (failed(emitPort(port, os))) - return failure(); + return success(); + }; + + // Emits a type that does not require arguments. + auto emitType = [&](FIRRTLBaseType baseType, + StringRef name) -> LogicalResult { + return emitTypeWithArguments(baseType, name, + [](bool) { return success(); }); + }; + + // Emits a type that requires a known width argument. + auto emitWidthQualifiedType = [&](auto type, + StringRef name) -> LogicalResult { + auto width = type.getWidth(); + if (!width.has_value()) { + return LogicalResult(emitError( + location, "Expected width to be inferred for exported port")); + } + return emitTypeWithArguments(type, name, [&](bool) { + os << *width << ".W"; + return success(); + }); + }; + + return TypeSwitch(type) + .Case( + [&](ClockType type) { return emitType(type, "Clock"); }) + .Case( + [&](AsyncResetType type) { return emitType(type, "AsyncReset"); }) + .Case([&](ResetType) { + return emitError( + location, "Expected reset type to be inferred for exported port"); + }) + .Case([&](UIntType uIntType) { + return emitWidthQualifiedType(uIntType, "UInt"); + }) + .Case([&](SIntType sIntType) { + return emitWidthQualifiedType(sIntType, "SInt"); + }) + .Case([&](AnalogType analogType) { + return emitWidthQualifiedType(analogType, "Analog"); + }) + .Case([&](BundleType bundleType) { + // Emit an anonymous bundle, emitting a `val` for each field. + return emitTypeWithArguments( + bundleType, "new Bundle ", + [&](bool hasEmittedDirection) { + os << "{\n"; + unsigned int nestedIndent = indent + indentIncrement; + for (const auto &element : bundleType.getElements()) { + os.indent(nestedIndent) + << "val " << element.name.getValue() << " = "; + auto elementResult = emitPortType( + location, element.type, + element.isFlip ? direction::flip(direction) : direction, + nestedIndent, hasEmittedDirection); + if (failed(elementResult)) + return failure(); + os << '\n'; + } + os.indent(indent) << "}"; + return success(); + }, + false); + }) + .Case([&](FVectorType vectorType) { + // Emit a vector type, emitting the type of its element as an + // argument. + return emitTypeWithArguments( + vectorType, "Vec", [&](bool hasEmittedDirection) { + os << vectorType.getNumElements() << ", "; + return emitPortType(location, vectorType.getElementType(), + direction, indent, hasEmittedDirection); + }); + }) + .Case([&](RefType refType) { + hasEmittedProbe = true; + StringRef name = refType.getForceable() ? "RWProbe" : "Probe"; + return emitTypeWithArguments( + nullptr, name, [&](bool hasEmittedDirection) { + return emitPortType(location, refType.getType(), direction, + indent, hasEmittedDirection); + }); + }) + .Default([&](Type type) { + mlir::emitError(location) << "Unhandled type: " << type; + return failure(); + }); } - os << "}\n"; - - return success(); -} + llvm::raw_ostream &os; + bool hasEmittedProbe = false; +}; +} // namespace /// Exports a Chisel interface to the output stream. static LogicalResult exportChiselInterface(CircuitOp circuit, @@ -179,13 +220,24 @@ static LogicalResult exportChiselInterface(CircuitOp circuit, // Emit version, package, and import declarations os << circt::getCirctVersionComment() << "package shelf." << circuit.getName().lower() - << "\n\nimport chisel3._\nimport chisel3.experimental._\n\n"; + << "\n\nimport chisel3._\nimport chisel3.experimental._\n"; + + std::string body; + llvm::raw_string_ostream bodyStream(body); + Emitter emitter(bodyStream); // Emit a class for the main circuit module. auto topModule = circuit.getMainModule(); - if (failed(emitModule(topModule, os))) + if (failed(emitter.emitModule(topModule))) return failure(); + // Emit an import for probe types if needed + if (emitter.hasEmittedProbeType()) + os << "import chisel3.probe._\n"; + + // Emit the body + os << '\n' << body; + return success(); } diff --git a/lib/Conversion/ExportVerilog/CMakeLists.txt b/lib/Conversion/ExportVerilog/CMakeLists.txt index bbf385a04c8a..540a0d4e4aa6 100644 --- a/lib/Conversion/ExportVerilog/CMakeLists.txt +++ b/lib/Conversion/ExportVerilog/CMakeLists.txt @@ -2,6 +2,7 @@ add_circt_translation_library(CIRCTExportVerilog ApplyLoweringOptions.cpp ExportVerilog.cpp + LegalizeAnonEnums.cpp LegalizeNames.cpp PrepareForEmission.cpp PruneZeroValuedLogic.cpp @@ -17,10 +18,14 @@ add_circt_translation_library(CIRCTExportVerilog LINK_LIBS PUBLIC CIRCTComb CIRCTHW + CIRCTLTL + CIRCTOM CIRCTSupport CIRCTSV + CIRCTSupport + CIRCTVerif MLIRIR MLIRPass MLIRSideEffectInterfaces MLIRTransforms - ) +) diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index d350e7b9fcf1..880cd9eea8a7 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -21,20 +21,26 @@ #include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/Comb/CombVisitors.h" #include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWTypes.h" #include "circt/Dialect/HW/HWVisitors.h" +#include "circt/Dialect/LTL/LTLVisitors.h" +#include "circt/Dialect/OM/OMOps.h" #include "circt/Dialect/SV/SVAttributes.h" #include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/SV/SVVisitors.h" +#include "circt/Dialect/Verif/VerifVisitors.h" #include "circt/Support/LLVM.h" #include "circt/Support/LoweringOptions.h" #include "circt/Support/Path.h" +#include "circt/Support/PrettyPrinter.h" #include "circt/Support/PrettyPrinterHelpers.h" #include "circt/Support/Version.h" #include "mlir/IR/BuiltinOps.h" -#include "mlir/IR/FunctionImplementation.h" #include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/IR/Location.h" #include "mlir/IR/Threading.h" +#include "mlir/Interfaces/FunctionImplementation.h" #include "mlir/Pass/PassManager.h" #include "mlir/Support/FileUtilities.h" #include "llvm/ADT/MapVector.h" @@ -42,6 +48,7 @@ #include "llvm/ADT/StringSet.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormattedStream.h" #include "llvm/Support/Path.h" #include "llvm/Support/SaveAndRestore.h" #include "llvm/Support/ToolOutputFile.h" @@ -106,11 +113,11 @@ struct SubExprInfo { // Helper routines //===----------------------------------------------------------------------===// -static Attribute getInt32Attr(MLIRContext *ctx, uint32_t value) { +static TypedAttr getInt32Attr(MLIRContext *ctx, uint32_t value) { return Builder(ctx).getI32IntegerAttr(value); } -static Attribute getIntAttr(MLIRContext *ctx, Type t, const APInt &value) { +static TypedAttr getIntAttr(MLIRContext *ctx, Type t, const APInt &value) { return Builder(ctx).getIntegerAttr(t, value); } @@ -152,7 +159,7 @@ static bool isDuplicatableExpression(Operation *op) { return isDuplicatableNullaryExpression(op); // It is cheap to inline extract op. - if (isa(op)) + if (isa(op)) return true; // We only inline array_get with a constant, port or wire index. @@ -210,42 +217,26 @@ static void emitZeroWidthIndexingValue(PPS &os) { os << "/*Zero width*/ 1\'b0"; } -/// Return the verilog name of the port for the module. -StringRef getPortVerilogName(Operation *module, ssize_t portArgNum) { - auto numInputs = hw::getModuleNumInputs(module); - // portArgNum is the index into the result of getAllModulePortInfos. - // Also ensure the correct index into the input/output list is computed. - ssize_t portId = portArgNum; +static StringRef getPortVerilogName(Operation *module, PortInfo port) { char verilogNameAttr[] = "hw.verilogName"; - // Check for input ports. - if (portArgNum < numInputs) { - if (auto argAttr = - cast(module).getArgAttrsAttr()) - if (auto argDict = argAttr[portArgNum].cast()) - if (auto updatedName = argDict.get(verilogNameAttr)) - return updatedName.cast().getValue(); - // Get the original name of input port if no renaming. - return module->getAttrOfType("argNames")[portArgNum] - .cast() - .getValue(); - } - - // If its an output port, get the index into the output port array. - portId = portArgNum - numInputs; - if (auto argAttr = cast(module).getResAttrsAttr()) - if (auto argDict = argAttr[portId].cast()) - if (auto updatedName = argDict.get(verilogNameAttr)) - return updatedName.cast().getValue(); - // Get the original name of output port if no renaming. - return module->getAttrOfType("resultNames")[portId] - .cast() - .getValue(); -} - -StringRef getPortVerilogName(Operation *module, PortInfo port) { + if (port.attrs) + if (auto updatedName = port.attrs.get(verilogNameAttr)) + return updatedName.cast().getValue(); + return port.name; +} + +/// Return the verilog name of the port for the module. +static StringRef getPortVerilogName(Operation *module, size_t portArgNum) { + auto htmo = cast(module); + return getPortVerilogName(module, htmo.getPort(portArgNum)); +} + +/// Return the verilog name of the port for the module. +static StringRef getInputPortVerilogName(Operation *module, size_t portArgNum) { + auto hml = cast(module); return getPortVerilogName( - module, port.isOutput() ? port.argNum + hw::getModuleNumInputs(module) - : port.argNum); + module, + hml.getPort(hml.getHWModuleType().getPortIdForInputId(portArgNum))); } /// This predicate returns true if the specified operation is considered a @@ -265,25 +256,7 @@ bool ExportVerilog::isVerilogExpression(Operation *op) { return isCombinational(op) || isExpression(op); } -/// Return the width of the specified type in bits or -1 if it isn't -/// supported. // NOLINTBEGIN(misc-no-recursion) -static int getBitWidthOrSentinel(Type type) { - return TypeSwitch(type) - .Case([](IntegerType integerType) { - // Verilog doesn't support zero bit integers. We only support them in - // limited cases. - return integerType.getWidth(); - }) - .Case([](InOutType inoutType) { - return getBitWidthOrSentinel(inoutType.getElementType()); - }) - .Case([](TypeAliasType alias) { - return getBitWidthOrSentinel(alias.getInnerType()); - }) - .Default([](Type) { return -1; }); -} - /// Push this type's dimension into a vector. static void getTypeDims(SmallVectorImpl &dims, Type type, Location loc) { @@ -293,7 +266,7 @@ static void getTypeDims(SmallVectorImpl &dims, Type type, return; } if (auto array = hw::type_dyn_cast(type)) { - dims.push_back(getInt32Attr(type.getContext(), array.getSize())); + dims.push_back(getInt32Attr(type.getContext(), array.getNumElements())); getTypeDims(dims, array.getElementType(), loc); return; @@ -327,17 +300,23 @@ static bool haveMatchingDims(Type a, Type b, Location loc) { // NOLINTBEGIN(misc-no-recursion) bool ExportVerilog::isZeroBitType(Type type) { + type = getCanonicalType(type); if (auto intType = type.dyn_cast()) return intType.getWidth() == 0; if (auto inout = type.dyn_cast()) return isZeroBitType(inout.getElementType()); if (auto uarray = type.dyn_cast()) - return uarray.getSize() == 0 || isZeroBitType(uarray.getElementType()); + return uarray.getNumElements() == 0 || + isZeroBitType(uarray.getElementType()); if (auto array = type.dyn_cast()) - return array.getSize() == 0 || isZeroBitType(array.getElementType()); + return array.getNumElements() == 0 || isZeroBitType(array.getElementType()); if (auto structType = type.dyn_cast()) return llvm::all_of(structType.getElements(), [](auto elem) { return isZeroBitType(elem.type); }); + if (auto enumType = type.dyn_cast()) + return enumType.getFields().empty(); + if (auto unionType = type.dyn_cast()) + return hw::getBitWidth(unionType) == 0; // We have an open type system, so assume it is ok. return false; @@ -381,6 +360,8 @@ static StringRef getVerilogDeclWord(Operation *op, op->getResult(0).getType().cast().getElementType(); if (elementType.isa()) return ""; + if (elementType.isa()) + return ""; if (elementType.isa()) return ""; if (auto innerType = elementType.dyn_cast()) { @@ -431,140 +412,338 @@ static StringRef getVerilogDeclWord(Operation *op, : "automatic logic"; } -/// Pull any FileLineCol locs out of the specified location and add it to the -/// specified set. +//===----------------------------------------------------------------------===// +// Location comparison +//===----------------------------------------------------------------------===// + // NOLINTBEGIN(misc-no-recursion) -static void collectFileLineColLocs(Location loc, - SmallPtrSetImpl &locationSet) { - if (auto fileLoc = loc.dyn_cast()) - locationSet.insert(fileLoc); - if (auto fusedLoc = loc.dyn_cast()) - for (auto loc : fusedLoc.getLocations()) - collectFileLineColLocs(loc, locationSet); +static int compareLocs(Location lhs, Location rhs); + +// NameLoc comparator - compare names, then child locations. +static int compareLocsImpl(mlir::NameLoc lhs, mlir::NameLoc rhs) { + if (auto name = lhs.getName().compare(rhs.getName())) + return name; + return compareLocs(lhs.getChildLoc(), rhs.getChildLoc()); } -// NOLINTEND(misc-no-recursion) -/// Return the location information as a (potentially empty) string. -static std::string -getLocationInfoAsStringImpl(const SmallPtrSetImpl &locationSet) { - std::string resultStr; - llvm::raw_string_ostream sstr(resultStr); - - auto printLoc = [&](FileLineColLoc loc) { - sstr << loc.getFilename().getValue(); - if (auto line = loc.getLine()) { - sstr << ':' << line; - if (auto col = loc.getColumn()) - sstr << ':' << col; - } - }; +// FileLineColLoc comparator. +static int compareLocsImpl(mlir::FileLineColLoc lhs, mlir::FileLineColLoc rhs) { + if (auto fn = lhs.getFilename().compare(rhs.getFilename())) + return fn; + if (lhs.getLine() != rhs.getLine()) + return lhs.getLine() < rhs.getLine() ? -1 : 1; + return lhs.getColumn() < rhs.getColumn() ? -1 : 1; +} - // Fast pass some common cases. - switch (locationSet.size()) { - case 1: - printLoc((*locationSet.begin()).cast()); - [[fallthrough]]; - case 0: - return sstr.str(); - default: - break; +// CallSiteLoc comparator. Compare first on the callee, then on the caller. +static int compareLocsImpl(mlir::CallSiteLoc lhs, mlir::CallSiteLoc rhs) { + Location lhsCallee = lhs.getCallee(); + Location rhsCallee = rhs.getCallee(); + if (auto res = compareLocs(lhsCallee, rhsCallee)) + return res; + + Location lhsCaller = lhs.getCaller(); + Location rhsCaller = rhs.getCaller(); + return compareLocs(lhsCaller, rhsCaller); +} + +template +FailureOr dispatchCompareLocations(Location lhs, Location rhs) { + auto lhsT = dyn_cast(lhs); + auto rhsT = dyn_cast(rhs); + if (lhsT && rhsT) { + // Both are of the target location type, compare them directly. + return compareLocsImpl(lhsT, rhsT); + } + if (lhsT) { + // lhs is TTargetLoc => it comes before rhs. + return -1; } + if (rhsT) { + // rhs is TTargetLoc => it comes before lhs. + return 1; + } + + return failure(); +} + +// Top-level comparator for two arbitrarily typed locations. +// First order comparison by location type: +// 1. FileLineColLoc +// 2. NameLoc +// 3. CallSiteLoc +// 4. Anything else... +// Intra-location type comparison is delegated to the corresponding +// compareLocsImpl() function. +static int compareLocs(Location lhs, Location rhs) { + // FileLineColLoc + if (auto res = dispatchCompareLocations(lhs, rhs); + succeeded(res)) + return *res; + + // NameLoc + if (auto res = dispatchCompareLocations(lhs, rhs); + succeeded(res)) + return *res; + + // CallSiteLoc + if (auto res = dispatchCompareLocations(lhs, rhs); + succeeded(res)) + return *res; + + // Anything else... + return 0; +} - // Sort the entries. - SmallVector locVector; - locVector.reserve(locationSet.size()); - for (auto loc : locationSet) - locVector.push_back(loc.cast()); +// NOLINTEND(misc-no-recursion) + +//===----------------------------------------------------------------------===// +// Location printing +//===----------------------------------------------------------------------===// + +/// Pull apart any fused locations into the location set, such that they are +/// uniqued. Any other location type will be added as-is. +static void collectAndUniqueLocations(Location loc, + SmallPtrSetImpl &locationSet) { + llvm::TypeSwitch(loc) + .Case([&](auto fusedLoc) { + for (auto subLoc : fusedLoc.getLocations()) + collectAndUniqueLocations(subLoc, locationSet); + }) + .Default([&](auto loc) { locationSet.insert(loc); }); +} +// Sorts a vector of locations in-place. +template +static void sortLocationVector(TVector &vec) { llvm::array_pod_sort( - locVector.begin(), locVector.end(), - [](const FileLineColLoc *lhs, const FileLineColLoc *rhs) -> int { - if (auto fn = lhs->getFilename().compare(rhs->getFilename())) - return fn; - if (lhs->getLine() != rhs->getLine()) - return lhs->getLine() < rhs->getLine() ? -1 : 1; - return lhs->getColumn() < rhs->getColumn() ? -1 : 1; + vec.begin(), vec.end(), [](const auto *lhs, const auto *rhs) -> int { + return compareLocs(cast(*lhs), cast(*rhs)); }); +} + +class LocationEmitter { +public: + // Generates location info for a single location in the specified style. + LocationEmitter(LoweringOptions::LocationInfoStyle style, Location loc) { + SmallPtrSet locationSet; + locationSet.insert(loc); + llvm::raw_string_ostream os(output); + emitLocationSetInfo(os, style, locationSet); + } + + // Generates location info for a set of operations in the specified style. + LocationEmitter(LoweringOptions::LocationInfoStyle style, + const SmallPtrSetImpl &ops) { + // Multiple operations may come from the same location or may not have + // useful + // location info. Unique it now. + SmallPtrSet locationSet; + for (auto *op : ops) + collectAndUniqueLocations(op->getLoc(), locationSet); + llvm::raw_string_ostream os(output); + emitLocationSetInfo(os, style, locationSet); + } + + StringRef strref() { return output; } + +private: + void emitLocationSetInfo(llvm::raw_string_ostream &os, + LoweringOptions::LocationInfoStyle style, + const SmallPtrSetImpl &locationSet) { + if (style == LoweringOptions::LocationInfoStyle::None) + return; + std::string resstr; + llvm::raw_string_ostream sstr(resstr); + LocationEmitter::Impl(sstr, style, locationSet); + if (resstr.empty() || style == LoweringOptions::LocationInfoStyle::Plain) { + os << resstr; + return; + } + assert(style == LoweringOptions::LocationInfoStyle::WrapInAtSquareBracket && + "other styles must be already handled"); + os << "@[" << resstr << "]"; + } + + std::string output; + + struct Impl { - // The entries are sorted by filename, line, col. Try to merge together - // entries to reduce verbosity on the column info. - StringRef lastFileName; - for (size_t i = 0, e = locVector.size(); i != e;) { - if (i != 0) - sstr << ", "; - - // Print the filename if it changed. - auto first = locVector[i]; - if (first.getFilename() != lastFileName) { - lastFileName = first.getFilename(); - sstr << lastFileName; + // NOLINTBEGIN(misc-no-recursion) + Impl(llvm::raw_string_ostream &os, LoweringOptions::LocationInfoStyle style, + const SmallPtrSetImpl &locationSet) + : os(os), style(style) { + emitLocationSetInfoImpl(locationSet); } - // Scan for entries with the same file/line. - size_t end = i + 1; - while (end != e && first.getFilename() == locVector[end].getFilename() && - first.getLine() == locVector[end].getLine()) - ++end; - - // If we have one entry, print it normally. - if (end == i + 1) { - if (auto line = first.getLine()) { - sstr << ':' << line; - if (auto col = first.getColumn()) - sstr << ':' << col; + // Emit CallSiteLocs. + void emitLocationInfo(mlir::CallSiteLoc loc) { + os << "{"; + emitLocationInfo(loc.getCallee()); + os << " <- "; + emitLocationInfo(loc.getCaller()); + os << "}"; + } + + // Emit NameLocs. + void emitLocationInfo(mlir::NameLoc loc) { + bool withName = !loc.getName().empty(); + if (withName) + os << "'" << loc.getName().strref() << "'("; + emitLocationInfo(loc.getChildLoc()); + + if (withName) + os << ")"; + } + + // Emit FileLineColLocs. + void emitLocationInfo(FileLineColLoc loc) { + os << loc.getFilename().getValue(); + if (auto line = loc.getLine()) { + os << ':' << line; + if (auto col = loc.getColumn()) + os << ':' << col; } - ++i; - continue; } - // Otherwise print a brace enclosed list. - sstr << ':' << first.getLine() << ":{"; - while (i != end) { - sstr << locVector[i++].getColumn(); + // Generates a string representation of a set of FileLineColLocs. + // The entries are sorted by filename, line, col. Try to merge together + // entries to reduce verbosity on the column info. + void + printFileLineColSetInfo(llvm::SmallVector locVector) { + // The entries are sorted by filename, line, col. Try to merge together + // entries to reduce verbosity on the column info. + StringRef lastFileName; + for (size_t i = 0, e = locVector.size(); i != e;) { + if (i != 0) + os << ", "; + + // Print the filename if it changed. + auto first = locVector[i]; + if (first.getFilename() != lastFileName) { + lastFileName = first.getFilename(); + os << lastFileName; + } - if (i != end) - sstr << ','; + // Scan for entries with the same file/line. + size_t end = i + 1; + while (end != e && + first.getFilename() == locVector[end].getFilename() && + first.getLine() == locVector[end].getLine()) + ++end; + + // If we have one entry, print it normally. + if (end == i + 1) { + if (auto line = first.getLine()) { + os << ':' << line; + if (auto col = first.getColumn()) + os << ':' << col; + } + ++i; + continue; + } + + // Otherwise print a brace enclosed list. + os << ':' << first.getLine() << ":{"; + while (i != end) { + os << locVector[i++].getColumn(); + + if (i != end) + os << ','; + } + os << '}'; + } } - sstr << '}'; - } - return sstr.str(); -} -static std::string -getLocationInfoAsStringImpl(const SmallPtrSetImpl &locationSet, - LoweringOptions::LocationInfoStyle style) { + /// Return the location information in the specified style. This is the main + /// dispatch function for calling the location-specific routines. + void emitLocationInfo(Location loc) { + llvm::TypeSwitch(loc) + .Case( + [&](auto loc) { emitLocationInfo(loc); }) + .Case([&](auto loc) { + SmallPtrSet locationSet; + collectAndUniqueLocations(loc, locationSet); + emitLocationSetInfoImpl(locationSet); + }) + .Default([&](auto loc) { + // Don't print anything for unhandled locations. + }); + } - if (style == LoweringOptions::LocationInfoStyle::None) - return ""; - auto str = getLocationInfoAsStringImpl(locationSet); - if (str.empty() || style == LoweringOptions::LocationInfoStyle::Plain) - return str; - assert(style == LoweringOptions::LocationInfoStyle::WrapInAtSquareBracket && - "other styles must be already handled"); - return "@[" + str + "]"; -} + /// Emit the location information of `locationSet` to `sstr`. The emitted + /// string + /// may potentially be an empty string given the contents of the + /// `locationSet`. + void + emitLocationSetInfoImpl(const SmallPtrSetImpl &locationSet) { + // Fast pass some common cases. + switch (locationSet.size()) { + case 1: + emitLocationInfo(cast(*locationSet.begin())); + [[fallthrough]]; + case 0: + return; + default: + break; + } -/// Return the location information in the specified style. -static std::string -getLocationInfoAsString(Location loc, - LoweringOptions::LocationInfoStyle style) { - SmallPtrSet locationSet; - collectFileLineColLocs(loc, locationSet); - return getLocationInfoAsStringImpl(locationSet, style); -} + // Sort the entries into distinct location printing kinds. + SmallVector flcLocs; + SmallVector otherLocs; + flcLocs.reserve(locationSet.size()); + otherLocs.reserve(locationSet.size()); + for (Attribute loc : locationSet) { + if (auto flcLoc = loc.dyn_cast()) + flcLocs.push_back(flcLoc); + else + otherLocs.push_back(loc); + } -/// Return the location information in the specified style. -static std::string -getLocationInfoAsString(const SmallPtrSetImpl &ops, - LoweringOptions::LocationInfoStyle style) { - // Multiple operations may come from the same location or may not have useful - // location info. Unique it now. - SmallPtrSet locationSet; - for (auto *op : ops) - collectFileLineColLocs(op->getLoc(), locationSet); - return getLocationInfoAsStringImpl(locationSet, style); -} + // SmallPtrSet iteration is non-deterministic, so sort the location + // vectors to ensure deterministic output. + sortLocationVector(otherLocs); + sortLocationVector(flcLocs); + + // To detect whether something actually got emitted, we inspect the stream + // for size changes. This is due to the possiblity of locations which are + // not supposed to be emitted (e.g. `loc("")`). + size_t sstrSize = os.tell(); + bool emittedAnything = false; + auto recheckEmittedSomething = [&]() { + size_t currSize = os.tell(); + bool emittedSomethingSinceLastCheck = currSize != sstrSize; + emittedAnything |= emittedSomethingSinceLastCheck; + sstrSize = currSize; + return emittedSomethingSinceLastCheck; + }; + + // First, emit the other locations through the generic location dispatch + // function. + llvm::interleave( + otherLocs, + [&](Attribute loc) { emitLocationInfo(cast(loc)); }, + [&] { + if (recheckEmittedSomething()) { + os << ", "; + recheckEmittedSomething(); // reset detector to reflect the comma. + } + }); + + // If we emitted anything, and we have FileLineColLocs, then emit a + // location-separating comma. + if (emittedAnything && !flcLocs.empty()) + os << ", "; + // Then, emit the FileLineColLocs. + printFileLineColSetInfo(flcLocs); + } + llvm::raw_string_ostream &os; + LoweringOptions::LocationInfoStyle style; + + // NOLINTEND(misc-no-recursion) + }; +}; /// Most expressions are invalid to bit-select from in Verilog, but some /// things are ok. Return true if it is ok to inline bitselect from the @@ -580,7 +759,8 @@ static bool isOkToBitSelectFrom(Value v) { return true; // Aggregate access can be inlined. - if (v.getDefiningOp() || v.getDefiningOp()) + if (isa_and_nonnull( + v.getDefiningOp())) return true; // Interface signal can be inlined. @@ -611,7 +791,7 @@ static bool isExpressionUnableToInline(Operation *op, // StructCreateOp needs to be assigning to a named temporary so that types // are inferred properly by verilog - if (isa(op)) + if (isa(op)) return true; // Aggregate literal syntax only works in an assignment expression, where @@ -634,7 +814,8 @@ static bool isExpressionUnableToInline(Operation *op, // assign bar = {{a}, {b}, {c}, {d}}[idx]; // // To handle these, we push the subexpression into a temporary. - if (isa(user)) + if (isa(user)) if (op->getResult(0) == user->getOperand(0) && // ignore index operands. !isOkToBitSelectFrom(op->getResult(0))) return true; @@ -658,7 +839,7 @@ enum class BlockStatementCount { Zero, One, TwoOrMore }; static BlockStatementCount countStatements(Block &block) { unsigned numStatements = 0; block.walk([&](Operation *op) { - if (isVerilogExpression(op)) + if (isVerilogExpression(op) || isa(op->getDialect())) return WalkResult::advance(); numStatements += TypeSwitch(op) @@ -812,9 +993,13 @@ StringRef getVerilogValueName(Value val) { if (auto *op = val.getDefiningOp()) return getSymOpName(op); - if (auto port = val.dyn_cast()) - return getPortVerilogName(port.getParentBlock()->getParentOp(), - port.getArgNumber()); + if (auto port = val.dyn_cast()) { + // If the value is defined by for op, use its associated verilog name. + if (auto forOp = dyn_cast(port.getParentBlock()->getParentOp())) + return forOp->getAttrOfType("hw.verilogName"); + return getInputPortVerilogName(port.getParentBlock()->getParentOp(), + port.getArgNumber()); + } assert(false && "unhandled value"); return {}; } @@ -834,10 +1019,12 @@ class VerilogEmitterState { const LoweringOptions &options, const HWSymbolCache &symbolCache, const GlobalNameTable &globalNames, - raw_ostream &os) + llvm::formatted_raw_ostream &os, + StringAttr fileName, OpLocMap &verilogLocMap) : designOp(designOp), shared(shared), options(options), symbolCache(symbolCache), globalNames(globalNames), os(os), - pp(os, options.emittedLineLength) { + verilogLocMap(verilogLocMap), pp(os, options.emittedLineLength), + fileName(fileName) { pp.setListener(&saver); } /// This is the root mlir::ModuleOp that holds the whole design being emitted. @@ -855,8 +1042,10 @@ class VerilogEmitterState { /// the IR name. const GlobalNameTable &globalNames; - /// The stream to emit to. - raw_ostream &os; + /// The stream to emit to. Use a formatted_raw_ostream, to easily get the + /// current location(line,column) on the stream. This is required to record + /// the verilog output location information corresponding to any op. + llvm::formatted_raw_ostream &os; bool encounteredError = false; unsigned currentIndent = 0; @@ -870,13 +1059,30 @@ class VerilogEmitterState { /// this. bool pendingNewline = false; + /// Used to record the verilog output file location of an op. + OpLocMap &verilogLocMap; /// String storage backing Tokens built from temporary strings. /// PrettyPrinter will clear this as appropriate. - TokenStringSaver saver; + PrintEventAndStorageListener> saver = + PrintEventAndStorageListener>( + verilogLocMap); /// Pretty printer. PrettyPrinter pp; + /// Name of the output file, used for debug information. + StringAttr fileName; + + /// Update the location attribute of the ops with the verilog locations + /// recorded in `verilogLocMap` and clear the map. `lineOffset` is added to + /// all the line numbers, this is required when the modules are exported in + /// parallel. + void addVerilogLocToOps(unsigned int lineOffset, StringAttr fileName) { + verilogLocMap.updateIRWithLoc(lineOffset, fileName, + shared.designOp->getContext()); + verilogLocMap.clear(); + } + private: VerilogEmitterState(const VerilogEmitterState &) = delete; void operator=(const VerilogEmitterState &) = delete; @@ -889,16 +1095,21 @@ class VerilogEmitterState { namespace { +/// The data that is unique to each callback. The operation and a flag to +/// indicate if the callback is for begin or end of the operation print +/// location. +using CallbackDataTy = std::pair; class EmitterBase { public: // All of the mutable state we are maintaining. VerilogEmitterState &state; /// Stream helper (pp, saver). - TokenStream<> ps; + TokenStreamWithCallback ps; explicit EmitterBase(VerilogEmitterState &state) - : state(state), ps(state.pp, state.saver) {} + : state(state), + ps(state.pp, state.saver, state.options.emitVerilogLocations) {} InFlightDiagnostic emitError(Operation *op, const Twine &message) { state.encounteredError = true; @@ -910,7 +1121,7 @@ class EmitterBase { return op->emitOpError(message); } - void emitLocationImpl(const std::string &location) { + void emitLocationImpl(llvm::StringRef location) { // Break so previous content is not impacted by following, // but use a 'neverbreak' so it always fits. ps << PP::neverbreak; @@ -920,7 +1131,7 @@ class EmitterBase { void emitLocationInfo(Location loc) { emitLocationImpl( - getLocationInfoAsString(loc, state.options.locationInfoStyle)); + LocationEmitter(state.options.locationInfoStyle, loc).strref()); } /// If we have location information for any of the specified operations, @@ -928,7 +1139,7 @@ class EmitterBase { /// operations came from. In any case, print a newline. void emitLocationInfoAndNewLine(const SmallPtrSetImpl &ops) { emitLocationImpl( - getLocationInfoAsString(ops, state.options.locationInfoStyle)); + LocationEmitter(state.options.locationInfoStyle, ops).strref()); setPendingNewline(); } @@ -1014,6 +1225,16 @@ void EmitterBase::emitTextWithSubstitutions( next--; continue; } + size_t operandNoLength = next - start; + + // Format string options follow a ':'. + StringRef fmtOptsStr; + if (string[next] == ':') { + size_t startFmtOpts = next + 1; + while (next < string.size() && string[next] != '}') + ++next; + fmtOptsStr = string.substr(startFmtOpts, next - startFmtOpts); + } // We must have a }} right after the digits. if (!string.substr(next).startswith("}}")) @@ -1022,7 +1243,7 @@ void EmitterBase::emitTextWithSubstitutions( // We must be able to decode the integer into an unsigned. unsigned operandNo = 0; if (string.drop_front(start) - .take_front(next - start) + .take_front(operandNoLength) .getAsInteger(10, operandNo)) { emitError(op, "operand substitution too large"); continue; @@ -1044,8 +1265,23 @@ void EmitterBase::emitTextWithSubstitutions( auto sym = symAttrs[symOpNum]; StringRef symVerilogName; if (auto fsym = sym.dyn_cast()) { - if (auto *symOp = state.symbolCache.getDefinition(fsym)) - symVerilogName = namify(sym, symOp); + if (auto *symOp = state.symbolCache.getDefinition(fsym)) { + if (auto globalRef = dyn_cast(symOp)) { + auto namepath = globalRef.getNamepathAttr().getValue(); + for (auto [index, sym] : llvm::enumerate(namepath)) { + // Emit the seperator string. + if (index > 0) + ps << (fmtOptsStr.empty() ? "." : fmtOptsStr); + + auto innerRef = cast(sym); + auto ref = state.symbolCache.getInnerDefinition( + innerRef.getModule(), innerRef.getName()); + ps << namify(innerRef, ref); + } + } else { + symVerilogName = namify(sym, symOp); + } + } } else if (auto isym = sym.dyn_cast()) { auto symOp = state.symbolCache.getInnerDefinition(isym.getModule(), isym.getName()); @@ -1221,13 +1457,17 @@ namespace { class ModuleEmitter : public EmitterBase { public: explicit ModuleEmitter(VerilogEmitterState &state) - : EmitterBase(state), - fieldNameResolver(FieldNameResolver(state.globalNames)) {} + : EmitterBase(state), currentModuleOp(nullptr), + fieldNameResolver(FieldNameResolver(state.globalNames, state.options)) { + } ~ModuleEmitter() { emitPendingNewlineIfNeeded(); ps.eof(); }; + void emitParameters(Operation *module, ArrayAttr params); + void emitPortList(Operation *module, const ModulePortInfo &portInfo); + void emitHWModule(HWModuleOp module); void emitHWExternModule(HWModuleExternOp module); void emitHWGeneratedModule(HWModuleGeneratedOp module); @@ -1284,7 +1524,7 @@ class ModuleEmitter : public EmitterBase { // Mutable state while emitting a module body. /// This is the current module being emitted for a HWModuleOp. - HWModuleOp currentModuleOp; + Operation *currentModuleOp; /// This set keeps track of expressions that were emitted into their /// 'automatic logic' or 'localparam' declaration. This is only used for @@ -1304,38 +1544,55 @@ class ModuleEmitter : public EmitterBase { //===----------------------------------------------------------------------===// // Methods for formatting types. -/// Emit a list of dimensions. +/// Emit a single dimension. +static void emitDim(Attribute width, raw_ostream &os, Location loc, + ModuleEmitter &emitter, bool downTo) { + if (!width) { + os << "<>"; + return; + } + if (auto intAttr = width.dyn_cast()) { + if (intAttr.getValue().isZero()) { + os << "/*Zero Width*/"; + } else { + os << '['; + if (!downTo) + os << "0:"; + os << (intAttr.getValue().getZExtValue() - 1); + if (downTo) + os << ":0"; + os << ']'; + } + return; + } + + // Otherwise it must be a parameterized dimension. Shove the "-1" into the + // attribute so it gets printed in canonical form. + auto typedAttr = width.dyn_cast(); + if (!typedAttr) { + mlir::emitError(loc, "untyped dimension attribute ") << width; + return; + } + auto negOne = + getIntAttr(loc.getContext(), typedAttr.getType(), + APInt(typedAttr.getType().getIntOrFloatBitWidth(), -1L, true)); + width = ParamExprAttr::get(PEO::Add, typedAttr, negOne); + os << '['; + if (!downTo) + os << "0:"; + emitter.printParamValue(width, os, [loc]() { + return mlir::emitError(loc, "invalid parameter in type"); + }); + if (downTo) + os << ":0"; + os << ']'; +} + +/// Emit a list of packed dimensions. static void emitDims(ArrayRef dims, raw_ostream &os, Location loc, ModuleEmitter &emitter) { for (Attribute width : dims) { - if (!width) { - os << "<>"; - continue; - } - if (auto intAttr = width.dyn_cast()) { - if (intAttr.getValue().isZero()) - os << "/*Zero Width*/"; - else - os << '[' << (intAttr.getValue().getZExtValue() - 1) << ":0]"; - continue; - } - - // Otherwise it must be a parameterized dimension. Shove the "-1" into the - // attribute so it gets printed in canonical form. - auto typedAttr = width.dyn_cast(); - if (!typedAttr) { - mlir::emitError(loc, "untyped dimension attribute ") << width; - continue; - } - auto negOne = getIntAttr( - loc.getContext(), typedAttr.getType(), - APInt(typedAttr.getType().getIntOrFloatBitWidth(), -1L, true)); - width = ParamExprAttr::get(PEO::Add, width, negOne); - os << '['; - emitter.printParamValue(width, os, [loc]() { - return mlir::emitError(loc, "invalid parameter in type"); - }); - os << ":0]"; + emitDim(width, os, loc, emitter, /*downTo=*/true); } } @@ -1392,7 +1649,10 @@ static bool printPackedTypeImpl(Type type, raw_ostream &os, Location loc, emitter); }) .Case([&](EnumType enumType) { - os << "enum {"; + os << "enum "; + if (enumType.getBitWidth() != 32) + os << "bit [" << enumType.getBitWidth() - 1 << ":0] "; + os << "{"; Type enumPrefixType = optionalAliasType ? optionalAliasType : enumType; llvm::interleaveComma( enumType.getFields().getAsRange(), os, @@ -1428,6 +1688,53 @@ static bool printPackedTypeImpl(Type type, raw_ostream &os, Location loc, emitDims(dims, os, loc, emitter); return true; }) + .Case([&](UnionType unionType) { + if (unionType.getElements().empty() || isZeroBitType(unionType)) { + os << "/*Zero Width*/"; + return true; + } + + int64_t unionWidth = hw::getBitWidth(unionType); + os << "union packed {"; + for (auto &element : unionType.getElements()) { + if (isZeroBitType(element.type)) { + os << "/*" << emitter.getVerilogStructFieldName(element.name) + << ": Zero Width;*/ "; + continue; + } + int64_t elementWidth = hw::getBitWidth(element.type); + bool needsPadding = elementWidth < unionWidth || element.offset > 0; + if (needsPadding) { + os << " struct packed {"; + if (element.offset) { + os << "logic [" << element.offset - 1 << ":0] " + << "__pre_padding_" << element.name.getValue() << "; "; + } + } + + SmallVector structDims; + printPackedTypeImpl(stripUnpackedTypes(element.type), os, loc, + structDims, + /*implicitIntType=*/false, + /*singleBitDefaultType=*/true, emitter); + os << ' ' << emitter.getVerilogStructFieldName(element.name); + emitter.printUnpackedTypePostfix(element.type, os); + os << ";"; + + if (needsPadding) { + if (elementWidth + (int64_t)element.offset < unionWidth) { + os << " logic [" + << unionWidth - (elementWidth + element.offset) - 1 << ":0] " + << "__post_padding_" << element.name.getValue() << ";"; + } + os << "} " << emitter.getVerilogStructFieldName(element.name) + << ";"; + } + } + os << '}'; + emitDims(dims, os, loc, emitter); + return true; + }) .Case([](InterfaceType ifaceType) { return false; }) .Case([&](UnpackedArrayType arrayType) { @@ -1485,7 +1792,10 @@ void ModuleEmitter::printUnpackedTypePostfix(Type type, raw_ostream &os) { printUnpackedTypePostfix(inoutType.getElementType(), os); }) .Case([&](UnpackedArrayType arrayType) { - os << "[0:" << (arrayType.getSize() - 1) << "]"; + auto loc = currentModuleOp ? currentModuleOp->getLoc() + : state.designOp->getLoc(); + emitDim(arrayType.getSizeAttr(), os, loc, *this, + /*downTo=*/false); printUnpackedTypePostfix(arrayType.getElementType(), os); }) .Case([&](auto) { @@ -1735,8 +2045,14 @@ class ExprEmitter : public EmitterBase, /// of any emitted expressions in the specified set. ExprEmitter(ModuleEmitter &emitter, SmallPtrSetImpl &emittedExprs) + : ExprEmitter(emitter, emittedExprs, localTokens) {} + + ExprEmitter(ModuleEmitter &emitter, + SmallPtrSetImpl &emittedExprs, + BufferingPP::BufferVec &tokens) : EmitterBase(emitter.state), emitter(emitter), - emittedExprs(emittedExprs), buffer(tokens), ps(buffer, state.saver) { + emittedExprs(emittedExprs), buffer(tokens), + ps(buffer, state.saver, state.options.emitVerilogLocations) { assert(state.pp.getListener() == &state.saver); } @@ -1745,14 +2061,18 @@ class ExprEmitter : public EmitterBase, /// already computed name. /// void emitExpression(Value exp, VerilogPrecedence parenthesizeIfLooserThan) { - assert(tokens.empty()); + assert(localTokens.empty()); // Wrap to this column. ps.scopedBox(PP::ibox0, [&]() { emitSubExpr(exp, parenthesizeIfLooserThan, /*signRequirement*/ NoRequirement, /*isSelfDeterminedUnsignedValue*/ false); }); - buffer.flush(state.pp); + // If we are not using an external token buffer provided through the + // constructor, but we're using the default `ExprEmitter`-scoped buffer, + // flush it. + if (&buffer.tokens == &localTokens) + buffer.flush(state.pp); } private: @@ -1892,6 +2212,9 @@ class ExprEmitter : public EmitterBase, } SubExprInfo visitSV(MacroRefExprOp op); SubExprInfo visitSV(MacroRefExprSEOp op); + template + SubExprInfo emitMacroCall(MacroTy op); + SubExprInfo visitSV(ConstantXOp op); SubExprInfo visitSV(ConstantZOp op); SubExprInfo visitSV(ConstantStrOp op); @@ -1923,6 +2246,9 @@ class ExprEmitter : public EmitterBase, SubExprInfo visitTypeOp(StructCreateOp op); SubExprInfo visitTypeOp(StructExtractOp op); SubExprInfo visitTypeOp(StructInjectOp op); + SubExprInfo visitTypeOp(UnionCreateOp op); + SubExprInfo visitTypeOp(UnionExtractOp op); + SubExprInfo visitTypeOp(EnumCmpOp op); SubExprInfo visitTypeOp(EnumConstantOp op); // Comb Dialect Operations @@ -2002,13 +2328,13 @@ class ExprEmitter : public EmitterBase, SmallPtrSetImpl &emittedExprs; /// Tokens buffered for inserting casts/parens after emitting children. - SmallVector tokens; + SmallVector localTokens; /// Stores tokens until told to flush. Uses provided buffer (tokens). BufferingPP buffer; /// Stream to emit expressions into, will add to buffer. - TokenStream ps; + TokenStreamWithCallback ps; }; } // end anonymous namespace @@ -2146,7 +2472,13 @@ SubExprInfo ExprEmitter::emitSubExpr(Value exp, return {Symbol, IsUnsigned}; } - unsigned subExprStartIndex = tokens.size(); + unsigned subExprStartIndex = buffer.tokens.size(); + if (op) + ps.addCallback({op, true}); + auto done = llvm::make_scope_exit([&]() { + if (op) + ps.addCallback({op, false}); + }); // Inform the visit method about the preferred sign we want from the result. // It may choose to ignore this, but some emitters can change behavior based @@ -2169,8 +2501,9 @@ SubExprInfo ExprEmitter::emitSubExpr(Value exp, // we know things about it. auto addPrefix = [&](StringToken &&t) { // insert {Prefix, ibox0}. - tokens.insert(tokens.begin() + subExprStartIndex, BeginToken(0)); - tokens.insert(tokens.begin() + subExprStartIndex, t); + buffer.tokens.insert(buffer.tokens.begin() + subExprStartIndex, + BeginToken(0)); + buffer.tokens.insert(buffer.tokens.begin() + subExprStartIndex, t); }; auto closeBoxAndParen = [&]() { ps << PP::end << ")"; }; if (signRequirement == RequireSigned && expInfo.signedness == IsUnsigned) { @@ -2354,40 +2687,24 @@ SubExprInfo ExprEmitter::visitSV(XMRRefOp op) { if (hasSVAttributes(op)) emitError(op, "SV attributes emission is unimplemented for the op"); - auto refAttr = op.getRefAttr(); - - // The XMR is pointing at an InnerRefAttr. - if (auto innerRef = dyn_cast(refAttr)) { + // The XMR is pointing at a GlobalRef. + auto globalRef = op.getReferencedPath(&state.symbolCache); + auto namepath = globalRef.getNamepathAttr().getValue(); + auto *module = state.symbolCache.getDefinition( + cast(namepath.front()).getModule()); + ps << PPExtString(getSymOpName(module)); + for (auto sym : namepath) { + ps << "."; + auto innerRef = cast(sym); auto ref = state.symbolCache.getInnerDefinition(innerRef.getModule(), innerRef.getName()); - ps << PPExtString(getSymOpName( - state.symbolCache.getDefinition(innerRef.getModule()))) - << "."; - if (ref.hasPort()) + if (ref.hasPort()) { ps << PPExtString(getPortVerilogName(ref.getOp(), ref.getPort())); - else - ps << PPExtString(getSymOpName(ref.getOp())); - } else { - // The XMR is pointing at a GlobalRef. - auto globalRef = cast(state.symbolCache.getDefinition( - cast(refAttr).getAttr())); - auto namepath = globalRef.getNamepathAttr().getValue(); - auto *module = state.symbolCache.getDefinition( - cast(namepath.front()).getModule()); - ps << PPExtString(getSymOpName(module)); - for (auto sym : namepath) { - ps << "."; - auto innerRef = cast(sym); - auto ref = state.symbolCache.getInnerDefinition(innerRef.getModule(), - innerRef.getName()); - if (ref.hasPort()) { - ps << PPExtString(getPortVerilogName(ref.getOp(), ref.getPort())); - continue; - } - ps << PPExtString(getSymOpName(ref.getOp())); + continue; } + ps << PPExtString(getSymOpName(ref.getOp())); } - auto leaf = op.getStringLeafAttr(); + auto leaf = op.getVerbatimSuffixAttr(); if (leaf && leaf.size()) ps << PPExtString(leaf); return {Selection, IsUnsigned}; @@ -2404,20 +2721,33 @@ SubExprInfo ExprEmitter::visitVerbatimExprOp(Operation *op, ArrayAttr symbols) { return {Unary, IsUnsigned}; } -SubExprInfo ExprEmitter::visitSV(MacroRefExprOp op) { +template +SubExprInfo ExprEmitter::emitMacroCall(MacroTy op) { if (hasSVAttributes(op)) emitError(op, "SV attributes emission is unimplemented for the op"); - ps << "`" << PPExtString(op.getIdent().getName()); + // Use the specified name or the symbol name as appropriate. + auto macroOp = op.getReferencedMacro(&state.symbolCache); + assert(macroOp && "Invalid IR"); + StringRef name = + macroOp.getVerilogName() ? *macroOp.getVerilogName() : macroOp.getName(); + ps << "`" << PPExtString(name); + if (!op.getInputs().empty()) { + ps << "("; + llvm::interleaveComma(op.getInputs(), ps, [&](Value val) { + emitExpression(val, LowestPrecedence); + }); + ps << ")"; + } return {LowestPrecedence, IsUnsigned}; } -SubExprInfo ExprEmitter::visitSV(MacroRefExprSEOp op) { - if (hasSVAttributes(op)) - emitError(op, "SV attributes emission is unimplemented for the op"); +SubExprInfo ExprEmitter::visitSV(MacroRefExprOp op) { + return emitMacroCall(op); +} - ps << "`" << PPExtString(op.getIdent().getName()); - return {LowestPrecedence, IsUnsigned}; +SubExprInfo ExprEmitter::visitSV(MacroRefExprSEOp op) { + return emitMacroCall(op); } SubExprInfo ExprEmitter::visitSV(ConstantXOp op) { @@ -2507,7 +2837,7 @@ SubExprInfo ExprEmitter::visitTypeOp(AggregateConstantOp op) { if (auto arrayType = hw::type_dyn_cast(type)) { auto elementType = arrayType.getElementType(); emitBracedList( - attr.cast(), [&]() { ps << "'{"; }, + attr.cast(), [&]() { ps << "{"; }, [&](Attribute attr) { printAggregate(attr, elementType); }, [&]() { ps << "}"; }); } else if (auto arrayType = hw::type_dyn_cast(type)) { @@ -2574,7 +2904,7 @@ SubExprInfo ExprEmitter::visitTypeOp(ArraySliceOp op) { auto arrayPrec = emitSubExpr(op.getInput(), Selection); - unsigned dstWidth = type_cast(op.getType()).getSize(); + unsigned dstWidth = type_cast(op.getType()).getNumElements(); ps << "["; emitSubExpr(op.getLowIndex(), LowestPrecedence); ps << " +: "; @@ -2695,9 +3025,6 @@ SubExprInfo ExprEmitter::visitSV(SampledOp op) { } SubExprInfo ExprEmitter::visitComb(MuxOp op) { - if (hasSVAttributes(op)) - emitError(op, "SV attributes emission is unimplemented for the op"); - // The ?: operator is right associative. // Layout: @@ -2716,7 +3043,9 @@ SubExprInfo ExprEmitter::visitComb(MuxOp op) { emitSubExpr(op.getCond(), VerilogPrecedence(Conditional - 1)); }); ps << BreakToken(1, 2); - ps << "? "; + ps << "?"; + emitSVAttributes(op); + ps << " "; auto lhsInfo = ps.scopedBox(PP::ibox0, [&]() { return emitSubExpr(op.getTrueValue(), VerilogPrecedence(Conditional - 1)); }); @@ -2769,7 +3098,7 @@ SubExprInfo ExprEmitter::visitTypeOp(StructExtractOp op) { emitSubExpr(op.getInput(), Selection); ps << "." - << PPExtString(emitter.getVerilogStructFieldName(op.getFieldAttr())); + << PPExtString(emitter.getVerilogStructFieldName(op.getFieldNameAttr())); return {Selection, IsUnsigned}; } @@ -2789,11 +3118,12 @@ SubExprInfo ExprEmitter::visitTypeOp(StructInjectOp op) { ps.scopedBox(PP::ibox2, [&]() { ps << PPExtString(emitter.getVerilogStructFieldName(field.name)) << ":" << PP::space; - if (field.name == op.getField()) { + if (field.name == op.getFieldNameAttr()) { emitSubExpr(op.getNewValue(), Selection); } else { emitSubExpr(op.getInput(), Selection); - ps << "." << PPExtString(field.name.getValue()); + ps << "." + << PPExtString(emitter.getVerilogStructFieldName(field.name)); } }); }, @@ -2806,6 +3136,81 @@ SubExprInfo ExprEmitter::visitTypeOp(EnumConstantOp op) { return {Selection, IsUnsigned}; } +SubExprInfo ExprEmitter::visitTypeOp(EnumCmpOp op) { + if (hasSVAttributes(op)) + emitError(op, "SV attributes emission is unimplemented for the op"); + auto result = emitBinary(op, Comparison, "==", NoRequirement); + // SystemVerilog 11.8.1: "Comparison... operator results are unsigned, + // regardless of the operands". + result.signedness = IsUnsigned; + return result; +} + +SubExprInfo ExprEmitter::visitTypeOp(UnionCreateOp op) { + if (hasSVAttributes(op)) + emitError(op, "SV attributes emission is unimplemented for the op"); + + // Check if this union type has been padded. + auto unionType = cast(getCanonicalType(op.getType())); + auto unionWidth = hw::getBitWidth(unionType); + auto &element = unionType.getElements()[op.getFieldIndex()]; + auto elementWidth = hw::getBitWidth(element.type); + + // If the element is 0 width, just fill the union with 0s. + if (!elementWidth) { + ps.addAsString(unionWidth); + ps << "'h0"; + return {Unary, IsUnsigned}; + } + + // If the element has no padding, emit it directly. + if (elementWidth == unionWidth) { + emitSubExpr(op.getInput(), LowestPrecedence); + return {Unary, IsUnsigned}; + } + + // Emit the value as a bitconcat, supplying 0 for the padding bits. + ps << "{"; + ps.scopedBox(PP::ibox0, [&]() { + if (auto prePadding = element.offset) { + ps.addAsString(prePadding); + ps << "'h0," << PP::space; + } + emitSubExpr(op.getInput(), Selection); + if (auto postPadding = unionWidth - elementWidth - element.offset) { + ps << "," << PP::space; + ps.addAsString(postPadding); + ps << "'h0"; + } + ps << "}"; + }); + + return {Unary, IsUnsigned}; +} + +SubExprInfo ExprEmitter::visitTypeOp(UnionExtractOp op) { + if (hasSVAttributes(op)) + emitError(op, "SV attributes emission is unimplemented for the op"); + emitSubExpr(op.getInput(), Selection); + + // Check if this union type has been padded. + auto unionType = cast(getCanonicalType(op.getInput().getType())); + auto unionWidth = hw::getBitWidth(unionType); + auto &element = unionType.getElements()[op.getFieldIndex()]; + auto elementWidth = hw::getBitWidth(element.type); + bool needsPadding = elementWidth < unionWidth || element.offset > 0; + auto verilogFieldName = emitter.getVerilogStructFieldName(element.name); + + // If the element needs padding then we need to get the actual element out + // of an anonymous structure. + if (needsPadding) + ps << "." << PPExtString(verilogFieldName); + + // Get the correct member from the union. + ps << "." << PPExtString(verilogFieldName); + return {Selection, IsUnsigned}; +} + SubExprInfo ExprEmitter::visitUnhandledExpr(Operation *op) { emitOpError(op, "cannot emit this expression to Verilog"); ps << "<getName().getStringRef()) @@ -2814,6 +3219,295 @@ SubExprInfo ExprEmitter::visitUnhandledExpr(Operation *op) { } // NOLINTEND(misc-no-recursion) +//===----------------------------------------------------------------------===// +// Property Emission +//===----------------------------------------------------------------------===// + +// NOLINTBEGIN(misc-no-recursion) + +namespace { +/// Precedence level of various property and sequence expressions. Lower numbers +/// bind tighter. +/// +/// See IEEE 1800-2017 section 16.12 "Declaring properties", specifically table +/// 16-3 on "Sequence and property operator precedence and associativity". +enum class PropertyPrecedence { + Symbol, // Atomic symbol like `foo` and regular boolean expressions + Repeat, // Sequence `[*]`, `[=]`, `[->]` + Concat, // Sequence `##` + Throughout, // Sequence `throughout` + Within, // Sequence `within` + Intersect, // Sequence `intersect` + Unary, // Property `not`, `nexttime`-like + And, // Sequence and property `and` + Or, // Sequence and property `or` + Iff, // Property `iff` + Until, // Property `until`-like, `implies` + Implication, // Property `|->`, `|=>`, `#-#`, `#=#` + Qualifier, // Property `always`-like, `eventually`-like, `if`, `case`, + // `accept`-like, `reject`-like + Clocking, // `@(...)`, `disable iff` (not specified in the standard) + Lowest, // Sentinel which is always the lowest precedence. +}; + +/// Additional information on emitted property and sequence expressions. +struct EmittedProperty { + /// The precedence of this expression. + PropertyPrecedence precedence; +}; + +/// A helper to emit recursively nested property and sequence expressions for +/// SystemVerilog assertions. +class PropertyEmitter : public EmitterBase, + public ltl::Visitor { +public: + /// Create a PropertyEmitter for the specified module emitter, and keeping + /// track of any emitted expressions in the specified set. + PropertyEmitter(ModuleEmitter &emitter, + SmallPtrSetImpl &emittedOps) + : PropertyEmitter(emitter, emittedOps, localTokens) {} + PropertyEmitter(ModuleEmitter &emitter, + SmallPtrSetImpl &emittedOps, + BufferingPP::BufferVec &tokens) + : EmitterBase(emitter.state), emitter(emitter), emittedOps(emittedOps), + buffer(tokens), + ps(buffer, state.saver, state.options.emitVerilogLocations) { + assert(state.pp.getListener() == &state.saver); + } + + /// Emit the specified value as an SVA property or sequence. This is the entry + /// point to print an entire tree of property or sequence expressions in one + /// go. + void emitProperty( + Value property, + PropertyPrecedence parenthesizeIfLooserThan = PropertyPrecedence::Lowest); + +private: + using ltl::Visitor::visitLTL; + friend class ltl::Visitor; + + /// Emit the specified value as an SVA property or sequence. + EmittedProperty + emitNestedProperty(Value property, + PropertyPrecedence parenthesizeIfLooserThan); + + EmittedProperty visitUnhandledLTL(Operation *op); + EmittedProperty visitLTL(ltl::AndOp op); + EmittedProperty visitLTL(ltl::OrOp op); + EmittedProperty visitLTL(ltl::DelayOp op); + EmittedProperty visitLTL(ltl::ConcatOp op); + EmittedProperty visitLTL(ltl::NotOp op); + EmittedProperty visitLTL(ltl::ImplicationOp op); + EmittedProperty visitLTL(ltl::EventuallyOp op); + EmittedProperty visitLTL(ltl::ClockOp op); + EmittedProperty visitLTL(ltl::DisableOp op); + + void emitLTLConcat(ValueRange inputs); + +public: + ModuleEmitter &emitter; + +private: + /// Keep track of all operations emitted within this subexpression for + /// location information tracking. + SmallPtrSetImpl &emittedOps; + + /// Tokens buffered for inserting casts/parens after emitting children. + SmallVector localTokens; + + /// Stores tokens until told to flush. Uses provided buffer (tokens). + BufferingPP buffer; + + /// Stream to emit expressions into, will add to buffer. + TokenStreamWithCallback ps; +}; +} // end anonymous namespace + +void PropertyEmitter::emitProperty( + Value property, PropertyPrecedence parenthesizeIfLooserThan) { + assert(localTokens.empty()); + // Wrap to this column. + ps.scopedBox(PP::ibox0, + [&] { emitNestedProperty(property, parenthesizeIfLooserThan); }); + // If we are not using an external token buffer provided through the + // constructor, but we're using the default `PropertyEmitter`-scoped buffer, + // flush it. + if (&buffer.tokens == &localTokens) + buffer.flush(state.pp); +} + +EmittedProperty PropertyEmitter::emitNestedProperty( + Value property, PropertyPrecedence parenthesizeIfLooserThan) { + // Emit the property as a plain expression if it doesn't have a property or + // sequence type, in which case it is just a boolean expression. + // + // We use the `LowestPrecedence` for the boolean expression such that it never + // gets parenthesized. According to IEEE 1800-2017, "the operators described + // in Table 11-2 have higher precedence than the sequence and property + // operators". Therefore any boolean expression behaves just like a + // `PropertyPrecedence::Symbol` and needs no parantheses, which is equivalent + // to `VerilogPrecedence::LowestPrecedence`. + if (!isa(property.getType())) { + ExprEmitter(emitter, emittedOps, buffer.tokens) + .emitExpression(property, LowestPrecedence); + return {PropertyPrecedence::Symbol}; + } + + unsigned startIndex = buffer.tokens.size(); + auto info = dispatchLTLVisitor(property.getDefiningOp()); + + // If this subexpression would bind looser than the expression it is bound + // into, then we need to parenthesize it. Insert the parentheses + // retroactively. + if (info.precedence > parenthesizeIfLooserThan) { + // Insert {"(", ibox0} before the subexpression. + buffer.tokens.insert(buffer.tokens.begin() + startIndex, BeginToken(0)); + buffer.tokens.insert(buffer.tokens.begin() + startIndex, StringToken("(")); + // Insert {end, ")" } after the subexpression. + ps << PP::end << ")"; + // Reset the precedence level. + info.precedence = PropertyPrecedence::Symbol; + } + + // Remember that we emitted this. + emittedOps.insert(property.getDefiningOp()); + return info; +} + +EmittedProperty PropertyEmitter::visitUnhandledLTL(Operation *op) { + emitOpError(op, "emission as Verilog property or sequence not supported"); + ps << "<getName().getStringRef()) << ">>"; + return {PropertyPrecedence::Symbol}; +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::AndOp op) { + llvm::interleave( + op.getInputs(), + [&](auto input) { emitNestedProperty(input, PropertyPrecedence::And); }, + [&]() { ps << PP::space << "and" << PP::nbsp; }); + return {PropertyPrecedence::And}; +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::OrOp op) { + llvm::interleave( + op.getInputs(), + [&](auto input) { emitNestedProperty(input, PropertyPrecedence::Or); }, + [&]() { ps << PP::space << "or" << PP::nbsp; }); + return {PropertyPrecedence::Or}; +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::DelayOp op) { + ps << "##"; + if (auto length = op.getLength()) { + if (*length == 0) { + ps.addAsString(op.getDelay()); + } else { + ps << "["; + ps.addAsString(op.getDelay()); + ps << ":"; + ps.addAsString(op.getDelay() + *length); + ps << "]"; + } + } else { + if (op.getDelay() == 0) { + ps << "[*]"; + } else if (op.getDelay() == 1) { + ps << "[+]"; + } else { + ps << "["; + ps.addAsString(op.getDelay()); + ps << ":$]"; + } + } + ps << PP::space; + emitNestedProperty(op.getInput(), PropertyPrecedence::Concat); + return {PropertyPrecedence::Concat}; +} + +void PropertyEmitter::emitLTLConcat(ValueRange inputs) { + bool addSeparator = false; + for (auto input : inputs) { + if (addSeparator) { + ps << PP::space; + if (!input.getDefiningOp()) + ps << "##0" << PP::space; + } + addSeparator = true; + emitNestedProperty(input, PropertyPrecedence::Concat); + } +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::ConcatOp op) { + emitLTLConcat(op.getInputs()); + return {PropertyPrecedence::Concat}; +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::NotOp op) { + ps << "not" << PP::space; + emitNestedProperty(op.getInput(), PropertyPrecedence::Unary); + return {PropertyPrecedence::Unary}; +} + +/// For a value `concat(..., delay(const(true), 1, 0))`, return `...`. This is +/// useful for emitting `(seq ##1 true) |-> prop` as `seq |=> prop`. +static ValueRange getNonOverlappingConcatSubrange(Value value) { + auto concatOp = value.getDefiningOp(); + if (!concatOp || concatOp.getInputs().size() < 2) + return {}; + auto delayOp = concatOp.getInputs().back().getDefiningOp(); + if (!delayOp || delayOp.getDelay() != 1 || delayOp.getLength() != 0) + return {}; + auto constOp = delayOp.getInput().getDefiningOp(); + if (!constOp || !constOp.getValue().isOne()) + return {}; + return concatOp.getInputs().drop_back(); +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::ImplicationOp op) { + // Emit `(seq ##1 true) |-> prop` as `seq |=> prop`. + if (auto range = getNonOverlappingConcatSubrange(op.getAntecedent()); + !range.empty()) { + emitLTLConcat(range); + ps << PP::space << "|=>" << PP::nbsp; + } else { + emitNestedProperty(op.getAntecedent(), PropertyPrecedence::Implication); + ps << PP::space << "|->" << PP::nbsp; + } + emitNestedProperty(op.getConsequent(), PropertyPrecedence::Implication); + return {PropertyPrecedence::Implication}; +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::EventuallyOp op) { + ps << "s_eventually" << PP::space; + emitNestedProperty(op.getInput(), PropertyPrecedence::Qualifier); + return {PropertyPrecedence::Qualifier}; +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::ClockOp op) { + ps << "@("; + ps.scopedBox(PP::ibox2, [&] { + ps << PPExtString(stringifyClockEdge(op.getEdge())) << PP::space; + emitNestedProperty(op.getClock(), PropertyPrecedence::Lowest); + ps << ")"; + }); + ps << PP::space; + emitNestedProperty(op.getInput(), PropertyPrecedence::Clocking); + return {PropertyPrecedence::Clocking}; +} + +EmittedProperty PropertyEmitter::visitLTL(ltl::DisableOp op) { + ps << "disable iff" << PP::nbsp << "("; + ps.scopedBox(PP::ibox2, [&] { + emitNestedProperty(op.getCondition(), PropertyPrecedence::Lowest); + ps << ")"; + }); + ps << PP::space; + emitNestedProperty(op.getInput(), PropertyPrecedence::Clocking); + return {PropertyPrecedence::Clocking}; +} + +// NOLINTEND(misc-no-recursion) + //===----------------------------------------------------------------------===// // NameCollector //===----------------------------------------------------------------------===// @@ -2833,6 +3527,11 @@ class NameCollector { private: size_t maxDeclNameWidth = 0, maxTypeWidth = 0; ModuleEmitter &moduleEmitter; + + /// Types that are longer than `maxTypeWidthBound` are not added to the + /// `maxTypeWidth` to prevent one single huge type from messing up the + /// alignment of all other declarations. + static constexpr size_t maxTypeWidthBound = 32; }; } // namespace @@ -2846,14 +3545,10 @@ void NameCollector::collectNames(Block &block) { // anyway. if (isa(op)) continue; + if (isa(op.getDialect())) + continue; - bool isExpr = isVerilogExpression(&op); - assert((!isExpr || - isExpressionEmittedInline(&op, moduleEmitter.state.options)) && - "If 'op' is a verilog expression, the expression must be inlinable. " - "Otherwise, it is a bug of PrepareForEmission"); - - if (!isExpr) { + if (!isVerilogExpression(&op)) { for (auto result : op.getResults()) { StringRef declName = getVerilogDeclWord(&op, moduleEmitter.state.options); @@ -2866,7 +3561,8 @@ void NameCollector::collectNames(Block &block) { moduleEmitter.printPackedType(stripUnpackedTypes(result.getType()), stringStream, op.getLoc()); } - maxTypeWidth = std::max(typeString.size(), maxTypeWidth); + if (typeString.size() <= maxTypeWidthBound) + maxTypeWidth = std::max(typeString.size(), maxTypeWidth); } } @@ -2880,14 +3576,6 @@ void NameCollector::collectNames(Block &block) { } continue; } - - // Recursively process any expressions in else blocks that can be emitted - // as `else if`. - if (auto ifOp = dyn_cast(op)) { - if (ifOp.hasElse() && findNestedElseIf(ifOp.getElseBlock())) - collectNames(*ifOp.getElseBlock()); - continue; - } } } @@ -2900,12 +3588,13 @@ namespace { // NOLINTBEGIN(misc-no-recursion) class StmtEmitter : public EmitterBase, public hw::StmtVisitor, - public sv::Visitor { + public sv::Visitor, + public verif::Visitor { public: /// Create an ExprEmitter for the specified module emitter, and keeping track /// of any emitted expressions in the specified set. - StmtEmitter(ModuleEmitter &emitter) - : EmitterBase(emitter.state), emitter(emitter) {} + StmtEmitter(ModuleEmitter &emitter, const LoweringOptions &options) + : EmitterBase(emitter.state), emitter(emitter), options(options) {} void emitStatement(Operation *op); void emitStatementBlock(Block &body); @@ -2921,16 +3610,20 @@ class StmtEmitter : public EmitterBase, VerilogPrecedence parenthesizeIfLooserThan = LowestPrecedence); void emitSVAttributes(Operation *op); - using StmtVisitor::visitStmt; - using Visitor::visitSV; + using hw::StmtVisitor::visitStmt; + using sv::Visitor::visitSV; + using verif::Visitor::visitVerif; friend class hw::StmtVisitor; friend class sv::Visitor; + friend class verif::Visitor; // Visitor methods. LogicalResult visitUnhandledStmt(Operation *op) { return failure(); } LogicalResult visitInvalidStmt(Operation *op) { return failure(); } LogicalResult visitUnhandledSV(Operation *op) { return failure(); } LogicalResult visitInvalidSV(Operation *op) { return failure(); } + LogicalResult visitUnhandledVerif(Operation *op) { return failure(); } + LogicalResult visitInvalidVerif(Operation *op) { return failure(); } LogicalResult visitSV(sv::WireOp op) { return emitDeclaration(op); } LogicalResult visitSV(RegOp op) { return emitDeclaration(op); } @@ -2940,6 +3633,10 @@ class StmtEmitter : public EmitterBase, LogicalResult emitAssignLike(Op op, PPExtString syntax, std::optional wordBeforeLHS = std::nullopt); + void emitAssignLike(llvm::function_ref emitLHS, + llvm::function_ref emitRHS, PPExtString syntax, + PPExtString postSyntax = PPExtString(";"), + std::optional wordBeforeLHS = std::nullopt); LogicalResult visitSV(AssignOp op); LogicalResult visitSV(BPAssignOp op); LogicalResult visitSV(PAssignOp op); @@ -2947,7 +3644,6 @@ class StmtEmitter : public EmitterBase, LogicalResult visitSV(ReleaseOp op); LogicalResult visitSV(AliasOp op); LogicalResult visitSV(InterfaceInstanceOp op); - LogicalResult visitStmt(ProbeOp op); LogicalResult visitStmt(OutputOp op); LogicalResult visitStmt(InstanceOp op); LogicalResult visitStmt(TypeScopeOp op); @@ -2988,7 +3684,9 @@ class StmtEmitter : public EmitterBase, LogicalResult visitSV(GenerateOp op); LogicalResult visitSV(GenerateCaseOp op); - void emitAssertionLabel(Operation *op, StringRef opName); + LogicalResult visitSV(ForOp op); + + void emitAssertionLabel(Operation *op); void emitAssertionMessage(StringAttr message, ValueRange args, SmallPtrSetImpl &ops, bool isConcurrent); @@ -3008,11 +3706,18 @@ class StmtEmitter : public EmitterBase, LogicalResult visitSV(InterfaceSignalOp op); LogicalResult visitSV(InterfaceModportOp op); LogicalResult visitSV(AssignInterfaceSignalOp op); + LogicalResult visitSV(MacroDefOp op); void emitBlockAsStatement(Block *block, const SmallPtrSetImpl &locationOps, StringRef multiLineComment = StringRef()); + LogicalResult emitVerifAssertLike(Operation *op, Value property, + PPExtString opName); + LogicalResult visitVerif(verif::AssertOp op); + LogicalResult visitVerif(verif::AssumeOp op); + LogicalResult visitVerif(verif::CoverOp op); + public: ModuleEmitter &emitter; @@ -3021,6 +3726,8 @@ class StmtEmitter : public EmitterBase, /// current statement scope. size_t maxDeclNameWidth = 0; size_t maxTypeWidth = 0; + + const LoweringOptions &options; }; } // end anonymous namespace @@ -3049,29 +3756,40 @@ void StmtEmitter::emitSVAttributes(Operation *op) { setPendingNewline(); } -template -LogicalResult -StmtEmitter::emitAssignLike(Op op, PPExtString syntax, - std::optional wordBeforeLHS) { - SmallPtrSet ops; - ops.insert(op); - - startStatement(); +void StmtEmitter::emitAssignLike(llvm::function_ref emitLHS, + llvm::function_ref emitRHS, + PPExtString syntax, PPExtString postSyntax, + std::optional wordBeforeLHS) { // If wraps, indent. ps.scopedBox(PP::ibox2, [&]() { if (wordBeforeLHS) { ps << *wordBeforeLHS << PP::space; } - emitExpression(op.getDest(), ops); + emitLHS(); // Allow breaking before 'syntax' (e.g., '=') if long assignment. ps << PP::space << syntax << PP::space; // RHS is boxed to right of the syntax. ps.scopedBox(PP::ibox0, [&]() { - emitExpression(op.getSrc(), ops); - ps << ";"; + emitRHS(); + ps << postSyntax; }); }); +} + +template +LogicalResult +StmtEmitter::emitAssignLike(Op op, PPExtString syntax, + std::optional wordBeforeLHS) { + SmallPtrSet ops; + ops.insert(op); + + startStatement(); + ps.addCallback({op, true}); + emitAssignLike([&]() { emitExpression(op.getDest(), ops); }, + [&]() { emitExpression(op.getSrc(), ops); }, syntax, + PPExtString(";"), wordBeforeLHS); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3123,11 +3841,13 @@ LogicalResult StmtEmitter::visitSV(ReleaseOp op) { startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps.scopedBox(PP::ibox2, [&]() { ps << "release" << PP::space; emitExpression(op.getDest(), ops); ps << ";"; }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3139,6 +3859,7 @@ LogicalResult StmtEmitter::visitSV(AliasOp op) { startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps.scopedBox(PP::ibox2, [&]() { ps << "alias" << PP::space; ps.scopedBox(PP::cbox0, [&]() { // If any breaks, all break. @@ -3148,6 +3869,7 @@ LogicalResult StmtEmitter::visitSV(AliasOp op) { ps << ";"; }); }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3162,6 +3884,7 @@ LogicalResult StmtEmitter::visitSV(InterfaceInstanceOp op) { startStatement(); StringRef prefix = ""; + ps.addCallback({op, true}); if (doNotPrint) { prefix = "// "; ps << "// This interface is elsewhere emitted as a bind statement." @@ -3182,6 +3905,7 @@ LogicalResult StmtEmitter::visitSV(InterfaceInstanceOp op) { << PP::nbsp /* don't break, may be comment line */ << PPExtString(op.getName()) << "();"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); @@ -3191,10 +3915,11 @@ LogicalResult StmtEmitter::visitSV(InterfaceInstanceOp op) { /// assign the module outputs to intermediate wires. LogicalResult StmtEmitter::visitStmt(OutputOp op) { SmallPtrSet ops; - HWModuleOp parent = op->getParentOfType(); + auto parent = op->getParentOfType(); size_t operandIndex = 0; - for (PortInfo port : parent.getPorts().outputs) { + ModulePortInfo ports(parent.getPortList()); + for (PortInfo port : ports.getOutputs()) { auto operand = op.getOperand(operandIndex); // Outputs that are set by the output port of an instance are handled // directly when the instance is emitted. @@ -3209,6 +3934,7 @@ LogicalResult StmtEmitter::visitStmt(OutputOp op) { ops.insert(op); startStatement(); + ps.addCallback({op, true}); bool isZeroBit = isZeroBitType(port.type); ps.scopedBox(isZeroBit ? PP::neverbox : PP::ibox2, [&]() { if (isZeroBit) @@ -3228,6 +3954,7 @@ LogicalResult StmtEmitter::visitStmt(OutputOp op) { ps << ";"; }); }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); ++operandIndex; @@ -3253,6 +3980,10 @@ LogicalResult StmtEmitter::visitStmt(TypedeclOp op) { emitError(op, "SV attributes emission is unimplemented for the op"); startStatement(); + auto zeroBitType = isZeroBitType(op.getType()); + if (zeroBitType) + ps << PP::neverbox << "// "; + SmallPtrSet ops; ops.insert(op); ps.scopedBox(PP::ibox2, [&]() { @@ -3266,6 +3997,8 @@ LogicalResult StmtEmitter::visitStmt(TypedeclOp op) { [&](auto &os) { emitter.printUnpackedTypePostfix(op.getType(), os); }); ps << ";"; }); + if (zeroBitType) + ps << PP::end; emitLocationInfoAndNewLine(ops); return success(); } @@ -3278,6 +4011,7 @@ LogicalResult StmtEmitter::visitSV(FWriteOp op) { SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps << "$fwrite("; ps.scopedBox(PP::ibox0, [&]() { emitExpression(op.getFd(), ops); @@ -3297,6 +4031,7 @@ LogicalResult StmtEmitter::visitSV(FWriteOp op) { } ps << ");"; }); + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3352,6 +4087,7 @@ StmtEmitter::emitSimulationControlTask(Operation *op, PPExtString taskName, startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps << taskName; if (verbosity && *verbosity != 1) { ps << "("; @@ -3359,6 +4095,7 @@ StmtEmitter::emitSimulationControlTask(Operation *op, PPExtString taskName, ps << ")"; } ps << ";"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3388,6 +4125,7 @@ StmtEmitter::emitSeverityMessageTask(Operation *op, PPExtString taskName, startStatement(); SmallPtrSet ops; ops.insert(op); + ps.addCallback({op, true}); ps << taskName; // In case we have a message to print, or the operation has an optional @@ -3418,6 +4156,7 @@ StmtEmitter::emitSeverityMessageTask(Operation *op, PPExtString taskName, } ps << ";"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3446,6 +4185,7 @@ LogicalResult StmtEmitter::visitSV(ReadMemOp op) { SmallPtrSet ops({op}); startStatement(); + ps.addCallback({op, true}); ps << "$readmem"; switch (op.getBaseAttr().getValue()) { case MemBaseTypeAttr::MemBaseBin: @@ -3463,6 +4203,7 @@ LogicalResult StmtEmitter::visitSV(ReadMemOp op) { }); ps << ");"; + ps.addCallback({op, false}); emitLocationInfoAndNewLine(ops); return success(); } @@ -3471,6 +4212,7 @@ LogicalResult StmtEmitter::visitSV(GenerateOp op) { emitSVAttributes(op); // TODO: location info? startStatement(); + ps.addCallback({op, true}); ps << "generate" << PP::newline; ps << "begin: " << PPExtString(getSymOpName(op)); setPendingNewline(); @@ -3478,6 +4220,7 @@ LogicalResult StmtEmitter::visitSV(GenerateOp op) { startStatement(); ps << "end: " << PPExtString(getSymOpName(op)) << PP::newline; ps << "endgenerate"; + ps.addCallback({op, false}); setPendingNewline(); return success(); } @@ -3486,6 +4229,7 @@ LogicalResult StmtEmitter::visitSV(GenerateCaseOp op) { emitSVAttributes(op); // TODO: location info? startStatement(); + ps.addCallback({op, true}); ps << "case ("; ps.invokeWithStringOS([&](auto &os) { emitter.printParamValue( @@ -3524,7 +4268,8 @@ LogicalResult StmtEmitter::visitSV(GenerateCaseOp op) { }); StringRef legalName = - legalizeName(caseNames[i].cast().getValue(), nextGenIds); + legalizeName(caseNames[i].cast().getValue(), nextGenIds, + options.caseInsensitiveKeywords); ps << ": begin: " << PPExtString(legalName); setPendingNewline(); emitStatementBlock(region.getBlocks().front()); @@ -3536,19 +4281,61 @@ LogicalResult StmtEmitter::visitSV(GenerateCaseOp op) { startStatement(); ps << "endcase"; + ps.addCallback({op, false}); + setPendingNewline(); + return success(); +} + +LogicalResult StmtEmitter::visitSV(ForOp op) { + emitSVAttributes(op); + llvm::SmallPtrSet ops; + ps.addCallback({op, true}); + startStatement(); + auto inductionVarName = op->getAttrOfType("hw.verilogName"); + ps << "for ("; + // Emit statements on same line if possible, or put each on own line. + ps.scopedBox(PP::cbox0, [&]() { + // Emit initialization assignment. + emitAssignLike( + [&]() { + ps << "logic" << PP::nbsp; + ps.invokeWithStringOS([&](auto &os) { + emitter.emitTypeDims(op.getInductionVar().getType(), op.getLoc(), + os); + }); + ps << PP::nbsp << PPExtString(inductionVarName); + }, + [&]() { emitExpression(op.getLowerBound(), ops); }, PPExtString("=")); + // Break between statements. + ps << PP::space; + + // Emit bounds-check statement. + emitAssignLike([&]() { ps << PPExtString(inductionVarName); }, + [&]() { emitExpression(op.getUpperBound(), ops); }, + PPExtString("<")); + // Break between statements. + ps << PP::space; + + // Emit update statement and trailing syntax. + emitAssignLike([&]() { ps << PPExtString(inductionVarName); }, + [&]() { emitExpression(op.getStep(), ops); }, + PPExtString("+="), PPExtString(") begin")); + }); + // Don't break for because of newline. + ps << PP::neverbreak; setPendingNewline(); + emitStatementBlock(op.getBody().getBlocks().front()); + startStatement(); + ps << "end"; + ps.addCallback({op, false}); + emitLocationInfoAndNewLine(ops); return success(); } -/// Emit the `