diff --git a/.github/workflows/buildAndTest.yml b/.github/workflows/buildAndTest.yml index e1ea229801b7..29e874b8e86a 100644 --- a/.github/workflows/buildAndTest.yml +++ b/.github/workflows/buildAndTest.yml @@ -14,7 +14,7 @@ jobs: name: Sanity Check runs-on: ubuntu-latest container: - image: ghcr.io/circt/images/circt-ci-build:20230126201226 + image: ghcr.io/circt/images/circt-ci-build:20240213211952 steps: # Clone the CIRCT repo and its submodules. Do shallow clone to save clone # time. @@ -218,7 +218,8 @@ jobs: -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} \ -DLLVM_EXTERNAL_LIT=`pwd`/../llvm/build/bin/llvm-lit \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -DLLVM_LIT_ARGS="-v" + -DLLVM_LIT_ARGS="-v" \ + -DCIRCT_SLANG_FRONTEND_ENABLED=ON ninja check-circt check-circt-unit -j$(nproc) ninja circt-doc diff --git a/.github/workflows/buildAndTestWindows.yml b/.github/workflows/buildAndTestWindows.yml index 2c4474c31cc3..43ccb228a887 100644 --- a/.github/workflows/buildAndTestWindows.yml +++ b/.github/workflows/buildAndTestWindows.yml @@ -13,6 +13,9 @@ jobs: build-circt: name: Build and Test uses: ./.github/workflows/unifiedBuildTestAndInstall.yml + # This should not be needed, but the unified job unconditionally requires this permission currently. + permissions: + contents: write # Upload assets to release. with: runner: windows-2022 cmake_build_type: release diff --git a/.github/workflows/nightlyIntegrationTests.yml b/.github/workflows/nightlyIntegrationTests.yml index 3b3f952d2490..478299f63099 100644 --- a/.github/workflows/nightlyIntegrationTests.yml +++ b/.github/workflows/nightlyIntegrationTests.yml @@ -19,7 +19,7 @@ jobs: # 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:v12.2 + image: ghcr.io/circt/images/circt-integration-test:v13.1 volumes: - /mnt:/__w/circt strategy: @@ -96,7 +96,8 @@ jobs: -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DCIRCT_BINDINGS_PYTHON_ENABLED=ON \ -DESI_RUNTIME=ON \ - -DLLVM_LIT_ARGS="-v --show-unsupported ${{ matrix.lit-flags }}" + -DLLVM_LIT_ARGS="-v --show-unsupported ${{ matrix.lit-flags }}" \ + -DCIRCT_SLANG_FRONTEND_ENABLED=ON - name: Test CIRCT run: | ninja -C build check-circt -j$(nproc) diff --git a/.github/workflows/shortIntegrationTests.yml b/.github/workflows/shortIntegrationTests.yml index c3866b74dc62..32a52c9f4f09 100644 --- a/.github/workflows/shortIntegrationTests.yml +++ b/.github/workflows/shortIntegrationTests.yml @@ -29,7 +29,7 @@ jobs: # 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:v12.2 + image: ghcr.io/circt/images/circt-integration-test:v13.1 volumes: - /mnt:/__w/circt strategy: @@ -89,7 +89,8 @@ jobs: -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DCIRCT_BINDINGS_PYTHON_ENABLED=ON \ -DESI_RUNTIME=ON \ - -DLLVM_LIT_ARGS="-v --show-unsupported" + -DLLVM_LIT_ARGS="-v --show-unsupported" \ + -DCIRCT_SLANG_FRONTEND_ENABLED=ON - name: Test CIRCT run: | ninja -C build check-circt -j$(nproc) diff --git a/.github/workflows/unifiedBuildTestAndInstall.yml b/.github/workflows/unifiedBuildTestAndInstall.yml index b3abff872666..1c21e9479942 100644 --- a/.github/workflows/unifiedBuildTestAndInstall.yml +++ b/.github/workflows/unifiedBuildTestAndInstall.yml @@ -127,6 +127,8 @@ on: jobs: build-test-and-install: runs-on: ${{ inputs.runner }} + permissions: + contents: write # Upload assets to release. steps: - name: Clone llvm/circt uses: actions/checkout@v3 @@ -269,9 +271,10 @@ jobs: retention-days: 7 - name: Upload Binaries (Tag) - uses: AButler/upload-release-assets@v2.0 + uses: AButler/upload-release-assets@v3.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 }} + release-tag: ${{ github.ref_name }} # Upload to release tag when manually run. diff --git a/.github/workflows/uploadReleaseArtifacts.yml b/.github/workflows/uploadReleaseArtifacts.yml index 43c5e0858cd1..8ba245c43689 100644 --- a/.github/workflows/uploadReleaseArtifacts.yml +++ b/.github/workflows/uploadReleaseArtifacts.yml @@ -13,13 +13,27 @@ on: - linux - macos - windows + + # The following options only influence workflow_dispatch, and are ignored otherwise. runTests: - type: choice description: Run CIRCT tests default: false + type: boolean + + llvm_enable_assertions: + description: Build with assertions. + default: false + type: boolean + + cmake_build_type: + required: true + type: choice options: - - true - - false + - release + - relwithdebinfo + - debug + default: release + # Run every day at 0700 UTC which is: # - 0000 PDT / 2300 PST # - 0300 EDT / 0200 EST @@ -30,6 +44,8 @@ jobs: publish-sources: if: github.ref_type == 'tag' runs-on: ubuntu-20.04 + permissions: + contents: write # Upload assets to release. steps: # Clone the CIRCT repo and its submodules. Do shallow clone to save clone # time. @@ -51,11 +67,12 @@ jobs: 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 + uses: AButler/upload-release-assets@v3.0 with: # The * will grab the .sha256 as well files: circt-full-sources.tar.gz* repo-token: ${{ secrets.GITHUB_TOKEN }} + release-tag: ${{ github.ref_name }} # Upload to release tag when manually run. # This job sets up the build matrix. choose-matrix: @@ -103,7 +120,7 @@ jobs: if: github.event_name == 'release' || ( github.event_name == 'workflow_dispatch' && inputs.os == 'windows' ) env: os: windows - runner: windows-2019 + runner: windows-2022 arch: x64 tar: tar czf archive: zip @@ -120,20 +137,33 @@ jobs: - name: Add Build Config for firtool and om-linker id: add-build-config-firtool run: | + # Default configuration template. 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 + case ${{ github.event_name }} in + # Workflow dispatch looks to input knobs for asserts and build type. + workflow_dispatch) + json=$(echo $json | jq -c '.assert = "${{ inputs.llvm_enable_assertions && 'ON' || 'OFF' }}" | .mode = "${{ inputs.cmake_build_type }}"') + ;; + # Scheulded runs are Release but with Asserts + Debug. + schedule) + json=$(echo $json | jq -c '.assert = "ON" | .mode = "relwithdebinfo"') + ;; + esac 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: Add Build Config for CIRCT-full (static) + id: add-build-config-circt-full-static + run: | + json='{"name":"CIRCT-full static","install_target":"install","package_name_prefix":"circt-full-static","mode":"release","assert":"OFF","shared":"OFF","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-build-config-firtool.outputs.out }}' '${{ steps.add-build-config-circt-full-shared.outputs.out }}' '${{ steps.add-build-config-circt-full-static.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 @@ -174,6 +204,8 @@ jobs: strategy: matrix: generated: ${{ fromJSON(needs.choose-matrix.outputs.matrix) }} + permissions: + contents: write # Upload assets to release. uses: ./.github/workflows/unifiedBuildTestAndInstall.yml with: runner: ${{ matrix.generated.runner }} diff --git a/.github/workflows/uploadWheels.yml b/.github/workflows/uploadWheels.yml index 1296f78bca21..631837723a52 100644 --- a/.github/workflows/uploadWheels.yml +++ b/.github/workflows/uploadWheels.yml @@ -5,7 +5,7 @@ on: types: [created] workflow_dispatch: schedule: - - cron: 0 12 * * * + - cron: 0 12 * * 1 jobs: build_wheels: @@ -18,20 +18,12 @@ jobs: config: - os: ubuntu-20.04 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-12 cibw_build: cp38-macosx_x86_64 - - os: macos-12 - cibw_build: cp39-macosx_x86_64 - os: macos-12 cibw_build: cp310-macosx_x86_64 - - os: macos-12 - cibw_build: cp311-macosx_x86_64 steps: - name: Get CIRCT @@ -67,7 +59,7 @@ jobs: if-no-files-found: error push_wheels: - name: Push wheels (Tag or Nightly) + name: Push wheels (Tag or Weekly) runs-on: ubuntu-20.04 if: github.repository == 'llvm/circt' && (github.ref_type == 'tag' || github.event_name == 'schedule') needs: build_wheels diff --git a/CMakeLists.txt b/CMakeLists.txt index 17d663caa5e4..c53729be2868 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,10 @@ if(POLICY CMP0116) cmake_policy(SET CMP0116 OLD) endif() +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() + set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED YES) @@ -527,11 +531,13 @@ endif() # slang Verilog Frontend #------------------------------------------------------------------------------- -option(CIRCT_SLANG_FRONTEND_ENABLED "Enables slang Verilog frontend." ON) +option(CIRCT_SLANG_FRONTEND_ENABLED "Enables the slang Verilog frontend." OFF) option(CIRCT_SLANG_BUILD_FROM_SOURCE "Build slang from source instead of finding an installed package" ON) llvm_canonicalize_cmake_booleans(CIRCT_SLANG_FRONTEND_ENABLED) +llvm_canonicalize_cmake_booleans(CIRCT_SLANG_BUILD_FROM_SOURCE) + if(CIRCT_SLANG_FRONTEND_ENABLED) message(STATUS "slang Verilog frontend is enabled") if(CIRCT_SLANG_BUILD_FROM_SOURCE) @@ -548,15 +554,19 @@ if(CIRCT_SLANG_FRONTEND_ENABLED) # Force Slang to be built as a static library to avoid messing around with # RPATHs and installing a slang dylib alongside CIRCT. The static library # will embed Slang into ImportVerilog. - set(original_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) - set(original_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}) - - set(CMAKE_CXX_FLAGS "") + set(ORIGINAL_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) + set(ORIGINAL_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}) + + if (MSVC) + set(CMAKE_CXX_FLAGS "/EHsc") + else () + set(CMAKE_CXX_FLAGS "") + endif () set(BUILD_SHARED_LIBS OFF) FetchContent_MakeAvailable(slang) - set(CMAKE_CXX_FLAGS ${original_CMAKE_CXX_FLAGS}) - set(BUILD_SHARED_LIBS ${original_BUILD_SHARED_LIBS}) + set(CMAKE_CXX_FLAGS ${ORIGINAL_CMAKE_CXX_FLAGS}) + set(BUILD_SHARED_LIBS ${ORIGINAL_BUILD_SHARED_LIBS}) if(BUILD_SHARED_LIBS) set_target_properties(slang_slang PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/docs/Dialects/ESI/cosim.md b/docs/Dialects/ESI/cosim.md index 60c596cb7d0e..2c0ac2b03906 100644 --- a/docs/Dialects/ESI/cosim.md +++ b/docs/Dialects/ESI/cosim.md @@ -76,7 +76,7 @@ module Cosim_Endpoint ``` The RPC interface allows clients to query all the registered endpoints, grab -a reference to one, and send/recieve messages and/or raw data. Once one +a reference to one, and send/receive messages and/or raw data. Once one client opens an Endpoint, it is locked until said client closes it. ```capnp diff --git a/docs/Dialects/ESI/types.md b/docs/Dialects/ESI/types.md index 2531936cca1e..95cf0a9aca91 100644 --- a/docs/Dialects/ESI/types.md +++ b/docs/Dialects/ESI/types.md @@ -38,7 +38,7 @@ rather, they speak to the signaling. ## Channels ESI "channels" are streaming connections upon which *messages* can be sent and -recieved. They are expressed by wrapping the type (e.g. `!esi.channel`) and +received. They are expressed by wrapping the type (e.g. `!esi.channel`) and using it like any other value type. ```mlir diff --git a/docs/Dialects/Emit/RationaleEmit.md b/docs/Dialects/Emit/RationaleEmit.md new file mode 100644 index 000000000000..9af512cf4d63 --- /dev/null +++ b/docs/Dialects/Emit/RationaleEmit.md @@ -0,0 +1,22 @@ +# Emission (Emit) Dialect Rationale + +This document describes various design points of the `emit` 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 `emit` dialects controls the structure and formatting of the files emitted +from CIRCT. It captures information about both SystemVerilog output files and +generic collateral files. The ops are translated to output files in +`ExportVerilog`. Presently, the dialect is intertwined with SystemVerilog - +it can reference items in a design through symbols to emit references to +them through SystemVerilog names in the output. + +## Operations + +The dialect is centred around the `emit.file` operation which groups a list +of statements in its body, responsible for producing the contents of the file. +The `emit.file_list` operation pins down a list of references to emitted files +and outputs a file list file enumerating the paths to them. +Together, these operations represent the SV and collateral output of CIRCT. diff --git a/docs/Dialects/Emit/_index.md b/docs/Dialects/Emit/_index.md new file mode 100644 index 000000000000..196ef16afd19 --- /dev/null +++ b/docs/Dialects/Emit/_index.md @@ -0,0 +1,3 @@ +# 'emit' Dialect + +[include "Dialects/Emit.md"] diff --git a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md index 826dd26499d3..3bbb8c5eb53a 100644 --- a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md +++ b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md @@ -27,7 +27,7 @@ indicate to the compiler that a wire "foo" should not be optimized away. ```json { "class":"firrtl.transforms.DontTouchAnnotation", - "target""~MyCircuit|MyModule>foo" + "target":"~MyCircuit|MyModule>foo" } ``` @@ -99,7 +99,7 @@ circuit Foo: | Folded Module | Unfolded Modules | | --------------- | ----------------- | -| | | +| | | Using targets (or multiple targets), any specific module, instance, or combination of instances can be expressed. Some examples include: diff --git a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md index 6788c080faa4..40c15b433e35 100644 --- a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md +++ b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md @@ -94,3 +94,108 @@ The enable input is sampled at the rising edge of the input clock; any changes o | in | input | Clock | input clock | | en | input | UInt<1> | enable for the output clock | | out | output | Clock | gated output clock | + +### circt.chisel_assert_assume + +Generate a clocked SV assertion with companion assume statement. + +Has legacy special behavior and should not be used by new code. + +| Parameter | Type | Description | +| --------- | ------ | ----------------------------------------------------------------------------------- | +| format | string | Format string per SV 20.10, 21.2.1. Optional. | +| label | string | Label for assert/assume. Optional. | +| guards | string | Semicolon-delimited list of pre-processor tokens to use as ifdef guards. Optional. | + +| Port | Direction | Type | Description | +| --------- | --------- | -------- | -------------------------- | +| clock | input | Clock | input clock | +| predicate | input | UInt<1> | predicate to assert/assume | +| enable | input | UInt<1> | enable signal | +| ... | input | Signals | arguments to format string | + +Example output: +```systemverilog +wire _GEN = ~enable | cond; +assert__label: assert property (@(posedge clock) _GEN) else $error("message"); +`ifdef USE_PROPERTY_AS_CONSTRAINT + assume__label: assume property (@(posedge clock) _GEN); +`endif // USE_PROPERTY_AS_CONSTRAINT +``` + +### circt.chisel_ifelsefatal + +Generate a particular Verilog sequence that's similar to an assertion. + +Has legacy special behavior and should not be used by new code. + +| Parameter | Type | Description | +| --------- | ------ | ---------------------------------------------- | +| format | string | Format string per SV 20.10, 21.2.1. Optional. | + +This intrinsic also accepts the `label` and `guard` parameters which +are recorded but not used in the normal emission. + +| Port | Direction | Type | Description | +| --------- | --------- | -------- | --------------------------- | +| clock | input | Clock | input clock | +| predicate | input | UInt<1> | predicate to check | +| enable | input | UInt<1> | enable signal | +| ... | input | Signals | arguments to format string | + +Example SV output: +```systemverilog +`ifndef SYNTHESIS + always @(posedge clock) begin + if (enable & ~cond) begin + if (`ASSERT_VERBOSE_COND_) + $error("message"); + if (`STOP_COND_) + $fatal; + end + end // always @(posedge) +`endif // not def SYNTHESIS +``` + +### circt.chisel_assume + +Generate a clocked SV assume statement, with optional formatted error message. + + +| Parameter | Type | Description | +| --------- | ------ | ----------------------------------------------------------------------------------- | +| format | string | Format string per SV 20.10, 21.2.1. Optional. | +| label | string | Label for assume statement. Optional. | +| guards | string | Semicolon-delimited list of pre-processor tokens to use as ifdef guards. Optional. | + +| Port | Direction | Type | Description | +| --------- | --------- | -------- | -------------------------- | +| clock | input | Clock | input clock | +| predicate | input | UInt<1> | predicate to assume | +| enable | input | UInt<1> | enable signal | +| ... | input | Signals | arguments to format string | + +Example SV output: +```systemverilog +assume__label: assume property (@(posedge clock) ~enable | cond) else $error("message"); +``` + +### circt.chisel_cover + +Generate a clocked SV cover statement. + +| Parameter | Type | Description | +| --------- | ------ | ----------------------------------------------------------------------------------- | +| label | string | Label for cover statement. Optional. | +| guards | string | Semicolon-delimited list of pre-processor tokens to use as ifdef guards. Optional. | + +| Port | Direction | Type | Description | +| --------- | --------- | -------- | ------------------ | +| clock | input | Clock | input clock | +| predicate | input | UInt<1> | predicate to cover | +| enable | input | UInt<1> | enable signal | + +Example SV output: +```systemverilog +cover__label: cover property (@(posedge clock) enable & cond); +``` diff --git a/docs/Dialects/FIRRTL/RationaleFIRRTL.md b/docs/Dialects/FIRRTL/RationaleFIRRTL.md index d8247510c5f0..022ad15fb1d2 100644 --- a/docs/Dialects/FIRRTL/RationaleFIRRTL.md +++ b/docs/Dialects/FIRRTL/RationaleFIRRTL.md @@ -332,8 +332,9 @@ enabled. ## Symbols and Inner Symbols 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. +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 of the compiler. This requires the entity be preserved in such a way as the @@ -558,20 +559,34 @@ verification. ### Non-FIRRTL Types -The FIRRTL dialect has limited support for foreign types, i.e., types that are defined outside the FIRRTL dialect. Almost all operations expect to be dealing with FIRRTL types, especially those that are sensitive to the type they operate on, like `firrtl.add` or `firrtl.connect`. However, a restricted set of operations allows for simple pass-through semantics of foreign types. These include the following: +The FIRRTL dialect has limited support for foreign types, i.e., types that are +defined outside the FIRRTL dialect. Almost all operations expect to be dealing +with FIRRTL types, especially those that are sensitive to the type they operate +on, like `firrtl.add` or `firrtl.connect`. However, a restricted set of +operations allows for simple pass-through semantics of foreign types. These +include the following: - Ports on a `firrtl.module`, where the foreign types are treated as opaque values moving in and out of the module - Ports on a `firrtl.instance` - `firrtl.wire` to allow for def-after-use cases; the wire must have a single strict connect that uniquely defines the wire's value - `firrtl.strictconnect` to module outputs, instance inputs, and wires -The expected lowering for strict connects is for the connect to be eliminated and the right-hand-side source value of the connect being instead materialized in all places where the left hand side is used. Basically we want wires and connects to disappear, and all places where the wire is "read" should instead read the value that was driven onto the wire. +The expected lowering for strict connects is for the connect to be eliminated +and the right-hand-side source value of the connect being instead materialized +in all places where the left hand side is used. Basically we want wires and +connects to disappear, and all places where the wire is "read" should instead +read the value that was driven onto the wire. -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. +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. +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 @@ -761,7 +776,7 @@ operations related to Chisel memories are often referred to as CHIRRTL. The main difference between Chisel and FIRRTL memories is that Chisel memories have an operation to add a memory port to a memory, while FIRRTL memories require all ports to be defined up front. Another difference is that Chisel -memories have "enable inferrence", and are usually inferred to be enabled where +memories have "enable inference", and are usually inferred to be enabled where they are declared. The following example shows a CHIRRTL memory declaration, and the standard FIRRTL memory equivalent. @@ -1035,7 +1050,7 @@ to a constant zero. ## Intrinsics Intrinsics are implementation-defined constructs. Intrinsics provide a way to -extend the system with funcitonality without changing the langauge. They form +extend the system with functionality without changing the language. They form an implementation-specific built-in library. Unlike traditional libraries, implementations of intrinsics have access to internals of the compiler, allowing them to implement features not possible in the language. diff --git a/docs/Dialects/HW/RationaleHW.md b/docs/Dialects/HW/RationaleHW.md index 886e72ca23d8..3317874c4237 100644 --- a/docs/Dialects/HW/RationaleHW.md +++ b/docs/Dialects/HW/RationaleHW.md @@ -90,7 +90,7 @@ 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. +optionally have an offset per variant which allows non-SV layouts. ## `hw.module` and `hw.instance` diff --git a/docs/Dialects/Moore.md b/docs/Dialects/Moore.md new file mode 100644 index 000000000000..0559e2661b6f --- /dev/null +++ b/docs/Dialects/Moore.md @@ -0,0 +1,22 @@ +# 'moore' Dialect + +This dialect provides operations and types to capture a SystemVerilog design after parsing, type checking, and elaboration. + +[TOC] + + +## Rationale + +The main goal of the `moore` dialect is to provide a set of operations and types for the `ImportVerilog` conversion to translate a fully parsed, type-checked, and elaborated Slang AST into MLIR operations. See IEEE 1800-2017 for more details about SystemVerilog. The dialect aims to faithfully capture the full SystemVerilog types and semantics, and provide a platform for transformation passes to resolve language quirks, analyze the design at a high level, and lower it to the core dialects. + +In contrast, the `sv` dialect is geared towards emission of SystemVerilog text, and is focused on providing a good lowering target to allow for emission. The `moore` and `sv` dialect may eventually converge into a single dialect. As we are building out the Verilog frontend capabilities of CIRCT it is valuable to have a separate ingestion dialect, such that we do not have to make disruptive changes to the load-bearing `sv` dialect used in production. + + +## Types + +[include "Dialects/MooreTypes.md"] + + +## Operations + +[include "Dialects/MooreOps.md"] diff --git a/docs/Dialects/OM/RationaleOM.md b/docs/Dialects/OM/RationaleOM.md index 2f80bb30c939..dabf50c13c27 100644 --- a/docs/Dialects/OM/RationaleOM.md +++ b/docs/Dialects/OM/RationaleOM.md @@ -108,6 +108,7 @@ parameters * References to formal parameters * Primitive values like integers, strings, and symbols * Container values like lists +* Expressions involving primitive and container values This modeling of Classes might be most similar to Java classes, which define public constructors and members. The proposed modeling of Classes is restricted @@ -161,6 +162,29 @@ within the Object and potential children Objects. Object Field accesses are values just like other expressions, and can be assigned to named Fields in a Class or passed as actual parameters in Object instantiations. +### Expressions + +The small expression grammar described under Classes includes expressions +involving primitive and container values. This sections describes the rationale +for such expressions. + +In order for a Class to effectively capture parts of a domain model, it may be +necessary for the Class to represent computation in terms of its formal +parameters. + +For example, a Class might represent a device that is attached to a bus, and +accessible at some address. If that address is implemented as an offset relative +to some base address, the Class could have an input integer as a formal +parameter representing the base address, a Field representing the device +address, and internally add some constant offset to the base address before +assigning the resulting value into the Field. + +As another example, a Class might internally instantiate Objects of some other +Classes, access Fields of those Objects, create a container holding the values +of those fields, and assign the container to a Field. Using container +construction expressions, this can be represented directly in the Class, +allowing it to abstract over the Objects it creates internally. + ## Alternatives Considered ### Other Libraries and Tools for Domain Modeling diff --git a/docs/Dialects/Pipeline/RationalePipeline.md b/docs/Dialects/Pipeline/RationalePipeline.md index 25057c5447c8..b52593d6d0a4 100644 --- a/docs/Dialects/Pipeline/RationalePipeline.md +++ b/docs/Dialects/Pipeline/RationalePipeline.md @@ -175,7 +175,7 @@ foo.bar %out_s4 : i32 **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 +to have _non-stallable stages_ (NS). NS stages are used wherever a pipeline access resources that are not able to stop on a dime, and thus require a fixed amount of cycles to complete. diff --git a/docs/Dialects/SV/RationaleSV.md b/docs/Dialects/SV/RationaleSV.md index 40e56ab64fe1..9e5dbfc2828e 100644 --- a/docs/Dialects/SV/RationaleSV.md +++ b/docs/Dialects/SV/RationaleSV.md @@ -145,7 +145,7 @@ 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 +- Symbol referring to a `hw.hierpath`: the separation string for joining names in the path. Defaults to ".". Example: diff --git a/docs/Dialects/Seq/RationaleSeq.md b/docs/Dialects/Seq/RationaleSeq.md index defea0ee29be..5f7d629f71a7 100644 --- a/docs/Dialects/Seq/RationaleSeq.md +++ b/docs/Dialects/Seq/RationaleSeq.md @@ -283,7 +283,7 @@ the structural details of a port into separate ops of which we currently only provide rudimentary read- and write ops. Example future ports could be: -* **Assymetric port widths** +* **Asymmetric port widths** Specified as a new `seq.asym_read` port which defines a read data width of some fraction of the native data size. ```mlir diff --git a/docs/Dialects/Sim/RationaleSim.md b/docs/Dialects/Sim/RationaleSim.md new file mode 100644 index 000000000000..0c7ce4eaa834 --- /dev/null +++ b/docs/Dialects/Sim/RationaleSim.md @@ -0,0 +1,22 @@ +# Simulation (Sim) Dialect Rationale + +This document describes various design points of the `sim` 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 `sim` dialect provides a high-level representation for simulator-specific +operations. The purpose of the dialect is to provide a high-level representation +for constructs which interact with simulators (Verilator, VCS, Arc, ...) that +are easy to analyze and transform in the compiler. + +## Operations + +### Plusargs + +The `sim.plusarg_test` and `sim.plusarg_value` operations are wrappers around +the SystemVerilog built-ins which access command-line arguments. +They are cleaner from a data-flow perspective, as they package the wires +and if-statements involved into compact operations that can be trivially +handled by analyses. diff --git a/docs/Dialects/Sim/_index.md b/docs/Dialects/Sim/_index.md new file mode 100644 index 000000000000..c8ee1675f2e2 --- /dev/null +++ b/docs/Dialects/Sim/_index.md @@ -0,0 +1 @@ +# 'sim' Dialect diff --git a/docs/PyCDE/basics.md b/docs/PyCDE/basics.md index 29bfdf4de4de..60ab01d5f873 100644 --- a/docs/PyCDE/basics.md +++ b/docs/PyCDE/basics.md @@ -71,7 +71,7 @@ Since CIRCT primarily targets hardware not software, it defines its own types. PyCDE exposes them through the `pycde.types.Type` class hierarchy. PyCDE signals represent values on the target device. Signals have a particular `Type` (which is distinct from the signal objects' Python `type`) stored in their -`type` instance member. `Type`s are heretofor referred to interchangably as +`type` instance member. `Type`s are heretofor referred to interchangeably as "PyCDE type", "CIRCT type", or "Type". All signals extend the `pycde.signals.Signal` class, specialized by their Type. diff --git a/frontends/PyCDE/.gitignore b/frontends/PyCDE/.gitignore new file mode 100644 index 000000000000..1be6e0334fa5 --- /dev/null +++ b/frontends/PyCDE/.gitignore @@ -0,0 +1,5 @@ + +# /src/circt created via +# `ln -s /tools/circt/python_packages/pycde/pycde/circt frontends/PyCDE/src/circt` +# to help PyCDE devs with code completion. +/src/circt diff --git a/frontends/PyCDE/integration_test/esi_ram.py b/frontends/PyCDE/integration_test/esi_ram.py index 33b1bd05e9d5..9b7414be92a6 100644 --- a/frontends/PyCDE/integration_test/esi_ram.py +++ b/frontends/PyCDE/integration_test/esi_ram.py @@ -2,29 +2,40 @@ # RUN: rm -rf %t # RUN: mkdir %t && cd %t # RUN: %PYTHON% %s %t 2>&1 -# RUN: esi-cosim-runner.py --tmpdir %t --exec %S/test_software/esi_ram.py `ls %t/hw/*.sv | grep -v driver.sv` +# RUN: esi-cosim.py -- %PYTHON% %S/test_software/esi_ram.py cosim env import pycde from pycde import (AppID, Clock, Input, Module, generator) from pycde.esi import DeclareRandomAccessMemory, ServiceDecl -from pycde.bsp import cosim +from pycde.bsp import cosim, xrt +from pycde.module import Metadata from pycde.types import Bits import sys -RamI64x8 = DeclareRandomAccessMemory(Bits(64), 8) -WriteType = RamI64x8.write.type.write +RamI64x8 = DeclareRandomAccessMemory(Bits(64), 256) +WriteType = RamI64x8.write.type.req @ServiceDecl class MemComms: - write = ServiceDecl.From(RamI64x8.write.type) - read = ServiceDecl.From(RamI64x8.read.type) + write = RamI64x8.write.type.inverted() + read = RamI64x8.read.type.inverted() + + +class Dummy(Module): + """To test completely automated metadata collection.""" + + @generator + def construct(ports): + pass class MemWriter(Module): """Write to address 3 the contents of address 2.""" + metadata = Metadata(version="0.1", misc={"numWriters": 1, "style": "stupid"}) + clk = Clock() rst = Input(Bits(1)) @@ -33,44 +44,60 @@ def construct(ports): read_bundle_type = RamI64x8.read.type address = 2 (address_chan, ready) = read_bundle_type.address.wrap(address, True) - read_bundle, [data_chan] = read_bundle_type.pack(address=address_chan) + read_bundle = RamI64x8.read(AppID("int_reader")) + bundled_channels = read_bundle.unpack(address=address_chan) + data_chan = bundled_channels["data"] read_data, read_valid = data_chan.unwrap(True) - RamI64x8.read(read_bundle, AppID("int_reader")) write_bundle_type = RamI64x8.write.type - write_data, _ = write_bundle_type.write.wrap( - { - 'data': read_data, - 'address': 3 - }, read_valid) - write_bundle, [ack] = write_bundle_type.pack(write=write_data) - RamI64x8.write(write_bundle, appid=AppID("int_writer")) + write_data, _ = write_bundle_type.req.wrap({ + 'data': read_data, + 'address': 3 + }, read_valid) + write_bundle = RamI64x8.write(appid=AppID("int_writer")) + write_bundle.unpack(req=write_data) -class Top(Module): - clk = Clock() - rst = Input(Bits(1)) +def Top(xrt: bool): - @generator - def construct(ports): - MemWriter(clk=ports.clk, rst=ports.rst) + class Top(Module): + clk = Clock() + rst = Input(Bits(1)) + + @generator + def construct(ports): + Dummy(appid=AppID("dummy")) + MemWriter(clk=ports.clk, rst=ports.rst, appid=AppID("mem_writer")) + + # We don't have support for host--device channel communication on XRT yet. + if not xrt: + # Pass through reads and writes from the host. + ram_write_host = MemComms.write(AppID("write")) + ram_write = RamI64x8.write(AppID("ram_write")) + ram_write.connect(ram_write_host) + + ram_read_host = MemComms.read(AppID("read")) + ram_read = RamI64x8.read(AppID("ram_read")) + ram_read.connect(ram_read_host) - # Pass through reads and writes from the host. - ram_write = MemComms.write(AppID("write")) - RamI64x8.write(ram_write, AppID("ram_write")) - ram_read = MemComms.read(AppID("read")) - RamI64x8.read(ram_read, AppID("ram_read")) + # Instantiate the RAM. + RamI64x8.instantiate_builtin(appid=AppID("mem"), + builtin="sv_mem", + result_types=[], + inputs=[ports.clk, ports.rst]) - # Instantiate the RAM. - RamI64x8.instantiate_builtin(appid=AppID("mem"), - builtin="sv_mem", - result_types=[], - inputs=[ports.clk, ports.rst]) + return Top if __name__ == "__main__": - s = pycde.System([cosim.CosimBSP(Top)], + is_xrt = len(sys.argv) > 2 and sys.argv[2] == "xrt" + if is_xrt: + bsp = xrt.XrtBSP + else: + bsp = cosim.CosimBSP + s = pycde.System(bsp(Top(is_xrt)), name="ESIMem", output_directory=sys.argv[1]) + s.generate() 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 deleted file mode 100644 index 1ff10000bc78..000000000000 --- a/frontends/PyCDE/integration_test/esi_ram_cpp/CMakeLists.txt +++ /dev/null @@ -1,102 +0,0 @@ -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 deleted file mode 100644 index a7573152defe..000000000000 --- a/frontends/PyCDE/integration_test/esi_ram_cpp/esi_ram.cpp +++ /dev/null @@ -1,138 +0,0 @@ -// 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 deleted file mode 100644 index 81261555b424..000000000000 --- a/frontends/PyCDE/integration_test/esi_ram_cpp/lit.local.cfg +++ /dev/null @@ -1 +0,0 @@ -config.suffixes.add('.cpp') diff --git a/frontends/PyCDE/integration_test/esi_test.py b/frontends/PyCDE/integration_test/esi_test.py index 916a7ceed768..7f771fa0cc94 100644 --- a/frontends/PyCDE/integration_test/esi_test.py +++ b/frontends/PyCDE/integration_test/esi_test.py @@ -2,42 +2,33 @@ # RUN: rm -rf %t # RUN: mkdir %t && cd %t # RUN: %PYTHON% %s %t 2>&1 -# RUN: esi-cosim-runner.py --tmpdir %t --exec %S/test_software/esi_test.py `ls %t/hw/*.sv | grep -v driver.sv` +# RUN: esi-cosim.py -- %PYTHON% %S/test_software/esi_test.py cosim env import pycde from pycde import (AppID, Clock, Input, Module, generator) from pycde.bsp import cosim from pycde.constructs import Wire -from pycde.esi import ServiceDecl +from pycde.esi import FuncService from pycde.types import (Bits, Bundle, BundledChannel, Channel, ChannelDirection, UInt) import sys -TestBundle = Bundle([ - BundledChannel("resp", ChannelDirection.TO, Bits(16)), - BundledChannel("req", ChannelDirection.FROM, Bits(24)) -]) - - -@ServiceDecl -class HostComms: - req_resp = TestBundle - class LoopbackInOutAdd7(Module): """Loopback the request from the host, adding 7 to the first 15 bits.""" @generator def construct(ports): - loopback = Wire(Channel(Bits(16))) - call_bundle, [from_host] = TestBundle.pack(resp=loopback) - HostComms.req_resp(call_bundle, AppID("loopback_inout")) + loopback = Wire(Channel(UInt(16))) + args = FuncService.get_call_chans(AppID("loopback_add7"), + arg_type=UInt(24), + result=loopback) ready = Wire(Bits(1)) - data, valid = from_host.unwrap(ready) - plus7 = data.as_uint(15) + UInt(8)(7) - data_chan, data_ready = loopback.type.wrap(plus7.as_bits(), valid) + data, valid = args.unwrap(ready) + plus7 = data + 7 + data_chan, data_ready = loopback.type.wrap(plus7.as_uint(16), valid) ready.assign(data_ready) loopback.assign(data_chan) diff --git a/frontends/PyCDE/integration_test/ibis_demo.py b/frontends/PyCDE/integration_test/ibis_demo.py new file mode 100644 index 000000000000..3b66e62af5f0 --- /dev/null +++ b/frontends/PyCDE/integration_test/ibis_demo.py @@ -0,0 +1,75 @@ +# REQUIRES: esi-runtime, esi-cosim, rtl-sim, questa, ibis +# RUN: rm -rf %t +# RUN: mkdir %t && cd %t +# RUN: %PYTHON% %s %t 2>&1 + +# Ibis does not currently simulate with Verilator. +# RUN: esi-cosim.py --sim questa -- %PYTHON% %S/test_software/ibis_foo.py cosim env + +from symbol import func_type +import pycde +from pycde import Input, Module, generator, esi +from pycde.module import Metadata +from pycde.common import Clock +from pycde.bsp import cosim +from pycde.ibis import IbisClass, method +from pycde.types import Array, Bits, UInt + +from pathlib import Path +import sys + +__dirname__ = Path(__file__).parent + + +class DemoTop(IbisClass): + # ibis -t=sv --circt --write-circt-ir --no-inspection \ + # --no-control-inspection --no-wrapper --no-debug-view \ + # --base-library $IBIS_LIB/base.pd --import-dir $IBIS_LIB/ + # ibistool --lo --ir ibis_esi_demoDemoTop.mlir > ibis_esi_demoDemoTop.lo.mlir + src_file = "ibis_esi_demoDemoTop.lo.mlir" + support_files = "support_files.f" + + metadata = Metadata(version="0.1", + summary="A demonstration of ESI and Ibis", + misc={ + "crcWidth": 64, + "style": "stupid" + }) + + @method + def add(self, a: UInt(8), b: UInt(8), arr: Array(UInt(8), 16)) -> UInt(8): + pass + + @method + def compute_crc( + self, identifier: UInt(8), input: Array(UInt(8), 64), + input_bytes: UInt(8), reset: UInt(8) + ) -> UInt(32): + pass + + +class IbisTestSystem(Module): + clk = Clock() + rst = Input(Bits(1)) + + @generator + def build(ports): + add = esi.FuncService.get(esi.AppID("add"), func_type=DemoTop.add.func_type) + crc = esi.FuncService.get(esi.AppID("crc"), + func_type=DemoTop.compute_crc.func_type) + DemoTop(clk=ports.clk, + rst=ports.rst, + appid=esi.AppID("demo"), + add=add, + compute_crc=crc) + + +if __name__ == "__main__": + + s = pycde.System(cosim.CosimBSP(IbisTestSystem), + name="IbisTest", + output_directory=sys.argv[1]) + s.generate() + s.print(file=open("ibis_test.mlir", "w")) + s.compile() + s.package() diff --git a/frontends/PyCDE/integration_test/lit.cfg.py b/frontends/PyCDE/integration_test/lit.cfg.py index 903f77060508..12fc346e5ace 100644 --- a/frontends/PyCDE/integration_test/lit.cfg.py +++ b/frontends/PyCDE/integration_test/lit.cfg.py @@ -42,6 +42,11 @@ llvm_config.use_default_substitutions() +llvm_config.with_environment('LIBRARY_PATH', [config.llvm_lib_dir], + append_path=True) +llvm_config.with_environment('LD_LIBRARY_PATH', [config.llvm_lib_dir], + append_path=True) + # Set the timeout, if requested. if config.timeout is not None and config.timeout != "": lit_config.maxIndividualTestTime = int(config.timeout) @@ -151,6 +156,8 @@ llvm_config.with_environment('PYTHONPATH', [f"{config.esi_runtime_path}/python/"], append_path=True) + tools.append("esi-cosim.py") + tool_dirs.append(f"{config.esi_runtime_path}/cosim") # Enable ESI cosim tests if they have been built. if config.esi_cosim_path != "": diff --git a/frontends/PyCDE/integration_test/test_software/esi_ram.py b/frontends/PyCDE/integration_test/test_software/esi_ram.py index d6011a4138b2..b0a0840bc80f 100644 --- a/frontends/PyCDE/integration_test/test_software/esi_ram.py +++ b/frontends/PyCDE/integration_test/test_software/esi_ram.py @@ -1,4 +1,4 @@ -from typing import List +from typing import Optional import esi import random import sys @@ -6,30 +6,42 @@ platform = sys.argv[1] acc = esi.AcceleratorConnection(platform, sys.argv[2]) -m = acc.manifest() -d = m.build_accelerator(acc) +d = acc.build_accelerator() -mem_write = d.ports[esi.AppID("write")].channels["write"] +mem_write = d.ports[esi.AppID("write")].write_port("req") mem_write.connect() -mem_read_addr = d.ports[esi.AppID("read")].channels["address"] +mem_read_addr = d.ports[esi.AppID("read")].write_port("address") mem_read_addr.connect() -mem_read_data = d.ports[esi.AppID("read")].channels["data"] +mem_read_data = d.ports[esi.AppID("read")].read_port("data") mem_read_data.connect() +# Baseline +m = acc.manifest() +if (platform == "cosim"): + # MMIO method + acc.cpp_accel.set_manifest_method(esi.esiCppAccel.ManifestMMIO) + m_alt = acc.manifest() + assert len(m.type_table) == len(m_alt.type_table) + +info = m.module_infos +assert len(info) == 3 +assert info[1].name == "Dummy" + -def read(addr: int) -> List[int]: +def read(addr: int) -> bytearray: mem_read_addr.write([addr]) - resp: List[int] = [] - while resp == []: - resp = mem_read_data.read(8) + got_data = False + resp: Optional[bytearray] = None + while not got_data: + (got_data, resp) = mem_read_data.read() print(f"resp: {resp}") return resp # The contents of address 3 are continuously updated to the contents of address # 2 by the accelerator. -data = [random.randint(0, 2**8 - 1) for _ in range(8)] -mem_write.write(data + [2]) +data = bytearray([random.randint(0, 2**8 - 1) for _ in range(8)]) +mem_write.write({"address": [2], "data": data}) resp = read(2) assert resp == data resp = read(3) @@ -37,7 +49,7 @@ def read(addr: int) -> List[int]: # Check this by writing to address 3 and reading from it. Shouldn't have # changed. -zeros = [0] * 8 -mem_write.write(zeros + [3]) +zeros = bytearray([0] * 8) +mem_write.write({"address": [3], "data": zeros}) resp = read(3) assert resp == data diff --git a/frontends/PyCDE/integration_test/test_software/esi_test.py b/frontends/PyCDE/integration_test/test_software/esi_test.py index 4a8df1a2e47c..57a537cf79e7 100644 --- a/frontends/PyCDE/integration_test/test_software/esi_test.py +++ b/frontends/PyCDE/integration_test/test_software/esi_test.py @@ -1,5 +1,7 @@ import esi + import sys +from typing import Optional platform = sys.argv[1] acc = esi.AcceleratorConnection(platform, sys.argv[2]) @@ -9,24 +11,24 @@ assert m.api_version == 1 print(m.type_table) -d = m.build_accelerator(acc) +d = acc.build_accelerator() -recv = d.ports[esi.AppID("loopback_inout")].channels["resp"] +recv = d.ports[esi.AppID("loopback_add7")].read_port("result") recv.connect() -send = d.ports[esi.AppID("loopback_inout")].channels["req"] +send = d.ports[esi.AppID("loopback_add7")].write_port("arg") send.connect() -data = [24, 42, 36] +data = 10234 send.write(data) -resp = [] +got_data = False +resp: Optional[int] = None # Reads are non-blocking, so we need to poll. -while resp == []: - resp = recv.read(2) +while not got_data: + (got_data, resp) = recv.read() print(f"data: {data}") print(f"resp: {resp}") -assert resp[0] == data[0] + 7 -assert resp[1] == data[1] +assert resp == data + 7 print("PASS") diff --git a/frontends/PyCDE/integration_test/test_software/ibis_demo.py b/frontends/PyCDE/integration_test/test_software/ibis_demo.py new file mode 100644 index 000000000000..fd044725ade6 --- /dev/null +++ b/frontends/PyCDE/integration_test/test_software/ibis_demo.py @@ -0,0 +1,54 @@ +import esi +from esi.types import FunctionPort + +import random +import sys +from typing import List, cast + +platform = sys.argv[1] +acc_conn = esi.AcceleratorConnection(platform, sys.argv[2]) +acc = acc_conn.build_accelerator() + +print("***** Testing add function") + +add = cast(FunctionPort, acc.ports[esi.AppID("add")]) +add.connect() + + +def add_golden(a: int, b: int, arr: List[int]) -> int: + return (a + b + sum(arr)) % 2**8 + + +for _ in range(10): + a = random.randint(0, 2**8 - 1) + b = random.randint(0, 2**8 - 1) + arr = [random.randint(0, 2**8 - 1) for _ in range(16)] + + expected = add_golden(a=a, b=b, arr=arr) + print(f"call(a={a}, b={b}, arr={arr})") + + resp = add(a=a, b=b, arr=arr).result() + if resp != expected: + print(f" = {resp} (expected {expected})") + else: + print(f" = {resp} (matches Python result)") + +print() +input("Press Enter to continue...") +print() +print() +print("***** Testing compute_crc function") + +compute_crc = cast(FunctionPort, acc.ports[esi.AppID("crc")]) +compute_crc.connect() + +data = [random.randint(0, 2**8 - 1) for _ in range(64)] +crc = compute_crc(identifier=0, input=data, input_bytes=64, reset=1).result() +print(f"crc({data})") +print(f" = 0x{crc:x}") + +new_data = [random.randint(0, 2**8 - 1) for _ in range(64)] +crc = compute_crc(identifier=0, input=new_data, input_bytes=64, + reset=0).result() +print(f"crc({new_data})") +print(f" = 0x{crc:x}") diff --git a/frontends/PyCDE/src/CMakeLists.txt b/frontends/PyCDE/src/CMakeLists.txt index 830a5d672c14..790d1b78ec39 100644 --- a/frontends/PyCDE/src/CMakeLists.txt +++ b/frontends/PyCDE/src/CMakeLists.txt @@ -8,10 +8,6 @@ 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 @@ -39,20 +35,16 @@ declare_mlir_python_sources(PyCDESources signals.py ndarray.py esi.py - esi_api.py - esi_runtime_common.py fsm.py + ibis.py testing.py - esi_api.py.j2 - Makefile.cosim - bsp/__init__.py + bsp/common.py bsp/cosim.py bsp/xrt.py - bsp/EsiXrtPython.cpp - bsp/Makefile.xrt.j2 - bsp/xrt_package.tcl.j2 + bsp/Makefile.xrt.mk + bsp/xrt_package.tcl bsp/xrt_api.py bsp/xrt.ini bsp/xsim.tcl @@ -125,32 +117,3 @@ foreach(CFile IN LISTS CollateralFiles) COMPONENT PyCDE ) endforeach() - -install(TARGETS circt-std-sim-drivers - PUBLIC_HEADER DESTINATION python_packages/pycde/collateral - COMPONENT PyCDE -) -if(ESI_COSIM) - add_dependencies(PyCDE EsiCosimDpiServer) - set_property(TARGET EsiCosimDpiServer PROPERTY INSTALL_RPATH "$ORIGIN") - install(TARGETS EsiCosimDpiServer - RUNTIME_DEPENDENCY_SET EsiCosimDpiServer_RUNTIME_DEPS - DESTINATION python_packages/pycde/collateral - COMPONENT PyCDE - ) - install(RUNTIME_DEPENDENCY_SET EsiCosimDpiServer_RUNTIME_DEPS - DESTINATION python_packages/pycde/collateral - PRE_EXCLUDE_REGEXES .* - PRE_INCLUDE_REGEXES capnp kj - COMPONENT PyCDE - ) - install(TARGETS MtiPli - 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/Makefile.cosim b/frontends/PyCDE/src/Makefile.cosim deleted file mode 100644 index e26c7a08dafe..000000000000 --- a/frontends/PyCDE/src/Makefile.cosim +++ /dev/null @@ -1,23 +0,0 @@ -mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) -mkfile_dir := $(dir $(mkfile_path)) - -VSIM_PATH := $(which vsim) - -ifneq ($(VSIM_PATH),) - QUESTA_PATH := $(basename $(VSIM_PATH)) -endif - -ifneq ($(QUESTA_PATH),) -run_questa: - $(QUESTA_PATH)/vlog hw/*.sv - $(QUESTA_PATH)/vsim driver -c -sv_lib hw/libEsiCosimDpiServer -do "run -all; quit" -endif - -VERILATOR_PATH := $(which verilator) -ifneq ($(VERILATOR_PATH),) -SV_SRCS = $(shell ls hw/*.sv | grep -v driver.sv) -VERILATOR_SRCS = $(SV_SRCS) $(mkfile_dir)/hw/*.so hw/*.cpp -run_verilator: - $(VERILATOR_PATH) --cc --top-module top -sv --build --exe --assert $(VERILATOR_SRCS) - LD_LIBRARY_PATH=hw obj_dir/Vtop -endif diff --git a/frontends/PyCDE/src/bsp/EsiXrtPython.cpp b/frontends/PyCDE/src/bsp/EsiXrtPython.cpp deleted file mode 100644 index 1fad82a04ad6..000000000000 --- a/frontends/PyCDE/src/bsp/EsiXrtPython.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include -#include -#include - -// pybind11 includes -#include "pybind11/pybind11.h" -#include "pybind11/stl.h" -namespace py = pybind11; - -// XRT includes -#include "experimental/xrt_bo.h" -#include "experimental/xrt_device.h" -#include "experimental/xrt_ip.h" -#include "experimental/xrt_xclbin.h" - -// We don't want to clutter up the symbol space any more than necessary, so use -// an anonymous namespace. -namespace { - -uint32_t MagicNumOffset = 16; -uint32_t MagicNumberLo = 0xE5100E51; -uint32_t MagicNumberHi = 0x207D98E5; -uint32_t ExpectedVersionNumber = 0; - -class Accelerator { - xrt::device m_device; - xrt::ip m_ip; - -public: - Accelerator(const std::string &xclbin_path, const std::string kernel_name) { - m_device = xrt::device(0); - auto uuid = m_device.load_xclbin(xclbin_path); - m_ip = xrt::ip(m_device, uuid, kernel_name); - - // Check that this is actually an ESI system. - uint32_t magicLo = m_ip.read_register(MagicNumOffset); - uint32_t magicHi = m_ip.read_register(MagicNumOffset + 4); - if (magicLo != MagicNumberLo || magicHi != MagicNumberHi) - throw std::runtime_error("Accelerator is not an ESI system"); - - // Check version is one we understand. - if (version() != ExpectedVersionNumber) - std::cerr - << "[ESI] Warning: accelerator ESI version may not be compatible\n"; - } - - uint32_t version() { return m_ip.read_register(MagicNumOffset + 8); } -}; - -} // namespace - -PYBIND11_MODULE(esiXrtPython, m) { - py::class_(m, "Accelerator") - .def(py::init()) - .def("version", &Accelerator::version); -} diff --git a/frontends/PyCDE/src/bsp/Makefile.xrt.j2 b/frontends/PyCDE/src/bsp/Makefile.xrt.mk similarity index 84% rename from frontends/PyCDE/src/bsp/Makefile.xrt.j2 rename to frontends/PyCDE/src/bsp/Makefile.xrt.mk index c35f76b9149f..f44902710c45 100644 --- a/frontends/PyCDE/src/bsp/Makefile.xrt.j2 +++ b/frontends/PyCDE/src/bsp/Makefile.xrt.mk @@ -11,7 +11,7 @@ VPP := $(XILINX_VITIS)/bin/v++ # Note, the Azure shell verison is not officially supported in hw_emu mode TARGET := hw_emu -NAME := {{system_name}} +NAME := esi_image SRC := hw BUILD := build_$(TARGET) TEMP := $(BUILD)/temp @@ -26,10 +26,6 @@ HOST_APP := $(BUILD)/host_app VPPFLAGS = --save-temps -# Used for compiling C++ apps for XRT -CXXFLAGS = -I$(XILINX_XRT)/include -I$(XILINX_VIVADO)/include -Wall -g -O0 -std=c++2a -fmessage-length=0 -LDFLAGS = -L$(XILINX_XRT)/lib -pthread -lxrt_coreutil - # Platform must match the device + shell you're using # For Azure NP-series, use the official Azure Shell # For a local card or hw_emu mode, use the latest U250 XDMA Shell @@ -53,9 +49,8 @@ device2xsa = $(strip $(patsubst %.xpfm, % , $(shell basename $(PLATFORM)))) XSA := $(call device2xsa, $(PLATFORM)) .PHONY: clean emconfig exec -.INTERMEDIATE: azure_creds -all: esiXrtPython $(XCL_OUT) emconfig +all: $(XCL_OUT) emconfig $(BUILD): mkdir -p $(BUILD) @@ -80,16 +75,8 @@ emconfig: $(BUILD)/emconfig.json $(BUILD)/emconfig.json: emconfigutil --platform $(PLATFORM) --od $(BUILD) -# Compile the Python interface driver. -# TODO: build for a list of python versions. -PY_EXT := $(shell $(PYTHON)-config --extension-suffix) -PYBIND11_INC := $(shell $(PYTHON) -m pybind11 --includes) -runtime/$(NAME)/esiXrtPython$(PY_EXT): runtime/$(NAME)/EsiXrtPython.cpp - $(CXX) -o runtime/$(NAME)/esiXrtPython$(PY_EXT) $^ $(CXXFLAGS) $(LDFLAGS) -shared -fPIC $(PYBIND11_INC) -esiXrtPython: runtime/$(NAME)/esiXrtPython$(PY_EXT) - clean: - rm -rf $(BUILD) .Xil vivado* kernel *.jou *.log *.wdb *.wcfg *.protoinst *.csv + rm -rf $(BUILD) temp_kernel .Xil vivado* kernel *.jou *.log *.wdb *.wcfg *.protoinst *.csv rm -f runtime/*.so # Targets which only apply to image builds. @@ -99,7 +86,7 @@ ifeq ($(TARGET), hw) # https://learn.microsoft.com/en-us/azure/virtual-machines/field-programmable-gate-arrays-attestation IMAGE_AZ_BASENAME ?= $(NAME)_$(shell date +%s).hw IMAGE_AZ_NAME := $(USER)_$(IMAGE_AZ_BASENAME) -azure: $(IMAGE_AZ_NAME).azure.xclbin +azure: azure_creds $(IMAGE_AZ_NAME).azure.xclbin azure_creds: @echo "*************************" @echo "* Getting Azure credentials. MUST 'az login' first!" @@ -135,7 +122,7 @@ $(IMAGE_AZ_NAME).azure.xclbin: azure_creds $(XCL_OUT) validate-fpgaimage.sh bash validate-fpgaimage.sh --storage-account $(AZ_FPGA_STORAGE_ACCOUNT) \ --container $(AZ_FPGA_STORAGE_CONTAINER) \ - --netlist-name $(IMAGE_AZ_NAME) \ + --netlist-name $(IMAGE_AZ_NAME).xclbin \ --blob-container-sas "$(SAS)" az storage blob download \ @@ -149,6 +136,7 @@ validate-fpgaimage.sh: wget -O azure_validate.zip \ https://fpgaattestation.blob.core.windows.net/validationscripts/validate.zip unzip azure_validate.zip + mv scripts/validate-fpgaimage.sh . azpackage: $(NAME)_azpackage.tar.gz $(NAME)_azpackage.tar.gz: $(IMAGE_AZ_NAME).azure.xclbin diff --git a/frontends/PyCDE/src/bsp/common.py b/frontends/PyCDE/src/bsp/common.py new file mode 100644 index 000000000000..c12fbd7a6d9c --- /dev/null +++ b/frontends/PyCDE/src/bsp/common.py @@ -0,0 +1,214 @@ +# 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, Output +from ..constructs import ControlReg, Mux, NamedWire, Wire +from .. import esi +from ..module import Module, generator +from ..signals import BundleSignal +from ..types import Array, Bits, ChannelDirection + +from typing import Dict, Tuple + +MagicNumberLo = 0xE5100E51 # ESI__ESI +MagicNumberHi = 0x207D98E5 # Random +VersionNumber = 0 # Version 0: format subject to change + + +class ESI_Manifest_ROM(Module): + """Module which will be created later by CIRCT which will contain the + compressed manifest.""" + + module_name = "__ESI_Manifest_ROM" + + clk = Clock() + address = Input(Bits(30)) + # Data is two cycles delayed after address changes. + data = Output(Bits(32)) + + +class AxiMMIO(esi.ServiceImplementation): + """MMIO service implementation with an AXI-lite protocol. This assumes a 20 + bit address bus for 1MB of addressable MMIO space. Which should be fine for + now, though nothing should assume this limit. It also only supports 32-bit + aligned accesses and just throws awary the lower two bits of address. + + Only allows for one outstanding request at a time. If a client doesn't return + a response, the MMIO service will hang. TODO: add some kind of timeout. + + Implementation-defined MMIO layout: + - 0x0: 0 constanst + - 0x4: 0 constanst + - 0x8: Magic number low (0xE5100E51) + - 0xC: Magic number high (random constant: 0x207D98E5) + - 0x10: ESI version number (0) + - 0x14: Location of the manifest ROM (absolute address) + + - 0x100: Start of MMIO space for requests. Mapping is contained in the + manifest so can be dynamically queried. + + - addr(Manifest ROM) + 0: Size of compressed manifest + - addr(Manifest ROM) + 4: Start of compressed manifest + + This layout _should_ be pretty standard, but different BSPs may have various + different restrictions. + """ + + clk = Clock() + rst = Input(Bits(1)) + + # MMIO read: address channel. + arvalid = Input(Bits(1)) + arready = Output(Bits(1)) + araddr = Input(Bits(20)) + + # MMIO read: data response channel. + rvalid = Output(Bits(1)) + rready = Input(Bits(1)) + rdata = Output(Bits(32)) + rresp = Output(Bits(2)) + + # MMIO write: address channel. + awvalid = Input(Bits(1)) + awready = Output(Bits(1)) + awaddr = Input(Bits(20)) + + # MMIO write: data channel. + wvalid = Input(Bits(1)) + wready = Output(Bits(1)) + wdata = Input(Bits(32)) + + # MMIO write: write response channel. + bvalid = Output(Bits(1)) + bready = Input(Bits(1)) + bresp = Output(Bits(2)) + + # Start at this address for assigning MMIO addresses to service requests. + initial_offset: int = 0x100 + + @generator + def generate(self, bundles: esi._ServiceGeneratorBundles): + read_table, write_table, manifest_loc = AxiMMIO.build_table(self, bundles) + AxiMMIO.build_read(self, manifest_loc, read_table) + AxiMMIO.build_write(self, write_table) + return True + + def build_table( + self, + bundles) -> Tuple[Dict[int, BundleSignal], Dict[int, BundleSignal], int]: + """Build a table of read and write addresses to BundleSignals.""" + offset = AxiMMIO.initial_offset + read_table = {} + write_table = {} + for bundle in bundles.to_client_reqs: + if bundle.direction == ChannelDirection.Input: + read_table[offset] = bundle + offset += 4 + elif bundle.direction == ChannelDirection.Output: + write_table[offset] = bundle + offset += 4 + + manifest_loc = 1 << offset.bit_length() + return read_table, write_table, manifest_loc + + def build_read(self, manifest_loc: int, bundles): + """Builds the read side of the MMIO service.""" + + # Currently just exposes the header and manifest. Not any of the possible + # service requests. + + i32 = Bits(32) + i2 = Bits(2) + i1 = Bits(1) + + address_written = NamedWire(i1, "address_written") + response_written = NamedWire(i1, "response_written") + + # Only allow one outstanding request at a time. Don't clear it until the + # output has been transmitted. This way, we don't have to deal with + # backpressure. + req_outstanding = ControlReg(self.clk, + self.rst, [address_written], + [response_written], + name="req_outstanding") + self.arready = ~req_outstanding + + # Capture the address if a the bus transaction occured. + address_written.assign(self.arvalid & ~req_outstanding) + address = self.araddr.reg(self.clk, ce=address_written, name="address") + address_valid = address_written.reg(name="address_valid") + address_words = address[2:] # Lop off the lower two bits. + + # Set up the output of the data response pipeline. `data_pipeline*` are to + # be connected below. + data_pipeline_valid = NamedWire(i1, "data_pipeline_valid") + data_pipeline = NamedWire(i32, "data_pipeline") + data_pipeline_rresp = NamedWire(i2, "data_pipeline_rresp") + data_out_valid = ControlReg(self.clk, + self.rst, [data_pipeline_valid], + [response_written], + name="data_out_valid") + self.rvalid = data_out_valid + self.rdata = data_pipeline.reg(self.clk, + self.rst, + ce=data_pipeline_valid, + name="data_pipeline_reg") + self.rresp = data_pipeline_rresp.reg(self.clk, + self.rst, + ce=data_pipeline_valid, + name="data_pipeline_rresp_reg") + # Clear the `req_outstanding` flag when the response has been transmitted. + response_written.assign(data_out_valid & self.rready) + + # Handle reads from the header (< 0x100). + header_upper = address_words[AxiMMIO.initial_offset.bit_length() - 2:] + # Is the address in the header? + header_sel = (header_upper == header_upper.type(0)) + header_sel.name = "header_sel" + # Layout the header as an array. + header = Array(Bits(32), 6)( + [0, 0, MagicNumberLo, MagicNumberHi, VersionNumber, manifest_loc]) + header.name = "header" + header_response_valid = address_valid # Zero latency read. + header_out = header[address[2:5]] + header_out.name = "header_out" + header_rresp = i2(0) + + # Handle reads from the manifest. + rom_address = NamedWire( + (address_words.as_uint() - (manifest_loc >> 2)).as_bits(30), + "rom_address") + mani_rom = ESI_Manifest_ROM(clk=self.clk, address=rom_address) + mani_valid = address_valid.reg( + self.clk, + self.rst, + rst_value=i1(0), + cycles=2, # Two cycle read to match the ROM latency. + name="mani_valid_reg") + mani_rresp = i2(0) + # mani_sel = (address.as_uint() >= manifest_loc) + + # Mux the output depending on whether or not the address is in the header. + sel = NamedWire(~header_sel, "sel") + data_mux_inputs = [header_out, mani_rom.data] + data_pipeline.assign(Mux(sel, *data_mux_inputs)) + data_valid_mux_inputs = [header_response_valid, mani_valid] + data_pipeline_valid.assign(Mux(sel, *data_valid_mux_inputs)) + rresp_mux_inputs = [header_rresp, mani_rresp] + data_pipeline_rresp.assign(Mux(sel, *rresp_mux_inputs)) + + def build_write(self, bundles): + # TODO: this. + + # So that we don't wedge the AXI-lite for writes, just ack all of them. + write_happened = Wire(Bits(1)) + latched_aw = ControlReg(self.clk, self.rst, [self.awvalid], + [write_happened]) + latched_w = ControlReg(self.clk, self.rst, [self.wvalid], [write_happened]) + write_happened.assign(latched_aw & latched_w) + + self.awready = 1 + self.wready = 1 + self.bvalid = write_happened + self.bresp = 0 diff --git a/frontends/PyCDE/src/bsp/cosim.py b/frontends/PyCDE/src/bsp/cosim.py index 7a2840805bf6..75cd6cf198b6 100644 --- a/frontends/PyCDE/src/bsp/cosim.py +++ b/frontends/PyCDE/src/bsp/cosim.py @@ -2,12 +2,18 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -from ..common import AppID, Clock, Input +from typing import Dict, Tuple + +from ..signals import BundleSignal +from ..common import AppID, Clock, Input, Output from ..module import Module, generator from ..system import System -from ..types import types +from ..types import Bits, Bundle, BundledChannel, ChannelDirection +from ..constructs import ControlReg, NamedWire, Reg, Wire from .. import esi +from .common import AxiMMIO + from ..circt import ir from ..circt.dialects import esi as raw_esi @@ -16,16 +22,52 @@ __root_dir__ = Path(__file__).parent.parent -def CosimBSP(user_module): +class Cosim_MMIO(Module): + """External module backed by DPI calls into the coimulation driver. Provides + an AXI-lite interface. An emulator.""" + + clk = Clock() + rst = Input(Bits(1)) + + # MMIO read: address channel. + arvalid = Output(Bits(1)) + arready = Input(Bits(1)) + araddr = Output(Bits(32)) + + # MMIO read: data response channel. + rvalid = Input(Bits(1)) + rready = Output(Bits(1)) + rdata = Input(Bits(32)) + rresp = Input(Bits(2)) + + # MMIO write: address channel. + awvalid = Output(Bits(1)) + awready = Input(Bits(1)) + awaddr = Output(Bits(32)) + + # MMIO write: data channel. + wvalid = Output(Bits(1)) + wready = Input(Bits(1)) + wdata = Output(Bits(32)) + + # MMIO write: write response channel. + bvalid = Input(Bits(1)) + bready = Output(Bits(1)) + bresp = Input(Bits(2)) + + +def CosimBSP(user_module: Module) -> Module: """Wrap and return a cosimulation 'board support package' containing 'user_module'""" - class top(Module): + class ESI_Cosim_Top(Module): clk = Clock() - rst = Input(types.int(1)) + rst = Input(Bits(1)) @generator def build(ports): + System.current().platform = "cosim" + user_module(clk=ports.clk, rst=ports.rst) raw_esi.ServiceInstanceOp(result=[], appID=AppID("cosim")._appid, @@ -33,6 +75,30 @@ def build(ports): impl_type=ir.StringAttr.get("cosim"), inputs=[ports.clk.value, ports.rst.value]) - System.current().add_packaging_step(esi.package) + # Instantiate both the Cosim MMIO emulator and the AXI-lite MMIO service + # implementation and wire them together. The CosimMMIO emulator has a + # 32-bit address whereas the AXI-lite MMIO service implementation has a + # 20-bit address. Other than that, the ports are the same so use some + # PyCDE magic to wire them together. + cosim_mmio_wire_inputs = { + port.name: Wire(port.type) + for port in Cosim_MMIO.inputs() + if port.name != "clk" and port.name != "rst" + } + cosim_mmio = Cosim_MMIO(clk=ports.clk, + rst=ports.rst, + **cosim_mmio_wire_inputs) + + axi_mmio_inputs = cosim_mmio.outputs() + axi_mmio_inputs["araddr"] = axi_mmio_inputs["araddr"][0:20] + axi_mmio = AxiMMIO(esi.MMIO, + appid=AppID("mmio"), + clk=ports.clk, + rst=ports.rst, + **axi_mmio_inputs) + for pn, s in axi_mmio.outputs().items(): + if pn == "awaddr": + s = s.pad_or_truncate(32) + cosim_mmio_wire_inputs[pn].assign(s) - return top + return ESI_Cosim_Top diff --git a/frontends/PyCDE/src/bsp/xrt.py b/frontends/PyCDE/src/bsp/xrt.py index 0155c6d86dcf..5e311db9389f 100644 --- a/frontends/PyCDE/src/bsp/xrt.py +++ b/frontends/PyCDE/src/bsp/xrt.py @@ -3,70 +3,19 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception from ..common import Clock, Input, Output -from ..constructs import ControlReg, Wire from ..module import Module, generator from ..system import System -from ..types import bit, types, Bits +from ..types import Bits from .. import esi +from .common import AxiMMIO + import glob -from io import FileIO -import math import pathlib import shutil __dir__ = pathlib.Path(__file__).parent -# Parameters for AXI4-Lite interface -axil_addr_width = 32 -axil_data_width = 32 -axil_data_width_bytes = int(axil_data_width / 8) - -# Constants for MMIO registers -MagicNumberLo = 0xE5100E51 # ESI__ESI -MagicNumberHi = 0x207D98E5 # Random -VersionNumber = 0 # Version 0: format subject to change - - -# Signals from master -def axil_in_type(addr_width, data_width): - return types.struct({ - "awvalid": types.i1, - "awaddr": types.int(addr_width), - "wvalid": types.i1, - "wdata": types.int(data_width), - "wstrb": types.int(data_width // 8), - "arvalid": types.i1, - "araddr": types.int(addr_width), - "rready": types.i1, - "bready": types.i1 - }) - - -# Signals to master -def axil_out_type(data_width): - return types.struct({ - "awready": types.i1, - "wready": types.i1, - "arready": types.i1, - "rvalid": types.i1, - "rdata": types.int(data_width), - "rresp": types.i2, - "bvalid": types.i1, - "bresp": types.i2 - }) - - -def output_tcl(os: FileIO): - """Output Vitis tcl describing the registers.""" - - from jinja2 import Environment, FileSystemLoader, StrictUndefined - - env = Environment(loader=FileSystemLoader(str(__dir__)), - undefined=StrictUndefined) - template = env.get_template("xrt_package.tcl.j2") - os.write(template.render(system_name=System.current().name)) - def XrtBSP(user_module): """Use the Xilinx RunTime (XRT) shell to implement ESI services and build an @@ -74,7 +23,7 @@ def XrtBSP(user_module): How to use this BSP: - Wrap your top PyCDE module in `XrtBSP`. - Run your script. This BSP will write a 'build package' to the output dir. - This package contains a Makefile.xrt which (given a proper Vitis dev + This package contains a Makefile.xrt.mk which (given a proper Vitis dev environment) will compile a hw image or hw_emu image. It is a free-standing build package -- you do not need PyCDE installed on the same machine as you want to do the image build. @@ -85,145 +34,70 @@ def XrtBSP(user_module): This target requires a few environment variables to be set (which the Makefile will tell you about). - To build a hw emulation image, run with TARGET=hw_emu. - - The makefile also builds a Python plugin. To specify the python version to - build against (if different from the version ran by 'python3' in your - environment), set the PYTHON variable (e.g. 'PYTHON=python3.9'). + - Validated ONLY on Vitis 2023.1. Known to NOT work with Vitis <2022.1. """ - class XrtService(Module): - clk = Clock(types.i1) - rst = Input(types.i1) - - axil_in = Input(axil_in_type(axil_addr_width, axil_data_width)) - axil_out = Output(axil_out_type(axil_data_width)) - - @generator - def generate(self): - clk = self.clk - rst = self.rst - - sys: System = System.current() - output_tcl((sys.hw_output_dir / "xrt_package.tcl").open("w")) - - ###### - # Write side. - - # So that we don't wedge the AXI-lite for writes, just ack all of them. - write_happened = Wire(bit) - latched_aw = ControlReg(self.clk, self.rst, [self.axil_in.awvalid], - [write_happened]) - latched_w = ControlReg(self.clk, self.rst, [self.axil_in.wvalid], - [write_happened]) - write_happened.assign(latched_aw & latched_w) - - ###### - # Read side. - - # Track the non-zero registers in the read address space. - rd_addr_data = { - 16: Bits(32)(MagicNumberLo), - 20: Bits(32)(MagicNumberHi), - 24: Bits(32)(VersionNumber), - } - - # Create an array out of the sparse value map 'rd_addr_data' and zero - # constants. Then create a potentially giant mux. There's probably a much - # better way to do this. I suspect this is a common high-level construct - # which should be automatically optimized, but I'm not sure how common - # this actually is. - - max_addr_log2 = int( - math.ceil(math.log2(max([a for a in rd_addr_data.keys()]) + 1))) - - # Convert the sparse dict into a zero filled array. - zero = types.int(axil_data_width)(0) - rd_space = [zero] * int(math.pow(2, max_addr_log2)) - for (addr, val) in rd_addr_data.items(): - rd_space[addr] = val - - # Create the address index signal and do the muxing. - addr_slice = self.axil_in.araddr.slice( - types.int(axil_addr_width)(0), max_addr_log2) - rd_addr = addr_slice.reg(clk, rst) - rvalid = self.axil_in.arvalid.reg(clk, rst, cycles=2) - rdata = types.array(types.int(axil_data_width), - len(rd_space))(rd_space)[rd_addr].reg(clk) - - # Assign the module outputs. - self.axil_out = axil_out_type(axil_data_width)({ - "awready": 1, - "wready": 1, - "arready": 1, - "rvalid": rvalid, - "rdata": rdata, - "rresp": 0, - "bvalid": write_happened, - "bresp": 0 - }) - - class top(Module): + class XrtTop(Module): ap_clk = Clock() - ap_resetn = Input(types.i1) + ap_resetn = Input(Bits(1)) # AXI4-Lite slave interface - s_axi_control_AWVALID = Input(types.i1) - s_axi_control_AWREADY = Output(types.i1) - s_axi_control_AWADDR = Input(types.int(axil_addr_width)) - s_axi_control_WVALID = Input(types.i1) - s_axi_control_WREADY = Output(types.i1) - s_axi_control_WDATA = Input(types.int(axil_data_width)) - s_axi_control_WSTRB = Input(types.int(axil_data_width // 8)) - s_axi_control_ARVALID = Input(types.i1) - s_axi_control_ARREADY = Output(types.i1) - s_axi_control_ARADDR = Input(types.int(axil_addr_width)) - s_axi_control_RVALID = Output(types.i1) - s_axi_control_RREADY = Input(types.i1) - s_axi_control_RDATA = Output(types.int(axil_data_width)) - s_axi_control_RRESP = Output(types.i2) - s_axi_control_BVALID = Output(types.i1) - s_axi_control_BREADY = Input(types.i1) - s_axi_control_BRESP = Output(types.i2) + s_axi_control_AWVALID = Input(Bits(1)) + s_axi_control_AWREADY = Output(Bits(1)) + s_axi_control_AWADDR = Input(Bits(20)) + s_axi_control_WVALID = Input(Bits(1)) + s_axi_control_WREADY = Output(Bits(1)) + s_axi_control_WDATA = Input(Bits(32)) + s_axi_control_WSTRB = Input(Bits(32 // 8)) + s_axi_control_ARVALID = Input(Bits(1)) + s_axi_control_ARREADY = Output(Bits(1)) + s_axi_control_ARADDR = Input(Bits(20)) + s_axi_control_RVALID = Output(Bits(1)) + s_axi_control_RREADY = Input(Bits(1)) + s_axi_control_RDATA = Output(Bits(32)) + s_axi_control_RRESP = Output(Bits(2)) + s_axi_control_BVALID = Output(Bits(1)) + s_axi_control_BREADY = Input(Bits(1)) + s_axi_control_BRESP = Output(Bits(2)) @generator def construct(ports): - - axil_in_sig = axil_in_type(axil_addr_width, axil_data_width)({ - "awvalid": ports.s_axi_control_AWVALID, - "awaddr": ports.s_axi_control_AWADDR, - "wvalid": ports.s_axi_control_WVALID, - "wdata": ports.s_axi_control_WDATA, - "wstrb": ports.s_axi_control_WSTRB, - "arvalid": ports.s_axi_control_ARVALID, - "araddr": ports.s_axi_control_ARADDR, - "rready": ports.s_axi_control_RREADY, - "bready": ports.s_axi_control_BREADY, - }) + System.current().platform = "fpga" rst = ~ports.ap_resetn - xrt = XrtService(clk=ports.ap_clk, rst=rst, axil_in=axil_in_sig) - - axil_out = xrt.axil_out + xrt = AxiMMIO( + esi.MMIO, + appid=esi.AppID("xrt_mmio"), + clk=ports.ap_clk, + rst=rst, + awvalid=ports.s_axi_control_AWVALID, + awaddr=ports.s_axi_control_AWADDR, + wvalid=ports.s_axi_control_WVALID, + wdata=ports.s_axi_control_WDATA, + wstrb=ports.s_axi_control_WSTRB, + arvalid=ports.s_axi_control_ARVALID, + araddr=ports.s_axi_control_ARADDR, + rready=ports.s_axi_control_RREADY, + bready=ports.s_axi_control_BREADY, + ) # AXI-Lite control - ports.s_axi_control_AWREADY = axil_out['awready'] - ports.s_axi_control_WREADY = axil_out['wready'] - ports.s_axi_control_ARREADY = axil_out['arready'] - ports.s_axi_control_RVALID = axil_out['rvalid'] - ports.s_axi_control_RDATA = axil_out['rdata'] - ports.s_axi_control_RRESP = axil_out['rresp'] - ports.s_axi_control_BVALID = axil_out['bvalid'] - ports.s_axi_control_BRESP = axil_out['bresp'] - - # Splice in the user's code - # NOTE: the clock is `ports.ap_clk` - # and reset is `ports.ap_resetn` which is active low + ports.s_axi_control_AWREADY = xrt.awready + ports.s_axi_control_WREADY = xrt.wready + ports.s_axi_control_ARREADY = xrt.arready + ports.s_axi_control_RVALID = xrt.rvalid + ports.s_axi_control_RDATA = xrt.rdata + ports.s_axi_control_RRESP = xrt.rresp + ports.s_axi_control_BVALID = xrt.bvalid + ports.s_axi_control_BRESP = xrt.bresp + user_module(clk=ports.ap_clk, rst=rst) # Copy additional sources sys: System = System.current() sys.add_packaging_step(esi.package) - sys.add_packaging_step(top.package) + sys.add_packaging_step(XrtTop.package) @staticmethod def package(sys: System): @@ -231,21 +105,16 @@ def package(sys: System): collateral (about which we are aware), build/debug scripts, and the generated runtime.""" - from jinja2 import Environment, FileSystemLoader, StrictUndefined - sv_sources = glob.glob(str(__dir__ / '*.sv')) tcl_sources = glob.glob(str(__dir__ / '*.tcl')) for source in sv_sources + tcl_sources: shutil.copy(source, sys.hw_output_dir) - env = Environment(loader=FileSystemLoader(str(__dir__)), - undefined=StrictUndefined) - makefile_template = env.get_template("Makefile.xrt.j2") - dst_makefile = sys.output_directory / "Makefile.xrt" - dst_makefile.open("w").write( - makefile_template.render(system_name=sys.name)) - + shutil.copy(__dir__ / "Makefile.xrt.mk", + sys.output_directory / "Makefile.xrt.mk") + shutil.copy(__dir__ / "xrt_package.tcl", + sys.output_directory / "xrt_package.mk") shutil.copy(__dir__ / "xrt.ini", sys.output_directory / "xrt.ini") shutil.copy(__dir__ / "xsim.tcl", sys.output_directory / "xsim.tcl") - return top + return XrtTop diff --git a/frontends/PyCDE/src/bsp/xrt_package.tcl.j2 b/frontends/PyCDE/src/bsp/xrt_package.tcl similarity index 69% rename from frontends/PyCDE/src/bsp/xrt_package.tcl.j2 rename to frontends/PyCDE/src/bsp/xrt_package.tcl index e723c3d21025..f5564c1f4206 100644 --- a/frontends/PyCDE/src/bsp/xrt_package.tcl.j2 +++ b/frontends/PyCDE/src/bsp/xrt_package.tcl @@ -12,7 +12,7 @@ set target [lindex $::argv 2] set xpfm_path [lindex $::argv 3] set device [lindex $::argv 4] -set krnl_name {{system_name}} +set krnl_name esi_kernel set suffix "${krnl_name}_${target}_${device}" set project_path "./temp_kernel" @@ -25,13 +25,13 @@ create_project -force kernel $project_path add_files -norecurse [glob $srcs/*.sv] # Use the correct top level module -set_property top top [current_fileset] +set_property top XrtTop [current_fileset] update_compile_order -fileset sources_1 update_compile_order -fileset sim_1 # Package the temporary project -ipx::package_project -root_dir $package_path -vendor circt.llvm.org -library {{system_name}} -taxonomy /KernelIP -import_files -set_current false +ipx::package_project -root_dir $package_path -taxonomy /KernelIP -import_files -set_current false # Load a new project to edit the packaged kernel IP ipx::unload_core $package_path/component.xml @@ -39,21 +39,33 @@ ipx::edit_ip_in_project -upgrade true -name tmp_prj -directory $package_path $pa set core [ipx::current_core] -# Associate AXI-Lite control interfaces +# Associate AXI data & AXI-Lite control interfaces ipx::associate_bus_interfaces -busif s_axi_control -clock ap_clk $core # Create the address space for CSRs -set mem_map [::ipx::add_memory_map -quiet "s_axi_control" $core] -set addr_block [::ipx::add_address_block -quiet "reg0" $mem_map] +set mem_map [::ipx::add_memory_map "s_axi_control" $core] +set addr_block [::ipx::add_address_block "reg0" $mem_map] -set reg [::ipx::add_register "EsiMagicNumber" $addr_block] +set_property range 0x100000 $addr_block +set_property range_resolve_type "immediate" $addr_block +set_property range_minimum 0x100000 $addr_block + +set reg [::ipx::add_register "EsiMagicNumberLow" $addr_block] set_property address_offset 16 $reg - set_property size 64 $reg + set_property size 32 $reg + +set reg [::ipx::add_register "EsiMagicNumberHigh" $addr_block] + set_property address_offset 20 $reg + set_property size 32 $reg set reg [::ipx::add_register "EsiVersionNumber" $addr_block] set_property address_offset 24 $reg set_property size 32 $reg +set reg [::ipx::add_register "EsiManifestLoc" $addr_block] + set_property address_offset 32 $reg + set_property size 32 $reg + set_property slave_memory_map_ref "s_axi_control" [::ipx::get_bus_interfaces -of $core "s_axi_control"] set_property xpm_libraries {XPM_CDC XPM_FIFO} $core diff --git a/frontends/PyCDE/src/common.py b/frontends/PyCDE/src/common.py index 29697c457e0f..b2e993164e8a 100644 --- a/frontends/PyCDE/src/common.py +++ b/frontends/PyCDE/src/common.py @@ -10,26 +10,39 @@ from .types import Type, Bundle, Channel, ChannelSignaling, ClockType, Bits from functools import singledispatchmethod -from typing import Optional +from typing import Callable, Optional -class ModuleDecl: +class ModuleDecl(property): """Represents an input or output port on a design module.""" - __slots__ = ["name", "_type"] + __slots__ = ["idx", "name", "type"] - def __init__(self, type: Type, name: str = None): - self.name: str = name - self._type: Type = type - - @property - def type(self): - return self._type + def __init__(self, + type: Type, + name: Optional[str] = None, + fget: Optional[Callable] = None, + fset: Optional[Callable] = None): + super().__init__(fget=fget, fset=fset) + self.idx: Optional[int] = None + self.name = name + self.type = type class Output(ModuleDecl): """Create an RTL-level output port""" + def __init__(self, type: Type, name: Optional[str] = None): + from .signals import _FromCirctValue + + def fget(mod_inst, self=self): + return _FromCirctValue(mod_inst.inst.operation.results[self.idx]) + + super().__init__(type, name, fget=fget) + + def __repr__(self) -> str: + return f"output '{self.name}': {self.type}" + class OutputChannel(Output): """Create an ESI output channel port.""" @@ -37,7 +50,7 @@ class OutputChannel(Output): def __init__(self, type: Type, signaling: int = ChannelSignaling.ValidReady, - name: str = None): + name: Optional[str] = None): type = Channel(type, signaling) super().__init__(type, name) @@ -45,25 +58,39 @@ def __init__(self, class SendBundle(Output): """Create an ESI bundle output port (aka sending port).""" - def __init__(self, bundle: Bundle, name: str = None): + def __init__(self, bundle: Bundle, name: Optional[str] = None): super().__init__(bundle, name) class Input(ModuleDecl): """Create an RTL-level input port.""" + def __init__(self, type: Type, name: Optional[str] = None): + from .signals import _FromCirctValue + + def fget(mod_inst, self=self): + return _FromCirctValue(mod_inst.inst.operation.operands[self.idx]) + + super().__init__(type, name, fget=fget) + + def __repr__(self) -> str: + return f"input '{self.name}': {self.type}" + class Clock(Input): """Create a clock input""" - def __init__(self, name: str = None): + def __init__(self, name: Optional[str] = None): super().__init__(ClockType(), name) + def __repr__(self) -> str: + return f"clock {self.name}" + class Reset(Input): """Create a reset input.""" - def __init__(self, name: str = None): + def __init__(self, name: Optional[str] = None): super().__init__(Bits(1), name) @@ -104,7 +131,7 @@ def name(self) -> str: def index(self) -> int: return self._appid.index - def __str__(self) -> str: + def __repr__(self) -> str: return f"{self.name}[{self.index}]" @@ -117,6 +144,10 @@ class _PyProxy: def __init__(self, name: str): self.name = name + def clear_op_refs(self): + """Clear all references to IR ops.""" + pass + class PortError(Exception): pass diff --git a/frontends/PyCDE/src/constructs.py b/frontends/PyCDE/src/constructs.py index 8fd4d778ad99..67528b5475e4 100644 --- a/frontends/PyCDE/src/constructs.py +++ b/frontends/PyCDE/src/constructs.py @@ -3,20 +3,21 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception from __future__ import annotations +from re import T from .common import Clock, Input, Output from .dialects import comb, msft, sv from .module import generator, modparams, Module, _BlockContext from .signals import ArraySignal, BitsSignal, BitVectorSignal, Signal from .signals import get_slice_bounds, _FromCirctValue -from .types import dim, types, InOut, Type +from .types import dim, types, Array, Bits, InOut, Type from .circt import ir from .circt.support import BackedgeBuilder from .circt.dialects import msft as raw_msft import typing -from typing import List, Union +from typing import List, Optional, Union def NamedWire(type_or_value: Union[Type, Signal], name: str): @@ -143,8 +144,11 @@ def assign(self, new_value: Signal): return value -def ControlReg(clk: Signal, rst: Signal, asserts: List[Signal], - resets: List[Signal]) -> BitVectorSignal: +def ControlReg(clk: Signal, + rst: Signal, + asserts: List[Signal], + resets: List[Signal], + name: Optional[str] = None) -> BitVectorSignal: """Constructs a 'control register' and returns the output. Asserts are signals which causes the output to go high (on the next cycle). Resets do the opposite. If both an assert and a reset are active on the same cycle, the @@ -155,10 +159,10 @@ def ControlReg(num_asserts: int, num_resets: int): class ControlReg(Module): clk = Clock() - rst = Input(types.i1) - out = Output(types.i1) - asserts = Input(dim(1, num_asserts)) - resets = Input(dim(1, num_resets)) + rst = Input(Bits(1)) + out = Output(Bits(1)) + asserts = Input(Array(Bits(1), num_asserts)) + resets = Input(Array(Bits(1), num_resets)) @generator def generate(ports): @@ -175,7 +179,8 @@ def generate(ports): return ControlReg(len(asserts), len(resets))(clk=clk, rst=rst, asserts=asserts, - resets=resets).out + resets=resets, + instance_name=name).out def Mux(sel: BitVectorSignal, *data_inputs: typing.List[Signal]): diff --git a/frontends/PyCDE/src/esi.py b/frontends/PyCDE/src/esi.py index 7a18defaed3a..2387c691b578 100644 --- a/frontends/PyCDE/src/esi.py +++ b/frontends/PyCDE/src/esi.py @@ -6,14 +6,14 @@ from .module import Generator, Module, ModuleLikeBuilderBase, PortProxyBase from .signals import BundleSignal, ChannelSignal, Signal, _FromCirctValue from .system import System -from .types import (Bits, Bundle, BundledChannel, Channel, ChannelDirection, - Type, types, _FromCirctType) +from .types import (Bits, Bundle, BundledChannel, ChannelDirection, Type, types, + _FromCirctType) from .circt import ir from .circt.dialects import esi as raw_esi, hw, msft from pathlib import Path -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Tuple __dir__ = Path(__file__).parent @@ -29,18 +29,6 @@ class ServiceDecl(_PyProxy): """Declare an ESI service interface.""" - class To: - """Indicates a service has a 'to server' port with the given bundle type. It is the default.""" - - def __init__(self, bundle_type: Bundle): - self.bundle_type = bundle_type - - class From: - """Indicates a service has a 'from server' port with the given bundle type.""" - - def __init__(self, bundle_type: Bundle): - self.bundle_type = bundle_type - def __init__(self, cls: type): self.name = cls.__name__ if hasattr(cls, "_op"): @@ -49,17 +37,10 @@ def __init__(self, cls: type): self._op = raw_esi.CustomServiceDeclOp for (attr_name, attr) in vars(cls).items(): if isinstance(attr, Bundle): - setattr(self, attr_name, - _RequestToServerConnection(self, attr, attr_name)) - elif isinstance(attr, ServiceDecl.To): - setattr(self, attr_name, - _RequestToServerConnection(self, attr.bundle_type, attr_name)) - elif isinstance(attr, ServiceDecl.From): - setattr(self, attr_name, - _RequestFromServerConnection(self, attr.bundle_type, attr_name)) + setattr(self, attr_name, _RequestConnection(self, attr, attr_name)) elif isinstance(attr, (Input, Output)): raise TypeError( - "Input and Output are not allowed in ESI service declarations. " + + "Input and Output are not allowed in ESI service declarations." " Use Bundles instead.") def _materialize_service_decl(self) -> str: @@ -82,10 +63,9 @@ def _materialize_service_decl(self) -> str: ports_block = ir.Block.create_at_start(decl.ports, []) with ir.InsertionPoint.at_block_begin(ports_block): for (_, attr) in self.__dict__.items(): - if isinstance(attr, _RequestToServerConnection): - raw_esi.ToServerOp(attr._name, ir.TypeAttr.get(attr.type._type)) - elif isinstance(attr, _RequestFromServerConnection): - raw_esi.ToClientOp(attr._name, ir.TypeAttr.get(attr.type._type)) + if isinstance(attr, _RequestConnection): + raw_esi.ServiceDeclPortOp(attr._name, + ir.TypeAttr.get(attr.type._type)) return sym_name def instantiate_builtin(self, @@ -107,26 +87,7 @@ def instantiate_builtin(self, return [_FromCirctValue(x) for x in impl_results] -class _RequestToServerConnection: - """Indicates a service with a 'to server' port. Call to create a 'to server' - (from client) connection request.""" - - def __init__(self, decl: ServiceDecl, type: Bundle, attr_name: str): - self.decl = decl - self._name = ir.StringAttr.get(attr_name) - self.type = type - - @property - def service_port(self) -> hw.InnerRefAttr: - return hw.InnerRefAttr.get(self.decl.symbol, self._name) - - def __call__(self, bundle: BundleSignal, appid: AppID): - self.decl._materialize_service_decl() - raw_esi.RequestToServerConnectionOp(self.service_port, bundle.value, - appid._appid) - - -class _RequestFromServerConnection: +class _RequestConnection: """Indicates a service with a 'from server' port. Call to create a 'from server' (to client) connection request.""" @@ -139,11 +100,13 @@ def __init__(self, decl: ServiceDecl, type: Bundle, attr_name: str): def service_port(self) -> hw.InnerRefAttr: return hw.InnerRefAttr.get(self.decl.symbol, self._name) - def __call__(self, appid: AppID): + def __call__(self, appid: AppID, type: Optional[Bundle] = None): + if type is None: + type = self.type self.decl._materialize_service_decl() return _FromCirctValue( - raw_esi.RequestToClientConnectionOp(self.type._type, self.service_port, - appid._appid).toClient) + raw_esi.RequestConnectionOp(type._type, self.service_port, + appid._appid).toClient) def Cosim(decl: ServiceDecl, clk, rst): @@ -159,52 +122,46 @@ def __init__(self, input_chan: ir.Value, client_name: List[str]): super().__init__(input_chan, _FromCirctType(input_chan.type)) -class _OutputChannelSetter: +class _OutputBundleSetter: """Return a list of these as a proxy for a 'request to client connection'. Users should call the 'assign' method with the `ChannelValue` which they have implemented for this request.""" - def __init__(self, req: raw_esi.RequestToClientConnectionOp, - old_chan_to_replace: ChannelSignal): - self.type = Channel(_FromCirctType(req.toClient.type)) - self.client_name = req.clientNamePath - self._chan_to_replace = old_chan_to_replace + def __init__(self, req: raw_esi.ServiceImplementConnReqOp, + old_value_to_replace: ir.OpResult): + self.type: Bundle = _FromCirctType(req.toClient.type) + self.client_name = req.relativeAppIDPath + self._bundle_to_replace: Optional[ir.OpResult] = old_value_to_replace def assign(self, new_value: ChannelSignal): """Assign the generated channel to this request.""" - if self._chan_to_replace is None: + if self._bundle_to_replace is None: name_str = ".".join(self.client_name) raise ValueError(f"{name_str} has already been connected.") if new_value.type != self.type: raise TypeError( f"Channel type mismatch. Expected {self.type}, got {new_value.type}.") - msft.replaceAllUsesWith(self._chan_to_replace, new_value.value) - self._chan_to_replace = None + msft.replaceAllUsesWith(self._bundle_to_replace, new_value.value) + self._bundle_to_replace = None class _ServiceGeneratorBundles: """Provide access to the bundles which the service generator is responsible for connecting up.""" - def __init__(self, mod: Module, req: raw_esi.ServiceImplementReqOp): + def __init__(self, mod: ModuleLikeBuilderBase, + req: raw_esi.ServiceImplementReqOp): self._req = req portReqsBlock = req.portReqs.blocks[0] - # Find the input channel requests and store named versions of the values. - self._input_reqs = [ - NamedChannelValue(x.toServer, x.clientNamePath) - for x in portReqsBlock - if isinstance(x, raw_esi.RequestToServerConnectionOp) - ] - # Find the output channel requests and store the settable proxies. num_output_ports = len(mod.outputs) to_client_reqs = [ req for req in portReqsBlock - if isinstance(req, raw_esi.RequestToClientConnectionOp) + if isinstance(req, raw_esi.ServiceImplementConnReqOp) ] self._output_reqs = [ - _OutputChannelSetter(req, self._req.results[num_output_ports + idx]) + _OutputBundleSetter(req, self._req.results[num_output_ports + idx]) for idx, req in enumerate(to_client_reqs) ] assert len(self._output_reqs) == len(req.results) - num_output_ports @@ -216,12 +173,12 @@ def reqs(self) -> List[NamedChannelValue]: return self._input_reqs @property - def to_client_reqs(self) -> List[_OutputChannelSetter]: + def to_client_reqs(self) -> List[_OutputBundleSetter]: return self._output_reqs def check_unconnected_outputs(self): for req in self._output_reqs: - if req._chan_to_replace is not None: + if req._bundle_to_replace is not None: name_str = ".".join(req.client_name) raise ValueError(f"{name_str} has not been connected.") @@ -231,7 +188,7 @@ class ServiceImplementationModuleBuilder(ModuleLikeBuilderBase): no distinction between definition and instance -- ESI service providers are built where they are instantiated.""" - def instantiate(self, impl, inputs: Dict[str, Signal], appid: AppID = None): + def instantiate(self, impl, inputs: Dict[str, Signal], appid: AppID): # Each instantiation of the ServiceImplementation has its own # registration. opts = _service_generator_registry.register(impl) @@ -241,11 +198,11 @@ def instantiate(self, impl, inputs: Dict[str, Signal], appid: AppID = None): if impl.decl is not None: decl_sym = ir.FlatSymbolRefAttr.get(impl.decl._materialize_service_decl()) return raw_esi.ServiceInstanceOp( - result=[t._type for _, t in self.outputs], + result=[p.type._type for p in self.outputs], appID=appid._appid, service_symbol=decl_sym, impl_type=_ServiceGeneratorRegistry._impl_type_name, - inputs=[inputs[pn].value for pn, _ in self.inputs], + inputs=[inputs[p.name].value for p in self.inputs], impl_opts=opts, loc=self.loc) @@ -260,14 +217,14 @@ def generate_svc_impl(self, with self.GeneratorCtxt(self, ports, serviceReq, generator.loc): # Run the generator. - channels = _ServiceGeneratorChannels(self, serviceReq) - rc = generator.gen_func(ports, channels=channels) + bundles = _ServiceGeneratorBundles(self, serviceReq) + rc = generator.gen_func(ports, bundles=bundles) if rc is None: rc = True elif not isinstance(rc, bool): raise ValueError("Generators must a return a bool or None") ports._check_unconnected_outputs() - channels.check_unconnected_outputs() + bundles.check_unconnected_outputs() # Replace the output values from the service implement request op with # the generated values. Erase the service implement request op. @@ -306,8 +263,9 @@ class _ServiceGeneratorRegistry: _registered = False _impl_type_name = ir.StringAttr.get("pycde") - def __init__(self): - self._registry: Dict[str, ServiceImplementation] = {} + def __init__(self) -> None: + self._registry: Dict[ir.StringAttr, Tuple[ServiceImplementation, + System]] = {} # Register myself with ESI so I can dispatch to my internal registry. assert _ServiceGeneratorRegistry._registered is False, \ @@ -330,6 +288,7 @@ def register(self, ctr += 1 name = basename + "_" + str(ctr) name_attr = ir.StringAttr.get(name) + self._registry[name_attr] = (service_implementation, System.current()) return ir.DictAttr.get({"name": name_attr}) @@ -343,7 +302,12 @@ def _implement_service(self, req: ir.Operation): return False (impl, sys) = self._registry[impl_name] with sys: - return impl._builder.generate_svc_impl(serviceReq=req.opview) + ret = impl._builder.generate_svc_impl(serviceReq=req.opview) + # The service implementation generator could have instantiated new modules, + # so we need to generate them. Don't run the appID indexer since during a + # pass, the IR can be invalid and the indexers assumes it is valid. + sys.generate(skip_appid_index=True) + return ret _service_generator_registry = _ServiceGeneratorRegistry() @@ -364,12 +328,12 @@ class DeclareRandomAccessMemory: ('data', inner_type)]) read = Bundle([ - BundledChannel("address", ChannelDirection.TO, address_type), - BundledChannel("data", ChannelDirection.FROM, inner_type) + BundledChannel("address", ChannelDirection.FROM, address_type), + BundledChannel("data", ChannelDirection.TO, inner_type) ]) write = Bundle([ - BundledChannel("write", ChannelDirection.TO, write_struct), - BundledChannel("ack", ChannelDirection.FROM, Bits(0)) + BundledChannel("req", ChannelDirection.FROM, write_struct), + BundledChannel("ack", ChannelDirection.TO, Bits(0)) ]) @staticmethod @@ -479,6 +443,70 @@ def param(name: str, type: Type = None): esi.ESIPureModuleParamOp(name, type_attr) +@ServiceDecl +class MMIO: + """ESI standard service to request access to an MMIO region.""" + + read = Bundle([ + BundledChannel("offset", ChannelDirection.TO, Bits(32)), + BundledChannel("data", ChannelDirection.FROM, Bits(32)) + ]) + + @staticmethod + def _op(sym_name: ir.StringAttr): + return raw_esi.MMIOServiceDeclOp(sym_name) + + +class _FuncService(ServiceDecl): + """ESI standard service to request execution of a function.""" + + def __init__(self): + super().__init__(self.__class__) + + def get(self, name: AppID, func_type: Bundle) -> BundleSignal: + """Expose a bundle to the host as a function. Bundle _must_ have 'arg' and + 'result' channels going FROM the server and TO the server, respectively.""" + self._materialize_service_decl() + + func_call = _FromCirctValue( + raw_esi.RequestConnectionOp( + func_type._type, + hw.InnerRefAttr.get(self.symbol, ir.StringAttr.get("call")), + name._appid).toClient) + assert isinstance(func_call, BundleSignal) + return func_call + + def get_call_chans(self, name: AppID, arg_type: Type, + result: Signal) -> ChannelSignal: + """Expose a function to the ESI system. Arguments: + 'name' is an AppID which is the function name. + 'arg_type' is the type of the argument to the function. + 'result' is a Signal which is the result of the function. Typically, it'll + be a Wire which gets assigned to later on. + + Returns a Signal of 'arg_type' type which is the argument value from the + caller.""" + + bundle = Bundle([ + BundledChannel("arg", ChannelDirection.TO, arg_type), + BundledChannel("result", ChannelDirection.FROM, result.type) + ]) + self._materialize_service_decl() + func_call = raw_esi.RequestConnectionOp( + bundle._type, hw.InnerRefAttr.get(self.symbol, + ir.StringAttr.get("call")), + name._appid) + to_funcs = _FromCirctValue(func_call.toClient).unpack(result=result) + return to_funcs['arg'] + + @staticmethod + def _op(sym_name: ir.StringAttr): + return raw_esi.FuncServiceDeclOp(sym_name) + + +FuncService = _FuncService() + + def package(sys: System): """Package all ESI collateral.""" diff --git a/frontends/PyCDE/src/esi_api.py b/frontends/PyCDE/src/esi_api.py deleted file mode 100644 index 64b1ae018e8f..000000000000 --- a/frontends/PyCDE/src/esi_api.py +++ /dev/null @@ -1,157 +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 jinja2 import Environment, FileSystemLoader, StrictUndefined - -from io import FileIO -import json -import pathlib -import re -import shutil -from typing import Dict, List - -__dir__ = pathlib.Path(__file__).parent - - -def _camel_to_snake(camel: str): - if camel.upper() == camel: - return camel.lower() - return re.sub(r'(? bool: - """Is a Python object compatible with HW type.""" - assert False, "unimplemented" - - -class VoidType(Type): - - def __init__(self, type_id: typing.Optional[int] = None): - super().__init__(0, type_id) - - def is_valid(self, obj) -> bool: - return obj is None - - -class IntType(Type): - - def __init__(self, - width: int, - signed: bool, - type_id: typing.Optional[int] = None): - super().__init__(width, type_id) - self.signed = signed - - def is_valid(self, obj) -> bool: - if self.width == 0: - return obj is None - if not isinstance(obj, int): - return False - if obj >= 2**self.width: - return False - return True - - def __str__(self): - return ("" if self.signed else "u") + \ - f"int{self.width}" - - -class StructType(Type): - - def __init__(self, - fields: typing.List[typing.Tuple[str, Type]], - type_id: typing.Optional[int] = None): - self.fields = fields - width = sum([ftype.width for (_, ftype) in self.fields]) - super().__init__(width, type_id) - - def is_valid(self, obj) -> bool: - fields_count = 0 - if isinstance(obj, dict): - for (fname, ftype) in self.fields: - if fname not in obj: - return False - if not ftype.is_valid(obj[fname]): - return False - fields_count += 1 - if fields_count != len(obj): - return False - return True - return False - - -class Port: - - def __init__(self, - client_path: typing.List[str], - backend, - impl_type: str, - read_type: typing.Optional[Type] = None, - write_type: typing.Optional[Type] = None): - # If a backend doesn't support a particular implementation type, just skip - # it. We don't want to error out on services which aren't being used. - if backend.supports_impl(impl_type): - self._backend = backend.get_port(client_path, read_type, write_type) - else: - self._backend = None - self.client_path = client_path - self.read_type = read_type - self.write_type = write_type - - -class WritePort(Port): - - def write(self, msg=None) -> bool: - assert self.write_type is not None, "Expected non-None write_type" - if not self.write_type.is_valid(msg): - raise ValueError(f"'{msg}' cannot be converted to '{self.write_type}'") - if self._backend is None: - raise ValueError("Backend does not support implementation of port") - return self._backend.write(msg) - - -class ReadPort(Port): - - def read(self, blocking_timeout: typing.Optional[float] = 1.0): - if self._backend is None: - raise ValueError("Backend does not support implementation of port") - return self._backend.read(blocking_timeout) - - -class ReadWritePort(Port): - - def __call__(self, - msg=None, - blocking_timeout: typing.Optional[float] = 1.0) -> typing.Any: - """Send a message and wait for a response. If 'timeout' is exceeded while - waiting for a response, there may well be one coming. It is the caller's - responsibility to clear the response channel before sending another request - so as to ensure correlation between request and response. - - Intended for blocking or synchronous interfaces.""" - - if not self.write(msg): - raise RuntimeError(f"Could not send message '{msg}'") - return self.read(blocking_timeout) - - def write(self, msg=None) -> bool: - assert self.write_type is not None, "Expected non-None write_type" - if not self.write_type.is_valid(msg): - raise ValueError(f"'{msg}' cannot be converted to '{self.write_type}'") - return self._backend.write(msg) - - def read(self, blocking_timeout: typing.Optional[float] = 1.0): - return self._backend.read(blocking_timeout) - - -class _CosimNode: - """Provides a capnp-based co-simulation backend.""" - - def __init__(self, root, prefix: typing.List[str]): - self._root: Cosim = root - self._endpoint_prefix = prefix - - def supports_impl(self, impl_type: str) -> bool: - """The cosim backend only supports cosim connectivity implementations.""" - return impl_type == "cosim" - - def get_child(self, child_name: str): - """When instantiating a child instance, get the backend node with which it - is associated.""" - child_path = self._endpoint_prefix + [child_name] - return _CosimNode(self._root, child_path) - - def get_port(self, - client_path: typing.List[str], - read_type: typing.Optional[Type] = None, - write_type: typing.Optional[Type] = None): - """When building a service port, get the backend port which it should use - for interactions.""" - path = ".".join(self._endpoint_prefix) + "." + "_".join(client_path) - ep = self._root._open_endpoint( - path, - write_type=write_type.type_id if write_type is not None else None, - read_type=read_type.type_id if read_type is not None else None) - return _CosimPort(self, ep, read_type, write_type) - - -class Cosim(_CosimNode): - """Connect to a Cap'N Proto RPC co-simulation and provide a cosim backend - service.""" - - def __init__(self, schemaPath, hostPort): - """Load the schema and connect to the RPC server""" - self._schema = capnp.load(schemaPath) - self._rpc_client = capnp.TwoPartyClient(hostPort) - self._cosim = self._rpc_client.bootstrap().cast_as( - self._schema.CosimDpiServer) - - # Find the simulation prefix and use it in our parent constructor. - ifaces = self.list() - prefix = [] if len(ifaces) == 0 else ifaces[0].endpointID.split(".")[:1] - super().__init__(self, prefix) - - def load_package(path: os.PathLike): - """Load a cosim connection from something running out of 'path' package dir. - Reads and parses 'cosim.cfg' from that directory to get the connection - information. Loads the capnp schema from the 'runtime' directory in that - package path.""" - path = Path(path) - simcfg = path / "cosim.cfg" - if not simcfg.exists(): - simcfg = Path.cwd() / "cosim.cfg" - if not simcfg.exists(): - raise RuntimeError("Could not find simulation connection file") - port_lines = filter(lambda x: x.startswith("port:"), - simcfg.open().readlines()) - port = int(list(port_lines)[0].split(":")[1]) - return Cosim(os.path.join(path, "runtime", "schema.capnp"), - f"{os.uname()[1]}:{port}") - - def list(self): - """List the available interfaces""" - return self._cosim.list().wait().ifaces - - def _open_endpoint(self, epid: str, write_type=None, read_type=None): - """Open the endpoint, optionally checking the send and recieve types""" - for iface in self.list(): - if iface.endpointID == epid: - # Optionally check that the type IDs match. - if write_type is not None: - assert iface.sendTypeID == write_type.schema.node.id - else: - assert write_type is None - if read_type is not None: - assert iface.recvTypeID == read_type.schema.node.id - else: - assert read_type is None - - openResp = self._cosim.open(iface).wait() - assert openResp.iface is not None - return openResp.iface - assert False, f"Could not find specified EndpointID: {epid}" - - -class _CosimPort: - """Cosim backend for service ports. This is where the real meat is buried.""" - - class _TypeConverter: - """Parent class for Capnp type converters.""" - - def __init__(self, schema, esi_type: Type): - self.esi_type = esi_type - assert hasattr(esi_type, "capnp_name") - if not hasattr(schema, esi_type.capnp_name): - raise ValueError("Cosim does not support non-capnp types.") - self.capnp_type = getattr(schema, esi_type.capnp_name) - - class _VoidConverter(_TypeConverter): - """Convert python ints to and from capnp messages.""" - - def write(self, py_int: None): - return self.capnp_type.new_message() - - def read(self, capnp_resp) -> None: - return capnp_resp.as_struct(self.capnp_type) - - class _IntConverter(_TypeConverter): - """Convert python ints to and from capnp messages.""" - - def write(self, py_int: int): - return self.capnp_type.new_message(i=py_int) - - def read(self, capnp_resp) -> int: - return capnp_resp.as_struct(self.capnp_type).i - - class _StructConverter(_TypeConverter): - """Convert python ints to and from capnp messages.""" - - def write(self, py_dict: dict): - return self.capnp_type.new_message(**py_dict) - - def read(self, capnp_resp) -> int: - capnp_msg = capnp_resp.as_struct(self.capnp_type) - ret = {} - for (fname, _) in self.esi_type.fields: - if hasattr(capnp_msg, fname): - ret[fname] = getattr(capnp_msg, fname) - return ret - - # Lookup table for getting the correct type converter for a given type. - ConvertLookup = { - VoidType: _VoidConverter, - IntType: _IntConverter, - StructType: _StructConverter - } - - def __init__(self, node: _CosimNode, endpoint, - read_type: typing.Optional[Type], - write_type: typing.Optional[Type]): - self._endpoint = endpoint - schema = node._root._schema - # For each type, lookup the type converter and store that instead of the - # type itself. - if read_type is not None: - converter = _CosimPort.ConvertLookup[type(read_type)] - self._read_convert = converter(schema, read_type) - if write_type is not None: - converter = _CosimPort.ConvertLookup[type(write_type)] - self._write_convert = converter(schema, write_type) - - def write(self, msg) -> bool: - """Write a message to this port.""" - self._endpoint.send(self._write_convert.write(msg)).wait() - return True - - def read(self, blocking_time: typing.Optional[float]): - """Read a message from this port. If 'blocking_timeout' is None, return - immediately. Otherwise, wait up to 'blocking_timeout' for a message. Returns - the message if found, None if no message was read.""" - - if blocking_time is None: - # Non-blocking. - recvResp = self._endpoint.recv(False).wait() - else: - # Blocking. Since our cosim rpc server doesn't currently support blocking - # reads, use polling instead. - e = time.time() + blocking_time - recvResp = None - while recvResp is None or e > time.time(): - recvResp = self._endpoint.recv(False).wait() - if recvResp.hasData: - break - else: - time.sleep(0.001) - if not recvResp.hasData: - return None - assert recvResp.resp is not None - return self._read_convert.read(recvResp.resp) diff --git a/frontends/PyCDE/src/fsm.py b/frontends/PyCDE/src/fsm.py index a59ef9e3dd40..6277e3d5ea9a 100644 --- a/frontends/PyCDE/src/fsm.py +++ b/frontends/PyCDE/src/fsm.py @@ -1,13 +1,15 @@ +from hmac import new +from .common import Input, Output from .dialects import fsm from .module import Module, ModuleLikeBuilderBase from .support import _obj_to_attribute -from .types import types +from .types import Bits, types 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 +from typing import Callable, Set class State: @@ -110,11 +112,11 @@ def scan_cls(self): initial_state = name from .types import ClockType - for name, v in self.inputs: - if not (isinstance(v, ClockType) or - (hasattr(v, "width") and v.width == 1)): + for port in self.inputs: + if not (isinstance(port.type, ClockType) or + (hasattr(port.type, "width") and port.type.width == 1)): raise ValueError( - f"Input port {name} has width {v.width}. For now, FSMs only " + f"Input port {port.name} has width {port.type.width}. For now, FSMs only " "support i1 inputs.") # At this point, the 'states' attribute should be considered an immutable, @@ -127,19 +129,25 @@ def scan_cls(self): "`initial=True`.") # Add an output port for each state. + num_outputs = len(self.outputs) for state_name, state in states.items(): state.output = len(self.outputs) - self.outputs.append(('is_' + state_name, types.i1)) + o = Output(Bits(1), name="is_" + state_name) + o.idx = num_outputs + num_outputs += 1 + setattr(self.modcls, o.name, o) + self.ports.append(o) - inputs_to_remove = [] + inputs_to_remove: Set[Input] = set() 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) + clock_port = self.inputs[idx] + self.clock_name = clock_port.name + inputs_to_remove.add(clock_port) if len(self.resets) > 1: raise ValueError("FSMs must have at most one reset") @@ -147,13 +155,24 @@ def scan_cls(self): 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) + reset_port = self.inputs[idx] + self.reset_name = reset_port.name + inputs_to_remove.add(reset_port) # Remove the clock and reset inputs, if necessary. - inputs_to_remove.sort(reverse=True) - for idx in inputs_to_remove: - self.inputs.pop(idx) + new_ports = [] + new_num_inputs = 0 + for port in self.ports: + if not isinstance(port, Input): + new_ports.append(port) + else: + if port in inputs_to_remove: + port.idx = None + else: + port.idx = new_num_inputs + new_num_inputs += 1 + new_ports.append(port) + self.ports = new_ports def create_op(self, sys, symbol): """Creation callback for creating a FSM MachineOp.""" @@ -164,9 +183,9 @@ def create_op(self, sys, symbol): # Add attributes for in- and output names. attributes = {} attributes["in_names"] = _obj_to_attribute( - [port_name for port_name, _ in self.inputs]) + [port.name for port in self.inputs]) attributes["out_names"] = _obj_to_attribute( - [port_name for port_name, _ in self.outputs]) + [port.name for port in self.outputs]) # Add attributes for clock and reset names. attributes["clock_name"] = _obj_to_attribute(self.clock_name) @@ -174,8 +193,8 @@ def create_op(self, sys, symbol): 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], + [(p.name, p.type._type) for p in self.inputs], + [(p.name, p.type._type) for p in self.outputs], attributes=attributes, loc=self.loc, ip=sys._get_ip()) @@ -203,7 +222,7 @@ def instantiate(self, impl, kwargs, instance_name: str): op = raw_fsm.HWInstanceOp(outputs=circt_mod.type.results, inputs=inputs, - sym_name=StringAttr.get(instance_name), + name=StringAttr.get(instance_name), machine=FlatSymbolRefAttr.get( StringAttr( circt_mod.attributes["sym_name"]).value), diff --git a/frontends/PyCDE/src/ibis.py b/frontends/PyCDE/src/ibis.py new file mode 100644 index 000000000000..5a1b2fa11c6b --- /dev/null +++ b/frontends/PyCDE/src/ibis.py @@ -0,0 +1,224 @@ +# 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 .constructs import Wire +from .module import Module, ModuleBuilder, generator +from .system import System +from .types import (Bits, Bundle, BundledChannel, ChannelDirection, Channel, + StructType, Type) + +from .circt.dialects import hw + +from pathlib import Path +from typing import Callable, Dict, List, Optional, get_type_hints + +import os +import shutil + +SvSupportPath = None +if "IBIS_SVSUPPORT" in os.environ: + SvSupportPath = Path(os.environ["IBIS_SVSUPPORT"]) + + +class IbisMethod: + """Replace a decorated method with this class as a record. Used during class + scanning to identify Ibis methods.""" + + def __init__(self, name: Optional[str], arg_types: Dict[str, Type], + return_type: Optional[Type]): + self.name = name + self.arg_types = arg_types + # Assemble the args into a single struct type. + self.arg_type = StructType(self.arg_types) + # i0 is used for void. + if return_type is None: + return_type = Bits(0) + self.return_type = return_type + self.func_type = Bundle([ + BundledChannel("arg", ChannelDirection.TO, self.arg_type), + BundledChannel("result", ChannelDirection.FROM, self.return_type) + ]) + + +def method(func: Callable): + """Decorator to mark a function as an Ibis function.""" + type_hints = get_type_hints(func) + arg_types: Dict[str, Type] = {} + return_type: Optional[Type] = None + for name, type in type_hints.items(): + if not isinstance(type, Type): + raise TypeError( + f"Argument {name} of method {func.__name__} is not a CIRCT type") + if name == "return": + return_type = type + else: + arg_types[name] = type + return IbisMethod(None, arg_types, return_type) + + +class IbisClassBuilder(ModuleBuilder): + """Ibis-specific module builder. This is used to scan a class for Ibis + methods, import the Ibis module, and create the wrapper module.""" + + SupportFiles: List[Path] = [] + + @staticmethod + def package(sys: System): + """Copy in the necessary support files.""" + if SvSupportPath is None: + raise RuntimeError( + "IBIS_SVSUPPORT environment variable not set. Cannot copy in " + "necessary support files.") + + # TODO: make this configurable. + platform = "SimOnly" + outdir = sys.hw_output_dir + + for idx, f in enumerate(IbisClassBuilder.SupportFiles): + shutil.copy(SvSupportPath / f, outdir / f"{idx}_{f.name}") + for f in (SvSupportPath / "HAL" / platform).glob("*.sv"): + shutil.copy(f, outdir) + + @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.""" + + 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 + + def scan_cls(self) -> None: + """Scan the class for Ibis methods and register them.""" + + self.methods: List[IbisMethod] = [] + for name, value in self.cls_dct.items(): + if isinstance(value, IbisMethod): + value.name = name + self.methods.append(value) + + self.src_file = None + if hasattr(self.modcls, "src_file"): + self.src_file = Path(self.modcls.src_file).resolve() + if hasattr(self.modcls, "support_files"): + IbisClassBuilder.SupportFiles = list([ + Path(f.strip()) + for f in Path(self.modcls.support_files).resolve().open().readlines() + ]) + + # Fixed ports -- clock and reset. + self.clocks = {0} + self.resets = {1} + self.ports = [ + Clock("clk"), + Input(Bits(1), "rst"), + ] + # Method ports. + self.ports.extend([ + Input(m.func_type, m.name) for m in self.methods if m.name is not None + ]) + # Add indexes to the ports. + for idx, port in enumerate(self.ports): + port.idx = idx + # Ibis-specific generator. + self.generators = {"default": generator(self.generate_wrapper)} + + def create_op(self, sys, symbol): + """Creation callback for creating an Ibis method wrapper.""" + + # Add metadata to the manifest, if any. + if hasattr(self.modcls, "metadata"): + meta = self.modcls.metadata + self.add_metadata(sys, symbol, meta) + else: + self.add_metadata(sys, symbol, None) + + # Import the Ibis module to be wrapped. + if self.src_file is None: + raise RuntimeError( + f"Could not find source file for module {self.modcls.name}") + imported = sys.import_mlir(open(self.src_file).read()) + if self.modcls.__name__ not in imported: + raise RuntimeError( + f"Could not find module {self.modcls.name} in {self.src_file}") + self.imported_mod = imported[self.modcls.__name__] + + # Finally, create the module which this method is supposed to return. + return hw.HWModuleOp( + symbol, + [(p.name, p.type._type) for p in self.inputs], + [(p.name, p.type._type) for p in self.outputs], + attributes=self.attributes, + loc=self.loc, + ip=sys._get_ip(), + ) + + def generate_wrapper(self, ports): + """Instantiate and wrap an Ibis module.""" + System.current().add_packaging_step(IbisClassBuilder.package) + + # Ports we shouldn't touch. + exclude_ports = { + "clk", "clk1", "rst_in", "stall_rate_in", "inspection_value_in", + "stall_rate_valid_in" + } + + # Create wires for all the inputs and instantiate the Ibis module. + ibis_inputs = { + p.name: Wire(p.type, p.name) + for p in self.imported_mod.inputs() + if p.name not in exclude_ports + } + ibis_instance = self.imported_mod( + clk=ports.clk.to_bit(), + clk1=ports.clk.to_bit(), + rst_in=ports.rst, + stall_rate_in=None, + inspection_value_in=None, + stall_rate_valid_in=None, + **ibis_inputs, + ) + inst_outputs = ibis_instance.outputs() + + # For each method, connect the Ibis module to the ESI ports. + for m in self.methods: + assert m.name is not None + mname = m.name + "_" + + # Return side is FIFO. + return_out_untyped = inst_outputs[mname + "result_out"] + return_out = return_out_untyped.bitcast(m.return_type) + + empty_out = inst_outputs[mname + "empty_out"] + return_chan, rdy = Channel(m.return_type).wrap(return_out, ~empty_out) + ibis_inputs[mname + "rden_in"].assign(rdy & ~empty_out) + + # Pack the bundle and set it on my output port. + arg_chan = getattr(ports, m.name).unpack(result=return_chan)["arg"] + + # Call side is ready/valid. + args_rdy = inst_outputs[mname + "rdy_out"] + args, args_valid = arg_chan.unwrap(args_rdy) + ibis_inputs[mname + "valid_in"].assign(args_valid) + for name, _ in m.arg_types.items(): + input_wire = ibis_inputs[mname + name + "_in"] + input_wire.assign(args[name].bitcast(input_wire.type)) + + @property + def name(self) -> str: + """Name the wrapper module.""" + ibis_cls_name = super().name + return ibis_cls_name + "_esi_wrapper" + + +class IbisClass(Module): + """Base class to be extended for describing an Ibis class method.""" + + BuilderType = IbisClassBuilder diff --git a/frontends/PyCDE/src/module.py b/frontends/PyCDE/src/module.py index fdffc3df5d28..20b4dd70df07 100644 --- a/frontends/PyCDE/src/module.py +++ b/frontends/PyCDE/src/module.py @@ -3,13 +3,15 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception from __future__ import annotations -from typing import List, Optional, Set, Tuple, Dict +from dataclasses import dataclass +from typing import Any, List, Optional, Set, Tuple, Dict -from .common import (AppID, Clock, Input, Output, PortError, _PyProxy, Reset) +from .common import (AppID, Clock, Input, ModuleDecl, 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 .types import ClockType, Type, _FromCirctType from .circt import ir, support from .circt.dialects import hw @@ -18,6 +20,7 @@ import builtins from contextvars import ContextVar import inspect +import os import sys # A memoization table for module parameterization function calls. @@ -122,9 +125,11 @@ class PortProxyBase: PyCDE developer.""" def __init__(self, block_args, builder): + assert builder is not None self._block_args = block_args if builder.outputs is not None: - self._output_values = [None] * len(builder.outputs) + self._output_values: List[Optional[Signal]] = [None] * len( + builder.outputs) self._builder = builder def _get_input(self, idx): @@ -135,13 +140,14 @@ def _get_input(self, idx): def _set_output(self, idx, signal): assert signal is not None - pname, ptype = self._builder.outputs[idx] + port = self._builder.outputs[idx] if isinstance(signal, Signal): - if ptype != signal.type: + if port.type != signal.type: raise PortError( - f"Input port {pname} expected type {ptype}, not {signal.type}") + f"Input port {port.name} expected type {port.type}, not {signal.type}" + ) else: - signal = ptype(signal) + signal = port.type(signal) self._output_values[idx] = signal def _set_outputs(self, signal_dict: Dict[str, Signal]): @@ -158,7 +164,7 @@ def _check_unconnected_outputs(self): if value is None: unconnected_ports.append(self._builder.outputs[idx][0]) if len(unconnected_ports) > 0: - raise support.UnconnectedSignalError(self.name, unconnected_ports) + raise support.UnconnectedSignalError(self._name, unconnected_ports) def _clear(self): """TL;DR: Downgrade a shotgun to a handgun. @@ -189,16 +195,14 @@ class ModuleLikeBuilderBase(_PyProxy): `ModuleBuilder`. The correspondence is given by the `BuilderType` class variable in `Module`.""" - def __init__(self, cls, cls_dct, loc): - from .types import Type + def __init__(self, cls: type, cls_dct: Dict[str, object], loc: ir.Location): self.modcls = cls self.cls_dct = cls_dct self.loc = 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.ports: List[ModuleDecl] = [] + self.clocks: Set[int] = set() + self.resets: Set[int] = set() self.generators = None self.generator_port_proxy = None self.parameters = None @@ -208,6 +212,14 @@ def __init__(self, cls, cls_dct, loc): ir.StringAttr.get(f"{cls.__name__}.sv"), False, True) } + @property + def inputs(self) -> List[Input]: + return [p for p in self.ports if isinstance(p, Input)] + + @property + def outputs(self) -> List[Output]: + return [p for p in self.ports if isinstance(p, Output)] + def go(self): """Execute the analysis and mutation to make a `ModuleLike` class operate as such.""" @@ -220,11 +232,12 @@ def scan_cls(self): """Scan the class for input/output ports and generators. (Most `ModuleLike` will use these.) Store the results for later use.""" - input_ports = [] - output_ports = [] + ports = [] clock_ports = set() reset_ports = set() generators = {} + num_inputs = 0 + num_outputs = 0 for attr_name, attr in self.cls_dct.items(): if attr_name.startswith("_"): continue @@ -243,45 +256,57 @@ def scan_cls(self): continue if isinstance(attr, Clock): - clock_ports.add(len(input_ports)) - input_ports.append((attr_name, attr.type)) + clock_ports.add(num_inputs) 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)) + reset_ports.add(num_inputs) + + if isinstance(attr, Input): + attr.idx = num_inputs + num_inputs += 1 + attr.name = attr_name + ports.append(attr) elif isinstance(attr, Output): - output_ports.append((attr_name, attr.type)) + attr.idx = num_outputs + num_outputs += 1 + attr.name = attr_name + ports.append(attr) elif isinstance(attr, Generator): generators[attr_name] = attr - self.outputs = output_ports - self.inputs = input_ports + self.ports = ports self.clocks = clock_ports self.resets = reset_ports self.generators = generators - def create_port_proxy(self): + def create_port_proxy(self) -> PortProxyBase: """Create a proxy class for generators to use in order to access module ports. Instances of this will (usually) be used in place of the `self` argument in generator calls. Replaces the dynamic lookup scheme previously utilized. Should be faster and (more importantly) reduces the amount of bookkeeping necessary.""" + assert self.inputs is not None + assert self.outputs is not None - proxy_attrs = {} - for idx, (name, port_type) in enumerate(self.inputs): - proxy_attrs[name] = property(lambda self, idx=idx: self._get_input(idx)) + proxy_attrs: Dict[str, object] = {} + for port in self.inputs: + assert port.name is not None + assert port.idx is not None + proxy_attrs[port.name] = property( + lambda self, idx=port.idx: self._get_input(idx)) output_port_lookup: Dict[str, int] = {} - for idx, (name, port_type) in enumerate(self.outputs): + for port in self.outputs: + assert port.name is not None + assert port.idx is not None - def fset(self, val, idx=idx): + def fset(self, val, idx=port.idx): self._set_output(idx, val) - proxy_attrs[name] = property(fget=None, fset=fset) - output_port_lookup[name] = idx + proxy_attrs[port.name] = property(fget=None, fset=fset) + output_port_lookup[port.name] = port.idx proxy_attrs["_output_port_lookup"] = output_port_lookup + proxy_attrs["_name"] = self.modcls.__name__ return type(self.modcls.__name__ + "Ports", (PortProxyBase,), proxy_attrs) @@ -289,25 +314,16 @@ def add_external_port_accessors(self): """For each port, replace it with a property to provide access to the instances output in OTHER generators which are instantiating this module.""" - for idx, (name, port_type) in enumerate(self.inputs): - - def fget(self): - raise PortError("Cannot access signal via instance input") - - setattr(self.modcls, name, property(fget=fget)) + def fgets_dict(s, outs): + return {n: g.fget(s) for n, g in outs.items()} named_outputs = {} - for idx, (name, port_type) in enumerate(self.outputs): - - def fget(self, idx=idx): - return _FromCirctValue(self.inst.operation.results[idx]) - - named_outputs[name] = fget - setattr(self.modcls, name, property(fget=fget)) + for port in self.outputs: + assert port.name is not None + named_outputs[port.name] = port setattr(self.modcls, - "_outputs", - lambda self, outputs=named_outputs: - {n: g(self) for n, g in outputs.items()}) + "outputs", + lambda s, outs=named_outputs: fgets_dict(s, outs)) @property def name(self): @@ -324,6 +340,60 @@ def print(self, out): f"outputs: {self.outputs}>", file=out) + def add_metadata(self, sys, symbol: str, meta: Optional[Metadata]): + """Add the metadata to the IR so it potentially gets included in the + manifest. (It'll only be included if one of the instances has an appid.) If + user did not specify the metadata (or components thereof), attempt to fill + them in automatically: + - Name defaults to the module name. + - Summary defaults to the module docstring. + - If GitPython is installed, the commit hash and repo are automatically + generated if neither are specified. + """ + + from .dialects.esi import esi + + if meta is None: + meta = Metadata() + elif not isinstance(meta, Metadata): + raise TypeError("Module metadata must be of type Metadata") + + if meta.name is None: + meta.name = self.modcls.__name__ + + try: + # Attempt to automatically generate repo and commit hash using GitPython. + if meta.repo is None and meta.commit_hash is None: + import git + import inspect + modclsmodule = inspect.getmodule(self.modcls) + if modclsmodule is not None: + r = git.Repo(os.path.dirname(modclsmodule.__file__), + search_parent_directories=True) + if r is not None: + meta.repo = r.remotes.origin.url + meta.commit_hash = r.head.object.hexsha + except Exception: + pass + + if meta.summary is None and self.modcls.__doc__ is not None: + meta.summary = self.modcls.__doc__ + + with ir.InsertionPoint(sys.mod.body): + meta_op = esi.SymbolMetadataOp( + symbolRef=ir.FlatSymbolRefAttr.get(symbol), + name=ir.StringAttr.get(meta.name), + repo=ir.StringAttr.get(meta.repo) if meta.repo is not None else None, + commitHash=ir.StringAttr.get(meta.commit_hash) + if meta.commit_hash is not None else None, + version=ir.StringAttr.get(meta.version) + if meta.version is not None else None, + summary=ir.StringAttr.get(meta.summary) + if meta.summary is not None else None) + if meta.misc is not None: + for k, v in meta.misc.items(): + meta_op.attributes[k] = _obj_to_attribute(v) + class GeneratorCtxt: """Provides an context which most genertors need.""" @@ -398,14 +468,20 @@ def circt_mod(self): def create_op(self, sys, symbol): """Callback for creating a module op.""" + if hasattr(self.modcls, "metadata"): + meta = self.modcls.metadata + self.add_metadata(sys, symbol, meta) + else: + self.add_metadata(sys, symbol, None) + 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 hw.HWModuleOp( symbol, - [(n, t._type) for (n, t) in self.inputs], - [(n, t._type) for (n, t) in self.outputs], + [(p.name, p.type._type) for p in self.inputs], + [(p.name, p.type._type) for p in self.outputs], attributes=self.attributes, loc=self.loc, ip=sys._get_ip(), @@ -427,8 +503,8 @@ def create_op(self, sys, symbol): } return hw.HWModuleExternOp( symbol, - input_ports=[(n, t._type) for (n, t) in self.inputs], - output_ports=[(n, t._type) for (n, t) in self.outputs], + input_ports=[(p.name, p.type._type) for p in self.inputs], + output_ports=[(p.name, p.type._type) for p in self.outputs], parameters=paramdecl_list, attributes=self.attributes, loc=self.loc, @@ -438,29 +514,29 @@ def create_op(self, sys, symbol): def instantiate(self, module_inst, inputs, instance_name: str): """"Instantiate this Module. Check that the input types match expectations.""" - port_input_lookup = {name: ptype for name, ptype in self.inputs} + port_input_lookup = {port.name: port for port in self.inputs} circt_inputs = {} for name, signal in inputs.items(): if name not in port_input_lookup: raise PortError(f"Input port {name} not found in module") - ptype = port_input_lookup[name] + port = port_input_lookup[name] if isinstance(signal, Signal): # If the input is a signal, the types must match. - if signal.type._type != ptype._type: + if signal.type != port.type: raise ValueError( f"Wrong type on input signal '{name}'. Got '{signal.type}'," - f" expected '{type}'") + f" expected '{port.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.") - circt_inputs[name] = create_const_zero(ptype) + circt_inputs[name] = create_const_zero(port.type).value else: # If it's not a signal, assume the user wants to specify a constant and # try to convert it to a hardware constant. - circt_inputs[name] = ptype(signal).value + circt_inputs[name] = port.type(signal).value missing = list( filter(lambda name: name not in circt_inputs, port_input_lookup.keys())) @@ -499,7 +575,7 @@ def generate(self): hw.OutputOp([o.value for o in ports._output_values]) -class Module(metaclass=ModuleLikeType): +class Module(_PyProxy, metaclass=ModuleLikeType): """Subclass this class to define a regular PyCDE or external module. To define a module in PyCDE, supply a `@generator` method. To create an external module, don't. In either case, a list of ports is required. @@ -514,12 +590,14 @@ class Module(metaclass=ModuleLikeType): instance constructed exclusively for the generator. """ - BuilderType = ModuleBuilder + BuilderType: type[ModuleLikeBuilderBase] = ModuleBuilder + _builder: ModuleBuilder def __init__(self, instance_name: str = None, appid: AppID = None, **inputs): """Create an instance of this module. Instance namd and appid are optional. All inputs must be specified. If a signal has not been produced yet, use the `Wire` construct and assign the signal to that wire later on.""" + from .system import System kwargs = dict() @@ -540,14 +618,25 @@ def __init__(self, instance_name: str = None, appid: AppID = None, **inputs): kwargs["appid"] = appid self.inst = self._builder.instantiate(self, inputs, **kwargs) - if appid is not None: self.inst.operation.attributes[AppID.AttributeName] = appid._appid + System.current()._op_cache.register_pyproxy(self) + + def clear_op_refs(self): + self.inst = None + @classmethod def print(cls, out=sys.stdout): cls._builder.print(out) + @classmethod + def inputs(cls) -> List[Tuple[str, Type]]: + """Get a dictionary of input port names to signals.""" + if cls._builder.inputs is None: + return [] + return cls._builder.inputs + class modparams: """Decorate a function to indicate that it is returning a Module which is @@ -603,6 +692,20 @@ def __call__(self, *args, **kwargs): return cls +@dataclass +class Metadata: + """Metadata for a module. This is used to provide information about a module + in the ESI manifest. Set the classvar 'metadata' to an instance of this class + to provide metadata for a module.""" + + name: Optional[str] = None + repo: Optional[str] = None + commit_hash: Optional[str] = None + version: Optional[str] = None + summary: Optional[str] = None + misc: Optional[Dict[str, Any]] = None + + class ImportedModSpec(ModuleBuilder): """Specialization to support imported CIRCT modules.""" @@ -620,27 +723,12 @@ def create_op(self, sys, symbol: str): self.modcls.hw_module = None return hw_module - def instantiate(self, module_inst, inputs, instance_name: str): - inst = self.circt_mod.instantiate( - instance_name, - **{ - n: i.value if isinstance(i, Signal) else i - for (n, i) in inputs.items() - }, - parameters={} if self.parameters is None else self.parameters, - loc=get_user_loc()) - inst.operation.verify() - return inst.operation - 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. - - THIS IS BROKEN: https://github.com/llvm/circt/issues/6130""" - # TODO: fix me + For now, the imported module name MUST NOT conflict with any other modules.""" # Get the module name to use in the generated class and as the external name. name = ir.StringAttr(hw_module.name).value @@ -648,9 +736,9 @@ def import_hw_module(hw_module: hw.HWModuleOp): # Collect input and output ports as named Inputs and Outputs. modattrs = {} for input_name, block_arg in hw_module.inputs().items(): - modattrs[input_name] = Input(block_arg.type, input_name) + modattrs[input_name] = Input(_FromCirctType(block_arg.type), input_name) for output_name, output_type in hw_module.outputs().items(): - modattrs[output_name] = Output(output_type, output_name) + modattrs[output_name] = Output(_FromCirctType(output_type), output_name) modattrs["BuilderType"] = ImportedModSpec modattrs["hw_module"] = hw_module diff --git a/frontends/PyCDE/src/signals.py b/frontends/PyCDE/src/signals.py index 12ae54c7a4ab..a4db5087f7e0 100644 --- a/frontends/PyCDE/src/signals.py +++ b/frontends/PyCDE/src/signals.py @@ -50,6 +50,11 @@ def create(obj) -> Signal: those signal types, assuming the types of all the `Signal` are the same.""" return _obj_to_value_infer_type(obj) + def bitcast(self, new_type: Type) -> Signal: + from .circt.dialects import hw + casted_value = hw.BitcastOp(new_type._type, self.value) + return _FromCirctValue(casted_value.result, new_type) + def reg(self, clk=None, rst=None, @@ -68,7 +73,6 @@ def reg(self, clk = ClockSignal._get_current_clock_block() if clk is None: raise ValueError("If 'clk' not specified, must be in clock block") - from .dialects import seq, hw from .types import types, Bits if name is None: @@ -112,7 +116,6 @@ def reg(self, clockEnable=ce, reset=rst, reset_value=rst_value, - clock_enable=ce, name=give_name, sym_name=give_name) if sv_attributes is not None: @@ -190,6 +193,12 @@ def __exit__(self, exc_type, exc_value, traceback): def _get_current_clock_block(): return _current_clock_context.get(None) + def to_bit(self): + from .dialects import seq + from .types import Bits + clk_i1 = seq.FromClockOp(self.value) + return BitsSignal(clk_i1, Bits(1)) + class InOutSignal(Signal): # Maintain a caching of the read value. @@ -747,6 +756,17 @@ def unpack(self, **kwargs: Dict[str, for idx, bc in enumerate(to_channels) } + def connect(self, other: BundleSignal): + """Connect two bundles together such that one drives the other.""" + from .constructs import Wire + froms = [(bc.name, Wire(bc.channel)) + for bc in other.type.channels + if bc.direction == ChannelDirection.FROM] + unpacked_other = other.unpack(**{name: wire for name, wire in froms}) + unpacked_self = self.unpack(**unpacked_other) + for name, wire in froms: + wire.assign(unpacked_self[name]) + class ListSignal(Signal): pass diff --git a/frontends/PyCDE/src/system.py b/frontends/PyCDE/src/system.py index 31d8d6d31953..61d5169ffdce 100644 --- a/frontends/PyCDE/src/system.py +++ b/frontends/PyCDE/src/system.py @@ -15,10 +15,10 @@ from . import circt from .circt import ir, passmanager from .circt.dialects import esi, hw, msft -from .esi_api import PythonApiBuilder from contextvars import ContextVar from collections.abc import Iterable +import weakref import gc import os import pathlib @@ -41,7 +41,7 @@ class System: "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", - "_appid_index" + "_appid_index", "platform" ] def __init__(self, @@ -69,6 +69,11 @@ def __init__(self, self._placedb: PlacementDB = None self._appid_index: esi.AppIDIndex = None + # To be set by a BSP. Gets passed through to CIRCT to direct lowerings. + # Should be replaced by a more general mechanism (a target triple type + # thing). + self.platform = "" + # The set of all files generated by PyCDE. self.files: Set[os.PathLike] = set() # The set of module SV files generated by PyCDE. @@ -132,7 +137,7 @@ def set_debug(): # "canonicalize", # ] - def import_mlir(self, module, lowering=None): + def import_mlir(self, module, lowering=None) -> Dict[str, Any]: """Import mlir asm created elsewhere into our space.""" compat_mod = ir.Module.parse(str(module)) @@ -190,7 +195,7 @@ def _create_circt_mod(self, builder: ModuleLikeBuilderBase): return op @staticmethod - def current(): + def current() -> System: """Get the top-most system in the stack created by `with System()`.""" bb = _current_system.get(None) if bb is None: @@ -216,7 +221,7 @@ def cleanup(self): pm = passmanager.PassManager.parse("builtin.module(canonicalize)") pm.run(self.mod.operation) - def generate(self, generator_names=[], iters=None): + def generate(self, generator_names=[], iters=None, skip_appid_index=False): """Fully generate the system unless iters is specified. Iters specifies the number of generators to run. Useful for debugging. Maybe.""" i = 0 @@ -226,7 +231,10 @@ def generate(self, generator_names=[], iters=None): m.generate() i += 1 - self._appid_index = esi.AppIDIndex(self.mod.operation) + if len(self._generate_queue) == 0: + self.mod.operation.verify() + if not skip_appid_index: + self._appid_index = esi.AppIDIndex(self.mod.operation) def get_instance(self, mod_cls: object, @@ -247,14 +255,17 @@ def get_instance(self, # defined so we can go through and output the typedefs delcarations. lambda sys: TypeAlias.declare_aliases(sys.mod), "builtin.module(esi-appid-hier{{top={tops} }}, esi-build-manifest{{top={tops} }})", - "builtin.module(lower-hwarith-to-hw, msft-lower-constructs, msft-lower-instances)", + "builtin.module(msft-lower-constructs, msft-lower-instances)", "builtin.module(esi-clean-metadata)", "builtin.module(hw.module(lower-seq-hlmem))", "builtin.module(lower-esi-to-physical)", # TODO: support more than just cosim. - "builtin.module(lower-esi-bundles, lower-esi-ports, lower-esi-to-hw{{platform=cosim}})", + "builtin.module(lower-esi-bundles, lower-esi-ports)", + "builtin.module(lower-esi-to-hw{{platform={platform}}})", "builtin.module(convert-fsm-to-sv)", + "builtin.module(lower-hwarith-to-hw)", "builtin.module(lower-seq-to-sv)", + "builtin.module(lower-comb)", "builtin.module(cse, canonicalize, cse)", "builtin.module(hw.module(prettify-verilog), hw.module(hw-cleanup))", "builtin.module(msft-export-tcl{{tops={tops} tcl-file={tcl_file}}})" @@ -283,7 +294,8 @@ def run_passes(self, debug=False): if isinstance(phase, str): passes = phase.format(tops=tops, verilog_file=verilog_file, - tcl_file=tcl_file).strip() + tcl_file=tcl_file, + platform=self.platform).strip() if aplog is not None: aplog.write(f"// passes ran: {passes}\n") aplog.flush() @@ -339,7 +351,7 @@ class _OpCache: __slots__ = [ "_module", "_symbols", "_pyproxy_symbols", "_symbol_pyproxy", "_instance_hier_cache", "_instance_hier_obj_cache", "_instance_cache", - "_module_inside_sym_cache", "_dyn_insts_in_inst" + "_module_inside_sym_cache", "_dyn_insts_in_inst", "_pyproxies" ] def __init__(self, module: ir.Module): @@ -347,6 +359,7 @@ def __init__(self, module: ir.Module): self._symbols: Dict[str, ir.OpView] = None self._pyproxy_symbols: dict[_PyProxy, str] = {} self._symbol_pyproxy: dict[str, _PyProxy] = {} + self._pyproxies: Set[weakref.ref[_PyProxy]] = set() # InstanceHier caches are indexes are (module_sym, instance_name) self._instance_hier_cache: dict[(ir.FlatSymbolRefAttr, ir.StringAttr), @@ -369,7 +382,18 @@ def release_ops(self): self._instance_cache.clear() self._module_inside_sym_cache.clear() self._dyn_insts_in_inst.clear() + for proxy_ref in self._pyproxies: + proxy = proxy_ref() + if proxy is not None: + proxy.clear_op_refs() + gc.collect() + # Pending https://github.com/llvm/llvm-project/pull/78663 + # live_ops = ir.Context.current._get_live_operation_objects() + # for op in live_ops: + # sys.stderr.write(f"Warning: {op} is still live. Referrers:\n") + # for referrer in gc.get_referrers(op)[0]: + # sys.stderr.write(f" {referrer}\n") num_ops_live = ir.Context.current._clear_live_operations() if num_ops_live > 0: sys.stderr.write( @@ -389,6 +413,10 @@ def op(self, symbol: str) -> ir.OpView: """Resolve a symbol to an op.""" return self.symbols[symbol] + def register_pyproxy(self, pyproxy: _PyProxy): + """Used to report a new _PyProxy to the cache which doesn't have a symbol.""" + self._pyproxies.add(weakref.ref(pyproxy)) + def create_symbol(self, pyproxy: _PyProxy) -> Tuple[str, Callable]: """Create a unique symbol and add it to the cache. If it is to be preserved, the caller must use it as the symbol on a top-level op. Returns the symbol @@ -409,6 +437,7 @@ def install(op): self._symbols[symbol] = op self._pyproxy_symbols[pyproxy] = symbol self._symbol_pyproxy[symbol] = pyproxy + self._pyproxies.add(weakref.ref(pyproxy)) return symbol, install diff --git a/frontends/PyCDE/src/types.py b/frontends/PyCDE/src/types.py index de1b352b0b11..734e485b9efe 100644 --- a/frontends/PyCDE/src/types.py +++ b/frontends/PyCDE/src/types.py @@ -2,11 +2,10 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +from __future__ import annotations + from collections import OrderedDict from functools import singledispatchmethod -from typing import Any - -from numpy import single from .support import get_user_loc @@ -90,7 +89,9 @@ def bitwidth(self): def __call__(self, obj, name: str = None) -> "Signal": """Create a Value of this type from a python object.""" - assert not isinstance(obj, ir.Value) + assert not isinstance( + obj, ir.Value + ), "Not intended to be called on CIRCT Values, only Python objects." v = self._from_obj_or_sig(obj) if name is not None: v.name = name @@ -331,8 +332,10 @@ def _from_obj(self, obj, alias: typing.Optional[TypeAlias] = None): class StructType(Type): - def __new__(cls, fields: typing.Union[typing.List[typing.Tuple[str, Type]], - typing.Dict[str, Type]]): + def __new__( + cls, fields: typing.Union[typing.List[typing.Tuple[str, Type]], + typing.Dict[str, Type]] + ) -> StructType: if isinstance(fields, dict): fields = list(fields.items()) if not isinstance(fields, list): @@ -583,7 +586,7 @@ def __repr__(self) -> str: class Bundle(Type): """A group of named, directed channels. Typically used in a service.""" - def __new__(cls, channels: typing.List[BundledChannel]): + def __new__(cls, channels: typing.List[BundledChannel]) -> Bundle: def wrap_in_channel(ty: Type): if isinstance(ty, Channel): @@ -606,6 +609,15 @@ def channels(self): for (name, dir, type) in self._type.channels ] + def inverted(self) -> "Bundle": + """Return a new bundle with all the channels direction inverted.""" + return Bundle([ + BundledChannel( + name, ChannelDirection.TO + if dir == ChannelDirection.FROM else ChannelDirection.FROM, + _FromCirctType(ty)) for (name, dir, ty) in self._type.channels + ]) + # Easy accessor for channel types by name. def __getattr__(self, attrname: str): for channel in self.channels: @@ -619,25 +631,34 @@ def __repr__(self): class PackSignalResults: """Access the FROM channels of a packed bundle in a convenient way.""" - def __init__(self, results: typing.List["ChannelSignal"], - bundle_type: "Bundle"): + def __init__(self, results: typing.List[ChannelSignal], + bundle_type: Bundle): self.results = results self.bundle_type = bundle_type - from_channels = [ + + self.from_channels = { + name: result for (name, result) in zip([ + c.name + for c in self.bundle_type.channels + if c.direction == ChannelDirection.FROM + ], results) + } + + from_channels_idx = [ c.name for c in self.bundle_type.channels if c.direction == ChannelDirection.FROM ] self._from_channels_idx = { - name: idx for idx, name in enumerate(from_channels) + name: idx for idx, name in enumerate(from_channels_idx) } @singledispatchmethod - def __getitem__(self, name: str) -> "ChannelSignal": + def __getitem__(self, name: str) -> ChannelSignal: return self.results[self._from_channels_idx[name]] @__getitem__.register(int) - def __getitem_int(self, idx: int) -> "ChannelSignal": + def __getitem_int(self, idx: int) -> ChannelSignal: return self.results[idx] def __getattr__(self, attrname: str): @@ -645,6 +666,12 @@ def __getattr__(self, attrname: str): return self.results[self._from_channels_idx[attrname]] return super().__getattribute__(attrname) + def __iter__(self): + return iter(self.from_channels.items()) + + def __len__(self): + return len(self.from_channels) + def pack( self, **kwargs: typing.Dict[str, "ChannelSignal"] ) -> ("BundleSignal", typing.Dict[str, "ChannelSignal"]): diff --git a/frontends/PyCDE/test/test_esi.py b/frontends/PyCDE/test/test_esi.py index 5a7319655ca5..7bb70cea54e3 100644 --- a/frontends/PyCDE/test/test_esi.py +++ b/frontends/PyCDE/test/test_esi.py @@ -4,20 +4,20 @@ from pycde import (Clock, Input, InputChannel, OutputChannel, Module, generator, types) from pycde import esi -from pycde.common import AppID, Output, RecvBundle, SendBundle +from pycde.common import AppID, RecvBundle, SendBundle from pycde.constructs import Wire +from pycde.esi import MMIO +from pycde.module import Metadata from pycde.types import (Bits, Bundle, BundledChannel, Channel, - ChannelDirection, ChannelSignaling, UInt, ClockType) + ChannelDirection, UInt, ClockType) from pycde.testing import unittestmodule -from pycde.signals import BitVectorSignal, ChannelSignal TestBundle = Bundle([ - BundledChannel("resp", ChannelDirection.TO, Bits(16)), - BundledChannel("req", ChannelDirection.FROM, Bits(24)) + BundledChannel("resp", ChannelDirection.FROM, Bits(16)), + BundledChannel("req", ChannelDirection.TO, Bits(24)) ]) -TestFromBundle = Bundle( - [BundledChannel("ch1", ChannelDirection.FROM, Bits(32))]) +TestFromBundle = Bundle([BundledChannel("ch1", ChannelDirection.TO, Bits(32))]) @esi.ServiceDecl @@ -26,10 +26,13 @@ class HostComms: from_host = TestFromBundle +# CHECK: esi.manifest.sym @LoopbackInOutTop name "LoopbackInOut" {{.*}}version "0.1" {bar = "baz", foo = 1 : i64} + + # CHECK-LABEL: hw.module @LoopbackInOutTop(in %clk : !seq.clock, in %rst : i1) # CHECK: esi.service.instance #esi.appid<"cosim"[0]> svc @HostComms impl as "cosim"(%clk, %rst) : (!seq.clock, i1) -> () -# CHECK: %bundle, %req = esi.bundle.pack %chanOutput : !esi.bundle<[!esi.channel to "resp", !esi.channel from "req"]> -# CHECK: esi.service.req.to_server %bundle -> <@HostComms::@req_resp>(#esi.appid<"loopback_inout"[0]>) : !esi.bundle<[!esi.channel to "resp", !esi.channel from "req"]> +# CHECK: [[B0:%.+]] = esi.service.req <@HostComms::@req_resp>(#esi.appid<"loopback_inout"[0]>) : !esi.bundle<[!esi.channel from "resp", !esi.channel to "req"]> +# CHECK: %req = esi.bundle.unpack %chanOutput from [[B0]] : !esi.bundle<[!esi.channel from "resp", !esi.channel to "req"]> # CHECK: %rawOutput, %valid = esi.unwrap.vr %req, %ready : i24 # CHECK: [[R0:%.+]] = comb.extract %rawOutput from 0 : (i24) -> i16 # CHECK: %chanOutput, %ready = esi.wrap.vr [[R0]], %valid : i16 @@ -38,15 +41,24 @@ class LoopbackInOutTop(Module): clk = Clock() rst = Input(types.i1) + metadata = Metadata( + name="LoopbackInOut", + version="0.1", + misc={ + "foo": 1, + "bar": "baz" + }, + ) + @generator def construct(self): # Use Cosim to implement the 'HostComms' service. esi.Cosim(HostComms, self.clk, self.rst) loopback = Wire(types.channel(types.i16)) - call_bundle, froms = TestBundle.pack(resp=loopback) + call_bundle = HostComms.req_resp(AppID("loopback_inout", 0)) + froms = call_bundle.unpack(resp=loopback) from_host = froms['req'] - HostComms.req_resp(call_bundle, AppID("loopback_inout", 0)) ready = Wire(types.i1) wide_data, valid = from_host.unwrap(ready) @@ -56,6 +68,46 @@ def construct(self): loopback.assign(data_chan) +CallBundle = Bundle([ + BundledChannel("result", ChannelDirection.FROM, Bits(16)), + BundledChannel("arg", ChannelDirection.TO, Bits(24)) +]) + + +# CHECK-LABEL: hw.module @LoopbackCall(in %clk : !seq.clock, in %rst : i1) attributes {output_file = #hw.output_file<"LoopbackCall.sv", includeReplicatedOps>} { +# CHECK-NEXT: [[R0:%.+]] = esi.service.req <@_FuncService::@call>(#esi.appid<"loopback">) : !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> +# CHECK-NEXT: %arg = esi.bundle.unpack %chanOutput from [[R0]] : !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> +# CHECK-NEXT: %rawOutput, %valid = esi.unwrap.vr %arg, %ready : i24 +# CHECK-NEXT: [[R1:%.+]] = comb.extract %rawOutput from 0 : (i24) -> i16 +# CHECK-NEXT: %chanOutput, %ready = esi.wrap.vr [[R1]], %valid : i16 +# CHECK-NEXT: hw.output +# CHECK-NEXT: } +# CHECK-NEXT: esi.service.std.func @_FuncService +@unittestmodule(print=True) +class LoopbackCall(Module): + clk = Clock() + rst = Input(Bits(1)) + + metadata = Metadata( + name="LoopbackCall", + version="0.1", + ) + + @generator + def construct(self): + loopback = Wire(types.channel(types.i16)) + args = esi.FuncService.get_call_chans(name=AppID("loopback"), + arg_type=Bits(24), + result=loopback) + + ready = Wire(types.i1) + wide_data, valid = args.unwrap(ready) + data = wide_data[0:16] + data_chan, data_ready = loopback.type.wrap(data, valid) + ready.assign(data_ready) + loopback.assign(data_chan) + + class Producer(Module): clk = Clock() int_out = OutputChannel(types.i32) @@ -126,3 +178,23 @@ class RecvBundleTest(Module): def build(self): to_channels = self.b_recv.unpack(resp=self.i1_in) self.s1_out = to_channels['req'] + + +# CHECK-LABEL: hw.module @MMIOReq() +# CHECK-NEXT: %c0_i32 = hw.constant 0 : i32 +# CHECK-NEXT: %false = hw.constant false +# CHECK-NEXT: [[B:%.+]] = esi.service.req <@MMIO::@read>(#esi.appid<"mmio_req">) : !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]> +# CHECK-NEXT: %chanOutput, %ready = esi.wrap.vr %c0_i32, %false : i32 +# CHECK-NEXT: %offset = esi.bundle.unpack %chanOutput from [[B]] : !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]> +@unittestmodule(esi_sys=True) +class MMIOReq(Module): + + @generator + def build(ports): + c32 = Bits(32)(0) + c1 = Bits(1)(0) + + read_bundle = MMIO.read(AppID("mmio_req")) + + data, _ = Channel(Bits(32)).wrap(c32, c1) + _ = read_bundle.unpack(data=data) diff --git a/frontends/PyCDE/test/test_esi_servicegens.py b/frontends/PyCDE/test/test_esi_servicegens.py index 33c09470b93d..5de81b942b2d 100644 --- a/frontends/PyCDE/test/test_esi_servicegens.py +++ b/frontends/PyCDE/test/test_esi_servicegens.py @@ -1,20 +1,20 @@ -# XFAIL: * # RUN: rm -rf %t # RUN: %PYTHON% %s %t 2>&1 | FileCheck %s -from pycde import (Clock, Input, InputChannel, OutputChannel, Module, generator, - types) +from pycde import (Clock, Input, Module, System, generator) from pycde import esi -from pycde.common import AppID, Output, RecvBundle, SendBundle +from pycde.common import AppID, Output from pycde.constructs import Wire from pycde.types import (Bits, Bundle, BundledChannel, Channel, - ChannelDirection, ChannelSignaling, UInt, ClockType) + ChannelDirection) from pycde.testing import unittestmodule -from pycde.signals import BitVectorSignal, ChannelSignal +from pycde.signals import BitsSignal, ChannelSignal + +from typing import Dict TestBundle = Bundle([ - BundledChannel("resp", ChannelDirection.TO, Bits(16)), - BundledChannel("req", ChannelDirection.FROM, Bits(24)) + BundledChannel("resp", ChannelDirection.FROM, Bits(16)), + BundledChannel("req", ChannelDirection.TO, Bits(24)) ]) @@ -27,11 +27,11 @@ class LoopbackInOut(Module): @generator def construct(self): - loopback = Wire(types.channel(types.i16)) - call_bundle, froms = TestBundle.pack(resp=loopback) + loopback = Wire(Channel(Bits(16))) + call_bundle = HostComms.req_resp(AppID("loopback_inout")) + froms = call_bundle.unpack(resp=loopback) from_host = froms['req'] - HostComms.req_resp(call_bundle, AppID("loopback_inout", 0)) - ready = Wire(types.i1) + ready = Wire(Bits(1)) wide_data, valid = from_host.unwrap(ready) data = wide_data[0:16] data_chan, data_ready = loopback.type.wrap(data, valid) @@ -41,32 +41,46 @@ def construct(self): class MultiplexerService(esi.ServiceImplementation): clk = Clock() - rst = Input(types.i1) + rst = Input(Bits(1)) # Underlying channel is an untyped, 256-bit LI channel. - trunk_in = Input(types.i256) - trunk_in_valid = Input(types.i1) - trunk_in_ready = Output(types.i1) - trunk_out = Output(types.i256) - trunk_out_valid = Output(types.i1) - trunk_out_ready = Input(types.i1) + trunk_in = Input(Bits(256)) + trunk_in_valid = Input(Bits(1)) + trunk_in_ready = Output(Bits(1)) + trunk_out = Output(Bits(256)) + trunk_out_valid = Output(Bits(1)) + trunk_out_ready = Input(Bits(1)) @generator - def generate(self, bundles): - - input_reqs = channels.to_server_reqs - if len(input_reqs) > 1: + def generate(self, bundles: esi._ServiceGeneratorBundles): + assert len( + bundles.to_client_reqs) == 1, "Only one connection request supported" + bundle = bundles.to_client_reqs[0] + to_req_types = {} + for bundled_chan in bundle.type.channels: + if bundled_chan.direction == ChannelDirection.TO: + to_req_types[bundled_chan.name] = bundled_chan.channel + + to_channels = MultiplexerService._generate_to(self, to_req_types) + bundle_sig, from_channels = bundle.type.pack(**to_channels) + bundle.assign(bundle_sig) + MultiplexerService._generate_from(self, from_channels) + + def _generate_from(self, from_reqs): + if len(from_reqs) > 1: raise Exception("Multiple to_server requests not supported") - MultiplexerService.unwrap_and_pad(self, input_reqs[0]) - - output_reqs = channels.to_client_reqs - if len(output_reqs) > 1: - raise Exception("Multiple to_client requests not supported") - output_req = output_reqs[0] - output_chan, ready = MultiplexerService.slice_and_wrap( - self, output_req.type) - output_req.assign(output_chan) + for _, chan in from_reqs: + MultiplexerService.unwrap_and_pad(self, chan) + + def _generate_to( + self, to_req_types: Dict[str, Channel]) -> Dict[str, ChannelSignal]: + if len(to_req_types) > 1: + raise Exception("Multiple TO channels not supported") + chan_name = list(to_req_types.keys())[0] + output_type = to_req_types[chan_name] + output_chan, ready = MultiplexerService.slice_and_wrap(self, output_type) self.trunk_in_ready = ready + return {chan_name: output_chan} @staticmethod def slice_and_wrap(ports, channel_type: Channel): @@ -80,7 +94,7 @@ def unwrap_and_pad(ports, input_channel: ChannelSignal): Unwrap the input channel and pad it to 256 bits. """ (data, valid) = input_channel.unwrap(ports.trunk_out_ready) - assert isinstance(data, BitVectorSignal) + assert isinstance(data, BitsSignal) assert len(data) <= 256 ports.trunk_out = data.pad_or_truncate(256) ports.trunk_out_valid = valid @@ -89,17 +103,19 @@ def unwrap_and_pad(ports, input_channel: ChannelSignal): @unittestmodule(run_passes=True, print_after_passes=True, emit_outputs=True) class MultiplexerTop(Module): clk = Clock() - rst = Input(types.i1) + rst = Input(Bits(1)) - trunk_in = Input(types.i256) - trunk_in_valid = Input(types.i1) - trunk_in_ready = Output(types.i1) - trunk_out = Output(types.i256) - trunk_out_valid = Output(types.i1) - trunk_out_ready = Input(types.i1) + trunk_in = Input(Bits(256)) + trunk_in_valid = Input(Bits(1)) + trunk_in_ready = Output(Bits(1)) + trunk_out = Output(Bits(256)) + trunk_out_valid = Output(Bits(1)) + trunk_out_ready = Input(Bits(1)) @generator def construct(ports): + System.current().platform = "cosim" + m = MultiplexerService(HostComms, appid=AppID("mux", 0), clk=ports.clk, @@ -115,40 +131,15 @@ def construct(ports): LoopbackInOut() -class PassUpService(esi.ServiceImplementation): - - @generator - def generate(self, channels): - for req in channels.to_server_reqs: - name = "out_" + "_".join(req.client_name) - esi.PureModule.output_port(name, req) - for req in channels.to_client_reqs: - name = "in_" + "_".join(req.client_name) - req.assign(esi.PureModule.input_port(name, req.type)) - - -# 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) -# CHECK-NEXT: hw.output %Producer.loopback_in_ready, %prod2.loopback_in_ready, %Consumer.loopback_out, %Consumer.loopback_out_valid, %prod2.int_out, %prod2.int_out_valid : i1, i1, i32, i1, i32, i1 -@unittestmodule(run_passes=True, print_after_passes=True, emit_outputs=True) -class PureTest(esi.PureModule): - - @generator - def construct(ports): - PassUpService(None) - - 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") - esi.PureModule.output_port("p2_int", p2.int_out) - esi.PureModule.param("FOO", Bits(5)) - esi.PureModule.param("STR") - - -ExStruct = types.struct({ - 'a': Bits(4), - 'b': UInt(32), -}) +# CHECK-LABEL: hw.module @MultiplexerTop(in %clk : i1, 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) attributes {output_file = #hw.output_file<"MultiplexerTop.sv", includeReplicatedOps>} { +# CHECK: %c0_i240 = hw.constant 0 : i240 +# CHECK: [[R0:%.+]] = comb.extract %trunk_in from 0 {sv.namehint = "trunk_in_0upto24"} : (i256) -> i24 +# CHECK: [[R1:%.+]] = comb.concat %c0_i240, %LoopbackInOut.loopback_inout_resp : i240, i16 +# CHECK: %LoopbackInOut.loopback_inout_req_ready, %LoopbackInOut.loopback_inout_resp, %LoopbackInOut.loopback_inout_resp_valid = hw.instance "LoopbackInOut" sym @LoopbackInOut @LoopbackInOut(loopback_inout_req: [[R0]]: i24, loopback_inout_req_valid: %trunk_in_valid: i1, loopback_inout_resp_ready: %trunk_out_ready: i1) -> (loopback_inout_req_ready: i1, loopback_inout_resp: i16, loopback_inout_resp_valid: i1) +# CHECK: hw.instance "__manifest" @__ESIManifest() -> () +# CHECK: hw.output %LoopbackInOut.loopback_inout_req_ready, [[R1]], %LoopbackInOut.loopback_inout_resp_valid : i1, i256, i1 +# CHECK: } +# CHECK-LABEL: hw.module @LoopbackInOut(in %loopback_inout_req : i24, in %loopback_inout_req_valid : i1, in %loopback_inout_resp_ready : i1, out loopback_inout_req_ready : i1, out loopback_inout_resp : i16, out loopback_inout_resp_valid : i1) attributes {output_file = #hw.output_file<"LoopbackInOut.sv", includeReplicatedOps>} { +# CHECK: [[R0:%.+]] = comb.extract %loopback_inout_req from 0 : (i24) -> i16 +# CHECK: hw.output %loopback_inout_resp_ready, [[R0]], %loopback_inout_req_valid : i1, i16, i1 +# CHECK: } diff --git a/frontends/PyCDE/test/test_import_hw_modules.py b/frontends/PyCDE/test/test_import_hw_modules.py index 60bc3c82b2b1..18129aa5d279 100644 --- a/frontends/PyCDE/test/test_import_hw_modules.py +++ b/frontends/PyCDE/test/test_import_hw_modules.py @@ -1,4 +1,3 @@ -# XFAIL: * # RUN: %PYTHON% %s %t | FileCheck %s from pycde.circt.ir import Module as IrModule @@ -10,12 +9,12 @@ import sys mlir_module = IrModule.parse(""" -hw.module @add(%a: i1, %b: i1) -> (out: i1) { +hw.module @add(in %a: i1, in %b: i1, out out: i1) { %0 = comb.add %a, %b : i1 hw.output %0 : i1 } -hw.module @and(%a: i1, %b: i1) -> (out: i1) { +hw.module @and(in %a: i1, in %b: i1, out out: i1) { %0 = comb.and %a, %b : i1 hw.output %0 : i1 } @@ -47,16 +46,16 @@ def generate(ports): system = System([Top], output_directory=sys.argv[1]) system.generate() -# 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: hw.module @Top(in %a : i1, in %b : i1, out out0 : i1, out out1 : i1) +# CHECK: %add.out = hw.instance "add" sym @add @add(a: %a: i1, b: %b: i1) -> (out: i1) +# CHECK: %and.out = hw.instance "and" sym @and @and(a: %a: i1, b: %b: i1) -> (out: i1) # CHECK: hw.output %add.out, %and.out : i1, i1 -# CHECK: hw.module @add(%a: i1, %b: i1) -> (out: i1) +# CHECK: hw.module @add(in %a : i1, in %b : i1, out out : i1) # CHECK: %0 = comb.add %a, %b : i1 # CHECK: hw.output %0 : i1 -# CHECK: hw.module @and(%a: i1, %b: i1) -> (out: i1) +# CHECK: hw.module @and(in %a : i1, in %b : i1, out out : i1) # CHECK: %0 = comb.and %a, %b : i1 # CHECK: hw.output %0 : i1 system.print() diff --git a/frontends/PyCDE/test/test_instances.py b/frontends/PyCDE/test/test_instances.py index e450d0b706e3..2198c3ca084b 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: +# CHECK: Test.print() -# CHECK: )] outputs: [('y', Bits<1>)]> +# CHECK: ] outputs: [output 'y': Bits<1>]> UnParameterized.print() print(PhysLocation(PrimitiveType.DSP, 39, 25)) diff --git a/frontends/PyCDE/test/test_polynomial.py b/frontends/PyCDE/test/test_polynomial.py index c5d31edc83c7..a8563ca5a4bb 100755 --- a/frontends/PyCDE/test/test_polynomial.py +++ b/frontends/PyCDE/test/test_polynomial.py @@ -109,7 +109,7 @@ def construct(self): m.name = "pexternInst" w1.assign(0) - self._set_outputs(poly._outputs()) + self._set_outputs(poly.outputs()) poly = pycde.System([PolynomialSystem], diff --git a/frontends/PyCDE/test/test_pycde_values.py b/frontends/PyCDE/test/test_pycde_values.py index 4ed76d5356b1..c72328880655 100644 --- a/frontends/PyCDE/test/test_pycde_values.py +++ b/frontends/PyCDE/test/test_pycde_values.py @@ -1,7 +1,7 @@ # RUN: %PYTHON% %s | FileCheck %s from pycde.dialects import comb, hw -from pycde import dim, generator, types, Input, Output, Module +from pycde import dim, generator, types, Clock, Input, Output, Module from pycde.signals import And, Or from pycde.testing import unittestmodule @@ -64,9 +64,10 @@ def construct(mod): return Mod -# CHECK-LABEL: hw.module @ArrayMod(in %inp : !hw.array<5xi1>) +# CHECK-LABEL: hw.module @ArrayMod(in %clk : !seq.clock, in %inp : !hw.array<5xi1>) @unittestmodule() class ArrayMod(Module): + clk = Clock() inp = Input(dim(types.i1, 5)) @generator @@ -110,3 +111,9 @@ def construct(ports): # CHECK: %16 = comb.and bin %12, %13, %14 : i1 And(a, b, c) + + # CHECK: hw.bitcast %inp : (!hw.array<5xi1>) -> i5 + ports.inp.bitcast(types.i5) + + # CHECK: seq.from_clock %clk + ports.clk.to_bit() diff --git a/frontends/PyCDE/test/test_xrt.py b/frontends/PyCDE/test/test_xrt.py index 1527556333e1..4c56ad9b9d11 100644 --- a/frontends/PyCDE/test/test_xrt.py +++ b/frontends/PyCDE/test/test_xrt.py @@ -1,16 +1,16 @@ # RUN: rm -rf %t # RUN: %PYTHON% %s %t 2>&1 -# RUN: ls %t/hw/top.sv +# RUN: ls %t/hw/XrtTop.sv # RUN: ls %t/hw/Main.sv # RUN: ls %t/hw/ESILoopback.tcl # RUN: ls %t/hw/filelist.f # RUN: ls %t/hw/xsim.tcl # RUN: ls %t/hw/xrt_package.tcl -# RUN: ls %t/Makefile.xrt +# RUN: ls %t/Makefile.xrt.mk # RUN: ls %t/xrt.ini # RUN: ls %t/xsim.tcl -# RUN: FileCheck %s --input-file %t/hw/top.sv --check-prefix=TOP +# RUN: FileCheck %s --input-file %t/hw/XrtTop.sv --check-prefix=TOP import pycde from pycde import Clock, Input, Module, generator, types @@ -37,16 +37,16 @@ def construct(ports): s.compile() s.package() -# TOP-LABEL: module top( +# TOP-LABEL: module XrtTop( # TOP: input ap_clk, # TOP: ap_resetn, # TOP: s_axi_control_AWVALID, -# TOP: input [31:0] s_axi_control_AWADDR, +# TOP: input [19:0] s_axi_control_AWADDR, # TOP: input s_axi_control_WVALID, # TOP: input [31:0] s_axi_control_WDATA, # TOP: input [3:0] s_axi_control_WSTRB, # TOP: input s_axi_control_ARVALID, -# TOP: input [31:0] s_axi_control_ARADDR, +# TOP: input [19:0] s_axi_control_ARADDR, # TOP: input s_axi_control_RREADY, # TOP: s_axi_control_BREADY, # TOP: output s_axi_control_AWREADY, @@ -58,25 +58,15 @@ def construct(ports): # TOP: output s_axi_control_BVALID, # TOP: output [1:0] s_axi_control_BRESP -# TOP: XrtService XrtService ( -# TOP: .clk (ap_clk), -# TOP: .rst (~ap_resetn), -# TOP: .axil_in (_GEN), -# TOP: .axil_out (_XrtService_axil_out) +# TOP: __ESI_Manifest_ROM ESI_Manifest_ROM ( +# TOP: .clk (ap_clk), +# TOP: .address (rom_address), +# TOP: .data (_ESI_Manifest_ROM_data) # TOP: ); # TOP: Main Main ( # TOP: .clk (ap_clk), -# TOP: .rst (~ap_resetn) +# TOP: .rst (inv_ap_resetn) # TOP: ); -# TOP: assign s_axi_control_AWREADY = _XrtService_axil_out.awready; -# TOP: assign s_axi_control_WREADY = _XrtService_axil_out.wready; -# TOP: assign s_axi_control_ARREADY = _XrtService_axil_out.arready; -# TOP: assign s_axi_control_RVALID = _XrtService_axil_out.rvalid; -# TOP: assign s_axi_control_RDATA = _XrtService_axil_out.rdata; -# TOP: assign s_axi_control_RRESP = _XrtService_axil_out.rresp; -# TOP: assign s_axi_control_BVALID = _XrtService_axil_out.bvalid; -# TOP: assign s_axi_control_BRESP = _XrtService_axil_out.bresp; - # TOP: endmodule diff --git a/include/circt-c/Dialect/Comb.h b/include/circt-c/Dialect/Comb.h index e31f49b39217..33e8511bcd38 100644 --- a/include/circt-c/Dialect/Comb.h +++ b/include/circt-c/Dialect/Comb.h @@ -15,6 +15,7 @@ extern "C" { #endif +MLIR_CAPI_EXPORTED void registerCombPasses(void); MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(Combinational, comb); #ifdef __cplusplus diff --git a/include/circt-c/Dialect/FIRRTL.h b/include/circt-c/Dialect/FIRRTL.h index 171c0ff508e3..6baba2a0c263 100644 --- a/include/circt-c/Dialect/FIRRTL.h +++ b/include/circt-c/Dialect/FIRRTL.h @@ -55,6 +55,14 @@ typedef enum FIRRTLEventControl { FIRRTL_EVENT_CONTROL_AT_EDGE, } FIRRTLEventControl; +// NOLINTNEXTLINE(modernize-use-using) +typedef enum FIRRTLValueFlow { + FIRRTL_VALUE_FLOW_NONE, + FIRRTL_VALUE_FLOW_SOURCE, + FIRRTL_VALUE_FLOW_SINK, + FIRRTL_VALUE_FLOW_DUPLEX, +} FIRRTLValueFlow; + // NOLINTNEXTLINE(modernize-use-using) typedef struct FIRRTLBundleField { MlirIdentifier name; @@ -138,6 +146,18 @@ MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetMemDir(MlirContext ctx, MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetEventControl(MlirContext ctx, FIRRTLEventControl eventControl); +//===----------------------------------------------------------------------===// +// Utility API. +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED FIRRTLValueFlow firrtlValueFoldFlow(MlirValue value, + FIRRTLValueFlow flow); + +MLIR_CAPI_EXPORTED bool +firrtlImportAnnotationsFromJSONRaw(MlirContext ctx, + MlirStringRef annotationsStr, + MlirAttribute *importedAnnotationsArray); + #ifdef __cplusplus } #endif diff --git a/include/circt-c/Dialect/HW.h b/include/circt-c/Dialect/HW.h index c03a9749d03b..4b2d7b3abb59 100644 --- a/include/circt-c/Dialect/HW.h +++ b/include/circt-c/Dialect/HW.h @@ -15,6 +15,17 @@ extern "C" { #endif +#define DEFINE_C_API_STRUCT(name, storage) \ + struct name { \ + storage *ptr; \ + }; \ + typedef struct name name + +DEFINE_C_API_STRUCT(HWInstanceGraph, void); +DEFINE_C_API_STRUCT(HWInstanceGraphNode, void); + +#undef DEFINE_C_API_STRUCT + struct HWStructFieldInfo { MlirIdentifier name; MlirType type; @@ -179,6 +190,34 @@ MLIR_CAPI_EXPORTED bool hwAttrIsAOutputFileAttr(MlirAttribute); MLIR_CAPI_EXPORTED MlirAttribute hwOutputFileGetFromFileName( MlirAttribute text, bool excludeFromFileList, bool includeReplicatedOp); +//===----------------------------------------------------------------------===// +// InstanceGraph API. +//===----------------------------------------------------------------------===// + +MLIR_CAPI_EXPORTED HWInstanceGraph hwInstanceGraphGet(MlirOperation operation); + +MLIR_CAPI_EXPORTED void hwInstanceGraphDestroy(HWInstanceGraph instanceGraph); + +MLIR_CAPI_EXPORTED HWInstanceGraphNode +hwInstanceGraphGetTopLevelNode(HWInstanceGraph instanceGraph); + +// NOLINTNEXTLINE(modernize-use-using) +typedef void (*HWInstanceGraphNodeCallback)(HWInstanceGraphNode, void *); + +MLIR_CAPI_EXPORTED void +hwInstanceGraphForEachNode(HWInstanceGraph instanceGraph, + HWInstanceGraphNodeCallback callback, + void *userData); + +MLIR_CAPI_EXPORTED bool hwInstanceGraphNodeEqual(HWInstanceGraphNode lhs, + HWInstanceGraphNode rhs); + +MLIR_CAPI_EXPORTED MlirModule +hwInstanceGraphNodeGetModule(HWInstanceGraphNode node); + +MLIR_CAPI_EXPORTED MlirOperation +hwInstanceGraphNodeGetModuleOp(HWInstanceGraphNode node); + #ifdef __cplusplus } #endif diff --git a/include/circt-c/Firtool/Firtool.h b/include/circt-c/Firtool/Firtool.h index 4cf9e34450eb..d3407a02b332 100644 --- a/include/circt-c/Firtool/Firtool.h +++ b/include/circt-c/Firtool/Firtool.h @@ -61,7 +61,7 @@ typedef enum CirctFirtoolRandomKind { } CirctFirtoolRandomKind; MLIR_CAPI_EXPORTED CirctFirtoolFirtoolOptions -circtFirtoolOptionsCreateDefault(); +circtFirtoolOptionsCreateDefault(void); MLIR_CAPI_EXPORTED void circtFirtoolOptionsDestroy(CirctFirtoolFirtoolOptions options); diff --git a/include/circt/Conversion/ExportVerilog.h b/include/circt/Conversion/ExportVerilog.h index 836148a5e170..6aa7eb1fa607 100644 --- a/include/circt/Conversion/ExportVerilog.h +++ b/include/circt/Conversion/ExportVerilog.h @@ -22,6 +22,7 @@ std::unique_ptr createTestApplyLoweringOptionPass(llvm::StringRef options); std::unique_ptr createTestApplyLoweringOptionPass(); +std::unique_ptr createHWLowerInstanceChoicesPass(); std::unique_ptr createPrepareForEmissionPass(); std::unique_ptr createLegalizeAnonEnumsPass(); diff --git a/include/circt/Conversion/ImportVerilog.h b/include/circt/Conversion/ImportVerilog.h index 788fba03c08a..59861ba0a843 100644 --- a/include/circt/Conversion/ImportVerilog.h +++ b/include/circt/Conversion/ImportVerilog.h @@ -34,11 +34,16 @@ namespace circt { /// `Driver::addStandardArgs()` for some inspiration on how to expose these on /// the command line. struct ImportVerilogOptions { - /// Only lint the input, without elaboration and lowering to CIRCT IR. - bool onlyLint = false; - - /// Only parse and elaborate the input, without mapping to CIRCT IR. - bool onlyParse = false; + /// Limit importing to linting or parsing only. + enum class Mode { + /// Only lint the input, without elaboration and lowering to CIRCT IR. + OnlyLint, + /// Only parse and elaborate the input, without mapping to CIRCT IR. + OnlyParse, + /// Perform a full import and mapping to CIRCT IR. + Full + }; + Mode mode = Mode::Full; //===--------------------------------------------------------------------===// // Include paths diff --git a/include/circt/Conversion/MooreToCore.h b/include/circt/Conversion/MooreToCore.h index 20912eb58e98..2fb82eb3f441 100644 --- a/include/circt/Conversion/MooreToCore.h +++ b/include/circt/Conversion/MooreToCore.h @@ -14,12 +14,36 @@ #ifndef CIRCT_CONVERSION_MOORETOCORE_H #define CIRCT_CONVERSION_MOORETOCORE_H +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/Moore/MooreOps.h" #include "circt/Support/LLVM.h" #include namespace circt { +/// Stores port interface into data structure to help convert moore module +/// structure to hw module structure. +struct MoorePortInfo { + std::unique_ptr hwPorts; -/// Create an Moore to Comb/HW/LLHD conversion pass. + // A mapping between the port name, port op and port type in moore module. + DenseMap> inputsPort, outputsPort; + + // Constructor + MoorePortInfo(moore::SVModuleOp moduleOp); +}; + +using MoorePortInfoMap = DenseMap; + +/// Get the Moore structure operations to HW conversion patterns. +void populateMooreStructureConversionPatterns(TypeConverter &typeConverter, + RewritePatternSet &patterns, + MoorePortInfoMap &portInfoMap); + +/// Get the Moore to HW/Comb/Seq conversion patterns. +void populateMooreToCoreConversionPatterns(TypeConverter &typeConverter, + RewritePatternSet &patterns); + +/// Create an Moore to HW/Comb/Seq conversion pass. std::unique_ptr> createConvertMooreToCorePass(); } // namespace circt diff --git a/include/circt/Conversion/Passes.h b/include/circt/Conversion/Passes.h index 897666d4c669..6691e467b8b6 100644 --- a/include/circt/Conversion/Passes.h +++ b/include/circt/Conversion/Passes.h @@ -40,6 +40,7 @@ #include "circt/Conversion/PipelineToHW.h" #include "circt/Conversion/SCFToCalyx.h" #include "circt/Conversion/SeqToSV.h" +#include "circt/Conversion/SimToSV.h" #include "circt/Conversion/VerifToSV.h" #include "mlir/IR/DialectRegistry.h" #include "mlir/Pass/Pass.h" diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 97f892700d2b..7792f4e7671e 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -104,6 +104,20 @@ def LegalizeAnonEnums : Pass<"legalize-anon-enums", "mlir::ModuleOp"> { ]; } +def HWLowerInstanceChoices : Pass<"hw-lower-instance-choices", + "mlir::ModuleOp"> { + let summary = "Prepare the collateral for instance choice emission"; + let description = [{ + This pass runs as part of verilog emission. + It introduces the macros & file lists to which instance choices lower to. + }]; + + let constructor = "createHWLowerInstanceChoicesPass()"; + let dependentDialects = [ + "circt::sv::SVDialect", "circt::hw::HWDialect" + ]; +} + def PrepareForEmission : Pass<"prepare-for-emission", "hw::HWModuleOp"> { let summary = "Prepare IR for ExportVerilog"; @@ -388,7 +402,7 @@ def CalyxRemoveGroupsFromFSM : Pass<"calyx-remove-groups-fsm", "calyx::Component //===----------------------------------------------------------------------===// def ConvertFSMToSV : Pass<"convert-fsm-to-sv", "mlir::ModuleOp"> { - let summary = "Convert FSM to HW"; + let summary = "Convert FSM to SV and HW"; let constructor = "circt::createConvertFSMToSVPass()"; let dependentDialects = ["circt::hw::HWDialect", "circt::comb::CombDialect", "circt::seq::SeqDialect", "circt::sv::SVDialect"]; @@ -406,7 +420,8 @@ def LowerFIRRTLToHW : Pass<"lower-firrtl-to-hw", "mlir::ModuleOp"> { let constructor = "circt::createLowerFIRRTLToHWPass()"; let dependentDialects = ["comb::CombDialect", "hw::HWDialect", "seq::SeqDialect", "sv::SVDialect", - "ltl::LTLDialect", "verif::VerifDialect"]; + "ltl::LTLDialect", "verif::VerifDialect", + "sim::SimDialect"]; let options = [ Option<"enableAnnotationWarning", "warn-on-unprocessed-annotations", "bool", "false", @@ -468,19 +483,17 @@ def HandshakeToHW : Pass<"lower-handshake-to-hw", "mlir::ModuleOp"> { } //===----------------------------------------------------------------------===// -// Moore to Comb/HW/LLHD +// Moore to HW/Comb/Seq //===----------------------------------------------------------------------===// def ConvertMooreToCore : Pass<"convert-moore-to-core", "mlir::ModuleOp"> { let summary = "Convert Moore to Core"; let description = [{ - This pass translates Moore to the core dialects (Comb/HW/LLHD). + This pass translates Moore to the core dialects (HW/Comb/Seq). }]; let constructor = "circt::createConvertMooreToCorePass()"; - let dependentDialects = ["comb::CombDialect", "hw::HWDialect", - "llhd::LLHDDialect"]; + let dependentDialects = ["comb::CombDialect", "hw::HWDialect", "seq::SeqDialect"]; } - //===----------------------------------------------------------------------===// // LLHDToLLVM //===----------------------------------------------------------------------===// @@ -570,7 +583,7 @@ def LowerHWToSV : Pass<"lower-hw-to-sv", "hw::HWModuleOp"> { def ConvertHWToBTOR2 : Pass<"convert-hw-to-btor2", "hw::HWModuleOp"> { let summary = "Convert HW to BTOR2"; let description = [{ - This pass converts a HW module into a state transition system that is then + This pass converts a HW module into a state transition system that is then directly used to emit btor2. The output of this pass is thus a btor2 string. }]; let constructor = "circt::createConvertHWToBTOR2Pass()"; @@ -691,4 +704,18 @@ def LowerFirMem : Pass<"lower-seq-firmem", "mlir::ModuleOp"> { let dependentDialects = ["circt::hw::HWDialect"]; } +//===----------------------------------------------------------------------===// +// ConvertSimToSV +//===----------------------------------------------------------------------===// + +def LowerSimToSV: Pass<"lower-sim-to-sv", "hw::HWModuleOp"> { + let summary = "Lower simulator-specific `sim` ops to SV."; + let constructor = "circt::createLowerSimToSVPass()"; + let dependentDialects = [ + "circt::comb::CombDialect", + "circt::sv::SVDialect", + "circt::hw::HWDialect" + ]; +} + #endif // CIRCT_CONVERSION_PASSES_TD diff --git a/include/circt/Conversion/SimToSV.h b/include/circt/Conversion/SimToSV.h new file mode 100644 index 000000000000..31dc09aa2715 --- /dev/null +++ b/include/circt/Conversion/SimToSV.h @@ -0,0 +1,28 @@ +//===- SimToSV.h - SV conversion for sim 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 `sim` to `sv` and `hw`. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_SIMTOSV_H +#define CIRCT_CONVERSION_SIMTOSV_H + +#include "circt/Support/LLVM.h" +#include + +namespace circt { + +#define GEN_PASS_DECL_LOWERSIMTOSV +#include "circt/Conversion/Passes.h.inc" + +std::unique_ptr createLowerSimToSVPass(); + +} // namespace circt + +#endif // CIRCT_CONVERSION_SIMTOSV_H diff --git a/include/circt/Dialect/Arc/ArcInterfaces.td b/include/circt/Dialect/Arc/ArcInterfaces.td index 7eb3e211a72a..188199fb9c51 100644 --- a/include/circt/Dialect/Arc/ArcInterfaces.td +++ b/include/circt/Dialect/Arc/ArcInterfaces.td @@ -21,11 +21,10 @@ def ClockedOpInterface : OpInterface<"ClockedOpInterface"> { 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. + StaticInterfaceMethod<[{ + Allows non-clocked counterparts to clocked operations (e.g., `arc.call`) + to implement this interface to simplify the implementation of some + passes. }], "bool", "isClocked", (ins), /*methodBody=*/[{}], @@ -33,18 +32,24 @@ def ClockedOpInterface : OpInterface<"ClockedOpInterface"> { 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. + the clock is defined by the operation with the clocked region, or if the + operation is not clocked as determined by the `isClocked` static + function. }], - "::mlir::Value", "getClock", (ins), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ return $_op.getClock(); }]>, + "::mlir::Value", "getClock">, InterfaceMethod<[{ Removes the clock value, e.g., used when moving a clocked operation into - a clocked region. + a clocked region. If the operation already does not have a clock, this + should be a nop. }], "void", "eraseClock", (ins), /*methodBody=*/[{}], /*defaultImplementation=*/[{ return $_op.getClockMutable().clear(); }]>, + InterfaceMethod<[{ + Returns the latency w.r.t. to the clock returned by the `getClock` + function. + }], + "uint32_t", "getLatency">, ]; } diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td index bb1fef6abdf3..c954a30fdbbf 100644 --- a/include/circt/Dialect/Arc/ArcOps.td +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -132,7 +132,7 @@ def StateOp : ArcOp<"state", [ CallOpInterface, DeclareOpInterfaceMethods, AttrSizedOperandSegments, - DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, ]> { let summary = "State transfer arc"; @@ -147,7 +147,7 @@ def StateOp : ArcOp<"state", [ let assemblyFormat = [{ $arc `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)? - (`reset` $reset^)? `lat` $latency attr-dict + (`reset` $reset^)? `latency` $latency attr-dict `:` functional-type($inputs, results) }]; @@ -230,6 +230,7 @@ def StateOp : ArcOp<"state", [ def CallOp : ArcOp<"call", [ MemRefsNormalizable, Pure, CallOpInterface, + DeclareOpInterfaceMethods, DeclareOpInterfaceMethods ]> { let summary = "calls an arc"; @@ -241,6 +242,13 @@ def CallOp : ArcOp<"call", [ $arc `(` $inputs `)` attr-dict `:` functional-type(operands, results) }]; + let builders = [ + OpBuilder<(ins "DefineOp":$arc, CArg<"mlir::ValueRange", "{}">:$inputs), [{ + build($_builder, $_state, arc.getFunctionType().getResults(), + mlir::SymbolRefAttr::get(arc), inputs); + }]>, + ]; + let extraClassDeclaration = [{ operand_range getArgOperands() { return {operand_begin(), operand_end()}; @@ -318,7 +326,7 @@ def MemoryWritePortOp : ArcOp<"memory_write_port", [ let assemblyFormat = [{ $memory `,` $arc `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)? - (`mask` $mask^)? `lat` $latency attr-dict `:` + (`mask` $mask^)? `latency` $latency attr-dict `:` type($memory) `,` type($inputs) }]; diff --git a/include/circt/Dialect/Arc/ArcPasses.h b/include/circt/Dialect/Arc/ArcPasses.h index 468d5635ee26..9d8468504ad0 100644 --- a/include/circt/Dialect/Arc/ArcPasses.h +++ b/include/circt/Dialect/Arc/ArcPasses.h @@ -46,8 +46,6 @@ 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(); diff --git a/include/circt/Dialect/Arc/ArcPasses.td b/include/circt/Dialect/Arc/ArcPasses.td index c14f9f3ee58b..825abd8488c4 100644 --- a/include/circt/Dialect/Arc/ArcPasses.td +++ b/include/circt/Dialect/Arc/ArcPasses.td @@ -268,15 +268,6 @@ def MuxToControlFlow : Pass<"arc-mux-to-control-flow", "mlir::ModuleOp"> { 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()"; diff --git a/include/circt/Dialect/Arc/ModelInfo.h b/include/circt/Dialect/Arc/ModelInfo.h new file mode 100644 index 000000000000..322f38b774c5 --- /dev/null +++ b/include/circt/Dialect/Arc/ModelInfo.h @@ -0,0 +1,63 @@ +//===- ModelInfo.h - Information about Arc models -------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Defines and computes information about Arc models. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_MODELINFO_H +#define CIRCT_DIALECT_ARC_MODELINFO_H + +#include "mlir/IR/BuiltinOps.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/raw_ostream.h" +#include + +namespace circt { +namespace arc { + +/// Gathers information about a given Arc state. +struct StateInfo { + enum Type { Input, Output, Register, Memory, Wire } type; + std::string name; + unsigned offset; + unsigned numBits; + unsigned memoryStride = 0; // byte separation between memory words + unsigned memoryDepth = 0; // number of words in a memory +}; + +/// Gathers information about a given Arc model. +struct ModelInfo { + std::string name; + size_t numStateBytes; + llvm::SmallVector states; + + ModelInfo(std::string name, size_t numStateBytes, + llvm::SmallVector states) + : name(std::move(name)), numStateBytes(numStateBytes), + states(std::move(states)) {} +}; + +/// Collects information about states within the provided Arc model storage +/// `storage`, assuming default `offset`, and adds it to `states`. +mlir::LogicalResult collectStates(mlir::Value storage, unsigned offset, + llvm::SmallVector &states); + +/// Collects information about all Arc models in the provided `module`, +/// and adds it to `models`. +mlir::LogicalResult collectModels(mlir::ModuleOp module, + llvm::SmallVector &models); + +/// Serializes `models` to `outputStream` in JSON format. +void serializeModelInfoToJson(llvm::raw_ostream &outputStream, + llvm::ArrayRef models); + +} // namespace arc +} // namespace circt + +#endif // CIRCT_DIALECT_ARC_MODELINFO_H diff --git a/include/circt/Dialect/Arc/ModelInfoExport.h b/include/circt/Dialect/Arc/ModelInfoExport.h new file mode 100644 index 000000000000..6b0f305c712a --- /dev/null +++ b/include/circt/Dialect/Arc/ModelInfoExport.h @@ -0,0 +1,32 @@ +//===- ModelInfoExport.h - Exports model info to JSON format --------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Register the MLIR translation to export model info to JSON format. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_MODELINFOEXPORT_H +#define CIRCT_DIALECT_ARC_MODELINFOEXPORT_H + +#include "mlir/IR/BuiltinOps.h" +#include "llvm/Support/raw_ostream.h" + +namespace circt { +namespace arc { + +/// Collects and exports Arc model info to JSON. +mlir::LogicalResult collectAndExportModelInfo(mlir::ModuleOp module, + llvm::raw_ostream &os); + +/// Registers CIRCT translation from Arc to JSON model info. +void registerArcModelInfoTranslation(); + +} // namespace arc +} // namespace circt + +#endif // CIRCT_DIALECT_ARC_MODELINFOEXPORT_H diff --git a/include/circt/Dialect/CMakeLists.txt b/include/circt/Dialect/CMakeLists.txt index a30c22d83d61..4f212153993a 100644 --- a/include/circt/Dialect/CMakeLists.txt +++ b/include/circt/Dialect/CMakeLists.txt @@ -11,6 +11,7 @@ add_subdirectory(Calyx) add_subdirectory(Comb) add_subdirectory(DC) add_subdirectory(Debug) +add_subdirectory(Emit) add_subdirectory(ESI) add_subdirectory(FIRRTL) add_subdirectory(FSM) @@ -27,6 +28,7 @@ add_subdirectory(OM) add_subdirectory(Pipeline) add_subdirectory(Ibis) add_subdirectory(Seq) +add_subdirectory(Sim) add_subdirectory(SSP) add_subdirectory(SV) add_subdirectory(SystemC) diff --git a/include/circt/Dialect/Calyx/CalyxControl.td b/include/circt/Dialect/Calyx/CalyxControl.td index 71d24b29e2f7..53548360401b 100644 --- a/include/circt/Dialect/Calyx/CalyxControl.td +++ b/include/circt/Dialect/Calyx/CalyxControl.td @@ -14,8 +14,11 @@ def ControlLike : NativeOpTrait<"ControlLike"> { let cppNamespace = "::circt::calyx"; } +// ControlOp is a SymbolTable even though ControlLike ops are not Symbols +// because in the lowering pipeline, we add FSM MachineOps under ControlOp, and +// MachineOps are Symbols. See https://github.com/llvm/circt/issues/6667. def ControlOp : CalyxContainer<"control", [ - HasParent<"ComponentOp"> + HasParent<"ComponentOp">, SymbolTable ]> { let summary = "Calyx Control"; let description = [{ diff --git a/include/circt/Dialect/Calyx/CalyxInterfaces.td b/include/circt/Dialect/Calyx/CalyxInterfaces.td index f743c525c0c0..33c743c3b832 100644 --- a/include/circt/Dialect/Calyx/CalyxInterfaces.td +++ b/include/circt/Dialect/Calyx/CalyxInterfaces.td @@ -119,11 +119,13 @@ def CellOpInterface : OpInterface<"CellInterface"> { /*defaultImplementation=*/[{ SmallVector ports; MLIRContext* context = $_op->getContext(); + auto portAttrs = $_op.portAttributes(); + portAttrs.resize($_op->getResults().size()); auto zippedPortInfo = llvm::zip_equal( $_op->getResults(), $_op.portDirections(), $_op.portNames(), - $_op.portAttributes() + portAttrs ); for (auto&& [result, direction, name, attributes] : zippedPortInfo) ports.push_back(PortInfo{ diff --git a/include/circt/Dialect/Calyx/CalyxLoweringUtils.h b/include/circt/Dialect/Calyx/CalyxLoweringUtils.h index a8c0ce521568..d6e249ee4392 100644 --- a/include/circt/Dialect/Calyx/CalyxLoweringUtils.h +++ b/include/circt/Dialect/Calyx/CalyxLoweringUtils.h @@ -546,7 +546,7 @@ class PartialLoweringPattern : public RewritePatternType { // Do the actual rewrite, marking this op as updated. Because the op is // marked as updated, the pattern driver will re-enqueue the op again. - rewriter.updateRootInPlace( + rewriter.modifyOpInPlace( op, [&] { partialPatternRes = partiallyLower(op, rewriter); }); // Mark that this pattern has been applied to this op. @@ -557,7 +557,7 @@ class PartialLoweringPattern : public RewritePatternType { // Hook for subclasses to lower the op using the rewriter. // - // Note that this call is wrapped in `updateRootInPlace`, so any direct IR + // Note that this call is wrapped in `modifyOpInPlace`, so any direct IR // mutations that are legal to apply during a root update of op are allowed. // // Also note that this means the op will be re-enqueued to the greedy @@ -615,7 +615,7 @@ class FuncOpPartialLoweringPattern // Hook for subclasses to lower the op using the rewriter. // - // Note that this call is wrapped in `updateRootInPlace`, so any direct IR + // Note that this call is wrapped in `modifyOpInPlace`, so any direct IR // mutations that are legal to apply during a root update of op are allowed. // // Also note that this means the op will be re-enqueued to the greedy diff --git a/include/circt/Dialect/Calyx/CalyxOps.h b/include/circt/Dialect/Calyx/CalyxOps.h index 6d64506aee34..a34dc811b54c 100644 --- a/include/circt/Dialect/Calyx/CalyxOps.h +++ b/include/circt/Dialect/Calyx/CalyxOps.h @@ -26,6 +26,13 @@ namespace circt { namespace calyx { +// the goPort, donePort, resetPort and clkPort identify the attributes of the +// go, done, reset and clk port of the circuit. +static constexpr std::string_view goPort = "go"; +static constexpr std::string_view donePort = "done"; +static constexpr std::string_view resetPort = "reset"; +static constexpr std::string_view clkPort = "clk"; + /// A helper function to verify each control-like operation /// has a valid parent and, if applicable, body. LogicalResult verifyControlLikeOp(Operation *op); diff --git a/include/circt/Dialect/ESI/ESIChannels.td b/include/circt/Dialect/ESI/ESIChannels.td index a49b60452082..330956c3fbe5 100644 --- a/include/circt/Dialect/ESI/ESIChannels.td +++ b/include/circt/Dialect/ESI/ESIChannels.td @@ -236,9 +236,9 @@ def ChannelBundleType : ESI_Type<"ChannelBundle"> { 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. + When requesting a bundle from a service, the service is always considered + the sender; so, "to" means the service is sending messages to the client and + "from" means the service is receiving messages from the client. }]; let mnemonic = "bundle"; @@ -517,4 +517,11 @@ def NullSourceOp : ESI_Physical_Op<"null", [Pure]> { let assemblyFormat = [{ attr-dict `:` qualified(type($out)) }]; } +def SinkChannelAttr : AttrDef { + let summary = "An attribute which indicates a channel is unconnected."; + let mnemonic = "null"; + let parameters = (ins "TypeAttr":$type); + let assemblyFormat = [{ $type }]; +} + #endif // CIRCT_DIALECT_ESI_CHANNELS_TD diff --git a/include/circt/Dialect/ESI/ESIInterfaces.td b/include/circt/Dialect/ESI/ESIInterfaces.td index c5057345471b..f37ccea5bccb 100644 --- a/include/circt/Dialect/ESI/ESIInterfaces.td +++ b/include/circt/Dialect/ESI/ESIInterfaces.td @@ -109,6 +109,15 @@ def ServiceDeclOpInterface : OpInterface<"ServiceDeclOpInterface"> { "void", "getPortList", (ins "llvm::SmallVectorImpl&":$ports) >, + InterfaceMethod< + "Return a well-known name for this service type.", + "std::optional", "getTypeName", + (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + return {}; + }] + >, InterfaceMethod< "Get info on a particular port.", "FailureOr", "getPortInfo", diff --git a/include/circt/Dialect/ESI/ESIManifest.td b/include/circt/Dialect/ESI/ESIManifest.td index 0df6f9d7a4ae..1fd3ef9089dc 100644 --- a/include/circt/Dialect/ESI/ESIManifest.td +++ b/include/circt/Dialect/ESI/ESIManifest.td @@ -57,14 +57,6 @@ def AppIDPathAttr : ESI_Attr<"AppIDPath"> { }]; } -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]> { @@ -78,12 +70,11 @@ def ServiceRequestRecordOp : ESI_Op<"manifest.req", [ let arguments = (ins AppIDAttr:$requestor, InnerRefAttr:$servicePort, OptionalAttr:$stdService, - BundleDirection:$direction, TypeAttrOf:$bundleType); let assemblyFormat = [{ qualified($requestor) `,` $servicePort (`std` $stdService^)? - `,` $direction `,` $bundleType attr-dict + `,` $bundleType attr-dict }]; let extraClassDeclaration = [{ @@ -209,7 +200,7 @@ def SymbolMetadataOp : ESI_Op<"manifest.sym", [ def BlobAttr : ESI_Attr<"Blob"> { let summary = "A binary blob"; - let parameters = (ins ArrayRefParameter<"char">:$data); + let parameters = (ins ArrayRefParameter<"uint8_t">:$data); let mnemonic = "blob"; let hasCustomAssemblyFormat = 1; } diff --git a/include/circt/Dialect/ESI/ESIOps.h b/include/circt/Dialect/ESI/ESIOps.h index 54f40959cc7d..f3f9ba057ac3 100644 --- a/include/circt/Dialect/ESI/ESIOps.h +++ b/include/circt/Dialect/ESI/ESIOps.h @@ -29,16 +29,8 @@ namespace esi { /// Describes a service port. In the unidirection case, either (but not both) /// type fields will be null. struct ServicePortInfo { - enum class Direction { toClient, toServer }; - hw::InnerRefAttr port; - Direction direction; ChannelBundleType type; - - StringRef directionAsString() { - return direction == ServicePortInfo::Direction::toClient ? "toClient" - : "toServer"; - } }; } // namespace esi diff --git a/include/circt/Dialect/ESI/ESIPasses.h b/include/circt/Dialect/ESI/ESIPasses.h index fa1ff0308500..102c3b337e9b 100644 --- a/include/circt/Dialect/ESI/ESIPasses.h +++ b/include/circt/Dialect/ESI/ESIPasses.h @@ -26,6 +26,7 @@ namespace esi { /// platform-specific lowerings. struct Platform { static constexpr char cosim[] = "cosim"; + static constexpr char fpga[] = "fpga"; }; std::unique_ptr> createESIPhysicalLoweringPass(); diff --git a/include/circt/Dialect/ESI/ESIServices.td b/include/circt/Dialect/ESI/ESIServices.td index 4cf97d3c2831..a0c3e61629d5 100644 --- a/include/circt/Dialect/ESI/ESIServices.td +++ b/include/circt/Dialect/ESI/ESIServices.td @@ -32,8 +32,8 @@ def CustomServiceDeclOp : ESI_Op<"service.decl", ```mlir esi.service.decl @HostComms { - esi.service.to_server send : !esi.channel - esi.service.to_client recieve : !esi.channel + esi.service.port send : !esi.bundle<[!esi.any from "send"]> + esi.service.port recieve : !esi.channel<[i8 to "recv"]> } ``` }]; @@ -46,18 +46,7 @@ def CustomServiceDeclOp : ESI_Op<"service.decl", }]; } -def ToServerOp : ESI_Op<"service.to_server", - [HasParent<"::circt::esi::CustomServiceDeclOp">]> { - let summary = "An ESI service bundle being sent to the service"; - - let arguments = (ins SymbolNameAttr:$inner_sym, - TypeAttrOf:$toServerType); - let assemblyFormat = [{ - $inner_sym attr-dict `:` $toServerType - }]; -} - -def ToClientOp : ESI_Op<"service.to_client", +def ServiceDeclPortOp : ESI_Op<"service.port", [HasParent<"::circt::esi::CustomServiceDeclOp">]> { let summary = "An ESI service bundle being received by the client"; @@ -167,21 +156,7 @@ def ServiceImplementConnReqOp : ESI_Op<"service.impl_req.req", [ }]; } -def RequestToServerConnectionOp : ESI_Op<"service.req.to_server", [ - DeclareOpInterfaceMethods, - DeclareOpInterfaceMethods]> { - let summary = "Request a connection to send data"; - - let arguments = (ins InnerRefAttr:$servicePort, - ChannelBundleType:$toServer, - AppIDAttr:$appID); - let assemblyFormat = [{ - $toServer `->` $servicePort `(` qualified($appID) `)` - attr-dict `:` qualified(type($toServer)) - }]; -} - -def RequestToClientConnectionOp : ESI_Op<"service.req.to_client", [ +def RequestConnectionOp : ESI_Op<"service.req", [ DeclareOpInterfaceMethods, DeclareOpInterfaceMethods]> { let summary = "Request a connection to receive data"; diff --git a/include/circt/Dialect/ESI/ESIStdServices.td b/include/circt/Dialect/ESI/ESIStdServices.td index 7260425b97e2..07122c44a78c 100644 --- a/include/circt/Dialect/ESI/ESIStdServices.td +++ b/include/circt/Dialect/ESI/ESIStdServices.td @@ -61,4 +61,24 @@ def FuncServiceDeclOp : ESI_Op<"service.std.func", let assemblyFormat = [{ $sym_name attr-dict }]; + + let extraClassDeclaration = [{ + std::optional getTypeName() { return "esi.service.std.func"; } + }]; +} + +def MMIOServiceDeclOp: ESI_Op<"service.std.mmio", + [HasParent<"::mlir::ModuleOp">, Symbol, + DeclareOpInterfaceMethods]> { + let summary = "MMIO service"; + let description = [{ + Declares a service to be backed by a MMIO interface, which is platform + dependent. Must be implemented by a BSP. + }]; + + let arguments = (ins SymbolNameAttr:$sym_name); + + let assemblyFormat = [{ + $sym_name attr-dict + }]; } diff --git a/include/circt/Dialect/Emit/CMakeLists.txt b/include/circt/Dialect/Emit/CMakeLists.txt new file mode 100644 index 000000000000..874084453376 --- /dev/null +++ b/include/circt/Dialect/Emit/CMakeLists.txt @@ -0,0 +1,14 @@ +##===- CMakeLists.txt - Emit dialect build definitions --------*- cmake -*-===// +## +## 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 +## +##===----------------------------------------------------------------------===// +## +## +##===----------------------------------------------------------------------===// + +add_circt_dialect(Emit emit) +add_circt_dialect_doc(Emit emit) +add_dependencies(circt-headers MLIREmitIncGen) diff --git a/include/circt/Dialect/Emit/Emit.td b/include/circt/Dialect/Emit/Emit.td new file mode 100644 index 000000000000..031eaedc2fbb --- /dev/null +++ b/include/circt/Dialect/Emit/Emit.td @@ -0,0 +1,24 @@ +//===- Emit.td - Emit 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 +// +//===----------------------------------------------------------------------===// +// +// This is the top level file for the `emit` dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_EMIT_EMIT_TD +#define CIRCT_DIALECT_EMIT_EMIT_TD + +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/OpBase.td" +include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/SymbolInterfaces.td" + +include "circt/Dialect/Emit/EmitDialect.td" +include "circt/Dialect/Emit/EmitOps.td" + +#endif // CIRCT_DIALECT_EMIT_EMIT_TD diff --git a/include/circt/Dialect/Emit/EmitDialect.h b/include/circt/Dialect/Emit/EmitDialect.h new file mode 100644 index 000000000000..44796799b819 --- /dev/null +++ b/include/circt/Dialect/Emit/EmitDialect.h @@ -0,0 +1,22 @@ +//===- EmitDialect.h - Emit dialect 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines an `emit` MLIR dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_EMIT_EMITDIALECT_H +#define CIRCT_DIALECT_EMIT_EMITDIALECT_H + +#include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Support/LLVM.h" +#include "mlir/IR/Dialect.h" + +#include "circt/Dialect/Emit/EmitDialect.h.inc" + +#endif // CIRCT_DIALECT_EMIT_EMITDIALECT_H diff --git a/include/circt/Dialect/Emit/EmitDialect.td b/include/circt/Dialect/Emit/EmitDialect.td new file mode 100644 index 000000000000..5a17bbbd8f3b --- /dev/null +++ b/include/circt/Dialect/Emit/EmitDialect.td @@ -0,0 +1,28 @@ +//===- EmitDialect.td - Emit 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 +// +//===----------------------------------------------------------------------===// +// +// This contains the EmitDialect definition to be included in other files. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_EMIT_EMITDIALECT +#define CIRCT_DIALECT_EMIT_EMITDIALECT + +def EmitDialect : Dialect { + let name = "emit"; + let cppNamespace = "::circt::emit"; + + let summary = "Types and operations for the `emit` dialect"; + let description = [{ + The `emit` dialect is intended to model the structure of the emitted RTL. + + It organizes the files, file lists, directories and collateral. + }]; +} + +#endif // CIRCT_DIALECT_EMIT_EMITDIALECT diff --git a/include/circt/Dialect/Emit/EmitOps.h b/include/circt/Dialect/Emit/EmitOps.h new file mode 100644 index 000000000000..2a0812822674 --- /dev/null +++ b/include/circt/Dialect/Emit/EmitOps.h @@ -0,0 +1,28 @@ +//===- EmitOps.h - Declare Emit 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 +// +//===----------------------------------------------------------------------===// +// +// This file declares the operation classes for the Emit dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_EMIT_EMITOPS_H +#define CIRCT_DIALECT_EMIT_EMITOPS_H + +#include "mlir/Bytecode/BytecodeOpInterface.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/SymbolTable.h" + +#include "circt/Dialect/Emit/EmitDialect.h" +#include "circt/Dialect/Seq/SeqDialect.h" +#include "circt/Dialect/Seq/SeqTypes.h" +#include "circt/Support/BuilderUtils.h" + +#define GET_OP_CLASSES +#include "circt/Dialect/Emit/Emit.h.inc" + +#endif // CIRCT_DIALECT_EMIT_EMITOPS_H diff --git a/include/circt/Dialect/Emit/EmitOps.td b/include/circt/Dialect/Emit/EmitOps.td new file mode 100644 index 000000000000..b7eaa3eeed21 --- /dev/null +++ b/include/circt/Dialect/Emit/EmitOps.td @@ -0,0 +1,112 @@ +//===- EmitOps.td - `emit` dialect ops ---------------------*- 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 MLIR ops for `emit`. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_EMIT_EMITOPS_TD +#define CIRCT_DIALECT_EMIT_EMITOPS_TD + +include "circt/Dialect/Emit/EmitDialect.td" +include "mlir/IR/OpAsmInterface.td" + +class EmitOp traits = []> : + Op; + +def FileOp : EmitOp<"file", [ + Symbol, + SingleBlock, + NoTerminator, + NoRegionArguments, + IsolatedFromAbove +]> { + let summary = "Represents the contents of an emitted file"; + + let description = [{ + This operation groups a set of nested operations to be emitted to a file. + + Other operations (such as file lists)can reference a file to access its + filename through an optional symbol. + }]; + + let regions = (region SizedRegion<1>:$body); + let arguments = (ins + StrAttr:$file_name, + OptionalAttr:$sym_name + ); + let results = (outs); + + let assemblyFormat = "$file_name (`sym` $sym_name^)? $body attr-dict"; + + let skipDefaultBuilders = 1; + let builders = [ + // Creates a file with a callback. While the callback is executed, the + // insertion point of the builder is moved inside the body of the file op. + OpBuilder<(ins "StringRef":$fileName, "StringRef":$symName, + CArg<"llvm::function_ref">:$bodyCtor)>, + OpBuilder<(ins "StringRef":$fileName, + CArg<"llvm::function_ref">:$bodyCtor)>, + ]; + + let extraClassDeclaration = [{ + // SymbolOpInterface + static bool isOptionalSymbol() { return true; } + + // Utilities + Block *getBodyBlock() { return &getBodyRegion().front(); } + }]; +} + +def VerbatimOp : EmitOp<"verbatim", [HasParent<"circt::emit::FileOp">]> { + let summary = "Verbatim opaque text emitted inline."; + let description = [{ + This operation produces opaque text inline in the file. + + `emit.verbatim` allows symbol reference substitutions with {{0}} syntax. + }]; + + let arguments = (ins + StrAttr:$text + ); + + let assemblyFormat = [{ + $text attr-dict + }]; +} + + +def FileListOp : EmitOp<"file_list", [ + Symbol, + DeclareOpInterfaceMethods +]> { + let summary = "Represents a file list"; + + let description = [{ + This operation emits a file list referencing a set of files. + + File lists can be references from other ops (including other file lists) + through an optional symbol. + }]; + + let arguments = (ins + StrAttr:$file_name, + FlatSymbolRefArrayAttr:$files, + OptionalAttr:$sym_name + ); + let results = (outs); + + let assemblyFormat = "$file_name `,` $files (`sym` $sym_name^)? attr-dict"; + + let extraClassDeclaration = [{ + // SymbolOpInterface + static bool isOptionalSymbol() { return true; } + }]; +} + +#endif // CIRCT_DIALECT_EMIT_EMITOPS_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTL.td b/include/circt/Dialect/FIRRTL/FIRRTL.td index 4b80721b4c38..860926c46d46 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTL.td +++ b/include/circt/Dialect/FIRRTL/FIRRTL.td @@ -21,6 +21,7 @@ include "FIRRTLStructure.td" include "FIRRTLDeclarations.td" include "FIRRTLStatements.td" include "FIRRTLExpressions.td" +include "FIRRTLIntrinsics.td" // Types include "FIRRTLTypes.td" diff --git a/include/circt/Dialect/FIRRTL/FIRRTLAttributes.td b/include/circt/Dialect/FIRRTL/FIRRTLAttributes.td index 093f93fc5c89..edc02c6ed2a3 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLAttributes.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLAttributes.td @@ -15,6 +15,7 @@ include "FIRRTLDialect.td" include "mlir/IR/BuiltinAttributeInterfaces.td" +include "circt/Types.td" //===----------------------------------------------------------------------===// // FIRRTL Annotations Definition @@ -82,41 +83,59 @@ def AugmentedBundleType : AugmentedType<"AugmentedBundleType"> { hasPrefix # [{ bool isRoot() { return getID() != nullptr; } }]; + let mnemonic = "augmentedBundle"; + let assemblyFormat = "`<` $underlying `>`"; } def AugmentedVectorType : AugmentedType<"AugmentedVectorType"> { let summary = "GrandCentral AugmentedVectorType"; let extraClassDeclaration = defaultClassDeclaration # hasElements; + let mnemonic = "augmentedVector"; + let assemblyFormat = "`<` $underlying `>`"; } def AugmentedGroundType : AugmentedType<"AugmentedGroundType"> { let summary = "GrandCentral AugmentedGroundType"; let extraClassDeclaration = hasID # hasName; + let mnemonic = "augmentedGround"; + let assemblyFormat = "`<` $underlying `>`"; } def AugmentedStringType : AugmentedType<"AugmentedStringType"> { let summary = "GrandCentral AugmentedStringType"; let extraClassDeclaration = hasName; + let mnemonic = "augmentedString"; + let assemblyFormat = "`<` $underlying `>`"; } def AugmentedBooleanType : AugmentedType<"AugmentedBooleanType"> { let summary = "GrandCentral AugmentedBooleanType"; let extraClassDeclaration = hasName; + let mnemonic = "augmentedBoolean"; + let assemblyFormat = "`<` $underlying `>`"; } def AugmentedIntegerType : AugmentedType<"AugmentedIntegerType"> { let summary = "GrandCentral AugmentedIntegerType"; let extraClassDeclaration = hasName; + let mnemonic = "augmentedInteger"; + let assemblyFormat = "`<` $underlying `>`"; } def AugmentedDoubleType : AugmentedType<"AugmentedDoubleType"> { let summary = "GrandCentral AugmentedDoubleType"; let extraClassDeclaration = hasName; + let mnemonic = "augmentedDouble"; + let assemblyFormat = "`<` $underlying `>`"; } def AugmentedLiteralType : AugmentedType<"AugmentedLiteralType"> { let summary = "GrandCentral AugmentedLiteralType"; let extraClassDeclaration = hasName; + let mnemonic = "augmentedLiteral"; + let assemblyFormat = "`<` $underlying `>`"; } def AugmentedDeletedType : AugmentedType<"AugmentedDeletedType"> { let summary = "GrandCentral AugmentedDeletedType"; let extraClassDeclaration = hasName; + let mnemonic = "augmentedDeleted"; + let assemblyFormat = "`<` $underlying `>`"; } @@ -211,4 +230,10 @@ def InternalPathAttr : AttrDef { def InternalPathArrayAttr : TypedArrayAttrBase; +//===----------------------------------------------------------------------===// +// Miscellaneous attributes +//===----------------------------------------------------------------------===// + +def LayerArrayAttr : TypedArrayRefAttrBase; + #endif // CIRCT_DIALECT_FIRRTL_FIRRTLATTRIBUTES_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td index bb2598962f89..b60d77a1deb5 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td @@ -62,6 +62,7 @@ def InstanceOp : HardwareDeclOp<"instance", [ APIntAttr:$portDirections, StrArrayAttr:$portNames, AnnotationArrayAttr:$annotations, PortAnnotationsAttr:$portAnnotations, + LayerArrayAttr:$layers, UnitAttr:$lowerToBind, OptionalAttr:$inner_sym); @@ -78,6 +79,7 @@ def InstanceOp : HardwareDeclOp<"instance", [ "::mlir::ArrayRef":$portNames, CArg<"ArrayRef", "{}">:$annotations, CArg<"ArrayRef", "{}">:$portAnnotations, + CArg<"::mlir::ArrayRef", "{}">:$layers, CArg<"bool","false">:$lowerToBind, CArg<"StringAttr", "StringAttr()">:$innerSym)>, OpBuilder<(ins "::mlir::TypeRange":$resultTypes, @@ -88,6 +90,7 @@ def InstanceOp : HardwareDeclOp<"instance", [ "::mlir::ArrayRef":$portNames, "ArrayRef":$annotations, "ArrayRef":$portAnnotations, + "::mlir::ArrayRef":$layers, "bool":$lowerToBind, "hw::InnerSymAttr":$innerSym)>, @@ -106,9 +109,9 @@ def InstanceOp : HardwareDeclOp<"instance", [ "mlir::StringRef":$name, CArg<"NameKindEnum", "NameKindEnum::DroppableName">:$nameKind, CArg<"ArrayRef", "{}">:$annotations, + CArg<"ArrayRef", "{}">:$layers, CArg<"bool","false">:$lowerToBind, CArg<"hw::InnerSymAttr", "hw::InnerSymAttr()">:$innerSym)> - ]; let extraClassDeclaration = [{ @@ -159,6 +162,8 @@ def InstanceOp : HardwareDeclOp<"instance", [ //===------------------------------------------------------------------===// SmallVector<::circt::hw::PortInfo> getPortList(); }]; + + let hasVerifier = true; } def InstanceChoiceOp : HardwareDeclOp<"instance_choice", [ @@ -186,6 +191,7 @@ def InstanceChoiceOp : HardwareDeclOp<"instance_choice", [ APIntAttr:$portDirections, StrArrayAttr:$portNames, AnnotationArrayAttr:$annotations, PortAnnotationsAttr:$portAnnotations, + LayerArrayAttr:$layers, OptionalAttr:$inner_sym); let results = (outs Variadic:$results); @@ -218,7 +224,7 @@ def InstanceChoiceOp : HardwareDeclOp<"instance_choice", [ FlatSymbolRefAttr getTargetOrDefaultAttr(OptionCaseOp option); /// Return the list of case-module mappings. - SmallVector, 1> + SmallVector, 1> getTargetChoices(); }]; @@ -572,7 +578,10 @@ def RegResetOp : HardwareDeclOp<"regreset", [Forceable]> { let hasVerifier = 1; } -def WireOp : HardwareDeclOp<"wire", [Forceable]> { +def WireOp : HardwareDeclOp<"wire", [ + Forceable, + DeclareOpInterfaceMethods +]> { let summary = "Define a new wire"; let description = [{ Declare a new wire: @@ -660,34 +669,6 @@ def WireOp : HardwareDeclOp<"wire", [Forceable]> { }]; } -//===----------------------------------------------------------------------===// -// 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 //===----------------------------------------------------------------------===// diff --git a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td index 64a43a905953..1df3aa1c2d95 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td @@ -60,8 +60,10 @@ class FIRRTLExprOp traits = []> : // Additional class declarations to emit alongside the type inference. code firrtlExtraClassDeclaration = ""; - let extraClassDeclaration = firrtlExtraClassDeclaration # inferTypeDecl # - parseValidatorDecl # [{ + // Declaration of the InferTypeOpInterface method. This is a default + // implementation, using the inferType and inferTypeDecl machinery, but + // subclasses can override this to omit it or provide something else. + code inferReturnTypesDecl = [{ /// Infer the return types of this operation. This is called by the /// `InferTypeOpInterface`. We simply forward to a narrower /// operation-specific implementation which is sufficient for FIRRTL ops. @@ -75,7 +77,10 @@ class FIRRTLExprOp traits = []> : return impl::inferReturnTypes(context, loc, operands, attrs, properties, regions, results, &inferReturnType); } + }]; + let extraClassDeclaration = firrtlExtraClassDeclaration # inferTypeDecl # + parseValidatorDecl # inferReturnTypesDecl # [{ /// Check that the parser has consumed the correct number of operands and /// constants, and infer the appropriate return type for the operation. static FIRRTLType validateAndInferReturnType(ValueRange operands, @@ -233,7 +238,7 @@ def FEnumCreateOp : FIRRTLOp<"enumcreate"> { let extraClassDeclaration = [{ /// Return the name attribute of the accessed field. StringAttr getFieldNameAttr() { - return getResult().getType().get().getElementNameAttr(getFieldIndex()); + return getResult().getType().base().getElementNameAttr(getFieldIndex()); } /// Return the name of the accessed field. @@ -337,7 +342,7 @@ def SubindexOp : FIRRTLExprOp<"subindex"> { let firrtlExtraClassDeclaration = [{ /// Return a `FieldRef` to the accessed field. FieldRef getAccessedField() { - return FieldRef(getInput(), getInput().getType().get().getFieldID(getIndex())); + return FieldRef(getInput(), getInput().getType().base().getFieldID(getIndex())); } using InputType = FVectorType; }]; @@ -464,13 +469,13 @@ def SubtagOp : FIRRTLExprOp<"subtag"> { let firrtlExtraClassDeclaration = [{ /// Return a `FieldRef` to the accessed field. FieldRef getAccessedField() { - return FieldRef(getInput(), getInput().getType().get() + return FieldRef(getInput(), getInput().getType().base() .getFieldID(getFieldIndex())); } /// Return the name of the accessed field. StringAttr getFieldNameAttr() { - return getInput().getType().get().getElementNameAttr(getFieldIndex()); + return getInput().getType().base().getElementNameAttr(getFieldIndex()); } /// Return the name of the accessed field. @@ -865,77 +870,6 @@ def Mux4CellIntrinsicOp : PrimOp<"int.mux4cell"> { "`(` operands `)` attr-dict `:` functional-type(operands, $result)"; } -//===----------------------------------------------------------------------===// -// Verif and SV specific -//===----------------------------------------------------------------------===// - -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 - 'x. FIRRTL doesn't have a notion of 'x per-se, but x can come in to the - system from external modules and from SV constructs. Verification - constructs need to explicitly test for 'x. - }]; - - let arguments = (ins FIRRTLBaseType:$arg); - let results = (outs NonConstUInt1Type:$result); - let hasFolder = 1; - let assemblyFormat = "$arg attr-dict `:` type($arg)"; -} - -def PlusArgsTestIntrinsicOp : FIRRTLOp<"int.plusargs.test", - [HasCustomSSAName, Pure]> { - let summary = "SystemVerilog `$test$plusargs` call"; - - let arguments = (ins StrAttr:$formatString); - let results = (outs NonConstUInt1Type:$found); - let assemblyFormat = "$formatString attr-dict"; -} - -def PlusArgsValueIntrinsicOp : FIRRTLOp<"int.plusargs.value", - [HasCustomSSAName, Pure]> { - let summary = "SystemVerilog `$value$plusargs` call"; - - let arguments = (ins StrAttr:$formatString); - 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)"; -} - -def FPGAProbeIntrinsicOp : FIRRTLOp<"int.fpga_probe", []> { - let summary = "Mark a value to be observed through FPGA debugging facilities"; - - let description = [{ - The `firrtl.int.fpga_probe` intrinsic marks a value in - the IR to be made observable through FPGA debugging facilities. Most FPGAs - offer a form of signal observation or logic analyzer to debug a design. This - operation allows the IR to indicate which signals should be made observable - for debugging. Later FPGA-specific passes may then pick this information up - and materialize the necessary logic analyzers or tool scripts. - }]; - - let arguments = (ins AnyType:$input, NonConstClockType:$clock); - let results = (outs); - let assemblyFormat = "$clock `,` $input attr-dict `:` type($input)"; -} - //===----------------------------------------------------------------------===// // Verbatim //===----------------------------------------------------------------------===// @@ -1012,7 +946,7 @@ def VerbatimWireOp : FIRRTLOp<"verbatim.wire", //===----------------------------------------------------------------------===// // This assumes operands are ground types without explicitly checking -class SameGroundTypeOperandConstness +class SameGroundTypeOperandConstness : PredOpTrait< "operand constness must match", CPred<"isConst($" # a # ".getType()) == isConst($" # b # ".getType())">>; @@ -1247,6 +1181,68 @@ def DoubleConstantOp : FIRRTLOp<"double", [Pure, ConstantLike]> { let assemblyFormat = "$value attr-dict"; } +class IntegerBinaryPrimOp traits = []> : + BinaryPrimOp { + // We use the standard inferReturnTypes from SameOperandsAndResultType. + let inferReturnTypesDecl = ""; + + // We use a static inferType that always returns FIntegerType. + let inferType = "getFIntegerType"; + + // Define the getFIntegerType inline in ODS. + let firrtlExtraClassDeclaration = [{ + static FIntegerType getFIntegerType(FIRRTLType lhs, + FIRRTLType rhs, + std::optional loc) { + return FIntegerType::get(lhs.getContext()); + } + }]; +} + +def IntegerAddOp : IntegerBinaryPrimOp<"integer.add", [Commutative]> { + let summary = "Add two FIntegerType values"; + let description = [{ + The add operation result is the arbitrary precision signed integer + arithmetic sum of the two operands. + + Example: + ```mlir + %2 = firrtl.integer.add %0, %1 : (!firrtl.integer, !firrtl.integer) -> + !firrtl.integer + ``` + }]; +} + +def IntegerMulOp : IntegerBinaryPrimOp<"integer.mul", [Commutative]> { + let summary = "Multiply two FIntegerType values"; + let description = [{ + The multiply operation result is the arbitrary precision signed integer + arithmetic product of the two operands. + + Example: + ```mlir + %2 = firrtl.integer.mul %0, %1 : (!firrtl.integer, !firrtl.integer) -> + !firrtl.integer + ``` + }]; +} + +def IntegerShrOp : IntegerBinaryPrimOp<"integer.shr"> { + let summary = "Shift an FIntegerType value right by an FIntegerType value"; + let description = [{ + The shift right operation result is the arbitrary precision signed integer + arithmetic shift right of the lhs operand by the rhs operand. The rhs + operand must be non-negative. + + Example: + ```mlir + %2 = firrtl.integer.shr %0, %1 : (!firrtl.integer, !firrtl.integer) -> + !firrtl.integer + ``` + }]; +} + //===----------------------------------------------------------------------===// // RefOperations: Operations on the RefType. // @@ -1291,6 +1287,7 @@ def RefCastOp : FIRRTLOp<"ref.cast", let results = (outs RefType:$result); let hasFolder = 1; + let hasVerifier = 1; let assemblyFormat = "$input attr-dict `:` functional-type($input, $result)"; @@ -1309,6 +1306,7 @@ def RefResolveOp: FIRRTLExprOp<"ref.resolve", let results = (outs FIRRTLBaseType:$result); let hasCanonicalizer = true; + let hasVerifier = 1; let assemblyFormat = "$ref attr-dict `:` qualified(type($ref))"; } diff --git a/include/circt/Dialect/FIRRTL/FIRRTLFieldSource.h b/include/circt/Dialect/FIRRTL/FIRRTLFieldSource.h index 8e48b0977f38..52a8d4ec01d6 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLFieldSource.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLFieldSource.h @@ -62,10 +62,13 @@ class FieldSource { private: void visitOp(Operation *op); void visitSubfield(SubfieldOp sf); + void visitOpenSubfield(OpenSubfieldOp sf); void visitSubindex(SubindexOp si); + void visitOpenSubindex(OpenSubindexOp si); void visitSubaccess(SubaccessOp sa); void visitMem(MemOp mem); void visitInst(InstanceOp inst); + void visitInstChoice(InstanceChoiceOp inst); void makeNodeForValue(Value dst, Value src, ArrayRef path, Flow flow); diff --git a/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.h b/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.h index 8c3f35356138..459a3b5b7e55 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.h @@ -40,7 +40,7 @@ class IntrinsicConverter { virtual bool check() { return false; } /// Transform an instance of the intrinsic. - virtual void convert(InstanceOp op) = 0; + virtual LogicalResult convert(InstanceOp op) = 0; protected: ParseResult hasNPorts(unsigned n); @@ -122,7 +122,7 @@ class IntrinsicLowerings { } /// Lowers a module to an intrinsic, given an intrinsic name. - LogicalResult lower(CircuitOp circuit); + LogicalResult lower(CircuitOp circuit, bool allowUnknownIntrinsics = false); /// Return the number of intrinsics converted. unsigned getNumConverted() const { return numConverted; } diff --git a/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td b/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td new file mode 100644 index 000000000000..a5864ebfd95f --- /dev/null +++ b/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td @@ -0,0 +1,138 @@ +//===- FIRRTLIntrinsics.td - FIRRTL intrinsic ops ----------*- 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 MLIR ops for FIRRTL intrinsics. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_FIRRTL_FIRRTLINTRINSICS_TD +#define CIRCT_DIALECT_FIRRTL_FIRRTLINTRINSICS_TD + +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 + 'x. FIRRTL doesn't have a notion of 'x per-se, but x can come in to the + system from external modules and from SV constructs. Verification + constructs need to explicitly test for 'x. + }]; + + let arguments = (ins FIRRTLBaseType:$arg); + let results = (outs NonConstUInt1Type:$result); + let hasFolder = 1; + let assemblyFormat = "$arg attr-dict `:` type($arg)"; +} + +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)"; +} + +//===----------------------------------------------------------------------===// +// Plusarg Intrinsics +//===----------------------------------------------------------------------===// + +def PlusArgsTestIntrinsicOp : FIRRTLOp<"int.plusargs.test", + [HasCustomSSAName, Pure]> { + let summary = "SystemVerilog `$test$plusargs` call"; + + let arguments = (ins StrAttr:$formatString); + let results = (outs NonConstUInt1Type:$found); + let assemblyFormat = "$formatString attr-dict"; +} + +def PlusArgsValueIntrinsicOp : FIRRTLOp<"int.plusargs.value", + [HasCustomSSAName, Pure]> { + let summary = "SystemVerilog `$value$plusargs` call"; + + let arguments = (ins StrAttr:$formatString); + let results = (outs NonConstUInt1Type:$found, AnyType:$result); + let assemblyFormat = "$formatString attr-dict `:` type($result)"; +} + +//===----------------------------------------------------------------------===// +// FPGA-specific intrinsics +//===----------------------------------------------------------------------===// + +def FPGAProbeIntrinsicOp : FIRRTLOp<"int.fpga_probe", []> { + let summary = "Mark a value to be observed through FPGA debugging facilities"; + + let description = [{ + The `firrtl.int.fpga_probe` intrinsic marks a value in + the IR to be made observable through FPGA debugging facilities. Most FPGAs + offer a form of signal observation or logic analyzer to debug a design. This + operation allows the IR to indicate which signals should be made observable + for debugging. Later FPGA-specific passes may then pick this information up + and materialize the necessary logic analyzers or tool scripts. + }]; + + let arguments = (ins AnyType:$input, NonConstClockType:$clock); + let results = (outs); + let assemblyFormat = "$clock `,` $input attr-dict `:` type($input)"; + + let hasCanonicalizeMethod = 1; +} + +//===----------------------------------------------------------------------===// +// Clock Intrinsics +//===----------------------------------------------------------------------===// + +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 + }]; +} + +def ClockInverterIntrinsicOp : FIRRTLOp<"int.clock_inv", []> { + let summary = "Inverts the clock signal"; + + let description = [{ + The `firrtl.int.clock.inv` intrinsic takes a clock signal and inverts it. + It can be used to build registers and other operations which are triggered + by a negative clock edge relative to a reference signal. The compiler is + free to optimize inverters (particularly double inverters). + + See the corresponding `seq.clock_inv` operation. + }]; + + let arguments = (ins NonConstClockType:$input); + let results = (outs NonConstClockType:$output); + let assemblyFormat = "$input attr-dict"; +} + +#endif // CIRCT_DIALECT_FIRRTL_FIRRTLINTRINSICS_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td b/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td index 6df868567c28..be8c0447243d 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLOpInterfaces.td @@ -39,6 +39,16 @@ def FModuleLike : OpInterface<"FModuleLike", [Symbol, PortList, InstanceGraphMod InterfaceMethod<"Get the module's instantiation convention", "Convention", "getConvention">, + //===------------------------------------------------------------------===// + // Enabled (AKA Required) Layers + //===------------------------------------------------------------------===// + + InterfaceMethod<"Get the module's enabled layers", + "ArrayAttr", "getLayersAttr">, + + InterfaceMethod<"Get the module's enabled layers.", + "ArrayRef", "getLayers">, + //===------------------------------------------------------------------===// // Port Directions //===------------------------------------------------------------------===// diff --git a/include/circt/Dialect/FIRRTL/FIRRTLOps.h b/include/circt/Dialect/FIRRTL/FIRRTLOps.h index 0ac49d3855b9..f5ade36951ba 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLOps.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLOps.h @@ -35,9 +35,6 @@ namespace firrtl { class StrictConnectOp; -// is the name useless? -bool isUselessName(circt::StringRef name); - // works for regs, nodes, and wires bool hasDroppableName(Operation *op); diff --git a/include/circt/Dialect/FIRRTL/FIRRTLStatements.td b/include/circt/Dialect/FIRRTL/FIRRTLStatements.td index 31d3b11c6c36..eaad44a8813b 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLStatements.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLStatements.td @@ -278,8 +278,8 @@ def MatchOp : FIRRTLOp<"match", [SingleBlock, NoTerminator, }]; } -def PropAssignOp : FIRRTLOp<"propassign", - [FConnectLike, SameTypeOperands, ParentOneOf<["FModuleOp", "ClassOp"]>]> { +def PropAssignOp : FIRRTLOp<"propassign", [FConnectLike, SameTypeOperands, + ParentOneOf<["FModuleOp", "ClassOp", "LayerBlockOp"]>]> { let summary = "Assign to a sink property value."; let description = [{ Assign an output property value. The types must match exactly. diff --git a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td index a5a6db831a9e..1b791a95211e 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td @@ -81,7 +81,7 @@ class FIRRTLModuleLike traits = []> : size_t $cppClass::getNumInputPorts() { size_t count = 0; - for (unsigned i = 0, e = getNumPorts(); i < e; ++i) + for (size_t i = 0, e = getNumPorts(); i < e; ++i) if (getPortDirection(i) == Direction::In) ++count; return count; @@ -89,14 +89,14 @@ class FIRRTLModuleLike traits = []> : size_t $cppClass::getNumOutputPorts() { size_t count = 0; - for (unsigned i = 0, e = getNumPorts(); i < e; ++i) + for (size_t 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) + for (size_t i = 0, e = getNumPorts(); i < e; ++i) if (getPortDirection(i) == Direction::In) { if (!idx) return i; @@ -107,7 +107,7 @@ class FIRRTLModuleLike traits = []> : } size_t $cppClass::getPortIdForOutputId(size_t idx) { - for (unsigned i = 0, e = getNumPorts(); i < e; ++i) + for (size_t i = 0, e = getNumPorts(); i < e; ++i) if (getPortDirection(i) == Direction::Out) { if (!idx) return i; @@ -131,7 +131,8 @@ def FModuleOp : FIRRTLModuleLike<"module", [SingleBlock, NoTerminator]> { let arguments = (ins ConventionAttr:$convention, ArrayRefAttr:$portLocations, - DefaultValuedAttr:$annotations); + DefaultValuedAttr:$annotations, + DefaultValuedAttr:$layers); let results = (outs); let regions = (region SizedRegion<1>:$body); @@ -142,7 +143,8 @@ def FModuleOp : FIRRTLModuleLike<"module", [SingleBlock, NoTerminator]> { let builders = [ OpBuilder<(ins "StringAttr":$name, "ConventionAttr":$convention, "ArrayRef":$ports, - CArg<"ArrayAttr", "ArrayAttr()">:$annotations)>, + CArg<"ArrayAttr", "ArrayAttr()">:$annotations, + CArg<"ArrayAttr", "ArrayAttr()">:$layers)> ]; let extraModuleClassDeclaration = [{ @@ -188,8 +190,9 @@ def FExtModuleOp : FIRRTLModuleLike<"extmodule"> { ConventionAttr:$convention, ArrayRefAttr:$portLocations, ParamDeclArrayAttr:$parameters, - DefaultValuedAttr:$annotations, + DefaultValuedAttr< + AnnotationArrayAttr, "{}">:$annotations, + DefaultValuedAttr:$layers, OptionalAttr:$internalPaths ); let results = (outs); @@ -203,7 +206,8 @@ def FExtModuleOp : FIRRTLModuleLike<"extmodule"> { CArg<"StringRef", "StringRef()">:$defnamAttr, CArg<"ArrayAttr", "ArrayAttr()">:$annotations, CArg<"ArrayAttr", "ArrayAttr()">:$parameters, - CArg<"ArrayAttr", "ArrayAttr()">:$internalPaths)> + CArg<"ArrayAttr", "ArrayAttr()">:$internalPaths, + CArg<"ArrayAttr", "ArrayAttr()">:$layers)> ]; let extraModuleClassDeclaration = [{ @@ -226,7 +230,8 @@ def FIntModuleOp : FIRRTLModuleLike<"intmodule"> { ParamDeclArrayAttr:$parameters, DefaultValuedAttr:$annotations, - OptionalAttr:$internalPaths + OptionalAttr:$internalPaths, + DefaultValuedAttr:$layers ); let results = (outs); let regions = (region AnyRegion:$body); @@ -238,7 +243,8 @@ def FIntModuleOp : FIRRTLModuleLike<"intmodule"> { CArg<"StringRef", "StringRef()">:$intrinsicNameAttr, CArg<"ArrayAttr", "ArrayAttr()">:$annotations, CArg<"ArrayAttr", "ArrayAttr()">:$parameters, - CArg<"ArrayAttr", "ArrayAttr()">:$internalPaths)> + CArg<"ArrayAttr", "ArrayAttr()">:$internalPaths, + CArg<"ArrayAttr", "ArrayAttr()">:$layers)> ]; let extraModuleClassDeclaration = [{ @@ -266,7 +272,8 @@ def FMemModuleOp : FIRRTLModuleLike<"memmodule"> { UI32Attr:$readLatency, UI32Attr:$writeLatency, UI64Attr:$depth, ArrayAttr:$extraPorts, ArrayRefAttr:$portLocations, - AnnotationArrayAttr:$annotations); + AnnotationArrayAttr:$annotations, + DefaultValuedAttr:$layers); let results = (outs); let regions = (region AnyRegion:$body); @@ -277,7 +284,8 @@ def FMemModuleOp : FIRRTLModuleLike<"memmodule"> { "uint32_t":$numReadWritePorts, "uint32_t":$dataWidth, "uint32_t":$maskBits, "uint32_t":$readLatency, "uint32_t":$writeLatency, "uint64_t":$depth, - CArg<"ArrayAttr", "ArrayAttr()">:$annotations)> + CArg<"ArrayAttr", "ArrayAttr()">:$annotations, + CArg<"ArrayAttr", "ArrayAttr()">:$layers)> ]; let extraModuleClassDeclaration = [{ @@ -294,9 +302,10 @@ def FMemModuleOp : FIRRTLModuleLike<"memmodule"> { def ClassOp : FIRRTLModuleLike<"class", [ SingleBlock, NoTerminator, DeclareOpInterfaceMethods, + // Class Ops do not support port annotations or enabled layers. + // Override these methods to return an empty array attr. + "getPortAnnotationsAttr", + "getLayersAttr"]>, DeclareOpInterfaceMethods, DeclareOpInterfaceMethods]> { let summary = "FIRRTL Class"; @@ -350,9 +359,10 @@ def ClassOp : FIRRTLModuleLike<"class", [ def ExtClassOp : FIRRTLModuleLike<"extclass", [ DeclareOpInterfaceMethods, + // ExtClassOps do not support port annotations or enabled layers. + // Override these methods to return an empty array attr. + "getPortAnnotationsAttr", + "getLayersAttr"]>, DeclareOpInterfaceMethods, DeclareOpInterfaceMethods]> { let summary = "FIRRTL external class"; diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypes.h b/include/circt/Dialect/FIRRTL/FIRRTLTypes.h index 39eb1cc9963a..9561cf16839c 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLTypes.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypes.h @@ -203,6 +203,9 @@ bool isConst(Type type); /// guaranteed to be unchanging at circuit execution time bool containsConst(Type type); +/// Return true if the type has zero bit width. +bool hasZeroBitWidth(FIRRTLType 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 @@ -612,7 +615,7 @@ class BaseTypeAliasOr // Support C++ implicit conversions to BaseTy. operator BaseTy() const { return circt::firrtl::type_cast(*this); } - BaseTy get() const { return circt::firrtl::type_cast(*this); } + BaseTy base() const { return circt::firrtl::type_cast(*this); } }; } // namespace firrtl diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td b/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td index 0904b217739f..b316ce982528 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td @@ -47,6 +47,7 @@ def SIntImpl : FIRRTLImplType<"SInt", TypeBuilder<(ins)>, ]; let genVerifyDecl = true; + let typeName = "firrtl.sint"; let extraClassDeclaration = [{ using WidthQualifiedTypeTrait::getWidth; using WidthQualifiedTypeTrait::hasWidth; @@ -66,6 +67,7 @@ def UIntImpl : FIRRTLImplType<"UInt", TypeBuilder<(ins)>, ]; let genVerifyDecl = true; + let typeName = "firrtl.uint"; let extraClassDeclaration = [{ using WidthQualifiedTypeTrait::getWidth; using WidthQualifiedTypeTrait::hasWidth; @@ -78,6 +80,7 @@ def ClockTypeImpl : FIRRTLImplType<"Clock"> { let summary = "Clock signal"; let parameters = (ins "bool":$isConst); let storageClass = "FIRRTLBaseTypeStorage"; + let typeName = "firrtl.clock"; let builders = [ TypeBuilder<(ins), [{ return $_get($_ctxt, false); @@ -92,6 +95,7 @@ def ResetTypeImpl : FIRRTLImplType<"Reset"> { let summary = "Reset Signal"; let parameters = (ins "bool":$isConst); let storageClass = "FIRRTLBaseTypeStorage"; + let typeName = "firrtl.reset"; let builders = [ TypeBuilder<(ins), [{ return $_get($_ctxt, false); @@ -106,6 +110,7 @@ def AsyncResetTypeImpl : FIRRTLImplType<"AsyncReset"> { let summary = "AsyncReset signal"; let parameters = (ins "bool":$isConst); let storageClass = "FIRRTLBaseTypeStorage"; + let typeName = "firrtl.asyncreset"; let builders = [ TypeBuilder<(ins), [{ return $_get($_ctxt, false); @@ -121,6 +126,7 @@ def AnalogTypeImpl : FIRRTLImplType<"Analog", let summary = "Analog signal"; let parameters = (ins "int32_t":$widthOrSentinel, "bool":$isConst); let storageClass = "WidthTypeStorage"; + let typeName = "firrtl.analog"; let builders = [ TypeBuilder<(ins "std::optional":$width, CArg<"bool", "false">:$isConst)>, TypeBuilder<(ins)>, @@ -132,7 +138,7 @@ def AnalogTypeImpl : FIRRTLImplType<"Analog", let genVerifyDecl = true; } -class BaseVectorTypeImpl traits = [], string BaseType = ElementType> +class BaseVectorTypeImpl traits = [], string BaseType = ElementType> : FIRRTLImplType], BaseType> { let summary = "a fixed size collection of elements, like an array."; let parameters = (ins @@ -173,6 +179,7 @@ class BaseVectorTypeImpl traits = [ } def FVectorImpl : BaseVectorTypeImpl<"FVector","::circt::firrtl::FIRRTLBaseType"> { + let typeName = "firrtl.vector"; let firrtlExtraClassDeclaration = [{ /// Return this type with any flip types recursively removed from itself. FIRRTLBaseType getPassiveType(); @@ -187,6 +194,7 @@ def FVectorImpl : BaseVectorTypeImpl<"FVector","::circt::firrtl::FIRRTLBaseType" def OpenVectorImpl : BaseVectorTypeImpl<"OpenVector","::circt::firrtl::FIRRTLType"> { let genVerifyDecl = 1; + let typeName = "firrtl.openvector"; } class BaseBundleTypeImpl traits = [], string BaseType = ElementType> @@ -204,7 +212,7 @@ class BaseBundleTypeImpl traits = [ let extraClassDeclaration = [{ using ElementType = }] # ElementType # [{; - + /// Each element of a bundle, which is a name and type. struct BundleElement { StringAttr name; @@ -270,6 +278,7 @@ class BaseBundleTypeImpl traits = [ } def BundleImpl : BaseBundleTypeImpl<"Bundle","::circt::firrtl::FIRRTLBaseType"> { + let typeName = "firrtl.bundle"; let firrtlExtraClassDeclaration = [{ /// Return this type with any flip types recursively removed from itself. FIRRTLBaseType getPassiveType(); @@ -283,6 +292,7 @@ def BundleImpl : BaseBundleTypeImpl<"Bundle","::circt::firrtl::FIRRTLBaseType"> } def OpenBundleImpl : BaseBundleTypeImpl<"OpenBundle","::circt::firrtl::FIRRTLType"> { + let typeName = "firrtl.openbundle"; let genVerifyDecl = 1; } @@ -291,6 +301,7 @@ def FEnumImpl : FIRRTLImplType<"FEnum", [DeclareTypeInterfaceMethods":$elements, "bool":$isConst); let storageClass = "FEnumTypeStorage"; let genVerifyDecl = true; + let typeName = "firrtl.enum"; let skipDefaultBuilders = true; let builders = [ TypeBuilder<(ins "ArrayRef":$elements, CArg<"bool", "false">:$isConst)> @@ -380,20 +391,29 @@ def RefImpl : FIRRTLImplType<"Ref", }]; let parameters = (ins TypeParameter<"::circt::firrtl::FIRRTLBaseType", "Type of reference target">:$type, - "bool":$forceable); + "bool":$forceable, + OptionalParameter<"::mlir::SymbolRefAttr">:$layer); let genAccessors = true; let genStorageClass = true; let genVerifyDecl = true; + let typeName = "firrtl.ref"; let skipDefaultBuilders = true; let builders = [ TypeBuilderWithInferredContext<(ins "::circt::firrtl::FIRRTLBaseType":$type, - CArg<"bool", "false">:$forceable)> + CArg<"bool", "false">:$forceable, + CArg<"::mlir::SymbolRefAttr", "{}">:$layer)> ]; let extraClassDeclaration = [{ /// Return the recursive properties of the type. RecursiveTypeProperties getRecursiveTypeProperties() const; + + RefType removeLayer() const { + if (getLayer() == nullptr) + return *this; + return get(getType(), getForceable()); + } }]; } @@ -404,6 +424,7 @@ def BaseTypeAliasImpl : FIRRTLImplType<"BaseTypeAlias", [DeclareTypeInterfaceMet (ins "StringAttr":$name, TypeParameter<"::circt::firrtl::FIRRTLBaseType", "An inner type">:$innerType); let storageClass = "BaseTypeAliasStorage"; + let typeName = "firrtl.basetypealias"; let genAccessors = true; let skipDefaultBuilders = true; let extraClassDeclaration = [{ @@ -451,6 +472,7 @@ def ClassImpl : PropImplType<"Class", [ ArrayRefParameter<"ClassElement">:$elements ); let genStorageClass = false; + let typeName = "firrtl.propClass"; let genAccessors = false; let builders = [ TypeBuilderWithInferredContext<(ins @@ -490,18 +512,21 @@ def AnyRefImpl : PropImplType<"AnyRef"> { about the referred to object's fields, so subfield access, etc. is illegal. }]; let genStorageClass = true; + let typeName = "firrtl.propRef"; } def StringImpl : PropImplType<"String"> { let summary = "An unlimited length string type. Not representable in hardware."; let parameters = (ins); let genStorageClass = true; + let typeName = "firrtl.propString"; } def IntegerImpl : PropImplType<"FInteger"> { let summary = "An unlimited length signed integer type. Not representable in hardware."; let parameters = (ins); let genStorageClass = true; + let typeName = "firrtl.propInt"; } def ListImpl : PropImplType<"List"> { @@ -509,6 +534,7 @@ def ListImpl : PropImplType<"List"> { let parameters = (ins TypeParameter<"circt::firrtl::PropertyType", "element type">:$elementType); let genStorageClass = true; let genAccessors = true; + let typeName = "firrtl.propList"; } def PathImpl : PropImplType<"Path"> { @@ -516,18 +542,21 @@ def PathImpl : PropImplType<"Path"> { let parameters = (ins); let genStorageClass = true; let genAccessors = true; + let typeName = "firrtl.propPath"; } def BoolImpl : PropImplType<"Bool"> { let summary = "A boolean property. Not representable in hardware."; let parameters = (ins); let genStorageClass = true; + let typeName = "firrtl.propBool"; } def DoubleImpl : PropImplType<"Double"> { let summary = "A double property. Not representable in hardware."; let parameters = (ins); let genStorageClass = true; + let typeName = "firrtl.propDouble"; } #endif // CIRCT_DIALECT_FIRRTL_FIRRTLTYPESIMPL_TD diff --git a/include/circt/Dialect/FIRRTL/FIRRTLUtils.h b/include/circt/Dialect/FIRRTL/FIRRTLUtils.h index 50cb3aa09167..14fc65845bd6 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLUtils.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLUtils.h @@ -234,7 +234,8 @@ inline FIRRTLType mapBaseType(FIRRTLType type, return TypeSwitch(type) .Case([&](auto base) { return fn(base); }) .Case([&](auto ref) { - return RefType::get(fn(ref.getType()), ref.getForceable()); + return RefType::get(fn(ref.getType()), ref.getForceable(), + ref.getLayer()); }); } @@ -250,7 +251,7 @@ mapBaseTypeNullable(FIRRTLType type, auto result = fn(ref.getType()); if (!result) return {}; - return RefType::get(result, ref.getForceable()); + return RefType::get(result, ref.getForceable(), ref.getLayer()); }); } diff --git a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h index 9e7289a2dd05..66191c9023b3 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h @@ -48,9 +48,9 @@ class ExprVisitor { CvtPrimOp, NegPrimOp, NotPrimOp, AndRPrimOp, OrRPrimOp, XorRPrimOp, // Intrinsic Expressions. IsXIntrinsicOp, PlusArgsValueIntrinsicOp, PlusArgsTestIntrinsicOp, - SizeOfIntrinsicOp, ClockGateIntrinsicOp, LTLAndIntrinsicOp, - LTLOrIntrinsicOp, LTLDelayIntrinsicOp, LTLConcatIntrinsicOp, - LTLNotIntrinsicOp, LTLImplicationIntrinsicOp, + SizeOfIntrinsicOp, ClockGateIntrinsicOp, ClockInverterIntrinsicOp, + LTLAndIntrinsicOp, LTLOrIntrinsicOp, LTLDelayIntrinsicOp, + LTLConcatIntrinsicOp, LTLNotIntrinsicOp, LTLImplicationIntrinsicOp, LTLEventuallyIntrinsicOp, LTLClockIntrinsicOp, LTLDisableIntrinsicOp, Mux2CellIntrinsicOp, Mux4CellIntrinsicOp, HasBeenResetIntrinsicOp, FPGAProbeIntrinsicOp, @@ -166,6 +166,7 @@ class ExprVisitor { HANDLE(PlusArgsTestIntrinsicOp, Unhandled); HANDLE(SizeOfIntrinsicOp, Unhandled); HANDLE(ClockGateIntrinsicOp, Unhandled); + HANDLE(ClockInverterIntrinsicOp, Unhandled); HANDLE(LTLAndIntrinsicOp, Unhandled); HANDLE(LTLOrIntrinsicOp, Unhandled); HANDLE(LTLDelayIntrinsicOp, Unhandled); diff --git a/include/circt/Dialect/FIRRTL/Import/FIRAnnotations.h b/include/circt/Dialect/FIRRTL/Import/FIRAnnotations.h new file mode 100644 index 000000000000..970f06789e5d --- /dev/null +++ b/include/circt/Dialect/FIRRTL/Import/FIRAnnotations.h @@ -0,0 +1,40 @@ +//===- FIRAnnotations.h - .fir file annotation 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 contains public APIs for loading annotations. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_FIRRTL_IMPORT_FIRANNOTATIONS_H +#define CIRCT_DIALECT_FIRRTL_IMPORT_FIRANNOTATIONS_H + +#include "circt/Support/LLVM.h" +#include "llvm/ADT/SmallVector.h" + +namespace llvm { +namespace json { +class Path; +class Value; +} // namespace json +} // namespace llvm + +namespace circt { +namespace firrtl { + +/// Deserialize a JSON value into FIRRTL Annotations. Annotations are +/// represented as a Target-keyed arrays of attributes. The input JSON value is +/// checked, at runtime, to be an array of objects. Returns true if successful, +/// false if unsuccessful. +bool importAnnotationsFromJSONRaw(llvm::json::Value &value, + SmallVectorImpl &annotations, + llvm::json::Path path, MLIRContext *context); + +} // namespace firrtl +} // namespace circt + +#endif // CIRCT_DIALECT_FIRRTL_IMPORT_FIRANNOTATIONS_H diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index c4f10534b0f7..e38f8bc0cb0a 100644 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -179,6 +179,7 @@ def Inliner : Pass<"firrtl-inliner", "firrtl::CircuitOp"> { ``` }]; let constructor = "circt::firrtl::createInlinerPass()"; + let dependentDialects = ["debug::DebugDialect"]; } def AddSeqMemPorts : Pass<"firrtl-add-seqmem-ports", "firrtl::CircuitOp"> { @@ -428,7 +429,11 @@ def BlackBoxReader : Pass<"firrtl-blackbox-reader", "CircuitOp"> { "directory where the input file was located, to allow for annotations " "relative to the input file."> ]; - let dependentDialects = ["sv::SVDialect", "hw::HWDialect"]; + let dependentDialects = [ + "emit::EmitDialect", + "hw::HWDialect", + "sv::SVDialect" + ]; } def PrefixModules : Pass<"firrtl-prefix-modules", "firrtl::CircuitOp"> { @@ -861,7 +866,7 @@ def SpecializeOption : let constructor = "circt::firrtl::createSpecializeOptionPass()"; let options = [ - ListOption<"select", "select", "std::string", + ListOption<"select", "select", "std::string", "Options to specialize, in option=case format">, ]; let statistics = [ diff --git a/include/circt/Dialect/FSM/FSMOps.td b/include/circt/Dialect/FSM/FSMOps.td index ecdaaab4714a..a2d68bb325fd 100644 --- a/include/circt/Dialect/FSM/FSMOps.td +++ b/include/circt/Dialect/FSM/FSMOps.td @@ -96,17 +96,17 @@ def MachineOp : FSMOp<"machine", [ let hasVerifier = 1; } -def InstanceOp : FSMOp<"instance", [Symbol, HasCustomSSAName]> { +def InstanceOp : FSMOp<"instance", [HasCustomSSAName]> { let summary = "Create an instance of a state machine"; let description = [{ `fsm.instance` represents an instance of a state machine, including an instance name and a symbol reference of the machine. }]; - let arguments = (ins StrAttr:$sym_name, FlatSymbolRefAttr:$machine); + let arguments = (ins StrAttr:$name, FlatSymbolRefAttr:$machine); let results = (outs InstanceType:$instance); - let assemblyFormat = [{ $sym_name $machine attr-dict }]; + let assemblyFormat = [{ $name $machine attr-dict }]; let extraClassDeclaration = [{ /// Lookup the machine for the symbol. This returns null on invalid IR. @@ -145,7 +145,6 @@ def TriggerOp : FSMOp<"trigger", []> { } def HWInstanceOp : FSMOp<"hw_instance", [ - Symbol, DeclareOpInterfaceMethods ]> { let summary = "Create a hardware-style instance of a state machine"; @@ -156,12 +155,12 @@ def HWInstanceOp : FSMOp<"hw_instance", [ machine. }]; - let arguments = (ins StrAttr:$sym_name, FlatSymbolRefAttr:$machine, + let arguments = (ins StrAttr:$name, FlatSymbolRefAttr:$machine, Variadic:$inputs, ClockType:$clock, I1:$reset); let results = (outs Variadic:$outputs); let assemblyFormat = [{ - $sym_name $machine attr-dict `(` $inputs `)` + $name $machine attr-dict `(` $inputs `)` `,` `clock` $clock `,` `reset` $reset `:` functional-type($inputs, $outputs) }]; diff --git a/include/circt/Dialect/HW/CMakeLists.txt b/include/circt/Dialect/HW/CMakeLists.txt index 8572e8a76cc3..717b71cc69f8 100644 --- a/include/circt/Dialect/HW/CMakeLists.txt +++ b/include/circt/Dialect/HW/CMakeLists.txt @@ -7,6 +7,7 @@ mlir_tablegen(HWAttributes.cpp.inc -gen-attrdef-defs) add_public_tablegen_target(MLIRHWAttrIncGen) add_dependencies(circt-headers MLIRHWAttrIncGen) +set(LLVM_TARGET_DEFINITIONS HWEnums.td) mlir_tablegen(HWEnums.h.inc -gen-enum-decls) mlir_tablegen(HWEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(MLIRHWEnumsIncGen) diff --git a/include/circt/Dialect/HW/HWAttributes.td b/include/circt/Dialect/HW/HWAttributes.td index 7bb1faf9a087..092bbd7bf9ff 100644 --- a/include/circt/Dialect/HW/HWAttributes.td +++ b/include/circt/Dialect/HW/HWAttributes.td @@ -227,38 +227,6 @@ def ParamVerbatimAttr : AttrDef; -def PEO_Mul : I32EnumAttrCase<"Mul", 1, "mul">; -def PEO_And : I32EnumAttrCase<"And", 2, "and">; -def PEO_Or : I32EnumAttrCase<"Or", 3, "or">; -def PEO_Xor : I32EnumAttrCase<"Xor", 4, "xor">; - -// Binary Expression Opcodes. -def PEO_Shl : I32EnumAttrCase<"Shl" , 5, "shl">; -def PEO_ShrU : I32EnumAttrCase<"ShrU", 6, "shru">; -def PEO_ShrS : I32EnumAttrCase<"ShrS", 7, "shrs">; -def PEO_DivU : I32EnumAttrCase<"DivU", 8, "divu">; -def PEO_DivS : I32EnumAttrCase<"DivS", 9, "divs">; -def PEO_ModU : I32EnumAttrCase<"ModU",10, "modu">; -def PEO_ModS : I32EnumAttrCase<"ModS",11, "mods">; - -// Unary Expression Opcodes. -def PEO_CLog2 : I32EnumAttrCase<"CLog2", 12, "clog2">; - -// String manipulation Opcodes. -def PEO_StrConcat : I32EnumAttrCase<"StrConcat", 13, "str.concat">; - -def PEOAttr : I32EnumAttr<"PEO", "Parameter Expression Opcode", - [PEO_Add, PEO_Mul, PEO_And, PEO_Or, PEO_Xor, - PEO_Shl, PEO_ShrU, PEO_ShrS, - PEO_DivU, PEO_DivS, PEO_ModU, PEO_ModS, - PEO_CLog2, PEO_StrConcat]>; -} - def ParamExprAttr : AttrDef { let summary = "Parameter expression combining operands"; let parameters = (ins "PEO":$opcode, diff --git a/include/circt/Dialect/HW/HWDialect.h b/include/circt/Dialect/HW/HWDialect.h index ace25ee52d0c..c05c5a406c9c 100644 --- a/include/circt/Dialect/HW/HWDialect.h +++ b/include/circt/Dialect/HW/HWDialect.h @@ -20,7 +20,4 @@ // Pull in the dialect definition. #include "circt/Dialect/HW/HWDialect.h.inc" -// Pull in all enum type definitions and utility function declarations. -#include "circt/Dialect/HW/HWEnums.h.inc" - #endif // CIRCT_DIALECT_HW_HWDIALECT_H diff --git a/include/circt/Dialect/HW/HWEnums.h b/include/circt/Dialect/HW/HWEnums.h new file mode 100644 index 000000000000..7395fdc2172a --- /dev/null +++ b/include/circt/Dialect/HW/HWEnums.h @@ -0,0 +1,24 @@ +//===- HWEnums.h - Enums for HW 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines HW dialect specific enums. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_HW_HWENUMS_H +#define CIRCT_DIALECT_HW_HWENUMS_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinTypes.h" +#include "llvm/ADT/StringRef.h" + +// Pull in all enum type definitions and utility function declarations. +#include "circt/Dialect/HW/HWEnums.h.inc" + +#endif // CIRCT_DIALECT_HW_HWENUMS_H diff --git a/include/circt/Dialect/HW/HWEnums.td b/include/circt/Dialect/HW/HWEnums.td new file mode 100644 index 000000000000..c315e0685734 --- /dev/null +++ b/include/circt/Dialect/HW/HWEnums.td @@ -0,0 +1,68 @@ +//===- HWEnums.td - Enums for HW 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines HW dialect specific enums. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_HW_HWENUM_TD +#define CIRCT_DIALECT_HW_HWENUM_TD + +include "mlir/IR/EnumAttr.td" + +let cppNamespace = "circt::hw" in { + +//----------------------------------------------------------------------------// +// Parameter Expression Opcodes. +//----------------------------------------------------------------------------// + +/// Fully Associative Expression Opcodes. +def PEO_Add : I32EnumAttrCase<"Add", 0, "add">; +def PEO_Mul : I32EnumAttrCase<"Mul", 1, "mul">; +def PEO_And : I32EnumAttrCase<"And", 2, "and">; +def PEO_Or : I32EnumAttrCase<"Or", 3, "or">; +def PEO_Xor : I32EnumAttrCase<"Xor", 4, "xor">; + +// Binary Expression Opcodes. +def PEO_Shl : I32EnumAttrCase<"Shl" , 5, "shl">; +def PEO_ShrU : I32EnumAttrCase<"ShrU", 6, "shru">; +def PEO_ShrS : I32EnumAttrCase<"ShrS", 7, "shrs">; +def PEO_DivU : I32EnumAttrCase<"DivU", 8, "divu">; +def PEO_DivS : I32EnumAttrCase<"DivS", 9, "divs">; +def PEO_ModU : I32EnumAttrCase<"ModU",10, "modu">; +def PEO_ModS : I32EnumAttrCase<"ModS",11, "mods">; + +// Unary Expression Opcodes. +def PEO_CLog2 : I32EnumAttrCase<"CLog2", 12, "clog2">; + +// String manipulation Opcodes. +def PEO_StrConcat : I32EnumAttrCase<"StrConcat", 13, "str.concat">; + +def PEOAttr : I32EnumAttr<"PEO", "Parameter Expression Opcode", + [PEO_Add, PEO_Mul, PEO_And, PEO_Or, PEO_Xor, + PEO_Shl, PEO_ShrU, PEO_ShrS, + PEO_DivU, PEO_DivS, PEO_ModU, PEO_ModS, + PEO_CLog2, PEO_StrConcat]>; + +//----------------------------------------------------------------------------// +// Edge behavior for trigger blocks. +//----------------------------------------------------------------------------// + +/// 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]>; + +} + +#endif // CIRCT_DIALECT_HW_HWENUM_TD diff --git a/include/circt/Dialect/HW/HWInstanceImplementation.h b/include/circt/Dialect/HW/HWInstanceImplementation.h index 612c09e8811b..1ba7ac6d6c95 100644 --- a/include/circt/Dialect/HW/HWInstanceImplementation.h +++ b/include/circt/Dialect/HW/HWInstanceImplementation.h @@ -31,11 +31,6 @@ namespace instance_like_impl { using EmitErrorFn = std::function)>; -/// 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. LogicalResult verifyReferencedModule(Operation *instanceOp, SymbolTableCollection &symbolTable, diff --git a/include/circt/Dialect/HW/HWOpInterfaces.td b/include/circt/Dialect/HW/HWOpInterfaces.td index ae833d9ddd13..7d3bd44f9eb4 100644 --- a/include/circt/Dialect/HW/HWOpInterfaces.td +++ b/include/circt/Dialect/HW/HWOpInterfaces.td @@ -52,8 +52,8 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ InterfaceMethod<"Get the module type", "::circt::hw::ModuleType", "getHWModuleType", (ins)>, - InterfaceMethod<"Get the port Attributes", - "SmallVector", "getAllPortAttrs", (ins)>, + InterfaceMethod<"Get the port Attributes. This will return either an empty array or an array of size numPorts.", + "ArrayRef", "getAllPortAttrs", (ins)>, InterfaceMethod<"Set the port Attributes", "void", "setAllPortAttrs", (ins "ArrayRef":$attrs)>, @@ -65,7 +65,7 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ "SmallVector", "getAllPortLocs", (ins)>, InterfaceMethod<"Set the port Locations", - "void", "setAllPortLocs", (ins "ArrayRef":$locs)>, + "void", "setAllPortLocsAttrs", (ins "ArrayRef":$locs)>, InterfaceMethod<"Set the module type (and port names)", "void", "setHWModuleType", (ins "::circt::hw::ModuleType":$type)>, @@ -112,16 +112,6 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ 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(); @@ -133,15 +123,18 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ } void setInputNames(ArrayRef names) { - SmallVector newNames(names.begin(), names.end()); - auto resNames = $_op.getOutputNames(); - newNames.append(resNames.begin(), resNames.end()); + auto type = $_op.getHWModuleType(); + SmallVector newNames(type.getPortNames()); + for (size_t idx = 0, e = names.size(); idx != e; ++idx) + newNames[type.getPortIdForInputId(idx)] = names[idx]; $_op.setAllPortNames(newNames); } void setOutputNames(ArrayRef names) { - SmallVector newNames = $_op.getInputNames(); - newNames.append(names.begin(), names.end()); + auto type = $_op.getHWModuleType(); + SmallVector newNames(type.getPortNames()); + for (size_t idx = 0, e = names.size(); idx != e; ++idx) + newNames[type.getPortIdForOutputId(idx)] = names[idx]; $_op.setAllPortNames(newNames); } @@ -169,11 +162,16 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ } Attribute getPortAttrs(size_t idx) { - return $_op.getAllPortAttrs()[idx]; + auto attrs = $_op.getAllPortAttrs(); + if (attrs.empty()) + return nullptr; + return attrs[idx]; } SmallVector getAllInputAttrs() { auto attrs = $_op.getAllPortAttrs(); + if (attrs.empty()) + return {}; auto num = $_op.getNumInputPorts(); SmallVector retval(num); auto modType = $_op.getHWModuleType(); @@ -184,6 +182,8 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ SmallVector getAllOutputAttrs() { auto attrs = $_op.getAllPortAttrs(); + if (attrs.empty()) + return {}; auto num = $_op.getNumOutputPorts(); SmallVector retval(num); auto modType = $_op.getHWModuleType(); @@ -193,16 +193,23 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ } void setAllInputAttrs(ArrayRef attrs) { - SmallVector retval(attrs.begin(), attrs.end()); - auto resAttrs = $_op.getAllOutputAttrs(); - retval.append(resAttrs.begin(), resAttrs.end()); - $_op.setAllPortAttrs(retval); + auto type = $_op.getHWModuleType(); + SmallVector newAttrs($_op.getAllPortAttrs()); + if (newAttrs.empty()) + newAttrs.resize($_op.getNumPorts()); + for (size_t idx = 0, e = attrs.size(); idx != e; ++idx) + newAttrs[type.getPortIdForInputId(idx)] = attrs[idx]; + $_op.setAllPortAttrs(newAttrs); } void setAllOutputAttrs(ArrayRef attrs) { - SmallVector retval = $_op.getAllInputAttrs(); - retval.append(attrs.begin(), attrs.end()); - $_op.setAllPortAttrs(retval); + auto type = $_op.getHWModuleType(); + SmallVector newAttrs($_op.getAllPortAttrs()); + if (newAttrs.empty()) + newAttrs.resize($_op.getNumPorts()); + for (size_t idx = 0, e = attrs.size(); idx != e; ++idx) + newAttrs[type.getPortIdForOutputId(idx)] = attrs[idx]; + $_op.setAllPortAttrs(newAttrs); } Attribute getInputAttrs(size_t idx) { @@ -214,13 +221,15 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ } void setPortAttrs(size_t idx, DictionaryAttr attr) { - auto attrs = $_op.getAllPortAttrs(); + SmallVector attrs($_op.getAllPortAttrs()); + attrs.resize($_op.getNumPorts()); attrs[idx] = attr; $_op.setAllPortAttrs(attrs); } void setPortAttr(size_t idx, StringAttr name, Attribute value) { - auto attrs = $_op.getAllPortAttrs(); + SmallVector attrs($_op.getAllPortAttrs()); + attrs.resize($_op.getNumPorts()); NamedAttrList pattr(cast(attrs[idx])); Attribute oldValue; if (!value) @@ -234,7 +243,8 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ } void setPortAttrs(StringAttr attrName, ArrayRef newAttrs) { - auto attrs = $_op.getAllPortAttrs(); + SmallVector attrs($_op.getAllPortAttrs()); + attrs.resize($_op.getNumPorts()); auto ctxt = $_op.getContext(); assert(newAttrs.size() == attrs.size()); for (size_t idx = 0, e = attrs.size(); idx != e; ++idx) { @@ -249,6 +259,11 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ $_op.setAllPortAttrs(attrs); } + void setAllPortLocs(ArrayRef locs) { + std::vector nLocs(locs.begin(), locs.end()); + $_op.setAllPortLocsAttrs(nLocs); + } + Location getPortLoc(size_t idx) { return $_op.getAllPortLocs()[idx]; } @@ -283,13 +298,14 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ 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); + void setInputLocs(ArrayRef inLocs) { + assert(inLocs.size() == $_op.getNumInputPorts()); + auto type = $_op.getHWModuleType(); + SmallVector newLocs($_op.getAllPortLocs()); + for (size_t idx = 0, e = inLocs.size(); idx != e; ++idx) + newLocs[type.getPortIdForInputId(idx)] = inLocs[idx]; + $_op.setAllPortLocs(newLocs); + } SmallVector getOutputLocs() { @@ -308,13 +324,13 @@ def HWModuleLike : OpInterface<"HWModuleLike", [ 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); + void setOutputLocs(ArrayRef outLocs) { + assert(outLocs.size() == $_op.getNumOutputPorts()); + auto type = $_op.getHWModuleType(); + SmallVector newLocs($_op.getAllPortLocs()); + for (size_t idx = 0, e = outLocs.size(); idx != e; ++idx) + newLocs[type.getPortIdForOutputId(idx)] = outLocs[idx]; + $_op.setAllPortLocs(newLocs); } }]; @@ -388,30 +404,9 @@ def HWMutableModuleLike : OpInterface<"HWMutableModuleLike", [HWModuleLike]> { def HWInstanceLike : OpInterface<"HWInstanceLike", [ - PortList, InstanceGraphInstanceOpInterface]> { + InstanceGraphInstanceOpInterface]> { let cppNamespace = "circt::hw"; let description = "Provide common module information."; - - let methods = [ - 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 via a HW symbol cache.", - "::mlir::Operation *", "getReferencedModuleCached", (ins - "const ::circt::hw::HWSymbolCache *":$cache), - /*methodBody=*/[{}], - /*defaultImplementation=*/[{ - return hw::instance_like_impl::getReferencedModule( - cache, $_op, $_op.getModuleNameAttr()); - }]>, - ]; } def InnerRefNamespace : NativeOpTrait<"InnerRefNamespace">; diff --git a/include/circt/Dialect/HW/HWOps.h b/include/circt/Dialect/HW/HWOps.h index bb15c9c8a1b7..8a15a898d11f 100644 --- a/include/circt/Dialect/HW/HWOps.h +++ b/include/circt/Dialect/HW/HWOps.h @@ -14,6 +14,7 @@ #define CIRCT_DIALECT_HW_OPS_H #include "circt/Dialect/HW/HWDialect.h" +#include "circt/Dialect/HW/HWEnums.h" #include "circt/Dialect/HW/HWOpInterfaces.h" #include "circt/Dialect/HW/HWTypes.h" #include "circt/Support/BuilderUtils.h" @@ -38,18 +39,6 @@ ModulePort::Direction flip(ModulePort::Direction direction); /// TODO: Move all these functions to a hw::ModuleLike interface. -/// 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 -/// the module in the same order as they were listed in the `insert*` array. -/// If 'body' is provided, additionally inserts/removes the corresponding -/// block arguments. -void modifyModulePorts(Operation *op, - ArrayRef> insertInputs, - ArrayRef> insertOutputs, - ArrayRef removeInputs, - ArrayRef removeOutputs, Block *body = nullptr); - // Helpers for working with modules. /// Return true if isAnyModule or instance. diff --git a/include/circt/Dialect/HW/HWStructure.td b/include/circt/Dialect/HW/HWStructure.td index f9b2e570effd..7b4daa64afe2 100644 --- a/include/circt/Dialect/HW/HWStructure.td +++ b/include/circt/Dialect/HW/HWStructure.td @@ -122,7 +122,7 @@ def HWModuleOp : HWModuleOpBase<"module", let arguments = (ins SymbolNameAttr:$sym_name, TypeAttrOf:$module_type, OptionalAttr:$per_port_attrs, - OptionalAttr:$port_locs, + OptionalAttr:$result_locs, ParamDeclArrayAttr:$parameters, OptionalAttr:$comment); let results = (outs); @@ -395,7 +395,9 @@ class HWInstanceOpBase traits = []> : DeclareOpInterfaceMethods ]> { - let extraClassDeclaration = [{ + code extraInstanceClassDeclaration = [{}]; + + let extraClassDeclaration = extraInstanceClassDeclaration # [{ /// Return the name of the specified input port or null if it cannot be /// determined. StringAttr getArgumentName(size_t idx) { @@ -434,7 +436,9 @@ class HWInstanceOpBase traits = []> : void getValues(SmallVectorImpl &values, const ModulePortInfo &mpi); }]; - let extraClassDefinition = [{ + code extraInstanceClassDefinition = [{}]; + + let extraClassDefinition = extraInstanceClassDefinition # [{ ::llvm::SmallVector<::circt::hw::PortInfo> $cppClass::getPortList() { return instance_like_impl::getPortList(getOperation()); } @@ -516,9 +520,26 @@ def InstanceOp : HWInstanceOpBase<"instance", [HWInstanceLike]> { let hasCustomAssemblyFormat = 1; let hasVerifier = 1; + + let extraInstanceClassDeclaration = [{ + /** + * Return the name of the default target. + */ + ::llvm::StringRef getReferencedModuleName() { + return getReferencedModuleNameAttr().getValue(); + } + + /** + * Return the name of the default target. + */ + ::mlir::StringAttr getReferencedModuleNameAttr() { + return getModuleNameAttr().getAttr(); + } + }]; } def InstanceChoiceOp : HWInstanceOpBase<"instance_choice", [ + HWInstanceLike, DeclareOpInterfaceMethods @@ -545,7 +566,8 @@ def InstanceChoiceOp : HWInstanceOpBase<"instance_choice", [ let arguments = (ins StrAttr:$instanceName, FlatSymbolRefArrayAttr:$moduleNames, - StrArrayAttr:$targetNames, + StrAttr:$optionName, + StrArrayAttr:$caseNames, Variadic:$inputs, StrArrayAttr:$argNames, StrArrayAttr:$resultNames, ParamDeclArrayAttr:$parameters, @@ -554,6 +576,15 @@ def InstanceChoiceOp : HWInstanceOpBase<"instance_choice", [ let hasCustomAssemblyFormat = 1; let hasVerifier = 1; + + let extraInstanceClassDeclaration = [{ + /** + * Return the name of the default target. + */ + StringAttr getDefaultModuleNameAttr() { + return getModuleNamesAttr()[0].cast().getAttr(); + } + }]; } def OutputOp : HWOp<"output", [Terminator, HasParent<"HWModuleOp">, diff --git a/include/circt/Dialect/HW/HWTypesImpl.td b/include/circt/Dialect/HW/HWTypesImpl.td index 9319a4847a77..2f165a2a17ad 100644 --- a/include/circt/Dialect/HW/HWTypesImpl.td +++ b/include/circt/Dialect/HW/HWTypesImpl.td @@ -259,8 +259,7 @@ def ModuleTypeImpl : HWType<"Module"> { Type getPortType(size_t); Type getInputType(size_t); Type getOutputType(size_t); - SmallVector getInputNamesStr(); - SmallVector getOutputNamesStr(); + SmallVector getPortNames(); SmallVector getInputNames(); SmallVector getOutputNames(); StringAttr getPortNameAttr(size_t); diff --git a/include/circt/Dialect/HW/HWVisitors.h b/include/circt/Dialect/HW/HWVisitors.h index 531ee140e3c7..cc749c26853f 100644 --- a/include/circt/Dialect/HW/HWVisitors.h +++ b/include/circt/Dialect/HW/HWVisitors.h @@ -89,10 +89,10 @@ class StmtVisitor { ResultType dispatchStmtVisitor(Operation *op, ExtraArgs... args) { auto *thisCast = static_cast(this); return TypeSwitch(op) - .template Case( - [&](auto expr) -> ResultType { - return thisCast->visitStmt(expr, args...); - }) + .template Case([&](auto expr) -> ResultType { + return thisCast->visitStmt(expr, args...); + }) .Default([&](auto expr) -> ResultType { return thisCast->visitInvalidStmt(op, args...); }); @@ -129,6 +129,7 @@ class StmtVisitor { // Basic nodes. HANDLE(OutputOp, Unhandled); HANDLE(InstanceOp, Unhandled); + HANDLE(InstanceChoiceOp, Uhandled); HANDLE(TypeScopeOp, Unhandled); HANDLE(TypedeclOp, Unhandled); #undef HANDLE diff --git a/include/circt/Dialect/HW/ModuleImplementation.h b/include/circt/Dialect/HW/ModuleImplementation.h index bfe8e0182db3..4ccbd565ffd2 100644 --- a/include/circt/Dialect/HW/ModuleImplementation.h +++ b/include/circt/Dialect/HW/ModuleImplementation.h @@ -22,6 +22,8 @@ namespace circt { namespace hw { +class HWModuleLike; + namespace module_like_impl { struct PortParse : OpAsmParser::Argument { @@ -48,7 +50,7 @@ void printModuleSignature(OpAsmPrinter &p, Operation *op, ParseResult parseModuleSignature(OpAsmParser &parser, SmallVectorImpl &args, TypeAttr &modType); -void printModuleSignatureNew(OpAsmPrinter &p, Operation *op); +void printModuleSignatureNew(OpAsmPrinter &p, HWModuleLike op); } // namespace module_like_impl } // namespace hw diff --git a/include/circt/Dialect/MSFT/MSFTConstructs.td b/include/circt/Dialect/MSFT/MSFTConstructs.td index d06b64113d0e..3531dca9be57 100644 --- a/include/circt/Dialect/MSFT/MSFTConstructs.td +++ b/include/circt/Dialect/MSFT/MSFTConstructs.td @@ -45,30 +45,6 @@ def PEOutputOp: MSFTOp<"pe.output", [Terminator]> { let assemblyFormat = "$output attr-dict `:` type($output)"; } -def ChannelOp: MSFTOp<"constructs.channel", - [Pure, Symbol, - AllTypesMatch<["input", "output"]>]> { - - let summary = "A pipeline-able connection"; - let description = [{ - A logical, feed-forward connection between a producer and consumer. Can be - pipelined with a number of stages (cycle delay) on a per-instance basis. - `defaultStages` is used when `stages` isn't specified by a - `DynamicInstance`. Non-resettable, for now. - - Per-instance specification is not yet supported, so the default pipelining - is always used. - }]; - - let arguments = (ins AnyType:$input, ClockType:$clk, StrAttr:$sym_name, - UI64Attr:$defaultStages); - let results = (outs AnyType:$output); - - let assemblyFormat = [{ - $input $clk $sym_name `(` $defaultStages `)` attr-dict `:` type($input) - }]; -} - // Linear, pipelineable datapath. def LinearOp : MSFTOp<"hlc.linear", [ SingleBlockImplicitTerminator<"OutputOp"> diff --git a/include/circt/Dialect/Moore/CMakeLists.txt b/include/circt/Dialect/Moore/CMakeLists.txt index 58d16ff7a52e..0fdc97c38bf8 100644 --- a/include/circt/Dialect/Moore/CMakeLists.txt +++ b/include/circt/Dialect/Moore/CMakeLists.txt @@ -1,5 +1,6 @@ add_circt_dialect(Moore moore) -add_circt_dialect_doc(Moore moore) +add_circt_doc(MooreOps Dialects/MooreOps -gen-op-doc) +add_circt_doc(MooreTypes Dialects/MooreTypes -gen-typedef-doc -dialect moore) set(LLVM_TARGET_DEFINITIONS Moore.td) diff --git a/include/circt/Dialect/Moore/MIRExpressions.td b/include/circt/Dialect/Moore/MIRExpressions.td deleted file mode 100644 index 7a24ed66fb80..000000000000 --- a/include/circt/Dialect/Moore/MIRExpressions.td +++ /dev/null @@ -1,227 +0,0 @@ -//===- MIRExpressions.td - Moore MIR expression ops --------*- 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 ops for Moore MIR expressions. -// -//===----------------------------------------------------------------------===// - -include "circt/Dialect/Moore/MooreTypes.td" -include "mlir/IR/EnumAttr.td" -include "mlir/Interfaces/InferTypeOpInterface.td" - -// Base class for binary operators. -class BinOp traits = []> : - MIROp { - let arguments = (ins AnyType:$lhs, AnyType:$rhs, UnitAttr:$twoState); - let results = (outs AnyType:$result); - - let assemblyFormat = - "$lhs `,` $rhs (`bin` $twoState^)? attr-dict `:` functional-type($args, $results)"; -} - -// Binary operator with uniform input types. -class UTBinOp traits = []> : - BinOp { - let assemblyFormat = "(`bin` $twoState^)? $lhs `,` $rhs attr-dict `:` qualified(type($result))"; -} - -// Base class for variadic operators. -class VariadicOp traits = []> : - MIROp { - let arguments = (ins Variadic:$inputs, UnitAttr:$twoState); - - let results = (outs AnyType:$result); -} - -// VariadicOp with uniform input types. -class UTVariadicOp traits = []> : - VariadicOp { - - let assemblyFormat = "(`bin` $twoState^)? $inputs attr-dict `:` qualified(type($result))"; - - let builders = [ - OpBuilder<(ins "Value":$lhs, "Value":$rhs, CArg<"bool", "false">:$twoState), [{ - return build($_builder, $_state, lhs.getType(), - ValueRange{lhs, rhs}, twoState); - }]> - ]; -} - -def ConcatOp : MIROp<"concat", [ - Pure, DeclareOpInterfaceMethods -]> { - let summary = "A concatenation of expressions"; - let description = [{ - This operation represents the SystemVerilog concatenation expression - `{x, y, z}`. See IEEE 1800-2017 §11.4.12 "Concatenation operators". - - All operands must be simple bit vector types. - - The concatenation result is a simple bit vector type. The result is unsigned - regardless of the sign of the operands (see concatenation-specific rules in - IEEE 1800-2017 §11.8.1 "Rules for expression types"). The size of the result - is the sum of the sizes of all operands. If any of the operands is - four-valued, the result is four-valued; otherwise it is two-valued. - }]; - let arguments = (ins Variadic:$values); - let results = (outs SimpleBitVectorType:$result); - let assemblyFormat = [{ - $values attr-dict `:` functional-type($values, $result) - }]; -} - -//===----------------------------------------------------------------------===// -// Shift operations -//===----------------------------------------------------------------------===// - -class ShiftOp : MIROp -]> { - let arguments = (ins SimpleBitVectorType:$value, - SimpleBitVectorType:$amount, - UnitAttr:$arithmetic); - let results = (outs SimpleBitVectorType:$result); - let assemblyFormat = [{ - ( `arithmetic` $arithmetic^ )? $value `,` $amount attr-dict - `:` type($value) `,` type($amount) - }]; -} - -def ShlOp : ShiftOp<"shl"> { - let summary = "A logical or arithmetic left-shift expression"; - let description = [{ - This operation represents the SystemVerilog logical and arithmetic - left-shift expressions `<<` and `<<<`. - See IEEE 1800-2017 §11.4.10 "Shift operators". - - The value to be shifted and the amount must be simple bit vector types. - The shift result is of the same type as the input value. - - The logical and arithmetic shift both insert zeros in place of the shifted - bits. - }]; -} - -def ShrOp : ShiftOp<"shr"> { - let summary = "A logical or arithmetic right-shift expression"; - let description = [{ - This operation represents the SystemVerilog logical and arithmetic - right-shift expressions `>>` and `>>>`. - See IEEE 1800-2017 §11.4.10 "Shift operators". - - The value to be shifted and the amount must be simple bit vector types. - The shift result is of the same type as the input value. - - The logical shift always inserts zeros in place of the shifted bits. - The arithmetic shift inserts zeros if the result type is unsigned or the - MSB (sign bit) if the result type is signed. - }]; -} - -//===---------------------------------------------------------------------===// -// Equality operations -//===---------------------------------------------------------------------===// - -class EqualOp : MIROp { - let arguments = (ins SimpleBitVectorType:$lhs, - SimpleBitVectorType:$rhs, - UnitAttr:$mode); - let results = (outs I1:$result); - let assemblyFormat = [{ - ( `case` $mode^ )? $lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs) - }]; -} - -def EqualityOp : EqualOp<"eq">{ - let summary = "A logical or case equality expression"; - let description = [{ - This operation represents the SystemVerilog logical and case - equality expressions `==` and `===`. - See IEEE1800-2017 11.4.5 "Equality operators". - - a == ba equal to b, result can be unknown. - a === ba equal to b, including x and z. - - The operators compare operands bit for bit. As with the relational - operators, the result shall be 0 if comparison fails and 1 if it succeeds. - }]; -} - -def InEqualityOp : EqualOp<"ne">{ - let summary = "A logical or case inequality expression"; - let description = [{ - This operation represents the SystemVerilog logical and case - inequality expressions `!=` and `!==`. - See IEEE1800-2017 11.4.5 "Equality operators". - - a != ba not equal to b, result can be unknown. - a !== ba not equal to b, including x and z. - }]; -} - -//===---------------------------------------------------------------------===// -// Logical operations -//===---------------------------------------------------------------------===// - -def LogicalAnd : I32EnumAttrCase<"LogicalAnd", 0, "and">; -def LogicalOr : I32EnumAttrCase<"LogicalOr", 1, "or">; -def LogicalImplication : I32EnumAttrCase<"LogicalImplication", 2, "impl">; -def LogicalEquivalence : I32EnumAttrCase<"LogicalEquivalence", 3, "equiv">; - -def LogicalAttr : I32EnumAttr<"Logic", "Logical predicate", - [LogicalAnd, LogicalOr, LogicalImplication, LogicalEquivalence]>{ - let cppNamespace = "circt::moore"; -} - -def LogicalOp : MIROp<"logic", [ - Pure, - TypesMatchWith<"lhs and result types must match", - "lhs", "result", "$_self"> -]> { - let summary = "Definitions of logical operators"; - let description = [{ - The result of the evaluation of a logical operation shall be `1`, `0`, - or, if the result is ambiguous, the unkonw value `x`. - See IEEE Std 1800-2017 11.4.7. - }]; - let arguments = (ins LogicalAttr:$logic, - SimpleBitVectorType:$lhs, - SimpleBitVectorType:$rhs); - let results = (outs SimpleBitVectorType:$result); - - let assemblyFormat = [{ - $logic $lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs) - }]; -} - -//===----------------------------------------------------------------------===// -// Vector bit/part select operations -//===----------------------------------------------------------------------===// -def ExtractOp : MIROp<"extract"> { - let summary = [{ - Extract a range of bits into a smaller value, - lowBit specifies the lowest bit included. - }]; - let arguments = (ins UnpackedType:$input, I32Attr:$lowBit); - let results = (outs UnpackedType:$result); - let assemblyFormat = - "$input `from` $lowBit attr-dict `:` functional-type($input, $result)"; - let builders = [ - OpBuilder<(ins "Value":$lhs, "int32_t":$lowBit, "int32_t":$bitWidth), [{ - auto resultType = $_builder.getIntegerType(bitWidth); - return build($_builder, $_state, resultType, lhs, lowBit); - }]> - ]; -} diff --git a/include/circt/Dialect/Moore/MIRStatements.td b/include/circt/Dialect/Moore/MIRStatements.td deleted file mode 100644 index 60948205f6a8..000000000000 --- a/include/circt/Dialect/Moore/MIRStatements.td +++ /dev/null @@ -1,84 +0,0 @@ -//===- MIRStatements.td - Moore MIR statements ops ---------*- 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 ops for Moore MIR statements. -// -//===----------------------------------------------------------------------===// - -include "circt/Dialect/Moore/MooreTypes.td" - -//===----------------------------------------------------------------------===// -// Assignment Statements -//===----------------------------------------------------------------------===// - -def AssignOp : MIROp<"assign", [SameTypeOperands]> { - let summary = "Continuous assignment"; - let description = [{ - A SystemVerilog assignment statement 'x = y;'. - These occur in module scope. See SV Spec 10.3.2. - }]; - let arguments = (ins UnpackedType:$dest, UnpackedType:$src); - let results = (outs); - let assemblyFormat = [{ - $dest `,` $src attr-dict `:` qualified(type($src)) - }]; -} - -def CAssignOp : MIROp<"cassign", [SameTypeOperands]> { - let summary = "Continuous assignment"; - let description = [{ - A SystemVerilog assignment statement 'x = y;'. - These occur in module scope. See SV Spec 10.3.2. - }]; - let arguments = (ins AnyType:$dest, AnyType:$src); - let results = (outs); - let assemblyFormat = [{ - $dest `,` $src attr-dict `:` qualified(type($src)) - }]; -} - -def BPAssignOp : MIROp<"bpassign", [SameTypeOperands]> { - let summary = "Blocking procedural assignment"; - let description = [{ - A SystemVerilog blocking procedural assignment statement 'x = y;'. - These occur in a sequential block. See SV Spec 10.4.1. - }]; - let arguments = (ins AnyType:$dest, AnyType:$src); - let results = (outs); - let assemblyFormat = [{ - $dest `,` $src attr-dict `:` qualified(type($src)) - }]; -} - -def PAssignOp : MIROp<"passign", [SameTypeOperands]> { - let summary = "Nonblocking procedural assignment"; - let description = [{ - A SystemVerilog nonblocking procedural assignment statement 'x <= y;'. - These occur in a sequential block. See SV Spec 10.4.12. - }]; - let arguments = (ins AnyType:$dest, AnyType:$src); - let results = (outs); - let assemblyFormat = [{ - $dest `,` $src attr-dict `:` qualified(type($src)) - }]; -} - -def PCAssignOp : MIROp<"pcassign", [SameTypeOperands]> { - let summary = "Procedural continuous assignment"; - let description = [{ - A SystemVerilog assignment statement 'assign x = y;'. - The procedural continuous assignments (using keywords assign and force) are procedural statements - that allow expressions to be driven continuously onto variables or nets - These occur in procedural scope. See SV Spec 10.6 - }]; - let arguments = (ins AnyType:$dest, AnyType:$src); - let results = (outs); - let assemblyFormat = [{ - $dest `,` $src attr-dict `:` qualified(type($src)) - }]; -} diff --git a/include/circt/Dialect/Moore/Moore.td b/include/circt/Dialect/Moore/Moore.td index 6a5416656dff..5c6284db230b 100644 --- a/include/circt/Dialect/Moore/Moore.td +++ b/include/circt/Dialect/Moore/Moore.td @@ -13,25 +13,8 @@ #ifndef CIRCT_DIALECT_MOORE_MOORE #define CIRCT_DIALECT_MOORE_MOORE -include "mlir/IR/AttrTypeBase.td" -include "mlir/IR/OpBase.td" -include "mlir/IR/OpAsmInterface.td" -include "mlir/IR/SymbolInterfaces.td" -include "mlir/Interfaces/SideEffectInterfaces.td" - include "circt/Dialect/Moore/MooreDialect.td" - -// Base class for the operations in this dialect. -class MooreOp traits = []> : - Op; - -// Base class for the MIR operations in this dialect. -class MIROp traits = []> : - MooreOp<"mir." # mnemonic, traits>; - include "circt/Dialect/Moore/MooreTypes.td" -include "circt/Dialect/Moore/MIRExpressions.td" -include "circt/Dialect/Moore/MIRStatements.td" include "circt/Dialect/Moore/MooreOps.td" #endif // CIRCT_DIALECT_MOORE_MOORE diff --git a/include/circt/Dialect/Moore/MooreDialect.td b/include/circt/Dialect/Moore/MooreDialect.td index 69f4eb0a63ef..e603be99b889 100644 --- a/include/circt/Dialect/Moore/MooreDialect.td +++ b/include/circt/Dialect/Moore/MooreDialect.td @@ -13,6 +13,8 @@ #ifndef CIRCT_DIALECT_MOORE_MOOREDIALECT #define CIRCT_DIALECT_MOORE_MOOREDIALECT +include "mlir/IR/DialectBase.td" + def MooreDialect : Dialect { let name = "moore"; diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index ab2536fd8d68..734a0b7b0003 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -9,9 +9,19 @@ #ifndef CIRCT_DIALECT_MOORE_MOOREOPS #define CIRCT_DIALECT_MOORE_MOOREOPS +include "circt/Dialect/Moore/MooreDialect.td" include "circt/Dialect/Moore/MooreTypes.td" +include "mlir/IR/EnumAttr.td" +include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/OpBase.td" include "mlir/IR/RegionKindInterface.td" +include "mlir/IR/SymbolInterfaces.td" include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +// Base class for the operations in this dialect. +class MooreOp traits = []> : + Op; //===----------------------------------------------------------------------===// // Structure @@ -20,23 +30,24 @@ include "mlir/Interfaces/InferTypeOpInterface.td" def SVModuleOp : MooreOp<"module", [ IsolatedFromAbove, Symbol, - RegionKindInterface, - NoTerminator + NoTerminator, + SingleBlock ]> { let summary = "A module definition"; + let description = [{ + The `moore.module` operation represents a SystemVerilog module, including + its name, port list, and the constituent parts that make up its body. The + module's body is an SSACFG region, since declarations within SystemVerilog + modules generally have to appear before their uses, and dedicated assignment + operators are used to make connections after declarations. - let arguments = (ins SymbolNameAttr:$sym_name); - let regions = (region SizedRegion<1>:$body); - let assemblyFormat = [{ - $sym_name attr-dict-with-keyword $body + See IEEE 1800-2017 § 3.3 "Modules" and § 23.2 "Module definitions". }]; - let extraClassDeclaration = [{ - static mlir::RegionKind getRegionKind(unsigned index) { - return mlir::RegionKind::SSACFG; - } - - mlir::Block &getBodyBlock() { return getBody().front(); } + let arguments = (ins SymbolNameAttr:$sym_name); + let regions = (region SizedRegion<1>:$bodyRegion); + let assemblyFormat = [{ + $sym_name attr-dict-with-keyword $bodyRegion }]; } @@ -44,6 +55,11 @@ def InstanceOp : MooreOp<"instance", [ DeclareOpInterfaceMethods ]> { let summary = "Create an instance of a module"; + let description = [{ + The `moore.instance` operation instantiates a `moore.module` operation. + + See IEEE 1800-2017 § 23.3 "Module instances". + }]; let arguments = (ins StrAttr:$instanceName, FlatSymbolRefAttr:$moduleName); @@ -160,12 +176,70 @@ def NetOp : MooreOp<"net", [ // Statements //===----------------------------------------------------------------------===// +def CAssignOp : MooreOp<"cassign", [SameTypeOperands]> { + let summary = "Continuous assignment"; + let description = [{ + A continuous assignment in module scope, such as `assign x = y;`. + + See IEEE 1800-2017 § 10.3.2 "The continuous assignment statement". + }]; + let arguments = (ins AnyType:$dest, AnyType:$src); + let results = (outs); + let assemblyFormat = [{ + $dest `,` $src attr-dict `:` qualified(type($src)) + }]; +} + +def BPAssignOp : MooreOp<"bpassign", [SameTypeOperands]> { + let summary = "Blocking procedural assignment"; + let description = [{ + A blocking procedural assignment in a sequential block, such as `x = y;`. + + See IEEE 1800-2017 § 10.4.1 "Blocking procedural assignments". + }]; + let arguments = (ins AnyType:$dest, AnyType:$src); + let results = (outs); + let assemblyFormat = [{ + $dest `,` $src attr-dict `:` qualified(type($src)) + }]; +} + +def PAssignOp : MooreOp<"passign", [SameTypeOperands]> { + let summary = "Nonblocking procedural assignment"; + let description = [{ + A nonblocking procedural assignment in a sequential block, such as `x <= y;` + or `x <= @(posedge y) z` or `x <= #1ns y`. + + See IEEE 1800-2017 § 10.4.2 "Nonblocking procedural assignments". + }]; + let arguments = (ins AnyType:$dest, AnyType:$src); + let results = (outs); + let assemblyFormat = [{ + $dest `,` $src attr-dict `:` qualified(type($src)) + }]; +} + +def PCAssignOp : MooreOp<"pcassign", [SameTypeOperands]> { + let summary = "Procedural continuous assignment"; + let description = [{ + A procedural continuous assignment in a sequential block, such as + `assign x = y;`. + + See IEEE 1800-2017 § 10.6 "Procedural continuous assignments". + }]; + let arguments = (ins AnyType:$dest, AnyType:$src); + let results = (outs); + let assemblyFormat = [{ + $dest `,` $src attr-dict `:` qualified(type($src)) + }]; +} + def None: I32EnumAttrCase<"None", 0, "none">; def PosEdge: I32EnumAttrCase<"PosEdge", 1, "posedge">; def NegEdge: I32EnumAttrCase<"NegEdge", 2, "negedge">; def BothEdges: I32EnumAttrCase<"BothEdges", 3, "bothedges">; -def EdgeAtrr: I32EnumAttr<"Edge", "Edge kind", +def EdgeAtrr: I32EnumAttr<"Edge", "Edge kind", [None, PosEdge, NegEdge, BothEdges]>{ let cppNamespace = "circt::moore"; } @@ -222,7 +296,7 @@ def ConversionOp : MooreOp<"conversion", [Pure]> { See IEEE 1800-2017 § 6.24 "Casting". }]; let arguments = (ins AnyType:$input); - let results = (outs UnpackedType:$result); + let results = (outs AnyType:$result); let assemblyFormat = [{ $input attr-dict `:` type($input) `->` type($result) }]; @@ -442,6 +516,42 @@ def XorOp : BinaryOpBase<"xor", [Commutative]> { }]; } +class ShiftOpBase : MooreOp +]> { + let description = [{ + Shifts the `value` to the left or right by `amount` number of bits. The + result has the same type as the input value. The amount is always treated as + an unsigned number and has no effect on the signedness off the result. X or + Z bits in the input value are simply shifted left or right the same way 0 or + 1 bits are. If the amount contains X or Z bits, all result bits are X. + + `shl` shifts bits to the left, filling in 0 for the vacated least + significant bits. `shr` and `ashr` shift bits to the right; `shr` fills in + 0 for the vacated most significant bits, and `ashr` copies the input's sign + bit into the vacated most significant bits. Note that in contrast to the SV + spec, the `ashr` _always_ fills in the sign bit regardless of the signedness + of the input. + + `shl` corresponds to the `<<` and `<<<` operators. `shr` corresponds to the + `>>` operator, and the `>>>` operator applied to an unsigned value. `ashr` + corresponds to the `>>>` operator applied to a signed value. + + See IEEE 1800-2017 § 11.4.10 "Shift operators". + }]; + let arguments = (ins SimpleBitVectorType:$value, SimpleBitVectorType:$amount); + let results = (outs SimpleBitVectorType:$result); + let assemblyFormat = [{ + $value `,` $amount attr-dict `:` type($value) `,` type($amount) + }]; +} + +def ShlOp : ShiftOpBase<"shl"> { let summary = "Logical left shift"; } +def ShrOp : ShiftOpBase<"shr"> { let summary = "Logical right shift"; } +def AShrOp : ShiftOpBase<"ashr"> { let summary = "Arithmetic right shift"; } + class LogicalEqOpBase : MooreOp { let summary = "Less than or equal"; } def GtOp : RelationalOpBase<"gt"> { let summary = "Greater than"; } def GeOp : RelationalOpBase<"ge"> { let summary = "Greater than or equal"; } +def ConcatOp : MooreOp<"concat", [ + Pure, DeclareOpInterfaceMethods +]> { + let summary = "A concatenation of expressions"; + let description = [{ + This operation represents the SystemVerilog concatenation expression + `{x, y, z}`. See IEEE 1800-2017 §11.4.12 "Concatenation operators". + + All operands must be simple bit vector types. + + The concatenation result is a simple bit vector type. The result is unsigned + regardless of the sign of the operands (see concatenation-specific rules in + IEEE 1800-2017 §11.8.1 "Rules for expression types"). The size of the result + is the sum of the sizes of all operands. If any of the operands is + four-valued, the result is four-valued; otherwise it is two-valued. + }]; + let arguments = (ins Variadic:$values); + let results = (outs SimpleBitVectorType:$result); + let assemblyFormat = [{ + $values attr-dict `:` functional-type($values, $result) + }]; +} + #endif // CIRCT_DIALECT_MOORE_MOOREOPS diff --git a/include/circt/Dialect/Moore/MoorePasses.h b/include/circt/Dialect/Moore/MoorePasses.h index ea73765c612a..a76d06081cb3 100644 --- a/include/circt/Dialect/Moore/MoorePasses.h +++ b/include/circt/Dialect/Moore/MoorePasses.h @@ -24,24 +24,21 @@ namespace circt { namespace moore { class Declaration { - // A map used to collect nets or variables and their values. + // A map used to collect output ports, nets or variables and their values. // Only record the value produced by the last assignment op, // including the declaration assignment. - DenseMap netOrVarInfo; + DenseMap assignmentChains; - // Identifying an assignment statement whether been used by a net/variable. - // If identified as true, delete it at the stage of conversion between - // dialects, except for the output ports assignment. - DenseMap isUsedAssignment; + // Record chains of port op & assignments op. It only records port which is + // out direction now. + DenseMap portChains; public: - void addValue(Operation *op, Value value) { netOrVarInfo[op] = value; } - void addIdentifier(Operation *op, bool b) { - isUsedAssignment.insert({op, b}); - } + void addValue(Operation *op); - auto getValue(Operation *op) { return netOrVarInfo.lookup(op); } - auto getIdentifier(Operation *op) { return isUsedAssignment.lookup(op); } + auto getValue(Operation *op) { return assignmentChains.lookup(op); } + + Operation *getOutputValue(Operation *op) { return portChains.lookup(op); } }; extern Declaration decl; diff --git a/include/circt/Dialect/Moore/MoorePasses.td b/include/circt/Dialect/Moore/MoorePasses.td index 284407fc81df..cc0dcbb5be08 100644 --- a/include/circt/Dialect/Moore/MoorePasses.td +++ b/include/circt/Dialect/Moore/MoorePasses.td @@ -19,10 +19,10 @@ def MooreDeclarations : Pass<"moore-declarations", "mlir::ModuleOp"> { let summary = "Collect all nets and variables declarations"; let description = [{ This pass will perform a pre-pass over the IR to find all concurrently- - assigned nets and variables. Basically go through all net/variable - declarations and collect the connected value in a hash map (either the - initial value in nets, or the cassigned value). At the same time add all - cassigns that has considered here into a set of ops to be deleted. + assigned output ports, nets and variables. Basically go through all output + port/net/variable declarations and collect the connected value in a hash map + (either the initial value in nets, or the cassigned value). At the same time + add all cassigns that has considered here into a set of ops to be deleted. }]; let constructor = "circt::moore::createMooreDeclarationsPass()"; } diff --git a/include/circt/Dialect/Moore/MooreTypes.h b/include/circt/Dialect/Moore/MooreTypes.h index 4c25199428e0..7a4987911cd9 100644 --- a/include/circt/Dialect/Moore/MooreTypes.h +++ b/include/circt/Dialect/Moore/MooreTypes.h @@ -469,6 +469,8 @@ class VoidType public: static VoidType get(MLIRContext *context); + static constexpr StringLiteral name = "moore.void"; + protected: using Base::Base; }; @@ -479,6 +481,8 @@ class StringType public: static StringType get(MLIRContext *context); + static constexpr StringLiteral name = "moore.string"; + protected: using Base::Base; }; @@ -489,6 +493,8 @@ class ChandleType public: static ChandleType get(MLIRContext *context); + static constexpr StringLiteral name = "moore.chandle"; + protected: using Base::Base; }; @@ -499,6 +505,8 @@ class EventType public: static EventType get(MLIRContext *context); + static constexpr StringLiteral name = "moore.event"; + protected: using Base::Base; }; @@ -585,6 +593,8 @@ class IntType /// to the user in diagnostics. void format(llvm::raw_ostream &os) const; + static constexpr StringLiteral name = "moore.int"; + protected: using Base::Base; }; @@ -623,6 +633,8 @@ class RealType /// Get the size of this type. unsigned getBitSize() const { return getBitSize(getKind()); } + static constexpr StringLiteral name = "moore.real"; + protected: using Base::Base; }; @@ -758,6 +770,9 @@ class UnpackedIndirectType : public IndirectTypeBase { /// A packed named type. See `NamedTypeBase` for details. class PackedNamedType : public NamedTypeBase { +public: + static constexpr StringLiteral name = "moore.packed_named"; + protected: using NamedBase::NamedBase; }; @@ -765,12 +780,18 @@ class PackedNamedType /// An unpacked named type. See `NamedTypeBase` for details. class UnpackedNamedType : public NamedTypeBase { +public: + static constexpr StringLiteral name = "moore.unpacked_named"; + protected: using NamedBase::NamedBase; }; /// A packed named type. See `NamedTypeBase` for details. class PackedRefType : public RefTypeBase { +public: + static constexpr StringLiteral name = "moore.packed_ref"; + protected: using RefBase::RefBase; }; @@ -778,6 +799,9 @@ class PackedRefType : public RefTypeBase { /// An unpacked named type. See `NamedTypeBase` for details. class UnpackedRefType : public RefTypeBase { +public: + static constexpr StringLiteral name = "moore.unpacked_ref"; + protected: using RefBase::RefBase; }; @@ -830,6 +854,8 @@ class PackedUnsizedDim : public Type::TypeBase getBound() const; + static constexpr StringLiteral name = "moore.unpacked_queue_dim"; + protected: using Base::Base; friend struct detail::DimStorage; @@ -1032,6 +1070,8 @@ class EnumType /// to the user in diagnostics. void format(llvm::raw_ostream &os) const; + static constexpr StringLiteral name = "moore.enum"; + protected: using Base::Base; }; @@ -1155,6 +1195,8 @@ class PackedStructType : public Type::TypeBase : DialectType; -def PackedType : MooreType()">, - "packed type", "moore::PackedType">; - -def UnpackedType : MooreType()">, - "unpacked type", "moore::UnpackedType">; - /// A simple bit vector type. def SimpleBitVectorType : MooreType() && @@ -47,11 +44,142 @@ def BitType : MooreType() && + $_self.cast().getBitSize() == 1 && + $_self.cast().getDomain() == moore::Domain::FourValued && + $_self.cast().getKeyword() == "logic" + }]>, "`logic` type", "moore::IntType"> { + let builderCall = [{ + $_builder.getLogic() + }]; +} + +/// A `reg` type. +def RegType : MooreType() && + $_self.cast().getBitSize() == 1 && + $_self.cast().getDomain() == moore::Domain::FourValued && + $_self.cast().getKeyword() == "reg" + }]>, "`reg` type", "moore::RegType"> { + let builderCall = [{ + $_builder.getType(IntType::Kind::Reg) + }]; +} + +/// A `byte` type. +def ByteType : MooreType() && + $_self.cast().getBitSize() == 8 + }]>, "`byte` type", "moore::ByteType"> { + let builderCall = [{ + $_builder.getType(IntType::Kind::Byte) + }]; +} + +/// A `shortInt` type. +def ShortIntType : MooreType() && + $_self.cast().getBitSize() == 16 + }]>, "`shortInt` type", "moore::ShortIntType"> { + let builderCall = [{ + $_builder.getType(IntType::Kind::ShortInt) + }]; +} + +/// A `int` type. +def IntType : MooreType() && + $_self.cast().getBitSize() == 32 && + $_self.cast().getDomain() == moore::Domain::TwoValued + }]>, "`int` type", "moore::IntType"> { + let builderCall = [{ + $_builder.getInt() + }]; +} + +/// A `longInt` type. +def LongIntType : MooreType() && + $_self.cast().getBitSize() == 64 && + $_self.cast().getDefaultSign() == moore::Sign::Signed + }]>, "`longInt` type", "moore::LongIntType"> { + let builderCall = [{ + $_builder.getType(IntType::Kind::LongInt) + }]; +} + +/// A `integer` type. +def IntegerType : MooreType() && + $_self.cast().getBitSize() == 32 && + $_self.cast().getDomain() == moore::Domain::FourValued + }]>, "`integer` type", "moore::IntegerType"> { + let builderCall = [{ + $_builder.getType(IntType::Kind::Integer) + }]; +} + +/// A `time` type. +def TimeType : MooreType() && + $_self.cast().getBitSize() == 64 && + $_self.cast().getDefaultSign() == moore::Sign::Unsigned + }]>, "`time` type", "moore::TimeType"> { + let builderCall = [{ + $_builder.getTime() + }]; +} + //===----------------------------------------------------------------------===// -// Integer atom types +// Packed type & Unpacked type //===----------------------------------------------------------------------===// +def PackedType : MooreType()">, + "packed type", "moore::PackedType">; -def MooreIntType : MooreType()">, - "an SystemVerilog int", "moore::IntType">; +def UnpackedType : MooreType()">, + "unpacked type", "moore::UnpackedType">; + +//===----------------------------------------------------------------------===// +// Real types +//===----------------------------------------------------------------------===// +/// A `shortReal` type. +def ShortRealType : MooreType() && + $_self.cast().getBitSize() == 32 + }]>, "`shortReal` type", "moore::ShortRealType"> { + let builderCall = [{ + $_builder.getType(RealType::Kind::ShortReal) + }]; + } + +/// A `real` type. +def RealType : MooreType() && + $_self.cast().getBitSize() == 64 && + $_self.cast().getKind() == moore::RealType::Real + }]>, "`real` type", "moore::RealType"> { + let builderCall = [{ + $_builder.getType(RealType::Kind::Real) + }]; + } + +/// A `realTime` type. +def RealTimeType : MooreType() && + $_self.cast().getBitSize() == 64 && + $_self.cast().getKind() == moore::RealType::RealTime + }]>, "`realTime` type", "moore::RealTimeType"> { + let builderCall = [{ + $_builder.getType(RealType::Kind::RealTime) + }]; + } +//===----------------------------------------------------------------------===// +// Enum type +//===----------------------------------------------------------------------===// +def EnumType : MooreType()">, + "a SystemVerilog Enum type", "moore::EnumType">; + #endif // CIRCT_DIALECT_MOORE_MOORETYPES diff --git a/include/circt/Dialect/OM/Evaluator/Evaluator.h b/include/circt/Dialect/OM/Evaluator/Evaluator.h index 303bca0971b1..3834e626c108 100644 --- a/include/circt/Dialect/OM/Evaluator/Evaluator.h +++ b/include/circt/Dialect/OM/Evaluator/Evaluator.h @@ -133,9 +133,17 @@ 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) { + : EvaluatorValue(attr.getContext(), Kind::Attr, loc), attr(attr), + type(cast(attr).getType()) { markFullyEvaluated(); } + + // Constructors for partially evaluated AttributeValue. + AttributeValue(Type type) + : AttributeValue(mlir::UnknownLoc::get(type.getContext())) {} + AttributeValue(Type type, Location loc) + : EvaluatorValue(type.getContext(), Kind::Attr, loc), type(type) {} + Attribute getAttr() const { return attr; } template AttrTy getAs() const { @@ -145,13 +153,17 @@ struct AttributeValue : EvaluatorValue { return e->getKind() == Kind::Attr; } + // Set Attribute for partially evaluated case. + LogicalResult setAttr(Attribute attr); + // Finalize the value. - LogicalResult finalizeImpl() { return success(); } + LogicalResult finalizeImpl(); - Type getType() const { return attr.cast().getType(); } + Type getType() const { return type; } private: Attribute attr = {}; + Type type; }; // This perform finalization to `value`. @@ -452,6 +464,11 @@ struct Evaluator { FailureOr evaluateConstant(ConstantOp op, ActualParameters actualParams, Location loc); + + FailureOr + evaluateIntegerBinaryArithmetic(IntegerBinaryArithmeticOp op, + ActualParameters actualParams, Location loc); + /// Instantiate an Object with its class name and actual parameters. FailureOr evaluateObjectInstance(StringAttr className, ActualParameters actualParams, diff --git a/include/circt/Dialect/OM/OMOpInterfaces.h b/include/circt/Dialect/OM/OMOpInterfaces.h index bfda5f89972f..e09ff6aca3a2 100644 --- a/include/circt/Dialect/OM/OMOpInterfaces.h +++ b/include/circt/Dialect/OM/OMOpInterfaces.h @@ -14,6 +14,7 @@ #define CIRCT_DIALECT_OM_OMOPINTERFACES_H #include "mlir/IR/OpDefinition.h" +#include "llvm/ADT/APSInt.h" #include "circt/Dialect/OM/OMOpInterfaces.h.inc" diff --git a/include/circt/Dialect/OM/OMOpInterfaces.td b/include/circt/Dialect/OM/OMOpInterfaces.td index 67e5640970f8..1934142a5a8c 100644 --- a/include/circt/Dialect/OM/OMOpInterfaces.td +++ b/include/circt/Dialect/OM/OMOpInterfaces.td @@ -50,7 +50,25 @@ def ClassFieldLike : OpInterface<"ClassFieldLike"> { let methods = [ InterfaceMethod<"Get the class-like field's type", - "mlir::Type", "getType", (ins)> + "mlir::Type", "getType", (ins)>, + InterfaceMethod<"Get the class-like field's name attribute", + "mlir::StringAttr", "getNameAttr", (ins)> + ]; +} + +def IntegerBinaryArithmeticInterface : OpInterface<"IntegerBinaryArithmeticOp"> { + let cppNamespace = "circt::om"; + let description = "Common interface for integer binary arithmetic ops."; + let methods = [ + InterfaceMethod<"Get the lhs Value", + "mlir::Value", "getLhs", (ins)>, + InterfaceMethod<"Get the rhs Value", + "mlir::Value", "getRhs", (ins)>, + InterfaceMethod<"Get the result Value", + "mlir::Value", "getResult", (ins)>, + InterfaceMethod<"Evaluate the integer binary arithmetic operation", + "mlir::FailureOr", "evaluateIntegerOperation", + (ins "const llvm::APSInt &":$lhs, "const llvm::APSInt &":$rhs)> ]; } diff --git a/include/circt/Dialect/OM/OMOps.td b/include/circt/Dialect/OM/OMOps.td index 11a876a35b58..6b543c8735ac 100644 --- a/include/circt/Dialect/OM/OMOps.td +++ b/include/circt/Dialect/OM/OMOps.td @@ -59,7 +59,6 @@ class OMClassLike traits = []> : class OMClassFieldLike traits = []> : OMOp]> { } @@ -89,12 +88,12 @@ def ClassOp : OMClassLike<"class"> { def ClassFieldOp : OMClassFieldLike<"class.field", [HasParent<"ClassOp">]> { let arguments = (ins - SymbolNameAttr:$sym_name, + SymbolNameAttr:$name, AnyType:$value ); let assemblyFormat = [{ - $sym_name `,` $value attr-dict `:` type($value) + $name `,` $value attr-dict `:` type($value) }]; } @@ -116,12 +115,12 @@ def ClassExternOp : OMClassLike<"class.extern"> { def ClassExternFieldOp : OMClassFieldLike<"class.extern.field", [HasParent<"ClassExternOp">]> { let arguments = (ins - SymbolNameAttr:$sym_name, + SymbolNameAttr:$name, TypeAttr:$type ); let assemblyFormat = [{ - $sym_name attr-dict `:` $type + $name attr-dict `:` $type }]; } @@ -448,4 +447,57 @@ def AnyCastOp : OMOp<"any_cast", [Pure]> { let assemblyFormat = "$input attr-dict `:` functional-type($input, $result)"; } + +class IntegerBinaryArithmeticOp traits = []> : + OMOp + ] # traits> { + let arguments = (ins OMIntegerType:$lhs, OMIntegerType:$rhs); + + let results = (outs OMIntegerType:$result); + + let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($result)"; +} + +def IntegerAddOp : IntegerBinaryArithmeticOp<"integer.add", [Commutative]> { + let summary = "Add two OMIntegerType values"; + let description = [{ + Perform arbitrary precision signed integer addition of two OMIntegerType + values. + + Example: + ```mlir + %2 = om.integer.add %0, %1 : !om.integer + ``` + }]; +} + +def IntegerMulOp : IntegerBinaryArithmeticOp<"integer.mul", [Commutative]> { + let summary = "Multiply two OMIntegerType values"; + let description = [{ + Perform arbitrary prevision signed integer multiplication of two + OMIntegerType values. + + Example: + ```mlir + %2 = om.integer.mul %0, %1 : !om.integer + ``` + }]; +} + +def IntegerShrOp : IntegerBinaryArithmeticOp<"integer.shr"> { + let summary = "Shift an OMIntegerType value right by an OMIntegerType value"; + let description = [{ + Perform arbitrary precision signed integer arithmetic shift right of the lhs + OMIntegerType value by the rhs OMIntegerType value. The rhs value must be + non-negative. + + Example: + ```mlir + %2 = om.integer.shr %0, %1 : !om.integer + ``` + }]; +} + #endif // CIRCT_DIALECT_OM_OMOPS_TD diff --git a/include/circt/Dialect/SV/SVPasses.h b/include/circt/Dialect/SV/SVPasses.h index 2844358f1789..89c477f8cb5f 100644 --- a/include/circt/Dialect/SV/SVPasses.h +++ b/include/circt/Dialect/SV/SVPasses.h @@ -27,6 +27,7 @@ std::unique_ptr createHWCleanupPass(bool mergeAlwaysBlocks = true); std::unique_ptr createHWStubExternalModulesPass(); std::unique_ptr createHWLegalizeModulesPass(); std::unique_ptr createSVTraceIVerilogPass(); +std::unique_ptr createHWLowerInstanceChoices(); std::unique_ptr createHWGeneratorCalloutPass(); std::unique_ptr createHWEliminateInOutPortsPass( const HWEliminateInOutPortsOptions &options = {}); diff --git a/include/circt/Dialect/SV/SVStatements.td b/include/circt/Dialect/SV/SVStatements.td index c269e1e0463d..ce1477cb7905 100644 --- a/include/circt/Dialect/SV/SVStatements.td +++ b/include/circt/Dialect/SV/SVStatements.td @@ -798,16 +798,16 @@ 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 + 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, + let arguments = (ins SymbolNameAttr:$sym_name, OptionalAttr:$args, OptionalAttr:$verilogName); let results = (outs); @@ -818,7 +818,7 @@ def MacroDeclOp : SVOp<"macro.decl", [Symbol]> { } -def MacroDefOp : SVOp<"macro.def", +def MacroDefOp : SVOp<"macro.def", [DeclareOpInterfaceMethods]> { let summary = "System verilog macro definition"; @@ -829,14 +829,14 @@ def MacroDefOp : SVOp<"macro.def", 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 + + 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, + let arguments = (ins FlatSymbolRefAttr:$macroName, StrAttr:$format_string, DefaultValuedAttr:$symbols); let results = (outs); diff --git a/include/circt/Dialect/Seq/SeqOps.td b/include/circt/Dialect/Seq/SeqOps.td index c7214071fe1d..e5e372f288b0 100644 --- a/include/circt/Dialect/Seq/SeqOps.td +++ b/include/circt/Dialect/Seq/SeqOps.td @@ -53,6 +53,13 @@ def CompRegOp : SeqOp<"compreg", let hasVerifier = 1; let builders = [ + /// Create a register with no name nor inner_sym. + OpBuilder<(ins "Value":$input, "Value":$clk), [{ + return build($_builder, $_state, input.getType(), input, clk, + /*name*/ StringAttr(), + /*reset*/ Value(), /*resetValue*/ Value(), + /*powerOnValue*/ Value(), hw::InnerSymAttr()); + }]>, /// 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()); @@ -289,14 +296,13 @@ def FIFOOp : SeqOp<"fifo", [ } def HLMemOp : SeqOp<"hlmem", [ - Symbol, Clocked, DeclareOpInterfaceMethods ]> { let summary = "Instantiate a high-level memory."; let description = "See the Seq dialect rationale for a longer description"; - let arguments = (ins ClockType:$clk, I1:$rst, SymbolNameAttr:$sym_name); + let arguments = (ins ClockType:$clk, I1:$rst, SymbolNameAttr:$name); let results = (outs HLMemType:$handle); let extraClassDeclaration = [{ @@ -304,11 +310,11 @@ def HLMemOp : SeqOp<"hlmem", [ }]; let builders = [ - OpBuilder<(ins "Value":$clk, "Value":$rst, "StringRef":$symName, + OpBuilder<(ins "Value":$clk, "Value":$rst, "StringRef":$name, "llvm::ArrayRef":$shape, "Type":$elementType)> ]; - let assemblyFormat = "$sym_name $clk `,` $rst attr-dict `:` type($handle)"; + let assemblyFormat = "$name $clk `,` $rst attr-dict `:` type($handle)"; } class HLMemTypeValueConstraint @@ -432,7 +438,7 @@ def ClockMuxOp : SeqOp<"clock_mux", [Pure]> { // Clock Dividers //===----------------------------------------------------------------------===// -def ClockDivider : SeqOp<"clock_div", [Pure]> { +def ClockDividerOp : 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. @@ -450,6 +456,31 @@ def ClockDivider : SeqOp<"clock_div", [Pure]> { }]; } +//===----------------------------------------------------------------------===// +// Clock Inverter and Buffer +//===----------------------------------------------------------------------===// + +def ClockInverterOp : SeqOp<"clock_inv", [Pure]> { + let summary = "Inverts the clock signal"; + let description = [{ + Note that the compiler can optimize inverters away, preventing their + use as part of explicit clock buffers. + + ``` + %inv_clock = seq.clock_inv %clock + ``` + }]; + + let arguments = (ins ClockType:$input); + let results = (outs ClockType:$output); + + let hasFolder = 1; + + let assemblyFormat = [{ + $input attr-dict + }]; +} + //===----------------------------------------------------------------------===// // FIRRTL-flavored memory //===----------------------------------------------------------------------===// diff --git a/include/circt/Dialect/Sim/CMakeLists.txt b/include/circt/Dialect/Sim/CMakeLists.txt new file mode 100644 index 000000000000..fa840cd2f665 --- /dev/null +++ b/include/circt/Dialect/Sim/CMakeLists.txt @@ -0,0 +1,14 @@ +##===- CMakeLists.txt - Sim dialect build definitions ---------*- cmake -*-===// +## +## 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 +## +##===----------------------------------------------------------------------===// +## +## +##===----------------------------------------------------------------------===// + +add_circt_dialect(Sim sim) +add_circt_dialect_doc(Sim sim) +add_dependencies(circt-headers MLIRSimIncGen) diff --git a/include/circt/Dialect/Sim/Sim.td b/include/circt/Dialect/Sim/Sim.td new file mode 100644 index 000000000000..dd052d2c8ede --- /dev/null +++ b/include/circt/Dialect/Sim/Sim.td @@ -0,0 +1,24 @@ +//===- Sim.td - Sim 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 +// +//===----------------------------------------------------------------------===// +// +// This is the top level file for the Sim dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SIM_SIM_TD +#define CIRCT_DIALECT_SIM_SIM_TD + +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/OpBase.td" +include "mlir/IR/OpAsmInterface.td" +include "mlir/IR/SymbolInterfaces.td" + +include "circt/Dialect/Sim/SimDialect.td" +include "circt/Dialect/Sim/SimOps.td" + +#endif // CIRCT_DIALECT_SIM_SIM_TD diff --git a/include/circt/Dialect/Sim/SimDialect.h b/include/circt/Dialect/Sim/SimDialect.h new file mode 100644 index 000000000000..fcdb02590f01 --- /dev/null +++ b/include/circt/Dialect/Sim/SimDialect.h @@ -0,0 +1,21 @@ +//===- SimDialect.h - Sim dialect 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines an Sim MLIR dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SIM_SIMDIALECT_H +#define CIRCT_DIALECT_SIM_SIMDIALECT_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/Dialect.h" + +#include "circt/Dialect/Sim/SimDialect.h.inc" + +#endif // CIRCT_DIALECT_SIM_SIMDIALECT_H diff --git a/include/circt/Dialect/Sim/SimDialect.td b/include/circt/Dialect/Sim/SimDialect.td new file mode 100644 index 000000000000..6db46c36873b --- /dev/null +++ b/include/circt/Dialect/Sim/SimDialect.td @@ -0,0 +1,29 @@ +//===- SimDialect.td - Sim 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 +// +//===----------------------------------------------------------------------===// +// +// This contains the SimDialect definition to be included in other files. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SIM_SIMDIALECT +#define CIRCT_DIALECT_SIM_SIMDIALECT + +def SimDialect : Dialect { + let name = "sim"; + let cppNamespace = "::circt::sim"; + + let summary = "Types and operations for the `sim` dialect"; + let description = [{ + The `sim` dialect is intented to model simulator-specific operations. + }]; + + let useDefaultTypePrinterParser = 0; + let useDefaultAttributePrinterParser = 0; +} + +#endif // CIRCT_DIALECT_SIM_SIMDIALECT diff --git a/include/circt/Dialect/Sim/SimOps.h b/include/circt/Dialect/Sim/SimOps.h new file mode 100644 index 000000000000..99c3b7bc4bb5 --- /dev/null +++ b/include/circt/Dialect/Sim/SimOps.h @@ -0,0 +1,28 @@ +//===- SimOps.h - Declare Sim 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 +// +//===----------------------------------------------------------------------===// +// +// This file declares the operation classes for the Sim dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SIM_SIMOPS_H +#define CIRCT_DIALECT_SIM_SIMOPS_H + +#include "mlir/Bytecode/BytecodeOpInterface.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/SymbolTable.h" + +#include "circt/Dialect/Seq/SeqDialect.h" +#include "circt/Dialect/Seq/SeqTypes.h" +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Support/BuilderUtils.h" + +#define GET_OP_CLASSES +#include "circt/Dialect/Sim/Sim.h.inc" + +#endif // CIRCT_DIALECT_SIM_SIMOPS_H diff --git a/include/circt/Dialect/Sim/SimOps.td b/include/circt/Dialect/Sim/SimOps.td new file mode 100644 index 000000000000..d2f03a00577c --- /dev/null +++ b/include/circt/Dialect/Sim/SimOps.td @@ -0,0 +1,57 @@ +//===- SimOps.td - `sim` dialect ops -----------------------*- 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 MLIR ops for `sim`. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SIM_SIMOPS_TD +#define CIRCT_DIALECT_SIM_SIMOPS_TD + +include "mlir/Interfaces/SideEffectInterfaces.td" +include "circt/Dialect/Sim/SimDialect.td" +include "circt/Dialect/Seq/SeqTypes.td" + +class SimOp traits = []> : + Op; + +def PlusArgsTestOp : SimOp<"plusargs.test", [Pure]> { + let summary = "SystemVerilog `$test$plusargs` call"; + + let arguments = (ins StrAttr:$formatString); + let results = (outs I1:$found); + let assemblyFormat = "$formatString attr-dict"; +} + +def PlusArgsValueOp : SimOp<"plusargs.value", [Pure]> { + let summary = "SystemVerilog `$value$plusargs` call"; + + let arguments = (ins StrAttr:$formatString); + let results = (outs I1:$found, AnyType:$result); + let assemblyFormat = "$formatString attr-dict `:` type($result)"; +} + +def FinishOp : SimOp<"finish"> { + let summary = "Simulation finish condition"; + + let arguments = (ins ClockType:$clk, I1:$cond); + let results = (outs); + + let assemblyFormat = "$clk `,` $cond attr-dict"; +} + +def FatalOp : SimOp<"fatal"> { + let summary = "Simulation failure condition"; + + let arguments = (ins ClockType:$clk, I1:$cond); + let results = (outs); + + let assemblyFormat = "$clk `,` $cond attr-dict"; +} + +#endif // CIRCT_DIALECT_SIM_SIMOPS_TD diff --git a/include/circt/Dialect/SystemC/SystemCStructure.td b/include/circt/Dialect/SystemC/SystemCStructure.td index 128b410a30ad..0a1a1982c1cc 100644 --- a/include/circt/Dialect/SystemC/SystemCStructure.td +++ b/include/circt/Dialect/SystemC/SystemCStructure.td @@ -237,11 +237,10 @@ def InteropVerilatedOp : SystemCOp<"interop.verilated", [ /// Lookup the module or extmodule for the symbol. This returns null on /// invalid IR. - Operation *getReferencedModule(const hw::HWSymbolCache *cache) { - return hw::instance_like_impl::getReferencedModule(cache, *this, - getModuleNameAttr()); + Operation *getReferencedModule() { + return SymbolTable::lookupNearestSymbolFrom(getOperation(), + getModuleNameAttr()); } - Operation *getReferencedModule() { return getReferencedModule(nullptr); } /// Get the instances's name. StringAttr getName() { return getInstanceNameAttr(); } diff --git a/include/circt/Dialect/SystemC/SystemCTypes.h b/include/circt/Dialect/SystemC/SystemCTypes.h index 84ec4951c6a7..94381dfea633 100644 --- a/include/circt/Dialect/SystemC/SystemCTypes.h +++ b/include/circt/Dialect/SystemC/SystemCTypes.h @@ -87,6 +87,8 @@ class IntBaseType static IntBaseType get(MLIRContext *context); static constexpr StringLiteral getMnemonic() { return "int_base"; } + static constexpr StringLiteral name = "systemc.int_base"; + protected: using Base::Base; }; @@ -103,6 +105,8 @@ class IntType : public Type::TypeBase::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(); } + bool operator==(const InstancePath &that) const { return path == that.path; } + /// Print the path to any stream-like object. void print(llvm::raw_ostream &into) const; diff --git a/include/circt/Support/Naming.h b/include/circt/Support/Naming.h new file mode 100644 index 000000000000..a69141c17e62 --- /dev/null +++ b/include/circt/Support/Naming.h @@ -0,0 +1,32 @@ +//===- Naming.h - Utilities for handling names ------------------*- 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_NAMING_H +#define CIRCT_SUPPORT_NAMING_H + +#include "circt/Support/LLVM.h" + +namespace circt { + +/// Return true if this is a possibly useless temporary name. +/// This method is FIRRTL-centric, dropping useless temporaries. +bool isUselessName(StringRef name); + +/// Choose a good name for an item from two options. +StringRef chooseName(StringRef a, StringRef b); + +/// Choose a good name for an item from two options. +StringAttr chooseName(StringAttr a, StringAttr b); + +/// Choose the better name between two ops. Picks the "name" attribute as first +/// preference, using "sv.namehint" as an alternative. +StringAttr chooseName(Operation *a, Operation *b); + +} // namespace circt + +#endif // CIRCT_SUPPORT_NAMING_H diff --git a/include/circt/Types.td b/include/circt/Types.td index d13a84074824..8434f860a6f8 100644 --- a/include/circt/Types.td +++ b/include/circt/Types.td @@ -36,6 +36,22 @@ def ArrayRefAttr : let convertFromStorage = [{ $_self.getValue() }]; } +// Similar to ArrayRefAttr, but requiring all elements are the same type. +class TypedArrayRefAttrBase: ArrayAttrBase< + And<[ + CPred<"::llvm::isa<::mlir::ArrayAttr>($_self)">, + Concat<"::llvm::all_of(::llvm::cast<::mlir::ArrayAttr>($_self), " + "[&](::mlir::Attribute attr) { return attr && (", + SubstLeaves<"$_self", "attr", element.predicate>, + "); })">]>, + summary> { + + Attr elementAttr = element; + + let returnType = [{ ::llvm::ArrayRef }]; + let convertFromStorage = [{ $_self.getValue() }]; +} + def DistinctAttr : Attr()">, "distinct attribute"> { let storageType = [{ ::mlir::DistinctAttr }]; diff --git a/integration_test/Bindings/Python/dialects/comb.py b/integration_test/Bindings/Python/dialects/comb.py index 72482b8eecf6..eade5e044841 100644 --- a/integration_test/Bindings/Python/dialects/comb.py +++ b/integration_test/Bindings/Python/dialects/comb.py @@ -6,6 +6,7 @@ from circt.dialects import comb, hw from circt.ir import Context, Location, InsertionPoint, IntegerType, IntegerAttr, Module +from circt import passmanager with Context() as ctx, Location.unknown(): circt.register_dialects(ctx) @@ -192,3 +193,6 @@ def build(module): # Dynamically get the Python class, and check that the op isinstance of the class. cls = getattr(comb, op_name) assert isinstance(op, cls) + + # Check that the comb passes are registered. + pm = passmanager.PassManager.parse("builtin.module(lower-comb)") diff --git a/integration_test/Bindings/Python/dialects/esi.py b/integration_test/Bindings/Python/dialects/esi.py index 2f7d6f702ea2..a664165c11a8 100644 --- a/integration_test/Bindings/Python/dialects/esi.py +++ b/integration_test/Bindings/Python/dialects/esi.py @@ -60,7 +60,7 @@ def testGen(reqOp: esi.ServiceImplementReqOp) -> bool: !recvI8 = !esi.bundle<[!esi.channel to "recv"]> esi.service.decl @HostComms { - esi.service.to_client @Recv : !recvI8 + esi.service.port @Recv : !recvI8 } hw.module @MsTop (in %clk : i1, out chksum : i8) { @@ -70,7 +70,7 @@ def testGen(reqOp: esi.ServiceImplementReqOp) -> bool: } hw.module @MsLoopback (in %clk : i1) { - %dataIn = esi.service.req.to_client <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) : !recvI8 + %dataIn = esi.service.req <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) : !recvI8 } """) pm = passmanager.PassManager.parse("builtin.module(esi-connect-services)") diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py index 6edc107ec012..e076fc1f58f6 100644 --- a/integration_test/Bindings/Python/dialects/om.py +++ b/integration_test/Bindings/Python/dialects/om.py @@ -7,6 +7,7 @@ from circt.support import var_to_attribute from dataclasses import dataclass +from typing import Dict with Context() as ctx, Location.unknown(): circt.register_dialects(ctx) @@ -200,7 +201,7 @@ print(k, v) obj = evaluator.instantiate("Client") -object_dict: dict[om.Object, str] = {} +object_dict: Dict[om.Object, str] = {} for field_name, data in obj: if isinstance(data, om.Object): object_dict[data] = field_name diff --git a/integration_test/CMakeLists.txt b/integration_test/CMakeLists.txt index ee3eb2360766..655a6c5e618a 100644 --- a/integration_test/CMakeLists.txt +++ b/integration_test/CMakeLists.txt @@ -34,6 +34,7 @@ if (ESI_RUNTIME) list(APPEND CIRCT_INTEGRATION_TEST_DEPENDS ESIRuntime ESIPythonRuntime + esiquery ) endif() diff --git a/integration_test/Dialect/ESI/cosim/loopback.mlir b/integration_test/Dialect/ESI/cosim/loopback.mlir index 31597dc3e6cb..414be44a30b7 100644 --- a/integration_test/Dialect/ESI/cosim/loopback.mlir +++ b/integration_test/Dialect/ESI/cosim/loopback.mlir @@ -5,27 +5,27 @@ // RUN: cd .. // RUN: %python esi-cosim-runner.py --exec %S/loopback.py %t6/*.sv -!sendI8 = !esi.bundle<[!esi.channel to "send"]> +!sendI8 = !esi.bundle<[!esi.channel from "send"]> !recvI8 = !esi.bundle<[!esi.channel to "recv"]> -!sendI0 = !esi.bundle<[!esi.channel to "send"]> +!sendI0 = !esi.bundle<[!esi.channel from "send"]> esi.service.decl @HostComms { - esi.service.to_server @Send : !sendI8 - esi.service.to_client @Recv : !recvI8 - esi.service.to_server @SendI0 : !sendI0 + esi.service.port @Send : !sendI8 + esi.service.port @Recv : !recvI8 + esi.service.port @SendI0 : !sendI0 } 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 + %dataInBundle = esi.service.req <@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 + %dataOutBundle = esi.service.req <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !sendI8 + esi.bundle.unpack %dataOut from %dataOutBundle : !sendI8 %c0_0 = hw.constant 0 : i0 %c0_1 = hw.constant 0 : i1 %sendi0_channel, %ready = esi.wrap.vr %c0_0, %c0_1 : i0 - %sendi0_bundle = esi.bundle.pack %sendi0_channel : !sendI0 - esi.service.req.to_server %sendi0_bundle -> <@HostComms::@SendI0> (#esi.appid<"loopback_fromhw_i0">) : !sendI0 + %sendi0_bundle = esi.service.req <@HostComms::@SendI0> (#esi.appid<"loopback_fromhw_i0">) : !sendI0 + esi.bundle.unpack %sendi0_channel from %sendi0_bundle : !sendI0 } esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir b/integration_test/Dialect/ESI/runtime/loopback.mlir index 3c65292a4070..4668bc18037d 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir @@ -1,14 +1,15 @@ // REQUIRES: esi-cosim, esi-runtime, rtl-sim // 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: circt-opt %t4.mlir --lower-esi-to-physical --lower-esi-bundles --lower-esi-ports --lower-esi-to-hw=platform=cosim --lower-seq-to-sv --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir // RUN: cd .. +// RUN: esiquery trace w:%t6/esi_system_manifest.json hier | FileCheck %s --check-prefix=QUERY-HIER // RUN: %python %s.py trace w:%t6/esi_system_manifest.json // RUN: %python esi-cosim-runner.py --exec %s.py %t6/*.sv -!sendI8 = !esi.bundle<[!esi.channel to "send"]> +!sendI8 = !esi.bundle<[!esi.channel from "send"]> !recvI8 = !esi.bundle<[!esi.channel to "recv"]> -!sendI0 = !esi.bundle<[!esi.channel to "send"]> +!sendI0 = !esi.bundle<[!esi.channel from "send"]> !recvI0 = !esi.bundle<[!esi.channel to "recv"]> !anyFrom = !esi.bundle<[ @@ -16,59 +17,80 @@ !esi.channel to "send"]> esi.service.decl @HostComms { - esi.service.to_server @Send : !sendI8 - esi.service.to_client @Recv : !recvI8 + esi.service.port @Send : !sendI8 + esi.service.port @Recv : !recvI8 } esi.service.decl @MyService { - esi.service.to_server @Send : !sendI0 - esi.service.to_client @Recv : !recvI0 + esi.service.port @Send : !sendI0 + esi.service.port @Recv : !recvI0 } 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 + %dataInBundle = esi.service.req <@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 + %dataOutBundle = esi.service.req <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !sendI8 + esi.bundle.unpack %dataOut from %dataOutBundle: !sendI8 - %send = esi.service.req.to_client <@MyService::@Recv> (#esi.appid<"mysvc_recv">) : !recvI0 + %send = esi.service.req <@MyService::@Recv> (#esi.appid<"mysvc_recv">) : !recvI0 %send_ch = esi.bundle.unpack from %send : !recvI0 - %sendi0_bundle = esi.bundle.pack %send_ch : !sendI0 - esi.service.req.to_server %sendi0_bundle -> <@MyService::@Send> (#esi.appid<"mysvc_send">) : !sendI0 + %sendi0_bundle = esi.service.req <@MyService::@Send> (#esi.appid<"mysvc_send">) : !sendI0 + esi.bundle.unpack %send_ch from %sendi0_bundle : !sendI0 } esi.service.std.func @funcs !structFunc = !esi.bundle<[ - !esi.channel> to "arg", - !esi.channel> from "result"]> + !esi.channel> to "arg", + !esi.channel> from "result"]> hw.module @LoopbackStruct() { - %callBundle = esi.service.req.to_client <@funcs::@call> (#esi.appid<"structFunc">) : !structFunc + %callBundle = esi.service.req <@funcs::@call> (#esi.appid<"structFunc">) : !structFunc %arg = esi.bundle.unpack %resultChan from %callBundle : !structFunc - %argData, %valid = esi.unwrap.vr %arg, %ready : !hw.struct - %resultElem = hw.struct_extract %argData["b"] : !hw.struct - %resultArray = hw.array_create %resultElem : si8 - %resultChan, %ready = esi.wrap.vr %resultArray, %valid : !hw.array<1xsi8> + %argData, %valid = esi.unwrap.vr %arg, %ready : !hw.struct + %resultElem = hw.struct_extract %argData["b"] : !hw.struct + %c1 = hwarith.constant 1 : si2 + %resultPlusOne = hwarith.add %resultElem, %c1 : (si8, si2) -> si9 + %resultPlusOneSliced = hwarith.cast %resultPlusOne : (si9) -> si8 + %result = hw.struct_create (%resultPlusOneSliced, %resultElem) : !hw.struct + %resultChan, %ready = esi.wrap.vr %result, %valid : !hw.struct +} + +!arrFunc = !esi.bundle<[ + !esi.channel> to "arg", + !esi.channel> from "result"]> + +hw.module @LoopbackArray() { + %callBundle = esi.service.req <@funcs::@call> (#esi.appid<"arrayFunc">) : !arrFunc + %arg = esi.bundle.unpack %resultChan from %callBundle : !arrFunc + + %argData, %valid = esi.unwrap.vr %arg, %ready : !hw.array<1xsi8> + %idx = hw.constant 0 : i0 + %resultElem = hw.array_get %argData[%idx] : !hw.array<1xsi8>, i0 + %c1 = hwarith.constant 1 : si2 + %resultPlusOne = hwarith.add %resultElem, %c1 : (si8, si2) -> si9 + %resultPlusOneSliced = hwarith.cast %resultPlusOne : (si9) -> si8 + %result = hw.array_create %resultPlusOneSliced, %resultElem : si8 + %resultChan, %ready = esi.wrap.vr %result, %valid : !hw.array<2xsi8> } esi.mem.ram @MemA i64 x 20 !write = !hw.struct -!writeBundle = !esi.bundle<[!esi.channel to "req", !esi.channel from "ack"]> +!writeBundle = !esi.bundle<[!esi.channel from "req", !esi.channel to "ack"]> hw.module @MemoryAccess1(in %clk : !seq.clock, in %rst : i1) { esi.service.instance #esi.appid<"mem"> svc @MemA impl as "sv_mem" (%clk, %rst) : (!seq.clock, i1) -> () %write_struct = hw.aggregate_constant [0 : i5, 0 : i64] : !write %valid = hw.constant 0 : i1 %write_ch, %ready = esi.wrap.vr %write_struct, %valid : !write - %writeBundle, %done = esi.bundle.pack %write_ch : !writeBundle - esi.service.req.to_server %writeBundle -> <@MemA::@write> (#esi.appid<"internal_write">) : !writeBundle + %writeBundle = esi.service.req <@MemA::@write> (#esi.appid<"internal_write">) : !writeBundle + %done = esi.bundle.unpack %write_ch from %writeBundle : !writeBundle } !func1Signature = !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> hw.module @CallableFunc1() { - %call = esi.service.req.to_client <@funcs::@call> (#esi.appid<"func1">) : !func1Signature + %call = esi.service.req <@funcs::@call> (#esi.appid<"func1">) : !func1Signature %arg = esi.bundle.unpack %arg from %call : !func1Signature } @@ -82,4 +104,44 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { hw.instance "int_mem" @MemoryAccess1 (clk: %clk: !seq.clock, rst: %rst: i1) -> () hw.instance "func1" @CallableFunc1() -> () hw.instance "loopback_struct" @LoopbackStruct() -> () + hw.instance "loopback_array" @LoopbackArray() -> () } + +// QUERY-HIER: ******************************** +// QUERY-HIER: * Design hierarchy +// QUERY-HIER: ******************************** +// QUERY-HIER: * Instance:top +// QUERY-HIER: * Ports: +// QUERY-HIER: internal_write: +// QUERY-HIER: ack: !esi.channel +// QUERY-HIER: req: !esi.channel> +// QUERY-HIER: func1: +// QUERY-HIER: arg: !esi.channel +// QUERY-HIER: result: !esi.channel +// QUERY-HIER: structFunc: +// QUERY-HIER: arg: !esi.channel> +// QUERY-HIER: result: !esi.channel> +// QUERY-HIER: arrayFunc: +// QUERY-HIER: arg: !esi.channel> +// QUERY-HIER: result: !esi.channel> +// QUERY-HIER: * Children: +// QUERY-HIER: * Instance:loopback_inst[0] +// QUERY-HIER: * Ports: +// QUERY-HIER: loopback_tohw: +// QUERY-HIER: recv: !esi.channel +// QUERY-HIER: loopback_fromhw: +// QUERY-HIER: send: !esi.channel +// QUERY-HIER: mysvc_recv: +// QUERY-HIER: recv: !esi.channel +// QUERY-HIER: mysvc_send: +// QUERY-HIER: send: !esi.channel +// QUERY-HIER: * Instance:loopback_inst[1] +// QUERY-HIER: * Ports: +// QUERY-HIER: loopback_tohw: +// QUERY-HIER: recv: !esi.channel +// QUERY-HIER: loopback_fromhw: +// QUERY-HIER: send: !esi.channel +// QUERY-HIER: mysvc_recv: +// QUERY-HIER: recv: !esi.channel +// QUERY-HIER: mysvc_send: +// QUERY-HIER: send: !esi.channel diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.py b/integration_test/Dialect/ESI/runtime/loopback.mlir.py index 662dcc6ea21b..1db166696394 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir.py +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.py @@ -1,5 +1,6 @@ -from typing import List +from typing import List, Optional import esi +import esi.types as types import sys platform = sys.argv[1] @@ -9,34 +10,10 @@ m = acc.manifest() assert m.api_version == 1 - -def strType(t: esi.Type) -> str: - if isinstance(t, esi.BundleType): - return "bundle<[{}]>".format(", ".join([ - f"{name} {direction} {strType(ty)}" for (name, direction, - ty) in t.channels - ])) - if isinstance(t, esi.ChannelType): - return f"channel<{strType(t.inner)}>" - if isinstance(t, esi.ArrayType): - return f"array<{strType(t.element)}, {t.size}>" - if isinstance(t, esi.StructType): - return "struct<{}>".format(", ".join( - ["{name}: {strType(ty)}" for (name, ty) in t.fields])) - if isinstance(t, esi.BitsType): - return f"bits<{t.width}>" - if isinstance(t, esi.UIntType): - return f"uint<{t.width}>" - if isinstance(t, esi.SIntType): - return f"sint<{t.width}>" - assert False, f"unknown type: {t}" - - for esiType in m.type_table: - print(f"{esiType}:") - print(f" {strType(esiType)}") + print(f"{esiType}") -d = m.build_accelerator(acc) +d = acc.build_accelerator() loopback = d.children[esi.AppID("loopback_inst", 0)] appid = loopback.id @@ -44,43 +21,70 @@ def strType(t: esi.Type) -> str: assert appid.name == "loopback_inst" assert appid.idx == 0 -mysvc_send = loopback.ports[esi.AppID("mysvc_recv")].channels["recv"] +mysvc_send = loopback.ports[esi.AppID("mysvc_recv")].write_port("recv") mysvc_send.connect() -mysvc_send.write([0]) -assert str(mysvc_send.type) == ">" +mysvc_send.write(None) +print(f"mysvc_send.type: {mysvc_send.type}") +assert isinstance(mysvc_send.type, types.VoidType) -mysvc_send = loopback.ports[esi.AppID("mysvc_send")].channels["send"] +mysvc_send = loopback.ports[esi.AppID("mysvc_send")].read_port("send") mysvc_send.connect() -resp: List[int] = [] +resp: bool = False # Reads are non-blocking, so we need to poll. -while resp == []: +while not resp: print("i0 polling") - resp = mysvc_send.read(1) + (resp, _) = mysvc_send.read() print(f"i0 resp: {resp}") -recv = loopback.ports[esi.AppID("loopback_tohw")].channels["recv"] +recv = loopback.ports[esi.AppID("loopback_tohw")].write_port("recv") recv.connect() +assert isinstance(recv.type, types.BitsType) -send = loopback.ports[esi.AppID("loopback_fromhw")].channels["send"] +send = loopback.ports[esi.AppID("loopback_fromhw")].read_port("send") send.connect() -data = [24] -recv.write(data) -resp = [] +data = 24 +recv.write(int.to_bytes(data, 1, "little")) +resp = False # Reads are non-blocking, so we need to poll. -while resp == []: +resp_data: bytearray +while not resp: print("polling") - resp = send.read(1) + (resp, resp_data) = send.read() +resp_int = int.from_bytes(resp_data, "little") +print(f"data: {data}") +print(f"resp: {resp_int}") # Trace platform intentionally produces random responses. if platform != "trace": - print(f"data: {data}") - print(f"resp: {resp}") - assert resp == data + assert resp_int == data # Placeholder until we have a runtime function API. -myfunc = d.ports[esi.AppID("func1")] -myfunc.channels["arg"].connect() -myfunc.channels["result"].connect() +myfunc = d.ports[esi.AppID("structFunc")] +myfunc.connect() + +for _ in range(10): + future_result = myfunc(a=10, b=-22) + result = future_result.result() + + print(f"result: {result}") + if platform != "trace": + assert result == {"y": -22, "x": -21} +myfunc = d.ports[esi.AppID("arrayFunc")] +arg_chan = myfunc.write_port("arg").connect() +result_chan = myfunc.read_port("result").connect() + +arg = [-22] +arg_chan.write(arg) + +result: Optional[List[int]] = None +resp = False +while not resp: + print("polling") + (resp, result) = result_chan.read() + +print(f"result: {result}") +if platform != "trace": + assert result == [-21, -22] print("PASS") diff --git a/integration_test/Dialect/Ibis/end_to_end.mlir b/integration_test/Dialect/Ibis/end_to_end.mlir index 7e3ab023a36a..3f4098f398f5 100644 --- a/integration_test/Dialect/Ibis/end_to_end.mlir +++ b/integration_test/Dialect/Ibis/end_to_end.mlir @@ -1,3 +1,5 @@ +// XFAIL: * +// See https://github.com/llvm/circt/issues/6658 // RUN: ibistool -lo %s // A class hierarchy with a shared parent, and accessing between the children diff --git a/integration_test/EmitVerilog/instance-choice.mlir b/integration_test/EmitVerilog/instance-choice.mlir new file mode 100644 index 000000000000..ddcb2d18b175 --- /dev/null +++ b/integration_test/EmitVerilog/instance-choice.mlir @@ -0,0 +1,54 @@ +// REQUIRES: verilator +// RUN: circt-opt %s -export-split-verilog='dir-name=%t.dir' +// RUN: verilator %driver --cc --sv --exe --build -I%t.dir -F %t.dir%{fs-sep}filelist.f -o %t.exe --top-module top +// RUN: %t.exe --cycles 10 2>&1 | FileCheck %s --check-prefix=DEFAULT + +hw.module private @TargetA(in %a: i32, out b: i32) { + %cst = hw.constant 5 : i32 + %out = comb.add %cst, %out : i32 + hw.output %out : i32 +} + +hw.module private @TargetB(in %a: i32, out b: i32) { + %cst = hw.constant 15 : i32 + %out = comb.add %cst, %out : i32 + hw.output %out : i32 +} + +hw.module private @TargetDefault(in %a: i32, out b: i32) { + hw.output %a : i32 +} + +hw.module public @top(in %clk : i1, in %rst : i1) { + %reg = sv.reg : !hw.inout + + %a = sv.read_inout %reg : !hw.inout + %b = hw.instance_choice "inst1" sym @inst1 option "Perf" @TargetDefault or @TargetA if "A" or @TargetB if "B"(a: %a: i32) -> (b: i32) + + sv.alwaysff(posedge %clk) { + sv.if %rst { + %zero = hw.constant 0 : i32 + sv.passign %reg, %zero : i32 + } else { + %one = hw.constant 1 : i32 + + %a_read = sv.read_inout %reg : !hw.inout + %next = comb.add %a_read, %one : i32 + sv.passign %reg, %next : i32 + + %fd = hw.constant 0x80000002 : i32 + sv.fwrite %fd, "b: %d\n" (%b) : i32 + } + } +} + +// DEFAULT: b: 0 +// DEFAULT: b: 1 +// DEFAULT: b: 2 +// DEFAULT: b: 3 +// DEFAULT: b: 4 +// DEFAULT: b: 5 +// DEFAULT: b: 6 +// DEFAULT: b: 7 +// DEFAULT: b: 8 +// DEFAULT: b: 9 diff --git a/integration_test/lit.cfg.py b/integration_test/lit.cfg.py index f2ffbf55e51b..bd9a28e2982f 100644 --- a/integration_test/lit.cfg.py +++ b/integration_test/lit.cfg.py @@ -172,6 +172,7 @@ # Enable ESI runtime tests. if config.esi_runtime == "1": config.available_features.add('esi-runtime') + tools.append('esiquery') llvm_config.with_environment('PYTHONPATH', [f"{config.esi_runtime_path}/python/"], @@ -205,6 +206,12 @@ config.available_features.add('circt-lec') tools.append('circt-lec') +# Add circt-verilog if the Slang frontend is enabled. +if config.slang_frontend_enabled: + config.available_features.add('slang') + tools.append('circt-verilog') + +config.substitutions.append(('%driver', f'{config.driver}')) 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 65cbc489cb55..835235f22f60 100644 --- a/integration_test/lit.site.cfg.py.in +++ b/integration_test/lit.site.cfg.py.in @@ -55,6 +55,8 @@ 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@" +config.slang_frontend_enabled = "@CIRCT_SLANG_FRONTEND_ENABLED@" +config.driver = "@CIRCT_SOURCE_DIR@/tools/circt-rtl-sim/driver.cpp" # 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/Bindings/Python/CIRCTModule.cpp b/lib/Bindings/Python/CIRCTModule.cpp index 22c3929ce39a..64d1c6c74a9d 100644 --- a/lib/Bindings/Python/CIRCTModule.cpp +++ b/lib/Bindings/Python/CIRCTModule.cpp @@ -36,6 +36,7 @@ namespace py = pybind11; static void registerPasses() { + registerCombPasses(); registerSeqPasses(); registerSVPasses(); registerFSMPasses(); diff --git a/lib/Bindings/Python/dialects/esi.py b/lib/Bindings/Python/dialects/esi.py index 255c5ae4e037..2ace03a88969 100644 --- a/lib/Bindings/Python/dialects/esi.py +++ b/lib/Bindings/Python/dialects/esi.py @@ -20,18 +20,7 @@ class ChannelSignaling: @_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): +class RequestConnectionOp(RequestConnectionOp): @property def clientNamePath(self) -> List[str]: diff --git a/lib/Bindings/Python/dialects/hw.py b/lib/Bindings/Python/dialects/hw.py index 826b9c1f4deb..2855bd8b2258 100644 --- a/lib/Bindings/Python/dialects/hw.py +++ b/lib/Bindings/Python/dialects/hw.py @@ -148,7 +148,6 @@ def __init__( 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)) @@ -156,7 +155,6 @@ def __init__( 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 = [] @@ -166,8 +164,6 @@ def __init__( 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: diff --git a/lib/Bindings/Tcl/circt_tcl.cpp b/lib/Bindings/Tcl/circt_tcl.cpp index ab86e6c9e351..bba1185ba7dd 100644 --- a/lib/Bindings/Tcl/circt_tcl.cpp +++ b/lib/Bindings/Tcl/circt_tcl.cpp @@ -16,7 +16,7 @@ static int operationTypeSetFromAnyProc(Tcl_Interp *interp, Tcl_Obj *obj) { static void operationTypeUpdateStringProc(Tcl_Obj *obj) { std::string str; - auto *op = unwrap((MlirOperation){obj->internalRep.otherValuePtr}); + auto *op = unwrap(MlirOperation{obj->internalRep.otherValuePtr}); llvm::raw_string_ostream stream(str); op->print(stream); obj->length = str.length(); @@ -26,12 +26,12 @@ static void operationTypeUpdateStringProc(Tcl_Obj *obj) { } static void operationTypeDupIntRepProc(Tcl_Obj *src, Tcl_Obj *dup) { - auto *op = unwrap((MlirOperation){src->internalRep.otherValuePtr})->clone(); + auto *op = unwrap(MlirOperation{src->internalRep.otherValuePtr})->clone(); dup->internalRep.otherValuePtr = wrap(op).ptr; } static void operationTypeFreeIntRepProc(Tcl_Obj *obj) { - auto *op = unwrap((MlirOperation){obj->internalRep.otherValuePtr}); + auto *op = unwrap(MlirOperation{obj->internalRep.otherValuePtr}); op->erase(); } diff --git a/lib/CAPI/Conversion/CMakeLists.txt b/lib/CAPI/Conversion/CMakeLists.txt index 93e6aa4c9976..70c4f1f264e1 100644 --- a/lib/CAPI/Conversion/CMakeLists.txt +++ b/lib/CAPI/Conversion/CMakeLists.txt @@ -29,7 +29,7 @@ add_mlir_public_c_api_library(CIRCTCAPIConversion CIRCTPipelineToHW CIRCTSCFToCalyx CIRCTSeqToSV + CIRCTSimToSV CIRCTCFToHandshake CIRCTVerifToSV ) - diff --git a/lib/CAPI/Dialect/CMakeLists.txt b/lib/CAPI/Dialect/CMakeLists.txt index f809698a74c5..e549132089cb 100644 --- a/lib/CAPI/Dialect/CMakeLists.txt +++ b/lib/CAPI/Dialect/CMakeLists.txt @@ -25,6 +25,7 @@ add_mlir_public_c_api_library(CIRCTCAPIComb LINK_LIBS PUBLIC MLIRCAPIIR CIRCTComb + CIRCTCombTransforms ) add_mlir_public_c_api_library(CIRCTCAPIDebug @@ -49,6 +50,7 @@ add_mlir_public_c_api_library(CIRCTCAPIFIRRTL LINK_LIBS PUBLIC MLIRCAPIIR CIRCTFIRRTL + CIRCTImportFIRFile ) add_mlir_public_c_api_library(CIRCTCAPICHIRRTL @@ -126,6 +128,9 @@ add_mlir_public_c_api_library(CIRCTCAPISV add_mlir_public_c_api_library(CIRCTCAPIFSM FSM.cpp + DEPENDS + CIRCTTransformsPassIncGen + LINK_LIBS PUBLIC MLIRCAPIIR CIRCTFSM diff --git a/lib/CAPI/Dialect/Comb.cpp b/lib/CAPI/Dialect/Comb.cpp index c9f664ffd3e4..ae6e13ec4571 100644 --- a/lib/CAPI/Dialect/Comb.cpp +++ b/lib/CAPI/Dialect/Comb.cpp @@ -8,9 +8,12 @@ #include "circt-c/Dialect/Comb.h" #include "circt/Dialect/Comb/CombDialect.h" +#include "circt/Dialect/Comb/CombPasses.h" #include "mlir/CAPI/IR.h" #include "mlir/CAPI/Registration.h" #include "mlir/CAPI/Support.h" +void registerCombPasses() { circt::comb::registerPasses(); } + MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(Combinational, comb, circt::comb::CombDialect) diff --git a/lib/CAPI/Dialect/FIRRTL.cpp b/lib/CAPI/Dialect/FIRRTL.cpp index f667329bc1a4..48cb961f1860 100644 --- a/lib/CAPI/Dialect/FIRRTL.cpp +++ b/lib/CAPI/Dialect/FIRRTL.cpp @@ -9,14 +9,19 @@ #include "circt-c/Dialect/FIRRTL.h" #include "circt/Dialect/FIRRTL/FIRRTLAttributes.h" #include "circt/Dialect/FIRRTL/FIRRTLDialect.h" +#include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/FIRRTLTypes.h" +#include "circt/Dialect/FIRRTL/Import/FIRAnnotations.h" #include "mlir/CAPI/IR.h" #include "mlir/CAPI/Registration.h" #include "mlir/CAPI/Support.h" +#include "llvm/Support/JSON.h" using namespace circt; using namespace firrtl; +namespace json = llvm::json; + //===----------------------------------------------------------------------===// // Dialect API. //===----------------------------------------------------------------------===// @@ -275,3 +280,56 @@ MlirAttribute firrtlAttrGetEventControl(MlirContext ctx, return wrap(EventControlAttr::get(unwrap(ctx), value)); } + +FIRRTLValueFlow firrtlValueFoldFlow(MlirValue value, FIRRTLValueFlow flow) { + Flow flowValue; + + switch (flow) { + case FIRRTL_VALUE_FLOW_NONE: + flowValue = Flow::None; + break; + case FIRRTL_VALUE_FLOW_SOURCE: + flowValue = Flow::Source; + break; + case FIRRTL_VALUE_FLOW_SINK: + flowValue = Flow::Sink; + break; + case FIRRTL_VALUE_FLOW_DUPLEX: + flowValue = Flow::Duplex; + break; + } + + auto flowResult = firrtl::foldFlow(unwrap(value), flowValue); + + switch (flowResult) { + case Flow::None: + return FIRRTL_VALUE_FLOW_NONE; + case Flow::Source: + return FIRRTL_VALUE_FLOW_SOURCE; + case Flow::Sink: + return FIRRTL_VALUE_FLOW_SINK; + case Flow::Duplex: + return FIRRTL_VALUE_FLOW_DUPLEX; + } +} + +bool firrtlImportAnnotationsFromJSONRaw( + MlirContext ctx, MlirStringRef annotationsStr, + MlirAttribute *importedAnnotationsArray) { + auto annotations = json::parse(unwrap(annotationsStr)); + if (!annotations) { + return false; + } + + auto *ctxUnwrapped = unwrap(ctx); + + json::Path::Root root; + SmallVector annos; + if (!importAnnotationsFromJSONRaw(annotations.get(), annos, root, + ctxUnwrapped)) { + return false; + } + + *importedAnnotationsArray = wrap(ArrayAttr::get(ctxUnwrapped, annos)); + return true; +} diff --git a/lib/CAPI/Dialect/HW.cpp b/lib/CAPI/Dialect/HW.cpp index 232f9526ef12..f58746e7e6f3 100644 --- a/lib/CAPI/Dialect/HW.cpp +++ b/lib/CAPI/Dialect/HW.cpp @@ -8,16 +8,21 @@ #include "circt-c/Dialect/HW.h" #include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Dialect/HW/HWInstanceGraph.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWTypes.h" #include "circt/Support/LLVM.h" #include "mlir/CAPI/IR.h" #include "mlir/CAPI/Registration.h" #include "mlir/CAPI/Support.h" +#include "llvm/ADT/PostOrderIterator.h" using namespace circt; using namespace circt::hw; +DEFINE_C_API_PTR_METHODS(HWInstanceGraph, InstanceGraph) +DEFINE_C_API_PTR_METHODS(HWInstanceGraphNode, igraph::InstanceGraphNode) + //===----------------------------------------------------------------------===// // Dialect API. //===----------------------------------------------------------------------===// @@ -296,3 +301,41 @@ hwOutputFileGetFromFileName(MlirAttribute fileName, bool excludeFromFileList, fileNameStrAttr.getContext(), fileNameStrAttr.getValue(), excludeFromFileList, includeReplicatedOp)); } + +MLIR_CAPI_EXPORTED HWInstanceGraph hwInstanceGraphGet(MlirOperation operation) { + return wrap(new InstanceGraph{unwrap(operation)}); +} + +MLIR_CAPI_EXPORTED void hwInstanceGraphDestroy(HWInstanceGraph instanceGraph) { + delete unwrap(instanceGraph); +} + +MLIR_CAPI_EXPORTED HWInstanceGraphNode +hwInstanceGraphGetTopLevelNode(HWInstanceGraph instanceGraph) { + return wrap(unwrap(instanceGraph)->getTopLevelNode()); +} + +MLIR_CAPI_EXPORTED void +hwInstanceGraphForEachNode(HWInstanceGraph instanceGraph, + HWInstanceGraphNodeCallback callback, + void *userData) { + InstanceGraph *graph = unwrap(instanceGraph); + for (const auto &inst : llvm::post_order(graph)) { + callback(wrap(inst), userData); + } +} + +MLIR_CAPI_EXPORTED bool hwInstanceGraphNodeEqual(HWInstanceGraphNode lhs, + HWInstanceGraphNode rhs) { + return unwrap(lhs) == unwrap(rhs); +} + +MLIR_CAPI_EXPORTED MlirModule +hwInstanceGraphNodeGetModule(HWInstanceGraphNode node) { + return wrap(dyn_cast(unwrap(node)->getModule())); +} + +MLIR_CAPI_EXPORTED MlirOperation +hwInstanceGraphNodeGetModuleOp(HWInstanceGraphNode node) { + return wrap(unwrap(node)->getModule()); +} diff --git a/lib/Conversion/AffineToLoopSchedule/AffineToLoopSchedule.cpp b/lib/Conversion/AffineToLoopSchedule/AffineToLoopSchedule.cpp index df982d5c449d..656d6583103c 100644 --- a/lib/Conversion/AffineToLoopSchedule/AffineToLoopSchedule.cpp +++ b/lib/Conversion/AffineToLoopSchedule/AffineToLoopSchedule.cpp @@ -216,7 +216,7 @@ struct IfOpHoisting : OpConversionPattern { LogicalResult matchAndRewrite(IfOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - rewriter.updateRootInPlace(op, [&]() { + rewriter.modifyOpInPlace(op, [&]() { if (!op.thenBlock()->without_terminator().empty()) { rewriter.splitBlock(op.thenBlock(), --op.thenBlock()->end()); rewriter.inlineBlockBefore(&op.getThenRegion().front(), op); diff --git a/lib/Conversion/CFToHandshake/CFToHandshake.cpp b/lib/Conversion/CFToHandshake/CFToHandshake.cpp index 6a4f214352c7..adccdabfc9fe 100644 --- a/lib/Conversion/CFToHandshake/CFToHandshake.cpp +++ b/lib/Conversion/CFToHandshake/CFToHandshake.cpp @@ -82,7 +82,7 @@ class LowerOpTarget : public ConversionTarget { /// A partial lowering function may only replace a subset of the operations /// within the funcOp currently being lowered. However, the dialect conversion /// scheme requires the matched root operation to be replaced/updated, if the -/// match was successful. To facilitate this, rewriter.updateRootInPlace +/// match was successful. To facilitate this, rewriter.modifyOpInPlace /// wraps the partial update function. /// Next, the function operation is expected to go from illegal to legalized, /// after matchAndRewrite returned true. To work around this, @@ -103,7 +103,7 @@ struct PartialLowerOp : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef /*operands*/, ConversionPatternRewriter &rewriter) const override { assert(isa(op)); - rewriter.updateRootInPlace( + rewriter.modifyOpInPlace( op, [&] { loweringRes = fun(dyn_cast(op), rewriter); }); target.loweredOps[op] = true; return loweringRes; @@ -171,7 +171,7 @@ struct PartialLowerRegion : public ConversionPattern { LogicalResult matchAndRewrite(Operation *op, ArrayRef /*operands*/, ConversionPatternRewriter &rewriter) const override { - rewriter.updateRootInPlace( + rewriter.modifyOpInPlace( op, [&] { loweringRes = fun(target.region, rewriter); }); target.opLowered = true; diff --git a/lib/Conversion/CMakeLists.txt b/lib/Conversion/CMakeLists.txt index 64bc93d4643d..e40387da5782 100644 --- a/lib/Conversion/CMakeLists.txt +++ b/lib/Conversion/CMakeLists.txt @@ -24,6 +24,7 @@ add_subdirectory(MooreToCore) add_subdirectory(PipelineToHW) add_subdirectory(SCFToCalyx) add_subdirectory(SeqToSV) +add_subdirectory(SimToSV) add_subdirectory(CFToHandshake) add_subdirectory(VerifToSV) add_subdirectory(CalyxNative) diff --git a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp index a502294c9f8d..803ef5e13061 100644 --- a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp +++ b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" #include "circt/Support/Namespace.h" +#include "mlir/IR/PatternMatch.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "convert-to-arcs" @@ -58,7 +59,7 @@ struct Converter { MapVector> faninMaskGroups; /// The arc uses generated by `extractArcs`. - SmallVector arcUses; + SmallVector arcUses; /// Whether registers should be made observable by assigning their arcs a /// "name" attribute. @@ -248,8 +249,7 @@ void Converter::extractArcs(HWModuleOp module) { // Create the call to the arc definition to replace the operations that // we have just extracted. builder.setInsertionPoint(module.getBodyBlock()->getTerminator()); - auto arcOp = builder.create(lastOp->getLoc(), defOp, Value{}, - Value{}, 0, inputs); + auto arcOp = builder.create(lastOp->getLoc(), defOp, inputs); arcUses.push_back(arcOp); for (auto [use, resultIdx] : externalUses) use->set(arcOp.getResult(resultIdx)); @@ -261,19 +261,20 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { // exactly one register each. unsigned outIdx = 0; unsigned numTrivialRegs = 0; - for (auto &arc : arcUses) { - Value clock = arc.getClock(); + for (auto callOp : arcUses) { + auto stateOp = dyn_cast(callOp.getOperation()); + Value clock = stateOp ? stateOp.getClock() : Value{}; Value reset; SmallVector absorbedRegs; - SmallVector absorbedNames(arc.getNumResults(), {}); - if (auto names = arc->getAttrOfType("names")) + SmallVector absorbedNames(callOp->getNumResults(), {}); + if (auto names = callOp->getAttrOfType("names")) absorbedNames.assign(names.getValue().begin(), names.getValue().end()); // Go through all every arc result and collect the single register that uses // it. If a result has multiple uses or is used by something other than a // register, skip the arc for now and handle it later. bool isTrivial = true; - for (auto result : arc.getResults()) { + for (auto result : callOp->getResults()) { if (!result.hasOneUse()) { isTrivial = false; break; @@ -314,7 +315,7 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { // If this wasn't a trivial case keep the arc around for a second iteration. if (!isTrivial) { - arcUses[outIdx++] = arc; + arcUses[outIdx++] = callOp; continue; } ++numTrivialRegs; @@ -322,8 +323,20 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { // 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, add the reset if // present and update the output names. Then replace the registers. - arc.getClockMutable().assign(clock); - arc.setLatency(arc.getLatency() + 1); + + auto arc = dyn_cast(callOp.getOperation()); + if (arc) { + arc.getClockMutable().assign(clock); + arc.setLatency(arc.getLatency() + 1); + } else { + mlir::IRRewriter rewriter(module->getContext()); + rewriter.setInsertionPoint(callOp); + arc = rewriter.replaceOpWithNewOp( + callOp.getOperation(), + callOp.getCallableForCallee().get(), + callOp->getResultTypes(), clock, Value{}, 1, callOp.getArgOperands()); + } + if (reset) { if (arc.getReset()) return arc.emitError( diff --git a/lib/Conversion/ExportVerilog/CMakeLists.txt b/lib/Conversion/ExportVerilog/CMakeLists.txt index aabf351c84ba..632cb879ebf4 100644 --- a/lib/Conversion/ExportVerilog/CMakeLists.txt +++ b/lib/Conversion/ExportVerilog/CMakeLists.txt @@ -4,6 +4,7 @@ add_circt_translation_library(CIRCTExportVerilog ExportVerilog.cpp LegalizeAnonEnums.cpp LegalizeNames.cpp + HWLowerInstanceChoices.cpp PrepareForEmission.cpp PruneZeroValuedLogic.cpp @@ -18,6 +19,7 @@ add_circt_translation_library(CIRCTExportVerilog LINK_LIBS PUBLIC CIRCTComb CIRCTDebug + CIRCTEmit CIRCTHW CIRCTLTL CIRCTOM diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index 4e14c08a6464..c2e13ec27b7d 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -21,6 +21,7 @@ #include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/Comb/CombVisitors.h" #include "circt/Dialect/Debug/DebugDialect.h" +#include "circt/Dialect/Emit/EmitOps.h" #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWTypes.h" @@ -856,8 +857,8 @@ static BlockStatementCount countStatements(Block &block) { // Skip single-use instance outputs, they don't get statements. // Keep this synchronized with visitStmt(InstanceOp,OutputOp). return llvm::count_if(oop->getOperands(), [&](auto operand) { - return !operand.hasOneUse() || - !dyn_cast_or_null(operand.getDefiningOp()); + Operation *op = operand.getDefiningOp(); + return !operand.hasOneUse() || !op || !isa(op); }); }) .Default([](auto) { return 1; }); @@ -1010,12 +1011,13 @@ class VerilogEmitterState { const LoweringOptions &options, const HWSymbolCache &symbolCache, const GlobalNameTable &globalNames, + const FileMapping &fileMapping, llvm::formatted_raw_ostream &os, StringAttr fileName, OpLocMap &verilogLocMap) : designOp(designOp), shared(shared), options(options), - symbolCache(symbolCache), globalNames(globalNames), os(os), - verilogLocMap(verilogLocMap), pp(os, options.emittedLineLength), - fileName(fileName) { + symbolCache(symbolCache), globalNames(globalNames), + fileMapping(fileMapping), os(os), verilogLocMap(verilogLocMap), + pp(os, options.emittedLineLength), fileName(fileName) { pp.setListener(&saver); } /// This is the root mlir::ModuleOp that holds the whole design being emitted. @@ -1033,6 +1035,9 @@ class VerilogEmitterState { /// the IR name. const GlobalNameTable &globalNames; + /// Tracks the referenceable files through their symbol. + const FileMapping &fileMapping; + /// 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. @@ -1228,7 +1233,7 @@ void EmitterBase::emitTextWithSubstitutions( } // We must have a }} right after the digits. - if (!string.substr(next).startswith("}}")) + if (!string.substr(next).starts_with("}}")) continue; // We must be able to decode the integer into an unsigned. @@ -3625,7 +3630,7 @@ void NameCollector::collectNames(Block &block) { // Instances have an instance name to recognize but we don't need to look // at the result values since wires used by instances should be traversed // anyway. - if (isa(op)) + if (isa(op)) continue; if (isa(op.getDialect())) continue; @@ -3728,7 +3733,12 @@ class StmtEmitter : public EmitterBase, LogicalResult visitSV(AliasOp op); LogicalResult visitSV(InterfaceInstanceOp op); LogicalResult visitStmt(OutputOp op); + LogicalResult visitStmt(InstanceOp op); + LogicalResult visitStmt(InstanceChoiceOp op); + void emitInstancePortList(Operation *op, ModulePortInfo &modPortInfo, + ArrayRef instPortValues); + LogicalResult visitStmt(TypeScopeOp op); LogicalResult visitStmt(TypedeclOp op); @@ -3884,7 +3894,7 @@ StmtEmitter::emitAssignLike(Op op, PPExtString syntax, LogicalResult StmtEmitter::visitSV(AssignOp op) { // prepare assigns wires to instance outputs, but these are logically handled // in the port binding list when outputing an instance. - if (dyn_cast_or_null(op.getSrc().getDefiningOp())) + if (dyn_cast_or_null(op.getSrc().getDefiningOp())) return success(); if (emitter.assignsInlined.count(op)) @@ -4011,8 +4021,8 @@ LogicalResult StmtEmitter::visitStmt(OutputOp op) { // Outputs that are set by the output port of an instance are handled // directly when the instance is emitted. // Keep synced with countStatements() and visitStmt(InstanceOp). - if (operand.hasOneUse() && - dyn_cast_or_null(operand.getDefiningOp())) { + if (operand.hasOneUse() && operand.getDefiningOp() && + isa(operand.getDefiningOp())) { ++operandIndex; continue; } @@ -4135,7 +4145,7 @@ LogicalResult StmtEmitter::visitSV(VerbatimOp op) { // Drop an extraneous \n off the end of the string if present. StringRef string = op.getFormatString(); - if (string.endswith("\n")) + if (string.ends_with("\n")) string = string.drop_back(); // Emit each \n separated piece of the string with each piece properly @@ -4943,7 +4953,8 @@ LogicalResult StmtEmitter::visitStmt(InstanceOp op) { ops.insert(op); // Use the specified name or the symbol name as appropriate. - auto *moduleOp = op.getReferencedModuleCached(&state.symbolCache); + auto *moduleOp = + state.symbolCache.getDefinition(op.getReferencedModuleNameAttr()); assert(moduleOp && "Invalid IR"); ps << PPExtString(getVerilogModuleName(moduleOp)); @@ -4985,9 +4996,58 @@ LogicalResult StmtEmitter::visitStmt(InstanceOp op) { } } - ps << PP::nbsp << PPExtString(getSymOpName(op)) << " ("; + ps << PP::nbsp << PPExtString(getSymOpName(op)); ModulePortInfo modPortInfo(cast(moduleOp).getPortList()); + SmallVector instPortValues(modPortInfo.size()); + op.getValues(instPortValues, modPortInfo); + emitInstancePortList(op, modPortInfo, instPortValues); + + ps.addCallback({op, false}); + emitLocationInfoAndNewLine(ops); + if (doNotPrint) { + ps << PP::end; + startStatement(); + ps << "*/"; + setPendingNewline(); + } + return success(); +} + +LogicalResult StmtEmitter::visitStmt(InstanceChoiceOp op) { + startStatement(); + Operation *choiceMacroDeclOp = state.symbolCache.getDefinition( + op->getAttrOfType("hw.choiceTarget")); + + ps << "`" << PPExtString(getSymOpName(choiceMacroDeclOp)) << PP::nbsp + << PPExtString(getSymOpName(op)); + + Operation *defaultModuleOp = + state.symbolCache.getDefinition(op.getDefaultModuleNameAttr()); + ModulePortInfo modPortInfo(cast(defaultModuleOp).getPortList()); + SmallVector instPortValues(modPortInfo.size()); + op.getValues(instPortValues, modPortInfo); + emitInstancePortList(op, modPortInfo, instPortValues); + + SmallPtrSet ops; + ops.insert(op); + ps.addCallback({op, false}); + emitLocationInfoAndNewLine(ops); + + return success(); +} + +void StmtEmitter::emitInstancePortList(Operation *op, + ModulePortInfo &modPortInfo, + ArrayRef instPortValues) { + SmallPtrSet ops; + ops.insert(op); + + auto containingModule = cast(emitter.currentModuleOp); + ModulePortInfo containingPortList(containingModule.getPortList()); + + ps << " ("; + // Get the max port name length so we can align the '('. size_t maxNameLength = 0; for (auto &elt : modPortInfo) { @@ -5002,10 +5062,6 @@ LogicalResult StmtEmitter::visitStmt(InstanceOp op) { bool isFirst = true; // True until we print a port. bool isZeroWidth = false; - auto containingModule = cast(emitter.currentModuleOp); - ModulePortInfo containingPortList(containingModule.getPortList()); - SmallVector instPortValues(modPortInfo.size()); - op.getValues(instPortValues, modPortInfo); for (size_t portNum = 0, portEnd = modPortInfo.size(); portNum < portEnd; ++portNum) { auto &modPort = modPortInfo.at(portNum); @@ -5087,15 +5143,6 @@ LogicalResult StmtEmitter::visitStmt(InstanceOp op) { startStatement(); } ps << ");"; - ps.addCallback({op, false}); - emitLocationInfoAndNewLine(ops); - if (doNotPrint) { - ps << PP::end; - startStatement(); - ps << "*/"; - setPendingNewline(); - } - return success(); } // This may be called in the top-level, not just in an hw.module. Thus we can't @@ -5584,7 +5631,8 @@ void ModuleEmitter::emitBind(BindOp op) { ModulePortInfo parentPortList(parentMod.getPortList()); auto parentVerilogName = getVerilogModuleNameAttr(parentMod); - Operation *childMod = inst.getReferencedModuleCached(&state.symbolCache); + Operation *childMod = + state.symbolCache.getDefinition(inst.getReferencedModuleNameAttr()); auto childVerilogName = getVerilogModuleNameAttr(childMod); startStatement(); @@ -5983,6 +6031,75 @@ void ModuleEmitter::emitHWModule(HWModuleOp module) { currentModuleOp = nullptr; } +//===----------------------------------------------------------------------===// +// Emitter for files & file lists. +//===----------------------------------------------------------------------===// + +class FileEmitter : public EmitterBase { +public: + explicit FileEmitter(VerilogEmitterState &state) : EmitterBase(state) {} + + void emit(emit::FileOp op); + void emit(emit::FileListOp op); + +private: + void emitOp(emit::VerbatimOp op); +}; + +void FileEmitter::emit(emit::FileOp op) { + for (Operation &op : *op.getBodyBlock()) { + TypeSwitch(&op) + .Case([&](auto op) { emitOp(op); }) + .Default([&](auto op) { + op->emitError("cannot be emitted to a file"); + state.encounteredError = true; + }); + } + ps.eof(); +} + +void FileEmitter::emit(emit::FileListOp op) { + // Find the associated file ops and write the paths on individual lines. + for (auto sym : op.getFiles()) { + auto fileName = cast(sym).getAttr(); + + auto it = state.fileMapping.find(fileName); + if (it == state.fileMapping.end()) { + op->emitError() << " references an invalid file: " << sym; + state.encounteredError = true; + continue; + } + + auto file = cast(it->second); + ps << PP::neverbox << PPExtString(file.getFileName()) << PP::end + << PP::newline; + } + ps.eof(); +} + +void FileEmitter::emitOp(emit::VerbatimOp op) { + startStatement(); + + SmallPtrSet ops; + ops.insert(op); + + // Emit each line of the string at a time, emitting the + // location comment after the last emitted line. + StringRef text = op.getText(); + + ps << PP::neverbox; + do { + const auto &[lhs, rhs] = text.split('\n'); + ps << PPExtString(lhs); + if (!rhs.empty()) + ps << PP::end << PP::newline << PP::neverbox; + text = rhs; + } while (!text.empty()); + ps << PP::end; + + emitLocationInfoAndNewLine(ops); +} + //===----------------------------------------------------------------------===// // Top level "file" emitter logic //===----------------------------------------------------------------------===// @@ -6029,9 +6146,11 @@ void SharedEmitterState::gatherFiles(bool separateModules) { for (auto &op : *designOp.getBody()) { auto info = OpFileInfo{&op, replicatedOps.size()}; + bool isFileOp = isa(&op); + bool hasFileName = false; - bool emitReplicatedOps = true; - bool addToFilelist = true; + bool emitReplicatedOps = !isFileOp; + bool addToFilelist = !isFileOp; outputPath.clear(); @@ -6075,7 +6194,7 @@ void SharedEmitterState::gatherFiles(bool separateModules) { file.ops.push_back(info); file.emitReplicatedOps = emitReplicatedOps; file.addToFilelist = addToFilelist; - file.isVerilog = outputPath.endswith(".sv"); + file.isVerilog = outputPath.ends_with(".sv"); for (auto fl : opFileList) fileLists[fl.getValue()].push_back(destFile); }; @@ -6083,6 +6202,11 @@ void SharedEmitterState::gatherFiles(bool separateModules) { // Separate the operation into dedicated output file, or emit into the // root file, or replicate in all output files. TypeSwitch(&op) + .Case([&](auto file) { + // Emit file ops to their respective files. + fileMapping.try_emplace(file.getSymNameAttr(), file); + separateFile(file, file.getFileName()); + }) .Case([&](auto mod) { // Build the IR cache. symbolCache.addDefinition(mod.getNameAttr(), mod); @@ -6228,6 +6352,8 @@ static void emitOperation(VerilogEmitterState &state, Operation *op) { .Case([&](auto typedecls) { ModuleEmitter(state).emitStatement(typedecls); }) + .Case( + [&](auto op) { FileEmitter(state).emit(op); }) .Case( [&](auto op) { ModuleEmitter(state).emitStatement(op); }) .Default([&](auto *op) { @@ -6254,7 +6380,8 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, // on the stream. OpLocMap verilogLocMap(os); VerilogEmitterState state(designOp, *this, options, symbolCache, - globalNames, os, fileName, verilogLocMap); + globalNames, fileMapping, os, fileName, + verilogLocMap); size_t lineOffset = 0; for (auto &entry : thingsToEmit) { entry.verilogLocs.setStream(os); @@ -6294,7 +6421,7 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, // Each `thingToEmit` (op) uses a unique map to store verilog locations. stringOrOp.verilogLocs.setStream(rs); VerilogEmitterState state(designOp, *this, options, symbolCache, - globalNames, rs, fileName, + globalNames, fileMapping, rs, fileName, stringOrOp.verilogLocs); emitOperation(state, op); stringOrOp.setString(buffer); @@ -6318,7 +6445,8 @@ void SharedEmitterState::emitOps(EmissionList &thingsToEmit, // If this wasn't emitted to a string (e.g. it is a bind) do so now. VerilogEmitterState state(designOp, *this, options, symbolCache, - globalNames, os, fileName, entry.verilogLocs); + globalNames, fileMapping, os, fileName, + entry.verilogLocs); emitOperation(state, op); state.addVerilogLocToOps(0, fileName); } @@ -6373,6 +6501,8 @@ static LogicalResult exportVerilogImpl(ModuleOp module, llvm::raw_ostream &os) { LogicalResult circt::exportVerilog(ModuleOp module, llvm::raw_ostream &os) { LoweringOptions options(module); + if (failed(lowerHWInstanceChoices(module))) + return failure(); SmallVector modulesToPrepare; module.walk([&](HWModuleOp op) { modulesToPrepare.push_back(op); }); if (failed(failableParallelForEach( @@ -6390,6 +6520,7 @@ struct ExportVerilogPass : public ExportVerilogBase { // Prepare the ops in the module for emission. mlir::OpPassManager preparePM("builtin.module"); preparePM.addPass(createLegalizeAnonEnumsPass()); + preparePM.addPass(createHWLowerInstanceChoicesPass()); auto &modulePM = preparePM.nest(); modulePM.addPass(createPrepareForEmissionPass()); if (failed(runPipeline(preparePM, getOperation()))) @@ -6547,6 +6678,8 @@ static LogicalResult exportSplitVerilogImpl(ModuleOp module, LogicalResult circt::exportSplitVerilog(ModuleOp module, StringRef dirname) { LoweringOptions options(module); + if (failed(lowerHWInstanceChoices(module))) + return failure(); SmallVector modulesToPrepare; module.walk([&](HWModuleOp op) { modulesToPrepare.push_back(op); }); if (failed(failableParallelForEach( @@ -6567,6 +6700,8 @@ struct ExportSplitVerilogPass void runOnOperation() override { // Prepare the ops in the module for emission. mlir::OpPassManager preparePM("builtin.module"); + preparePM.addPass(createHWLowerInstanceChoicesPass()); + auto &modulePM = preparePM.nest(); modulePM.addPass(createPrepareForEmissionPass()); if (failed(runPipeline(preparePM, getOperation()))) diff --git a/lib/Conversion/ExportVerilog/ExportVerilogInternals.h b/lib/Conversion/ExportVerilog/ExportVerilogInternals.h index 70627fda5878..5fe57ac1004c 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilogInternals.h +++ b/lib/Conversion/ExportVerilog/ExportVerilogInternals.h @@ -325,6 +325,9 @@ class StringOrOpToEmit { size_t length; }; +/// Mapping from symbols to file operations. +using FileMapping = DenseMap; + /// This class tracks the top-level state for the emitters, which is built and /// then shared across all per-file emissions that happen in parallel. struct SharedEmitterState { @@ -363,6 +366,9 @@ struct SharedEmitterState { /// Information about renamed global symbols, parameters, etc. const GlobalNameTable globalNames; + /// Tracks the referenceable files through their symbol. + FileMapping fileMapping; + explicit SharedEmitterState(ModuleOp designOp, const LoweringOptions &options, GlobalNameTable globalNames) : designOp(designOp), options(options), @@ -426,6 +432,9 @@ bool isZeroBitType(Type type); /// that uses it. bool isExpressionEmittedInline(Operation *op, const LoweringOptions &options); +/// Generates the macros used by instance choices. +LogicalResult lowerHWInstanceChoices(mlir::ModuleOp module); + /// For each module we emit, do a prepass over the structure, pre-lowering and /// otherwise rewriting operations we don't want to emit. LogicalResult prepareHWModule(Block &block, const LoweringOptions &options); diff --git a/lib/Conversion/ExportVerilog/HWLowerInstanceChoices.cpp b/lib/Conversion/ExportVerilog/HWLowerInstanceChoices.cpp new file mode 100644 index 000000000000..6d25ac0d304a --- /dev/null +++ b/lib/Conversion/ExportVerilog/HWLowerInstanceChoices.cpp @@ -0,0 +1,111 @@ +//===- HWLowerInstanceChoices.cpp - IR Prepass for Emitter ----------------===// +// +// 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 the "prepare" pass that walks the IR before the emitter +// gets involved. This allows us to do some transformations that would be +// awkward to implement inline in the emitter. +// +// NOTE: This file covers the preparation phase of `ExportVerilog` which mainly +// legalizes the IR and makes adjustments necessary for emission. This is the +// place to mutate the IR if emission needs it. The IR cannot be modified during +// emission itself, which happens in parallel. +// +//===----------------------------------------------------------------------===// + +#include "../PassDetail.h" +#include "ExportVerilogInternals.h" +#include "circt/Conversion/ExportVerilog.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Support/LLVM.h" +#include "circt/Support/Namespace.h" +#include "circt/Support/SymCache.h" +#include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/IR/SymbolTable.h" + +using namespace mlir; +using namespace circt; +using namespace hw; +using namespace sv; +using namespace ExportVerilog; + +namespace { + +struct HWLowerInstanceChoicesPass + : public HWLowerInstanceChoicesBase { + void runOnOperation() override; +}; + +} // end anonymous namespace + +LogicalResult ExportVerilog::lowerHWInstanceChoices(mlir::ModuleOp module) { + // Collect all instance choices & symbols. + SmallVector instances; + SymbolCache symCache; + for (Operation &op : *module.getBody()) { + if (auto sym = dyn_cast(&op)) + symCache.addSymbol(sym); + + if (auto module = dyn_cast(&op)) + module.walk([&](InstanceChoiceOp inst) { instances.push_back(inst); }); + } + + // Build a namespace to generate unique macro names. + Namespace ns; + ns.add(symCache); + + auto declBuilder = OpBuilder::atBlockBegin(module.getBody()); + for (InstanceChoiceOp inst : instances) { + auto parent = inst->getParentOfType(); + + auto defaultModuleOp = cast( + symCache.getDefinition(inst.getDefaultModuleNameAttr())); + + // Generate a macro name to describe the target of this instance. + SmallString<128> name; + { + llvm::raw_svector_ostream os(name); + os << "__circt_choice_" << parent.getName() << "_" + << inst.getInstanceName(); + } + + auto symName = ns.newName(name); + auto symNameAttr = declBuilder.getStringAttr(symName); + auto symRef = FlatSymbolRefAttr::get(symNameAttr); + declBuilder.create(inst.getLoc(), symNameAttr, + /*args=*/ArrayAttr{}, + /*verilogName=*/StringAttr{}); + + // This pass now generates the macros and attaches them to the instance + // choice as an attribute. As a better solution, this pass should be moved + // out of the umbrella of ExportVerilog and it should lower the `hw` + // instance choices to a better SV-level representation of the operation. + ImplicitLocOpBuilder builder(inst.getLoc(), inst); + builder.create( + symName, [&] {}, + [&] { + builder.create( + symRef, builder.getStringAttr("{{0}}"), + builder.getArrayAttr( + {FlatSymbolRefAttr::get(defaultModuleOp.getNameAttr())})); + }); + inst->setAttr("hw.choiceTarget", symRef); + } + + return success(); +} + +void HWLowerInstanceChoicesPass::runOnOperation() { + ModuleOp module = getOperation(); + if (failed(lowerHWInstanceChoices(module))) + signalPassFailure(); +} + +std::unique_ptr circt::createHWLowerInstanceChoicesPass() { + return std::make_unique(); +} diff --git a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp index 7d3f008c05f5..5e5fc9aa3b62 100644 --- a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp +++ b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp @@ -66,51 +66,61 @@ static bool shouldSpillWire(Operation &op, const LoweringOptions &options) { return !ExportVerilog::isExpressionEmittedInline(&op, options); } +static StringAttr getArgName(Operation *op, size_t idx) { + if (auto inst = dyn_cast(op)) + return inst.getArgumentName(idx); + else if (auto inst = dyn_cast(op)) + return inst.getArgumentName(idx); + return {}; +} + // Given an instance, make sure all inputs are driven from wires or ports. -static void spillWiresForInstanceInputs(InstanceOp op) { +static void spillWiresForInstanceInputs(HWInstanceLike op) { Block *block = op->getParentOfType().getBodyBlock(); auto builder = ImplicitLocOpBuilder::atBlockBegin(op.getLoc(), block); SmallString<32> nameTmp{"_", op.getInstanceName(), "_"}; auto namePrefixSize = nameTmp.size(); - size_t nextOpNo = 0; - ModulePortInfo ports(op.getPortList()); - for (auto &port : ports.getInputs()) { - auto src = op.getOperand(nextOpNo); - ++nextOpNo; + for (size_t opNum = 0, e = op->getNumOperands(); opNum != e; ++opNum) { + auto src = op->getOperand(opNum); if (isSimpleReadOrPort(src)) continue; nameTmp.resize(namePrefixSize); - if (port.name) - nameTmp += port.name.getValue().str(); + if (auto n = getArgName(op, opNum)) + nameTmp += n.getValue().str(); else - nameTmp += std::to_string(nextOpNo - 1); + nameTmp += std::to_string(opNum); auto newWire = builder.create(src.getType(), nameTmp); auto newWireRead = builder.create(newWire); auto connect = builder.create(newWire, src); newWireRead->moveBefore(op); connect->moveBefore(op); - op.setOperand(nextOpNo - 1, newWireRead); + op->setOperand(opNum, newWireRead); } } +static StringAttr getResName(Operation *op, size_t idx) { + if (auto inst = dyn_cast(op)) + return inst.getResultName(idx); + else if (auto inst = dyn_cast(op)) + return inst.getResultName(idx); + return {}; +} + // Ensure that each output of an instance are used only by a wire -static void lowerInstanceResults(InstanceOp op) { +static void lowerInstanceResults(HWInstanceLike op) { Block *block = op->getParentOfType().getBodyBlock(); auto builder = ImplicitLocOpBuilder::atBlockBegin(op.getLoc(), block); SmallString<32> nameTmp{"_", op.getInstanceName(), "_"}; auto namePrefixSize = nameTmp.size(); - size_t nextResultNo = 0; - ModulePortInfo ports(op.getPortList()); - for (auto &port : ports.getOutputs()) { - auto result = op.getResult(nextResultNo); - ++nextResultNo; + for (size_t resNum = 0, e = op->getNumResults(); resNum != e; ++resNum) { + auto result = op->getResult(resNum); // If the result doesn't have a user, the connection won't be emitted by // Emitter, so there's no need to create a wire for it. However, if the @@ -131,10 +141,10 @@ static void lowerInstanceResults(InstanceOp op) { } nameTmp.resize(namePrefixSize); - if (port.name) - nameTmp += port.name.getValue().str(); + if (auto n = getResName(op, resNum)) + nameTmp += n.getValue().str(); else - nameTmp += std::to_string(nextResultNo - 1); + nameTmp += std::to_string(resNum); Value newWire = builder.create(result.getType(), nameTmp); while (!result.use_empty()) { @@ -163,25 +173,50 @@ static void lowerUsersToTemporaryWire(Operation &op, auto createWireForResult = [&](Value result, StringAttr name) { Value newWire; + Type wireElementType = result.getType(); + bool isResultInOut = false; + + // If the result already is an InOut, make sure to not wrap it again + if (auto inoutType = hw::type_dyn_cast(result.getType())) { + wireElementType = inoutType.getElementType(); + isResultInOut = true; + } + // If the op is in a procedural region, use logic op. if (isProceduralRegion) - newWire = builder.create(result.getType(), name); + newWire = builder.create(wireElementType, name); else - newWire = builder.create(result.getType(), name); + newWire = builder.create(wireElementType, name); + // Replace all uses with newWire. Wrap in ReadInOutOp if required. while (!result.use_empty()) { - auto newWireRead = builder.create(newWire); OpOperand &use = *result.getUses().begin(); - use.set(newWireRead); - newWireRead->moveBefore(use.getOwner()); + if (isResultInOut) { + use.set(newWire); + } else { + auto newWireRead = builder.create(newWire); + use.set(newWireRead); + newWireRead->moveBefore(use.getOwner()); + } } + // Assign the original result to the temporary wire. Operation *connect; + ReadInOutOp resultRead; + + if (isResultInOut) + resultRead = builder.create(result); + if (isProceduralRegion) - connect = builder.create(newWire, result); + connect = builder.create( + newWire, isResultInOut ? resultRead.getResult() : result); else - connect = builder.create(newWire, result); + connect = builder.create( + newWire, isResultInOut ? resultRead.getResult() : result); + connect->moveAfter(&op); + if (resultRead) + resultRead->moveBefore(connect); // Move the temporary to the appropriate place. if (!emitWireAtBlockBegin) { @@ -467,9 +502,15 @@ static bool hoistNonSideEffectExpr(Operation *op) { /// Check whether an op is a declaration that can be moved. static bool isMovableDeclaration(Operation *op) { - return op->getNumResults() == 1 && - op->getResult(0).getType().isa() && - op->getNumOperands() == 0; + if (op->getNumResults() != 1 || + !op->getResult(0).getType().isa()) + return false; + + // If all operands (e.g. init value) are constant, it is safe to move + return llvm::all_of(op->getOperands(), [](Value operand) -> bool { + auto *defOp = operand.getDefiningOp(); + return !!defOp && isConstantExpression(defOp); + }); } //===----------------------------------------------------------------------===// @@ -627,7 +668,7 @@ bool EmittedExpressionStateManager::dispatchHeuristic(Operation &op) { LoweringOptions::SpillLargeTermsWithNamehints)) if (auto hint = op.getAttrOfType("sv.namehint")) { // Spill wires if the name doesn't have a prefix "_". - if (!hint.getValue().startswith("_")) + if (!hint.getValue().starts_with("_")) return true; // If the name has prefix "_", spill if the size is greater than the // threshould. @@ -653,8 +694,8 @@ bool EmittedExpressionStateManager::shouldSpillWireBasedOnState(Operation &op) { // to a wire. if (op.hasOneUse()) { auto *singleUser = *op.getUsers().begin(); - if (isa( - singleUser)) + if (isa(singleUser)) return false; // If the single user is bitcast, we check the same property for the bitcast @@ -856,13 +897,13 @@ static LogicalResult legalizeHWModule(Block &block, // (e.g. letting a temporary take the name of an unvisited wire). Adding // them now ensures any temporary generated will not use one of the names // previously declared. - if (auto instance = dyn_cast(op)) { + if (auto inst = dyn_cast(op)) { // Anchor return values to wires early - lowerInstanceResults(instance); + lowerInstanceResults(inst); // Anchor ports of instances when `disallowExpressionInliningInPorts` is // enabled. if (options.disallowExpressionInliningInPorts) - spillWiresForInstanceInputs(instance); + spillWiresForInstanceInputs(inst); } // If logic op is located in a procedural region, we have to move the logic diff --git a/lib/Conversion/FIRRTLToHW/CMakeLists.txt b/lib/Conversion/FIRRTLToHW/CMakeLists.txt index ac804cc4baec..2e39194e869f 100644 --- a/lib/Conversion/FIRRTLToHW/CMakeLists.txt +++ b/lib/Conversion/FIRRTLToHW/CMakeLists.txt @@ -13,6 +13,7 @@ add_circt_conversion_library(CIRCTFIRRTLToHW CIRCTHW CIRCTLTL CIRCTSeq + CIRCTSim CIRCTSV CIRCTVerif MLIRTransforms diff --git a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index 32ccdb6a272d..af0d4eb82849 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -29,6 +29,7 @@ #include "circt/Dialect/LTL/LTLOps.h" #include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Sim/SimOps.h" #include "circt/Dialect/Verif/VerifOps.h" #include "circt/Support/BackedgeBuilder.h" #include "circt/Support/Namespace.h" @@ -1026,17 +1027,11 @@ FIRRTLModuleLowering::lowerModule(FModuleOp oldModule, Block *topLevelModule, newModule.setCommentAttr(comment); // Copy over any attributes which are not required for FModuleOp. - SmallVector attrNames = {"annotations", - "convention", - "portNames", - "sym_name", - "portDirections", - "portTypes", - "portAnnotations", - "portSyms", - "portLocations", - "parameters", - SymbolTable::getVisibilityAttrName()}; + SmallVector attrNames = { + "annotations", "convention", "layers", + "portNames", "sym_name", "portDirections", + "portTypes", "portAnnotations", "portSyms", + "portLocations", "parameters", SymbolTable::getVisibilityAttrName()}; DenseSet attrSet(attrNames.begin(), attrNames.end()); SmallVector newAttrs(newModule->getAttrs()); @@ -1209,7 +1204,7 @@ static SmallVector getAllFieldAccesses(Value structValue, assert(isa(op)); auto fieldAccess = cast(op); auto elemIndex = - fieldAccess.getInput().getType().get().getElementIndex(field); + fieldAccess.getInput().getType().base().getElementIndex(field); if (elemIndex && *elemIndex == fieldAccess.getFieldIndex()) accesses.push_back(fieldAccess); } @@ -1543,6 +1538,7 @@ struct FIRRTLLowering : public FIRRTLVisitor { LogicalResult visitExpr(PlusArgsTestIntrinsicOp op); LogicalResult visitExpr(PlusArgsValueIntrinsicOp op); LogicalResult visitExpr(FPGAProbeIntrinsicOp op); + LogicalResult visitExpr(ClockInverterIntrinsicOp op); LogicalResult visitExpr(SizeOfIntrinsicOp op); LogicalResult visitExpr(ClockGateIntrinsicOp op); LogicalResult visitExpr(LTLAndIntrinsicOp op); @@ -3540,65 +3536,29 @@ LogicalResult FIRRTLLowering::visitExpr(IsXIntrinsicOp op) { getOrCreateXConstant(input.getType().getIntOrFloatBitWidth()), true); } -LogicalResult FIRRTLLowering::visitExpr(PlusArgsTestIntrinsicOp op) { - auto resultType = builder.getIntegerType(1); - auto str = builder.create(op.getFormatString()); - auto reg = - builder.create(resultType, builder.getStringAttr("_pargs")); - addToInitialBlock([&]() { - auto call = builder.create( - resultType, "test$plusargs", ArrayRef{str}); - builder.create(reg, call); - }); - return setLoweringTo(op, reg); -} - LogicalResult FIRRTLLowering::visitExpr(FPGAProbeIntrinsicOp op) { auto operand = getLoweredValue(op.getInput()); builder.create(operand); return success(); } +LogicalResult FIRRTLLowering::visitExpr(PlusArgsTestIntrinsicOp op) { + return setLoweringTo(op, builder.getIntegerType(1), + op.getFormatStringAttr()); +} + LogicalResult FIRRTLLowering::visitExpr(PlusArgsValueIntrinsicOp op) { - auto resultType = builder.getIntegerType(1); auto type = lowerType(op.getResult().getType()); if (!type) return failure(); - auto regv = - builder.create(type, builder.getStringAttr("_pargs_v_")); - auto regf = - builder.create(resultType, builder.getStringAttr("_pargs_f")); - builder.create( - "SYNTHESIS", - [&]() { - auto cst0 = getOrCreateIntConstant(1, 0); - auto assignZ = - builder.create(regv, getOrCreateZConstant(type)); - circt::sv::setSVAttributes( - assignZ, sv::SVAttributeAttr::get( - builder.getContext(), - "This dummy assignment exists to avoid undriven lint " - "warnings (e.g., Verilator UNDRIVEN).", - /*emitAsComment=*/true)); - builder.create(regf, cst0); - }, - [&]() { - addToInitialBlock([&]() { - auto zero32 = getOrCreateIntConstant(32, 0); - auto tmpResultType = builder.getIntegerType(32); - auto str = builder.create(op.getFormatString()); - auto call = builder.create( - tmpResultType, "value$plusargs", ArrayRef{str, regv}); - auto truevalue = builder.create(ICmpPredicate::ne, call, - zero32, true); - builder.create(regf, truevalue); - }); - }); - auto readf = builder.create(regf); - auto readv = builder.create(regv); - (void)setLowering(op.getResult(), readv); - return setLowering(op.getFound(), readf); + auto valueOp = builder.create( + builder.getIntegerType(1), type, op.getFormatStringAttr()); + if (failed(setLowering(op.getResult(), valueOp.getResult()))) + return failure(); + if (failed(setLowering(op.getFound(), valueOp.getFound()))) + return failure(); + return success(); } LogicalResult FIRRTLLowering::visitExpr(SizeOfIntrinsicOp op) { @@ -3615,6 +3575,11 @@ LogicalResult FIRRTLLowering::visitExpr(ClockGateIntrinsicOp op) { testEnable, /*inner_sym=*/hw::InnerSymAttr{}); } +LogicalResult FIRRTLLowering::visitExpr(ClockInverterIntrinsicOp op) { + auto operand = getLoweredValue(op.getInput()); + return setLoweringTo(op, operand); +} + LogicalResult FIRRTLLowering::visitExpr(LTLAndIntrinsicOp op) { return setLoweringToLTL( op, @@ -3793,7 +3758,7 @@ LogicalResult FIRRTLLowering::visitExpr(ShrPrimOp op) { if (shiftAmount >= inWidth) { // Unsigned shift by full width returns a single-bit zero. if (type_cast(op.getInput().getType()).isUnsigned()) - return setLowering(op, getOrCreateIntConstant(1, 0)); + return setLowering(op, {}); // Signed shift by full width is equivalent to extracting the sign bit. shiftAmount = inWidth - 1; @@ -4240,30 +4205,21 @@ LogicalResult FIRRTLLowering::visitStmt(PrintFOp op) { // Stop lowers into a nested series of behavioral statements plus $fatal // or $finish. LogicalResult FIRRTLLowering::visitStmt(StopOp op) { - auto clock = getLoweredNonClockValue(op.getClock()); + auto clock = getLoweredValue(op.getClock()); auto cond = getLoweredValue(op.getCond()); if (!clock || !cond) return failure(); - // Emit an "#ifndef SYNTHESIS" guard into the always block. - addToIfDefBlock("SYNTHESIS", std::function(), [&]() { - // Emit this into an "sv.always posedge" body. - addToAlwaysBlock(clock, [&]() { - circuitState.used_STOP_COND = true; + circuitState.used_STOP_COND = true; - // Emit an "sv.if '`STOP_COND_ & cond' into the #ifndef. - Value ifCond = - builder.create(cond.getType(), "STOP_COND_"); - ifCond = builder.createOrFold(ifCond, cond, true); - addIfProceduralBlock(ifCond, [&]() { - // Emit the sv.fatal or sv.finish. - if (op.getExitCode()) - builder.create(); - else - builder.create(); - }); - }); - }); + Value stopCond = + builder.create(cond.getType(), "STOP_COND_"); + Value exitCond = builder.createOrFold(stopCond, cond, true); + + if (op.getExitCode()) + builder.create(clock, exitCond); + else + builder.create(clock, exitCond); return success(); } diff --git a/lib/Conversion/HWToBTOR2/HWToBTOR2.cpp b/lib/Conversion/HWToBTOR2/HWToBTOR2.cpp index 20fe139d9452..ae62a5353189 100644 --- a/lib/Conversion/HWToBTOR2/HWToBTOR2.cpp +++ b/lib/Conversion/HWToBTOR2/HWToBTOR2.cpp @@ -156,15 +156,20 @@ struct ConvertHWToBTOR2Pass // If so, the original operation is returned // Otherwise the argument is returned as it is the original op Operation *getOpAlias(Operation *op) { + + // Remove the alias until none are left (for wires of wires of wires ...) if (auto it = opAliasMap.find(op); it != opAliasMap.end()) return it->second; + // If the op isn't an alias then simply return it return op; } // Updates or creates an entry for the given operation // associating it with the current lid - void setOpAlias(Operation *alias, Operation *op) { opAliasMap[alias] = op; } + void setOpAlias(Operation *alias, Operation *op) { + opAliasMap[alias] = getOpAlias(op); + } // Checks if a sort was declared with the given width // If so, its lid will be returned @@ -461,24 +466,31 @@ struct ConvertHWToBTOR2Pass // Generates the transitions required to finalize the register to state // transition system conversion void finalizeRegVisit(Operation *op) { - // Check the register type (done to support non-firrtl registers as well) - auto reg = cast(op); + int64_t width; + Value next, resetVal; + + // Extract the operands depending on the register type + if (auto reg = dyn_cast(op)) { + width = hw::getBitWidth(reg.getType()); + next = reg.getInput(); + resetVal = reg.getResetValue(); + } else if (auto reg = dyn_cast(op)) { + width = hw::getBitWidth(reg.getType()); + next = reg.getNext(); + resetVal = reg.getResetValue(); + } else { + op->emitError("Invalid register operation !"); + return; + } - // Generate the reset condition (for sync & async resets) - // We assume for now that the reset value is always 0 - size_t width = hw::getBitWidth(reg.getType()); genSort("bitvec", width); - // Extract the `next` operation for each register (used to define the - // transition). We need to check if next is a port to avoid nullptrs - Value next = reg.getNext(); - // Next should already be associated to an LID at this point // As we are going to override it, we need to keep track of the original // instruction size_t nextLID = noLID; - // Check for special case where next is actually a port + // We need to check if the next value is a port to avoid nullptrs // To do so, we start by checking if our operation is a block argument if (BlockArgument barg = dyn_cast(next)) { // Extract the block argument index and use that to get the line number @@ -503,8 +515,8 @@ struct ConvertHWToBTOR2Pass size_t resetValLID = noLID; // Check for a reset value, if none exists assume it's zero - if (auto resval = reg.getResetValue()) - resetValLID = getOpLID(resval.getDefiningOp()); + if (resetVal) + resetValLID = getOpLID(resetVal.getDefiningOp()); else resetValLID = genZero(width); @@ -514,7 +526,7 @@ struct ConvertHWToBTOR2Pass } // Finally generate the next statement - genNext(next, reg, width); + genNext(next, op, width); } public: @@ -690,10 +702,8 @@ struct ConvertHWToBTOR2Pass void visitComb(Operation *op) { visitInvalidComb(op); } - void visitInvalidComb(Operation *op) { - // try sv ops - dispatchSVVisitor(op); - } + // Try sv ops when comb is done + void visitInvalidComb(Operation *op) { dispatchSVVisitor(op); } // Assertions are negated then converted to a btor2 bad instruction void visitSV(sv::AssertOp op) { @@ -762,6 +772,21 @@ struct ConvertHWToBTOR2Pass regOps.push_back(reg); } + // Compregs behave in a similar way as firregs for btor2 emission + void visit(seq::CompRegOp reg) { + // Start by retrieving the register's name and width + StringRef regName = reg.getName().value(); + int64_t w = requireSort(reg.getType()); + + // Generate state instruction (represents the register declaration) + genState(reg, w, regName); + + // Record the operation for future `next` instruction generation + // This is required to model transitions between states (i.e. how a + // register's value evolves over time) + regOps.push_back(reg); + } + // Ignore all other explicitly mentionned operations // ** Purposefully left empty ** void ignore(Operation *op) {} @@ -809,10 +834,12 @@ void ConvertHWToBTOR2Pass::runOnOperation() { // Previsit all registers in the module in order to avoid dependency cylcles module.walk([&](Operation *op) { - if (auto reg = dyn_cast(op)) { - visit(reg); - handledOps.insert(op); - } + TypeSwitch(op) + .Case([&](auto reg) { + visit(reg); + handledOps.insert(op); + }) + .Default([&](auto expr) {}); }); // Visit all of the operations in our module diff --git a/lib/Conversion/HWToLLHD/HWToLLHD.cpp b/lib/Conversion/HWToLLHD/HWToLLHD.cpp index 70d5636fa3d1..a0c26ac5a7ae 100644 --- a/lib/Conversion/HWToLLHD/HWToLLHD.cpp +++ b/lib/Conversion/HWToLLHD/HWToLLHD.cpp @@ -72,7 +72,7 @@ struct ConvertHWModule : public OpConversionPattern { // Set the entity name attributes. Add block arguments for each output, // since LLHD entity outputs are still block arguments to the op. - rewriter.updateRootInPlace(entity, [&] { + rewriter.modifyOpInPlace(entity, [&] { entity.setName(module.getName()); entityBodyRegion.addArguments( moduleOutputs, SmallVector(moduleOutputs.size(), @@ -241,7 +241,7 @@ struct ConvertInstance : public OpConversionPattern { // a ConnectOp rather than a PrbOp+DrvOp combo. for (auto &use : llvm::make_early_inc_range(result.getUses())) { if (isa(use.getOwner())) { - rewriter.updateRootInPlace(use.getOwner(), [&]() { use.set(sig); }); + rewriter.modifyOpInPlace(use.getOwner(), [&]() { use.set(sig); }); } } diff --git a/lib/Conversion/HWToSystemC/HWToSystemC.cpp b/lib/Conversion/HWToSystemC/HWToSystemC.cpp index 31437ac14d34..4fa33b2dc43e 100644 --- a/lib/Conversion/HWToSystemC/HWToSystemC.cpp +++ b/lib/Conversion/HWToSystemC/HWToSystemC.cpp @@ -59,7 +59,8 @@ struct ConvertHWModule : public OpConversionPattern { scModule.setVisibility(module.getVisibility()); auto portAttrs = module.getAllPortAttrs(); - scModule.setAllArgAttrs(portAttrs); + if (!portAttrs.empty()) + scModule.setAllArgAttrs(portAttrs); // Create a systemc.func operation inside the module after the ctor. // TODO: implement logic to extract a better name and properly unique it. diff --git a/lib/Conversion/ImportVerilog/CMakeLists.txt b/lib/Conversion/ImportVerilog/CMakeLists.txt index df41fe702946..e8e42b1a59ca 100644 --- a/lib/Conversion/ImportVerilog/CMakeLists.txt +++ b/lib/Conversion/ImportVerilog/CMakeLists.txt @@ -29,13 +29,18 @@ else () endif () add_circt_translation_library(CIRCTImportVerilog + Expression.cpp ImportVerilog.cpp + Statement.cpp Structure.cpp Types.cpp Statement.cpp Expression.cpp TimingControl.cpp + DEPENDS + slang_slang + LINK_LIBS PUBLIC CIRCTMoore MLIRTranslateLib diff --git a/lib/Conversion/ImportVerilog/Expression.cpp b/lib/Conversion/ImportVerilog/Expression.cpp index a2a05a72b888..5e30ffc9cc33 100644 --- a/lib/Conversion/ImportVerilog/Expression.cpp +++ b/lib/Conversion/ImportVerilog/Expression.cpp @@ -72,7 +72,9 @@ struct ExprVisitor { Value visit(const slang::ast::AssignmentExpression &expr) { auto lhs = context.convertExpression(expr.left()); + context.pushLValue(&lhs); auto rhs = context.convertExpression(expr.right()); + context.popLValue(); if (!lhs || !rhs) return {}; @@ -238,26 +240,71 @@ struct ExprVisitor { case BinaryOperator::LessThan: return createBinary(lhs, rhs); - case BinaryOperator::LogicalAnd: - return builder.create(loc, moore::Logic::LogicalAnd, - lhs, rhs); - case BinaryOperator::LogicalOr: - return builder.create(loc, moore::Logic::LogicalOr, lhs, - rhs); - case BinaryOperator::LogicalImplication: - return builder.create( - loc, moore::Logic::LogicalImplication, lhs, rhs); - case BinaryOperator::LogicalEquivalence: - return builder.create( - loc, moore::Logic::LogicalEquivalence, lhs, rhs); + case BinaryOperator::LogicalAnd: { + // See IEEE 1800-2017 § 11.4.7 "Logical operators". + // TODO: This should short-circuit. Put the RHS code into an scf.if. + lhs = convertToBool(lhs); + rhs = convertToBool(rhs); + if (!lhs || !rhs) + return {}; + return builder.create(loc, lhs, rhs); + } + + case BinaryOperator::LogicalOr: { + // See IEEE 1800-2017 § 11.4.7 "Logical operators". + // TODO: This should short-circuit. Put the RHS code into an scf.if. + lhs = convertToBool(lhs); + rhs = convertToBool(rhs); + if (!lhs || !rhs) + return {}; + return builder.create(loc, lhs, rhs); + } + + case BinaryOperator::LogicalImplication: { + // See IEEE 1800-2017 § 11.4.7 "Logical operators". + // `(lhs -> rhs)` equivalent to `(!lhs || rhs)`. + lhs = convertToBool(lhs); + rhs = convertToBool(rhs); + if (!lhs || !rhs) + return {}; + auto notLHS = builder.create(loc, lhs); + return builder.create(loc, notLHS, rhs); + } + + case BinaryOperator::LogicalEquivalence: { + // See IEEE 1800-2017 § 11.4.7 "Logical operators". + // `(lhs <-> rhs)` equivalent to `(lhs && rhs) || (!lhs && !rhs)`. + lhs = convertToBool(lhs); + rhs = convertToBool(rhs); + if (!lhs || !rhs) + return {}; + auto notLHS = builder.create(loc, lhs); + auto notRHS = builder.create(loc, rhs); + auto both = builder.create(loc, lhs, rhs); + auto notBoth = builder.create(loc, notLHS, notRHS); + return builder.create(loc, both, notBoth); + } + case BinaryOperator::LogicalShiftLeft: - return builder.create(loc, lhs, rhs); + return createBinary(lhs, rhs); case BinaryOperator::LogicalShiftRight: - return builder.create(loc, lhs, rhs); + return createBinary(lhs, rhs); case BinaryOperator::ArithmeticShiftLeft: - return builder.create(loc, lhs, rhs, builder.getUnitAttr()); - case BinaryOperator::ArithmeticShiftRight: - return builder.create(loc, lhs, rhs, builder.getUnitAttr()); + return createBinary(lhs, rhs); + case BinaryOperator::ArithmeticShiftRight: { + // The `>>>` operator is an arithmetic right shift if the LHS operand is + // signed, or a logical right shift if the operand is unsigned. + lhs = convertToSimpleBitVector(lhs); + rhs = convertToSimpleBitVector(rhs); + if (!lhs || !rhs) + return {}; + if (cast(lhs.getType()) + .getSimpleBitVector() + .isSigned()) + return builder.create(loc, lhs, rhs); + return builder.create(loc, lhs, rhs); + } + case BinaryOperator::Power: mlir::emitError(loc, "unsupported binary operator: power"); return {}; @@ -274,6 +321,15 @@ struct ExprVisitor { return builder.create(loc, type, value); } + Value visit(const slang::ast::LValueReferenceExpression &expr) { + auto *lvalue = context.getTopLValue(); + if (nullptr == lvalue) { + mlir::emitError(loc, "invalid expression"); + return {}; + } + return *lvalue; + } + Value visit(const slang::ast::ConcatenationExpression &expr) { SmallVector operands; for (auto *operand : expr.operands()) { @@ -285,6 +341,26 @@ struct ExprVisitor { return builder.create(loc, operands); } + Value visit(const slang::ast::ElementSelectExpression &expr) { + auto type = context.convertType(*expr.type); + auto value = context.convertExpression(expr.value()); + auto lowBit = *expr.selector().constant->integer().getRawPtr(); + if (!value && !lowBit) + return {}; + return builder.create(loc, type, value, lowBit); + } + + Value visit(const slang::ast::RangeSelectExpression &expr) { + auto type = context.convertType(*expr.type); + auto value = context.convertExpression(expr.value()); + auto lhs = *expr.left().constant->integer().getRawPtr(); + auto rhs = *expr.right().constant->integer().getRawPtr(); + if (!value && !lhs && !rhs) + return {}; + auto lowBit = (lhs > rhs) ? rhs : lhs; + return builder.create(loc, type, value, lowBit); + } + /// Emit an error for all other expressions. template Value visit(T &&node) { diff --git a/lib/Conversion/ImportVerilog/ImportVerilog.cpp b/lib/Conversion/ImportVerilog/ImportVerilog.cpp index de927fd4edaa..bee1177db319 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilog.cpp +++ b/lib/Conversion/ImportVerilog/ImportVerilog.cpp @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// #include "ImportVerilogInternals.h" -#include "circt/Dialect/Moore/MooreDialect.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/Verifier.h" @@ -48,12 +47,12 @@ std::string circt::getSlangVersion() { //===----------------------------------------------------------------------===// /// Convert a slang `SourceLocation` to an MLIR `Location`. -Location ImportVerilog::convertLocation( - MLIRContext *context, const slang::SourceManager &sourceManager, - llvm::function_ref getBufferFilePath, - slang::SourceLocation loc) { +static Location +convertLocation(MLIRContext *context, const slang::SourceManager &sourceManager, + SmallDenseMap &bufferFilePaths, + slang::SourceLocation loc) { if (loc && loc.buffer() != slang::SourceLocation::NoLocation.buffer()) { - auto fileName = getBufferFilePath(loc.buffer()); + auto fileName = bufferFilePaths.lookup(loc.buffer()); auto line = sourceManager.getLineNumber(loc); auto column = sourceManager.getColumnNumber(loc); return FileLineColLoc::get(context, fileName, line, column); @@ -62,8 +61,15 @@ Location ImportVerilog::convertLocation( } Location Context::convertLocation(slang::SourceLocation loc) { - return ImportVerilog::convertLocation(getContext(), sourceManager, - getBufferFilePath, loc); + return ::convertLocation(getContext(), sourceManager, bufferFilePaths, loc); +} + +void Context::pushLValue(mlir::Value *lval) { lvalueStack.push_back(lval); } +void Context::popLValue() { lvalueStack.pop_back(); } +mlir::Value *Context::getTopLValue() const { + if (lvalueStack.empty()) + return nullptr; + return lvalueStack.back(); } namespace { @@ -73,8 +79,8 @@ class MlirDiagnosticClient : public slang::DiagnosticClient { public: MlirDiagnosticClient( MLIRContext *context, - std::function getBufferFilePath) - : context(context), getBufferFilePath(std::move(getBufferFilePath)) {} + SmallDenseMap &bufferFilePaths) + : context(context), bufferFilePaths(bufferFilePaths) {} void report(const slang::ReportedDiagnostic &diag) override { // Generate the primary MLIR diagnostic. @@ -104,8 +110,7 @@ class MlirDiagnosticClient : public slang::DiagnosticClient { /// Convert a slang `SourceLocation` to an MLIR `Location`. Location convertLocation(slang::SourceLocation loc) const { - return ImportVerilog::convertLocation(context, *sourceManager, - getBufferFilePath, loc); + return ::convertLocation(context, *sourceManager, bufferFilePaths, loc); } static DiagnosticSeverity getSeverity(slang::DiagnosticSeverity severity) { @@ -125,11 +130,11 @@ class MlirDiagnosticClient : public slang::DiagnosticClient { private: MLIRContext *context; - std::function getBufferFilePath; + SmallDenseMap &bufferFilePaths; }; } // namespace -// Allow for slang::BufferID to be used as hash map keys. +// Allow for `slang::BufferID` to be used as hash map keys. namespace llvm { template <> struct DenseMapInfo { @@ -149,10 +154,19 @@ struct DenseMapInfo { // Driver //===----------------------------------------------------------------------===// -static ImportVerilogOptions defaultOptions; - namespace { -struct ImportContext { +const static ImportVerilogOptions defaultOptions; + +struct ImportDriver { + ImportDriver(MLIRContext *mlirContext, TimingScope &ts, + const ImportVerilogOptions *options) + : mlirContext(mlirContext), ts(ts), + options(options ? *options : defaultOptions) {} + + LogicalResult prepareDriver(SourceMgr &sourceMgr); + LogicalResult importVerilog(ModuleOp module); + LogicalResult preprocessVerilog(llvm::raw_ostream &os); + MLIRContext *mlirContext; TimingScope &ts; const ImportVerilogOptions &options; @@ -170,32 +184,22 @@ struct ImportContext { // // See: https://github.com/MikePopoloski/slang/discussions/658 SmallDenseMap bufferFilePaths; - - ImportContext(MLIRContext *mlirContext, TimingScope &ts, - const ImportVerilogOptions *options) - : mlirContext(mlirContext), ts(ts), - options(options ? *options : defaultOptions) {} - - LogicalResult prepareDriver(SourceMgr &sourceMgr); - LogicalResult importVerilog(ModuleOp module); - LogicalResult preprocessVerilog(llvm::raw_ostream &os); }; } // namespace /// Populate the Slang driver with source files from the given `sourceMgr`, and /// configure driver options based on the `ImportVerilogOptions` passed to the -/// `ImportContext` constructor. -LogicalResult ImportContext::prepareDriver(SourceMgr &sourceMgr) { +/// `ImportDriver` constructor. +LogicalResult ImportDriver::prepareDriver(SourceMgr &sourceMgr) { // Use slang's driver which conveniently packages a lot of the things we // need for compilation. - auto diagClient = std::make_shared( - mlirContext, - [&](slang::BufferID id) { return bufferFilePaths.lookup(id); }); + auto diagClient = + std::make_shared(mlirContext, bufferFilePaths); driver.diagEngine.addClient(diagClient); // Populate the source manager with the source files. // NOTE: This is a bit ugly since we're essentially copying the Verilog - // source text in memory. At a later stage we might want to extend slang's + // source text in memory. At a later stage we'll want to extend slang's // SourceManager such that it can contain non-owned buffers. This will do // for now. for (unsigned i = 0, e = sourceMgr.getNumBuffers(); i < e; ++i) { @@ -223,7 +227,8 @@ LogicalResult ImportContext::prepareDriver(SourceMgr &sourceMgr) { driver.options.timeScale = options.timeScale; driver.options.allowUseBeforeDeclare = options.allowUseBeforeDeclare; driver.options.ignoreUnknownModules = options.ignoreUnknownModules; - driver.options.onlyLint = options.onlyLint; + driver.options.onlyLint = + options.mode == ImportVerilogOptions::Mode::OnlyLint; driver.options.topModules = options.topModules; driver.options.paramOverrides = options.paramOverrides; @@ -242,7 +247,7 @@ LogicalResult ImportContext::prepareDriver(SourceMgr &sourceMgr) { /// Parse and elaborate the prepared source files, and populate the given MLIR /// `module` with corresponding operations. -LogicalResult ImportContext::importVerilog(ModuleOp module) { +LogicalResult ImportDriver::importVerilog(ModuleOp module) { // Parse the input. auto parseTimer = ts.nest("Verilog parser"); bool parseSuccess = driver.parseAllSources(); @@ -260,9 +265,7 @@ LogicalResult ImportContext::importVerilog(ModuleOp module) { // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops. mlirContext->loadDialect(); auto conversionTimer = ts.nest("Verilog to dialect mapping"); - Context context(module, driver.sourceManager, [&](slang::BufferID id) { - return bufferFilePaths.lookup(id); - }); + Context context(module, driver.sourceManager, bufferFilePaths); if (failed(context.convertCompilation(*compilation))) return failure(); conversionTimer.stop(); @@ -274,14 +277,12 @@ LogicalResult ImportContext::importVerilog(ModuleOp module) { /// Preprocess the prepared source files and print them to the given output /// stream. -LogicalResult ImportContext::preprocessVerilog(llvm::raw_ostream &os) { - for (auto &buffer : driver.buffers) { - slang::BumpAllocator alloc; - slang::Diagnostics diagnostics; - slang::parsing::Preprocessor preprocessor( - driver.sourceManager, alloc, diagnostics, driver.createOptionBag()); - preprocessor.pushSource(buffer); +LogicalResult ImportDriver::preprocessVerilog(llvm::raw_ostream &os) { + auto parseTimer = ts.nest("Verilog preprocessing"); + // Run the preprocessor to completion across all sources previously added with + // `pushSource`, report diagnostics, and print the output. + auto preprocessAndPrint = [&](slang::parsing::Preprocessor &preprocessor) { slang::syntax::SyntaxPrinter output; output.setIncludeComments(false); while (true) { @@ -291,14 +292,44 @@ LogicalResult ImportContext::preprocessVerilog(llvm::raw_ostream &os) { break; } - for (auto &diag : diagnostics) { + for (auto &diag : preprocessor.getDiagnostics()) { if (diag.isError()) { driver.diagEngine.issue(diag); return failure(); } } os << output.str(); + return success(); + }; + + // Depending on whether the single-unit option is set, either add all source + // files to a single preprocessor such that they share define macros and + // directives, or create a separate preprocessor for each, such that each + // source file is in its own compilation unit. + auto optionBag = driver.createOptionBag(); + if (driver.options.singleUnit == true) { + slang::BumpAllocator alloc; + slang::Diagnostics diagnostics; + slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc, + diagnostics, optionBag); + // Sources have to be pushed in reverse, as they form a stack in the + // preprocessor. Last pushed source is processed first. + for (auto &buffer : slang::make_reverse_range(driver.buffers)) + preprocessor.pushSource(buffer); + if (failed(preprocessAndPrint(preprocessor))) + return failure(); + } else { + for (auto &buffer : driver.buffers) { + slang::BumpAllocator alloc; + slang::Diagnostics diagnostics; + slang::parsing::Preprocessor preprocessor(driver.sourceManager, alloc, + diagnostics, optionBag); + preprocessor.pushSource(buffer); + if (failed(preprocessAndPrint(preprocessor))) + return failure(); + } } + return success(); } @@ -317,16 +348,16 @@ catchExceptions(llvm::function_ref callback) { } } -// Parse the specified Verilog inputs into the specified MLIR context. +/// Parse the specified Verilog inputs into the specified MLIR context. LogicalResult circt::importVerilog(SourceMgr &sourceMgr, MLIRContext *mlirContext, TimingScope &ts, ModuleOp module, const ImportVerilogOptions *options) { return catchExceptions([&] { - ImportContext context(mlirContext, ts, options); - if (failed(context.prepareDriver(sourceMgr))) + ImportDriver importDriver(mlirContext, ts, options); + if (failed(importDriver.prepareDriver(sourceMgr))) return failure(); - return context.importVerilog(module); + return importDriver.importVerilog(module); }); } @@ -337,13 +368,14 @@ LogicalResult circt::preprocessVerilog(SourceMgr &sourceMgr, TimingScope &ts, llvm::raw_ostream &os, const ImportVerilogOptions *options) { return catchExceptions([&] { - ImportContext context(mlirContext, ts, options); - if (failed(context.prepareDriver(sourceMgr))) + ImportDriver importDriver(mlirContext, ts, options); + if (failed(importDriver.prepareDriver(sourceMgr))) return failure(); - return context.preprocessVerilog(os); + return importDriver.preprocessVerilog(os); }); } +/// Entry point as an MLIR translation. void circt::registerFromVerilogTranslation() { static TranslateToMLIRRegistration fromVerilog( "import-verilog", "import Verilog or SystemVerilog", diff --git a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h index e62119ec8867..f14a13b9e23e 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h +++ b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h @@ -13,15 +13,10 @@ #include "circt/Conversion/ImportVerilog.h" #include "circt/Dialect/Moore/MooreOps.h" #include "mlir/Dialect/SCF/IR/SCF.h" -#include "mlir/IR/BuiltinOps.h" #include "slang/ast/ASTVisitor.h" -#include "slang/ast/Compilation.h" -#include "slang/ast/Definition.h" -#include "slang/syntax/SyntaxTree.h" -#include "slang/syntax/SyntaxVisitor.h" -#include "slang/text/SourceManager.h" #include "llvm/ADT/ScopedHashTable.h" #include "llvm/Support/Debug.h" +#include #include #define DEBUG_TYPE "import-verilog" @@ -29,13 +24,15 @@ namespace circt { namespace ImportVerilog { +/// A helper class to facilitate the conversion from a Slang AST to MLIR +/// operations. Keeps track of the destination MLIR module, builders, and +/// various worklists and utilities needed for conversion. struct Context { Context(mlir::ModuleOp intoModuleOp, const slang::SourceManager &sourceManager, - std::function getBufferFilePath) + SmallDenseMap &bufferFilePaths) : intoModuleOp(intoModuleOp), sourceManager(sourceManager), - getBufferFilePath(std::move(getBufferFilePath)), - rootBuilder(OpBuilder::atBlockEnd(intoModuleOp.getBody())), + bufferFilePaths(bufferFilePaths), builder(OpBuilder::atBlockEnd(intoModuleOp.getBody())), symbolTable(intoModuleOp) {} Context(const Context &) = delete; @@ -53,15 +50,43 @@ struct Context { Type convertType(const slang::ast::Type &type, LocationAttr loc = {}); Type convertType(const slang::ast::DeclaredType &type); + /// Convert hierarchy and structure AST nodes to MLIR ops. LogicalResult convertCompilation(slang::ast::Compilation &compilation); - Operation *convertModuleHeader(const slang::ast::InstanceBodySymbol *module); + moore::SVModuleOp + convertModuleHeader(const slang::ast::InstanceBodySymbol *module); LogicalResult convertModuleBody(const slang::ast::InstanceBodySymbol *module); + LogicalResult + convertStatementBlock(const slang::ast::StatementBlockSymbol *stmt); // Convert a slang statement into an MLIR statement. LogicalResult convertStatement(const slang::ast::Statement *statement); + // Convert a slang expression into an MLIR expression. + Value convertExpression(const slang::ast::Expression &expr); + + // Convert a slang timing control into an MLIR timing control. + LogicalResult + visitTimingControl(const slang::ast::TimingControl *timingControl); + + LogicalResult visitDelay(const slang::ast::DelayControl *delay); + LogicalResult visitDelay3(const slang::ast::Delay3Control *delay3); + LogicalResult + visitSignalEvent(const slang::ast::SignalEventControl *signalEventControl); + LogicalResult + visitImplicitEvent(const slang::ast::ImplicitEventControl *implEventControl); + LogicalResult visitRepeatedEvent( + const slang::ast::RepeatedEventControl *repeatedEventControl); LogicalResult - visitConditionalStmt(const slang::ast::ConditionalStatement *conditionalStmt); + visitOneStepDelay(const slang::ast::OneStepDelayControl *oneStepDelayControl); + LogicalResult + visitCycleDelay(const slang::ast::CycleDelayControl *cycleDelayControl); + + void pushLValue(mlir::Value *lval); + void popLValue(); + mlir::Value *getTopLValue() const; + + // Convert a slang statement into an MLIR statement. + LogicalResult convertStatement(const slang::ast::Statement *statement); // Convert a slang expression into an MLIR expression. Value convertExpression(const slang::ast::Expression &expr); @@ -83,13 +108,15 @@ struct Context { LogicalResult visitCycleDelay(const slang::ast::CycleDelayControl *cycleDelayControl); + void pushLValue(mlir::Value *lval); + void popLValue(); + mlir::Value *getTopLValue() const; + mlir::ModuleOp intoModuleOp; const slang::SourceManager &sourceManager; - std::function getBufferFilePath; + SmallDenseMap &bufferFilePaths; - /// A builder for modules and other top-level ops. - OpBuilder rootBuilder; - /// A builder for all other operations, such as statements and expressions. + /// The builder used to create IR operations. OpBuilder builder; /// A symbol table of the MLIR module we are emitting into. SymbolTable symbolTable; @@ -101,18 +128,21 @@ struct Context { /// scope is destroyed and the mappings created in this scope are dropped. llvm::ScopedHashTable varSymbolTable; using SymbolTableScopeT = llvm::ScopedHashTableScope; + /// The top-level operations ordered by their Slang source location. This is + /// used to produce IR that follows the source file order. + std::map orderedRootOps; + /// Mapping port address with its direction that is convenient to sort ports + /// of different types in handling instance logic. + DenseMap pInfo; /// How we have lowered modules to MLIR. - DenseMap moduleOps; + DenseMap moduleOps; /// A list of modules for which the header has been created, but the body has /// not been converted yet. std::queue moduleWorklist; -}; -/// Convert a slang `SourceLocation` to an MLIR `Location`. -Location convertLocation( - MLIRContext *context, const slang::SourceManager &sourceManager, - llvm::function_ref getBufferFilePath, - slang::SourceLocation loc); +private: + std::vector lvalueStack; +}; } // namespace ImportVerilog } // namespace circt diff --git a/lib/Conversion/ImportVerilog/Statement.cpp b/lib/Conversion/ImportVerilog/Statement.cpp index 4e3e32327edb..2175951450bc 100644 --- a/lib/Conversion/ImportVerilog/Statement.cpp +++ b/lib/Conversion/ImportVerilog/Statement.cpp @@ -19,121 +19,333 @@ using namespace circt; using namespace ImportVerilog; -LogicalResult Context::visitConditionalStmt( - const slang::ast::ConditionalStatement *conditionalStmt) { - auto loc = convertLocation(conditionalStmt->sourceRange.start()); - auto type = conditionalStmt->conditions.begin()->expr->type; - - Value cond = convertExpression(*conditionalStmt->conditions.begin()->expr); - if (!cond) - return failure(); - - // The numeric value of the if expression is tested for being zero. - // And if (expression) is equivalent to if (expression != 0). - // So the following code is for handling `if (expression)`. - if (!cond.getType().isa()) { - auto zeroValue = - builder.create(loc, convertType(*type), 0); - cond = builder.create(loc, cond, zeroValue); - } +namespace { +struct StmtVisitor { + Context &context; + Location loc; + OpBuilder &builder; + LogicalResult visit(const slang::ast::ConditionalStatement &conditionalStmt) { - auto ifOp = builder.create( - loc, cond, conditionalStmt->ifFalse != nullptr); - OpBuilder::InsertionGuard guard(builder); + Value cond = + context.convertExpression(*conditionalStmt.conditions.begin()->expr); + if (!cond) + return failure(); + cond = builder.create(loc, cond); + cond = builder.create(loc, builder.getI1Type(), cond); + // TODO: The above should probably be a `moore.bit_to_i1` op. - builder.setInsertionPoint(ifOp.thenYield()); - if (failed(convertStatement(&conditionalStmt->ifTrue))) - return failure(); + auto ifOp = builder.create( + loc, cond, conditionalStmt.ifFalse != nullptr); + OpBuilder::InsertionGuard guard(builder); - if (conditionalStmt->ifFalse) { - builder.setInsertionPoint(ifOp.elseYield()); - if (failed(convertStatement(conditionalStmt->ifFalse))) + builder.setInsertionPoint(ifOp.thenYield()); + if (conditionalStmt.ifTrue.visit(*this).failed()) return failure(); + + if (conditionalStmt.ifFalse) { + builder.setInsertionPoint(ifOp.elseYield()); + if (conditionalStmt.ifFalse->visit(*this).failed()) + return failure(); + } + + return success(); } - return success(); -} + LogicalResult + visit(const slang::ast::ProceduralAssignStatement &proceduralAssignStmt) { + return success(context.convertExpression( + proceduralAssignStmt.as() + .assignment)); + } + + LogicalResult visit(const slang::ast::VariableDeclStatement &) { + // TODO: not sure + return success(); + } + + LogicalResult visit(const slang::ast::ExpressionStatement &exprStmt) { + return success(context.convertExpression( + exprStmt.as().expr)); + } + + LogicalResult visit(const slang::ast::StatementList &listStmt) { + for (auto *stmt : listStmt.list) { + auto succeeded = (*stmt).visit(*this); + if (succeeded.failed()) + return succeeded; + } + return success(); + } + + LogicalResult visit(const slang::ast::BlockStatement &blockStmt) { + Context::SymbolTableScopeT varScope(context.varSymbolTable); + return blockStmt.body.visit(*this); + } + + LogicalResult visit(const slang::ast::EmptyStatement &emptyStmt) { + return success(); + } + + LogicalResult visit(const slang::ast::TimedStatement &timeStmt) { + if (failed(context.visitTimingControl( + &timeStmt.as().timing))) + return failure(); + if (failed(timeStmt.stmt.visit(*this))) + return failure(); -// It can handle the statements like case, conditional(if), for loop, and etc. -LogicalResult -Context::convertStatement(const slang::ast::Statement *statement) { - auto loc = convertLocation(statement->sourceRange.start()); - switch (statement->kind) { - case slang::ast::StatementKind::Empty: return success(); - case slang::ast::StatementKind::List: - for (auto *stmt : statement->as().list) - if (failed(convertStatement(stmt))) + } + + /// Handle Loop + LogicalResult visit(const slang::ast::ForLoopStatement &forStmt) { + // reuse scf::whileOp to rewrite ForLoop + // ------------ + // for (init_stmt; cond_expr; step_stmt) begin + // statements + // end + // ------------- + // init_stmt; + // while (cond_expr) { + // body; + // step_stmt; + // } + // ------------- + mlir::SmallVector types; + + auto whileOp = builder.create( + loc, types, mlir::SmallVector{}); + OpBuilder::InsertionGuard guard(builder); + + // The before-region of the WhileOp. + Block *before = builder.createBlock(&whileOp.getBefore()); + builder.setInsertionPointToEnd(before); + Value cond = context.convertExpression(*forStmt.stopExpr); + if (!cond) + return failure(); + + cond = builder.create(loc, cond); + cond = builder.create(loc, builder.getI1Type(), cond); + // TODO: The above should probably be a `moore.bit_to_i1` op. + + builder.create(loc, cond, before->getArguments()); + + // The after-region of the WhileOp. + Block *after = builder.createBlock(&whileOp.getAfter()); + builder.setInsertionPointToStart(after); + + auto succeeded = forStmt.body.visit(*this); + // step_stmt in forLoop + for (auto *steps : forStmt.steps) { + context.convertExpression(*steps); + } + builder.create(loc); + return succeeded.success(); + } + + // Unroll ForeachLoop into nested for loops, parse the body in the innermost + // layer, and break out to the outermost layer. + LogicalResult visit(const slang::ast::ForeachLoopStatement &foreachStmt) { + + // Store unrolled loops in Dimension order + SmallVector loops; + Context::SymbolTableScopeT varScope(context.varSymbolTable); + auto type = moore::IntType::get(context.getContext(), moore::IntType::Int); + auto step = builder.create(loc, type, 1); + for (auto &dimension : foreachStmt.loopDims) { + // Skip null dimension loopVar between i,j in foreach(array[i, ,j,k]) + if (!dimension.loopVar) + continue; + + // lower bound + builder.create(loc, type, dimension.range->lower()); + // uppper bound + + auto ub = builder.create(loc, type, + dimension.range->upper()); + auto index = builder.create(loc, type, + dimension.range->lower()); + + // insert nested whileOp in after region + if (!loops.empty()) + builder.setInsertionPointToEnd(loops.back().getAfterBody()); + auto whileOp = builder.create( + loc, mlir::SmallVector{type}, + mlir::SmallVector{index}); + + // The before-region of the WhileOp. + Block *before = builder.createBlock(&whileOp.getBefore(), {}, type, loc); + builder.setInsertionPointToEnd(before); + + // Check if index overflows + Value cond; + if (dimension.range->lower() <= dimension.range->upper()) { + cond = builder.create(loc, index, ub); + } else { + cond = builder.create(loc, index, ub); + } + + cond = + builder.create(loc, builder.getI1Type(), cond); + builder.create(loc, cond, before->getArguments()); + + // Remember the iterator variable in each loops + context.varSymbolTable.insert(dimension.loopVar->name, + before->getArgument(0)); + + // The after-region of the WhileOp. + Block *after = builder.createBlock(&whileOp.getAfter(), {}, type, loc); + builder.setInsertionPointToStart(after); + loops.push_back(whileOp); + } + + // gen body in innermost block + if (!foreachStmt.body.bad()) { + if (foreachStmt.body.visit(*this).failed()) return failure(); - break; - case slang::ast::StatementKind::Block: - return convertStatement(&statement->as().body); - case slang::ast::StatementKind::ExpressionStatement: - return success(convertExpression( - statement->as().expr)); - case slang::ast::StatementKind::VariableDeclaration: - return mlir::emitError(loc, "unsupported statement: variable declaration"); - case slang::ast::StatementKind::Return: - return mlir::emitError(loc, "unsupported statement: return"); - case slang::ast::StatementKind::Break: - return mlir::emitError(loc, "unsupported statement: break"); - case slang::ast::StatementKind::Continue: - return mlir::emitError(loc, "unsupported statement: continue"); - case slang::ast::StatementKind::Case: - return mlir::emitError(loc, "unsupported statement: case"); - case slang::ast::StatementKind::PatternCase: - return mlir::emitError(loc, "unsupported statement: pattern case"); - case slang::ast::StatementKind::ForLoop: - return mlir::emitError(loc, "unsupported statement: for loop"); - case slang::ast::StatementKind::RepeatLoop: - return mlir::emitError(loc, "unsupported statement: repeat loop"); - case slang::ast::StatementKind::ForeachLoop: - return mlir::emitError(loc, "unsupported statement: foreach loop"); - case slang::ast::StatementKind::WhileLoop: - return mlir::emitError(loc, "unsupported statement: while loop"); - case slang::ast::StatementKind::DoWhileLoop: - return mlir::emitError(loc, "unsupported statement: do while loop"); - case slang::ast::StatementKind::ForeverLoop: - return mlir::emitError(loc, "unsupported statement: forever loop"); - case slang::ast::StatementKind::Timed: - if (failed(visitTimingControl( - &statement->as().timing))) + } + + // gen index iteration in the end + for (auto it = foreachStmt.loopDims.rbegin(); + it != foreachStmt.loopDims.rend(); ++it) { + if (!it->loopVar) + continue; + auto whileOp = loops.back(); + if (!whileOp.getAfter().hasOneBlock()) { + mlir::emitError(loc, "no block in while after region"); + return failure(); + } + + builder.setInsertionPointToEnd(whileOp.getAfterBody()); + auto index = whileOp.getAfterArguments().back(); + Value afterIndex; + if (it->range->lower() <= it->range->upper()) { + // step ++ + afterIndex = builder.create(loc, index, step); + } else { + // step -- + afterIndex = builder.create(loc, index, step); + } + + builder.create( + loc, mlir::SmallVector{afterIndex}); + builder.setInsertionPointAfter(whileOp); + loops.pop_back(); + } + + return success(); + } + + LogicalResult visit(const slang::ast::RepeatLoopStatement &repeatStmt) { + auto type = context.convertType(*repeatStmt.count.type, loc); + Value countExpr = context.convertExpression(repeatStmt.count); + if (!countExpr) return failure(); - if (failed(convertStatement( - &statement->as().stmt))) + auto whileOp = builder.create(loc, type, countExpr); + + // The before-region of the WhileOp. + Block *before = builder.createBlock(&whileOp.getBefore(), {}, type, loc); + + builder.setInsertionPointToEnd(before); + Value cond; + cond = builder.create(loc, countExpr); + cond = builder.create(loc, builder.getI1Type(), cond); + // TODO: The above should probably be a `moore.bit_to_i1` op. + + builder.create(loc, cond, before->getArguments()); + + // The after-region of the WhileOp. + Block *after = builder.createBlock(&whileOp.getAfter(), {}, type, loc); + builder.setInsertionPointToStart(after); + + auto succeeded = repeatStmt.body.visit(*this); + + // count decrement + auto one = builder.create(loc, type, 1); + auto count = after->getArgument(0); + auto result = builder.create(loc, count, one); + + builder.create(loc, result->getResults()); + + return succeeded.success(); + } + + LogicalResult visit(const slang::ast::WhileLoopStatement &whileStmt) { + mlir::SmallVector types; + + auto whileOp = builder.create( + loc, types, mlir::SmallVector{}); + OpBuilder::InsertionGuard guard(builder); + + // The before-region of the WhileOp. + Block *before = builder.createBlock(&whileOp.getBefore()); + builder.setInsertionPointToEnd(before); + Value cond = context.convertExpression(whileStmt.cond); + if (!cond) return failure(); - break; - case slang::ast::StatementKind::ImmediateAssertion: - return mlir::emitError(loc, "unsupported statement: immediate assertion"); - case slang::ast::StatementKind::ConcurrentAssertion: - return mlir::emitError(loc, "unsupported statement: concurrent assertion"); - case slang::ast::StatementKind::DisableFork: - return mlir::emitError(loc, "unsupported statement: disable fork"); - case slang::ast::StatementKind::Wait: - return mlir::emitError(loc, "unsupported statement: wait"); - case slang::ast::StatementKind::WaitFork: - return mlir::emitError(loc, "unsupported statement: wait fork"); - case slang::ast::StatementKind::WaitOrder: - return mlir::emitError(loc, "unsupported statement: wait order"); - case slang::ast::StatementKind::EventTrigger: - return mlir::emitError(loc, "unsupported statement: event trigger"); - case slang::ast::StatementKind::ProceduralAssign: - return success(convertExpression( - statement->as().assignment)); - case slang::ast::StatementKind::ProceduralDeassign: - return mlir::emitError(loc, "unsupported statement: procedural deassign"); - case slang::ast::StatementKind::RandCase: - return mlir::emitError(loc, "unsupported statement: rand case"); - case slang::ast::StatementKind::RandSequence: - return mlir::emitError(loc, "unsupported statement: rand sequence"); - case slang::ast::StatementKind::Conditional: - return visitConditionalStmt( - &statement->as()); - default: - mlir::emitRemark(loc, "unsupported statement: ") - << slang::ast::toString(statement->kind); - return failure(); + + cond = builder.create(loc, cond); + cond = builder.create(loc, builder.getI1Type(), cond); + // TODO: The above should probably be a `moore.bit_to_i1` op. + + builder.create(loc, cond, before->getArguments()); + + // The after-region of the WhileOp. + Block *after = builder.createBlock(&whileOp.getAfter()); + builder.setInsertionPointToStart(after); + + auto succeeded = whileStmt.body.visit(*this); + builder.create(loc); + return succeeded.success(); + } + + LogicalResult visit(const slang::ast::DoWhileLoopStatement &dowhileStmt) { + mlir::SmallVector types; + + auto whileOp = builder.create( + loc, types, mlir::SmallVector{}); + OpBuilder::InsertionGuard guard(builder); + + // The before-region of the WhileOp. + Block *before = builder.createBlock(&whileOp.getBefore()); + builder.setInsertionPointToEnd(before); + + auto succeeded = dowhileStmt.body.visit(*this); + Value cond = context.convertExpression(dowhileStmt.cond); + if (!cond) + return failure(); + cond = builder.create(loc, cond); + cond = builder.create(loc, builder.getI1Type(), cond); + // TODO: The above should probably be a `moore.bit_to_i1` op. + + builder.create(loc, cond, before->getArguments()); + + // The after-region of the WhileOp. + Block *after = builder.createBlock(&whileOp.getAfter()); + builder.setInsertionPointToStart(after); + + builder.create(loc); + return succeeded.success(); + } + + /// Emit an error for all other statement. + template + LogicalResult visit(T &&node) { + mlir::emitError(loc, "unsupported statement: ") + << slang::ast::toString(node.kind); + return mlir::failure(); + } + + LogicalResult visitInvalid(const slang::ast::Statement &stmt) { + mlir::emitError(loc, "invalid statement"); + return mlir::failure(); } +}; +} // namespace + +// It can handle the statements like case, conditional(if), for loop, and etc. +LogicalResult +Context::convertStatement(const slang::ast::Statement *statement) { + auto loc = convertLocation(statement->sourceRange.start()); - return success(); + return (*statement).visit(StmtVisitor{*this, loc, builder}); } diff --git a/lib/Conversion/ImportVerilog/Structure.cpp b/lib/Conversion/ImportVerilog/Structure.cpp index fbe527b616b3..f43512f8319f 100644 --- a/lib/Conversion/ImportVerilog/Structure.cpp +++ b/lib/Conversion/ImportVerilog/Structure.cpp @@ -7,19 +7,53 @@ //===----------------------------------------------------------------------===// #include "ImportVerilogInternals.h" -#include "slang/ast/ASTVisitor.h" -#include "slang/ast/Symbol.h" -#include "slang/ast/symbols/CompilationUnitSymbols.h" -#include "slang/ast/symbols/InstanceSymbols.h" -#include "slang/ast/symbols/VariableSymbols.h" -#include "slang/ast/types/AllTypes.h" -#include "slang/ast/types/Type.h" -#include "slang/syntax/SyntaxVisitor.h" -#include "llvm/ADT/StringRef.h" +#include "slang/ast/Compilation.h" + +// #include "slang/ast/ASTVisitor.h" +// #include "slang/ast/Symbol.h" +// #include "slang/ast/symbols/CompilationUnitSymbols.h" +// #include "slang/ast/symbols/InstanceSymbols.h" +// #include "slang/ast/symbols/VariableSymbols.h" +// #include "slang/ast/types/AllTypes.h" +// #include "slang/ast/types/Type.h" +// #include "slang/syntax/SyntaxVisitor.h" +// #include "llvm/ADT/StringRef.h" using namespace circt; using namespace ImportVerilog; +//===----------------------------------------------------------------------===// +// Top-Level Item Conversion +//===----------------------------------------------------------------------===// + +namespace { +struct RootMemberVisitor { + Context &context; + Location loc; + OpBuilder &builder; + + RootMemberVisitor(Context &context, Location loc) + : context(context), loc(loc), builder(context.builder) {} + + /// Skip semicolons. + LogicalResult visit(const slang::ast::EmptyMemberSymbol &) { + return success(); + } + + /// Emit an error for all other members. + template + LogicalResult visit(T &&node) { + mlir::emitError(loc, "unsupported top-level construct: ") + << slang::ast::toString(node.kind); + return failure(); + } +}; +} // namespace + +//===----------------------------------------------------------------------===// +// Module Member Conversion +//===----------------------------------------------------------------------===// + static moore::ProcedureKind convertProcedureKind(slang::ast::ProceduralBlockKind kind) { switch (kind) { @@ -39,24 +73,186 @@ convertProcedureKind(slang::ast::ProceduralBlockKind kind) { llvm_unreachable("all procedure kinds handled"); } +namespace { +struct MemberVisitor { + Context &context; + Location loc; + OpBuilder &builder; + + MemberVisitor(Context &context, Location loc) + : context(context), loc(loc), builder(context.builder) {} + + /// Skip semicolons. + LogicalResult visit(const slang::ast::EmptyMemberSymbol &) { + return success(); + } + + // Skip parameters. The AST is already monomorphized. + LogicalResult visit(const slang::ast::ParameterSymbol &) { return success(); } + + // Skip type-related declarations. These are absorbedby the types. + LogicalResult visit(const slang::ast::TypeAliasType &) { return success(); } + LogicalResult visit(const slang::ast::TypeParameterSymbol &) { + return success(); + } + LogicalResult visit(const slang::ast::TransparentMemberSymbol &) { + return success(); + } + + // Handle instances. + LogicalResult visit(const slang::ast::InstanceSymbol &instNode) { + auto targetModule = context.convertModuleHeader(&instNode.body); + if (!targetModule) + return failure(); + + const auto *instBodySymbol = + instNode.body.as_if(); + + for (auto &member : instBodySymbol->members()) + if (member.kind == slang::ast::SymbolKind::Port) { + const auto *p = member.as_if(); + context.pInfo[&p->location] = p->direction; + } + + SmallVector inPorts, outPorts; + for (auto *connections : instNode.getPortConnections()) { + Value port; + if (connections->getExpression()) { + if (auto *expr = connections->getExpression() + ->as_if()) { + port = context.convertExpression(expr->left()); + } else { + port = context.convertExpression(*connections->getExpression()); + } + + auto it = context.pInfo.find(&connections->port.location); + if (it->second != slang::ast::ArgumentDirection::Out) + inPorts.push_back(port); + else + outPorts.push_back(port); + + } else { + // TODO: connect interface to mudule instance. + } + } + builder.create( + loc, builder.getStringAttr(instNode.name), + FlatSymbolRefAttr::get(SymbolTable::getSymbolName(targetModule)), + inPorts, outPorts); + + return success(); + } + + // Handle variables. + LogicalResult visit(const slang::ast::VariableSymbol &varNode) { + auto loweredType = context.convertType(*varNode.getDeclaredType()); + if (!loweredType) + return failure(); + + Value initial; + if (varNode.getInitializer()) { + initial = context.convertExpression(*varNode.getInitializer()); + if (!initial) + return failure(); + } + + auto varOp = builder.create( + loc, loweredType, builder.getStringAttr(varNode.name), initial); + context.varSymbolTable.insert(varNode.name, varOp); + return success(); + } + + // Handle nets. + LogicalResult visit(const slang::ast::NetSymbol &netNode) { + auto loweredType = context.convertType(*netNode.getDeclaredType()); + if (!loweredType) + return failure(); + + Value assignment; + if (netNode.getInitializer()) { + assignment = context.convertExpression(*netNode.getInitializer()); + if (!assignment) + return failure(); + } + + if (!context.varSymbolTable.lookup(netNode.name)) { + auto netOp = builder.create( + loc, loweredType, builder.getStringAttr(netNode.name), + builder.getStringAttr(netNode.netType.name), assignment); + context.varSymbolTable.insert(netNode.name, netOp); + } + return success(); + } + + // Handle ports. + LogicalResult visit(const slang::ast::PortSymbol &portNode) { + auto loweredType = context.convertType(portNode.getType()); + if (!loweredType) + return failure(); + // TODO: Fix the `static_cast` here. + auto portOp = builder.create( + loc, loweredType, builder.getStringAttr(portNode.name), + static_cast(portNode.direction)); + context.varSymbolTable.insert(portNode.name, portOp); + return success(); + } + + // Handle continuous assignments. + LogicalResult visit(const slang::ast::ContinuousAssignSymbol &assignNode) { + if (!context.convertExpression(assignNode.getAssignment())) + return failure(); + return success(); + } + + // Handle statement blocks. + LogicalResult visit(const slang::ast::StatementBlockSymbol &blockNode) { + return context.convertStatementBlock(&blockNode); + } + + // Handle procedures. + LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) { + auto procOp = builder.create( + loc, convertProcedureKind(procNode.procedureKind)); + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(&procOp.getBodyBlock()); + return context.convertStatement(&procNode.getBody()); + } + + /// Emit an error for all other members. + template + LogicalResult visit(T &&node) { + mlir::emitError(loc, "unsupported construct: ") + << slang::ast::toString(node.kind); + return failure(); + } +}; +} // namespace + +//===----------------------------------------------------------------------===// +// Structure and Hierarchy Conversion +//===----------------------------------------------------------------------===// + +/// Convert an entire Slang compilation to MLIR ops. This is the main entry +/// point for the conversion. LogicalResult Context::convertCompilation(slang::ast::Compilation &compilation) { - auto &root = compilation.getRoot(); - - // Visit all the compilation units. This will mainly cover non-instantiable - // things like packages. - for (auto *unit : root.compilationUnits) - for (auto &member : unit->members()) - LLVM_DEBUG(llvm::dbgs() << "Converting symbol " << member.name << "\n"); + const auto &root = compilation.getRoot(); + + // Visit all top-level declarations in all compilation units. This does not + // include instantiable constructs like modules, interfaces, and programs, + // which are listed separately as top instances. + for (auto *unit : root.compilationUnits) { + for (const auto &member : unit->members()) { + // Error out on all top-level declarations. + auto loc = convertLocation(member.location); + if (failed(member.visit(RootMemberVisitor(*this, loc)))) + return failure(); + } + } - // Prime the root definition worklist by adding all the top-level modules to - // it. Explicitly sort the instances by their location in the source file, - // such that the generated IR's order matches the source file. + // Prime the root definition worklist by adding all the top-level modules. SmallVector topInstances; - topInstances.assign(root.topInstances.begin(), root.topInstances.end()); - llvm::sort(topInstances, - [&](auto *a, auto *b) { return a->location < b->location; }); - for (auto *inst : topInstances) + for (auto *inst : root.topInstances) convertModuleHeader(&inst->body); // Convert all the root module definitions. @@ -70,11 +266,16 @@ Context::convertCompilation(slang::ast::Compilation &compilation) { return success(); } -Operation * +/// Convert a module and its ports to an empty module op in the IR. Also adds +/// the op to the worklist of module bodies to be lowered. This acts like a +/// module "declaration", allowing instances to already refer to a module even +/// before its body has been lowered. +moore::SVModuleOp Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) { - if (auto *op = moduleOps.lookup(module)) + if (auto op = moduleOps.lookup(module)) return op; auto loc = convertLocation(module->location); + OpBuilder::InsertionGuard g(builder); // We only support modules for now. Extension to interfaces and programs // should be trivial though, since they are essentially the same thing with @@ -83,18 +284,17 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) { slang::ast::DefinitionKind::Module) { mlir::emitError(loc, "unsupported construct: ") << module->getDefinition().getKindString(); - return nullptr; + return {}; } // Handle the port list. - LLVM_DEBUG(llvm::dbgs() << "Ports of module " << module->name << "\n"); for (auto *symbol : module->getPortList()) { auto portLoc = convertLocation(symbol->location); auto *port = symbol->as_if(); if (!port) { - mlir::emitError(portLoc, "unsupported port: `") + mlir::emitError(portLoc, "unsupported module port: `") << symbol->name << "` (" << slang::ast::toString(symbol->kind) << ")"; - return nullptr; + return {}; } LLVM_DEBUG(llvm::dbgs() << "- " << port->name << " " << slang::ast::toString(port->direction) << "\n"); @@ -108,9 +308,18 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) { } } + // Pick an insertion point for this module according to the source file + // location. + auto it = orderedRootOps.upper_bound(module->location); + if (it == orderedRootOps.end()) + builder.setInsertionPointToEnd(intoModuleOp.getBody()); + else + builder.setInsertionPoint(it->second); + // Create an empty module that corresponds to this module. - auto moduleOp = rootBuilder.create(loc, module->name); - moduleOp.getBody().emplaceBlock(); + auto moduleOp = builder.create(loc, module->name); + orderedRootOps.insert(it, {module->location, moduleOp}); + moduleOp.getBodyRegion().emplaceBlock(); // Add the module to the symbol table of the MLIR module, which uniquifies its // name as we'd expect. @@ -122,14 +331,16 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) { return moduleOp; } +/// Convert a module's body to the corresponding IR ops. The module op must have +/// already been created earlier through a `convertModuleHeader` call. LogicalResult Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) { LLVM_DEBUG(llvm::dbgs() << "Converting body of module " << module->name << "\n"); - auto *moduleOp = moduleOps.lookup(module); + auto moduleOp = moduleOps.lookup(module); assert(moduleOp); - builder.setInsertionPointToEnd( - &cast(moduleOp).getBodyBlock()); + OpBuilder::InsertionGuard g(builder); + builder.setInsertionPointToEnd(moduleOp.getBody()); // Create a new scope in a module. When the processing of a module is // terminated, the scope is destroyed and the mappings created in this scope @@ -137,34 +348,20 @@ Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) { SymbolTableScopeT varScope(varSymbolTable); for (auto &member : module->members()) { - LLVM_DEBUG(llvm::dbgs() - << "- Handling " << slang::ast::toString(member.kind) << "\n"); auto loc = convertLocation(member.location); + if (failed(member.visit(MemberVisitor(*this, loc)))) + return failure(); + } - // Skip parameters. The AST is already monomorphized. - if (member.kind == slang::ast::SymbolKind::Parameter) - continue; - - // Skip type-related declarations. These are absorbedby the types. - if (member.kind == slang::ast::SymbolKind::TypeAlias || - member.kind == slang::ast::SymbolKind::TypeParameter || - member.kind == slang::ast::SymbolKind::TransparentMember) - continue; - - // Skip semicolons. - if (member.kind == slang::ast::SymbolKind::EmptyMember) - continue; + return success(); +} - // Handle instances. - if (auto *instAst = member.as_if()) { - auto *targetModule = convertModuleHeader(&instAst->body); - if (!targetModule) - return failure(); - builder.create( - loc, builder.getStringAttr(instAst->name), - FlatSymbolRefAttr::get(SymbolTable::getSymbolName(targetModule))); - continue; - } +LogicalResult +Context::convertStatementBlock(const slang::ast::StatementBlockSymbol *stmt) { + for (auto &member : stmt->members()) { + LLVM_DEBUG(llvm::dbgs() + << "- Handling " << slang::ast::toString(member.kind) << "\n"); + auto loc = convertLocation(member.location); // Handle variables. if (auto *varAst = member.as_if()) { @@ -183,62 +380,15 @@ Context::convertModuleBody(const slang::ast::InstanceBodySymbol *module) { continue; } - // Handle Nets. - if (auto *netAst = member.as_if()) { - auto loweredType = convertType(*netAst->getDeclaredType()); - if (!loweredType) + // Handle StatementBlock. + if (auto *stmtAst = member.as_if()) { + if (failed(convertStatementBlock(stmtAst))) return failure(); - - Value assignment; - if (netAst->getInitializer()) - assignment = convertExpression(*netAst->getInitializer()); - - auto netOp = builder.create( - convertLocation(netAst->location), loweredType, - builder.getStringAttr(netAst->name), - builder.getStringAttr(netAst->netType.name), assignment); - varSymbolTable.insert(netAst->name, netOp); continue; } - // Handle Ports. - if (auto *portAst = member.as_if()) { - auto loweredType = convertType(portAst->getType()); - if (!loweredType) - return failure(); - builder.create( - convertLocation(portAst->location), - builder.getStringAttr(portAst->name), - static_cast(portAst->direction)); - continue; - } - - // Handle AssignOp. - if (auto *assignAst = member.as_if()) { - if (!convertExpression(assignAst->getAssignment())) - return failure(); - continue; - } - - // Handle ProceduralBlock. - if (auto *procAst = member.as_if()) { - auto loc = convertLocation(procAst->location); - auto procOp = builder.create( - loc, convertProcedureKind(procAst->procedureKind)); - OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToEnd(&procOp.getBodyBlock()); - if (failed(convertStatement(&procAst->getBody()))) - return failure(); - continue; - } - - // Otherwise just report that we don't support this SV construct yet and - // skip over it. We'll want to make this an error, but in the early phases - // we'll just want to cover ground as quickly as possible and skip over - // things we don't support. mlir::emitWarning(loc, "unsupported construct ignored: ") << slang::ast::toString(member.kind); } - - return success(); + return mlir::success(); } diff --git a/lib/Conversion/MooreToCore/CMakeLists.txt b/lib/Conversion/MooreToCore/CMakeLists.txt index d97747ce8404..931634a1494f 100644 --- a/lib/Conversion/MooreToCore/CMakeLists.txt +++ b/lib/Conversion/MooreToCore/CMakeLists.txt @@ -9,7 +9,7 @@ add_circt_conversion_library(CIRCTMooreToCore LINK_LIBS PUBLIC CIRCTMoore - CIRCTLLHD + CIRCTSeq CIRCTHW CIRCTComb MLIRControlFlowDialect diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 59b96d7ef9c4..014d1723ebab 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -14,11 +14,11 @@ #include "../PassDetail.h" #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" -#include "circt/Dialect/LLHD/IR/LLHDOps.h" #include "circt/Dialect/Moore/MooreOps.h" #include "circt/Dialect/Moore/MoorePasses.h" +#include "circt/Dialect/Seq/SeqOps.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" -#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/BuiltinDialect.h" #include "mlir/Pass/PassManager.h" #include "mlir/Transforms/DialectConversion.h" @@ -28,6 +28,53 @@ using namespace circt; using namespace moore; Declaration moore::decl; + +/// Stores port interface into data structure to help convert moore module +/// structure to hw module structure. +MoorePortInfo::MoorePortInfo(moore::SVModuleOp moduleOp) { + SmallVector inputs, outputs; + + // Gather all input or output ports. + for (auto portOp : moduleOp.getBody()->getOps()) { + auto portName = portOp.getNameAttr(); + auto portLoc = portOp.getLoc(); + auto portTy = portOp.getType(); + auto argNum = inputs.size(); + + switch (portOp.getDirection()) { + case Direction::In: + inputs.push_back( + hw::PortInfo{{portName, portTy, hw::ModulePort::Direction::Input}, + argNum, + {}, + portLoc}); + inputsPort[portName] = std::make_pair(portOp, portTy); + break; + case Direction::InOut: + inputs.push_back(hw::PortInfo{{portName, hw::InOutType::get(portTy), + hw::ModulePort::Direction::InOut}, + argNum, + {}, + portLoc}); + inputsPort[portName] = std::make_pair(portOp, portTy); + break; + case Direction::Out: + if (!portOp->getUsers().empty()) + outputs.push_back( + hw::PortInfo{{portName, portTy, hw::ModulePort::Direction::Output}, + argNum, + {}, + portOp.getLoc()}); + outputsPort[portName] = std::make_pair(portOp, portTy); + break; + case Direction::Ref: + // TODO: Support parsing Direction::Ref port into portInfo structure. + break; + } + } + hwPorts = std::make_unique(inputs, outputs); +} + namespace { /// Returns the passed value if the integer width is already correct. @@ -58,7 +105,9 @@ static Value adjustIntegerWidth(OpBuilder &builder, Value value, return builder.create(loc, isZero, lo, max, false); } -static bool isSignedResultType(Operation *op) { +/// Due to the result type of the `lt`, or `le`, or `gt`, or `ge` ops are +/// always unsigned, estimating their operands type. +static bool isSignedType(Operation *op) { return TypeSwitch(op) .template Case([&](auto op) -> bool { return cast(op->getOperand(0).getType()) @@ -75,56 +124,209 @@ static bool isSignedResultType(Operation *op) { }); } -struct VariableOpConversion : public OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +/// Not define the predicate for `relation` and `equality` operations in the +/// MooreDialect, but comb needs it. Return a correct `comb::ICmpPredicate` +/// corresponding to different moore `relation` and `equality` operations. +static comb::ICmpPredicate getCombPredicate(Operation *op) { + using comb::ICmpPredicate; + return TypeSwitch(op) + .Case([&](auto op) { + return isSignedType(op) ? ICmpPredicate::slt : ICmpPredicate::ult; + }) + .Case([&](auto op) { + return isSignedType(op) ? ICmpPredicate::sle : ICmpPredicate::ule; + }) + .Case([&](auto op) { + return isSignedType(op) ? ICmpPredicate::sgt : ICmpPredicate::ugt; + }) + .Case([&](auto op) { + return isSignedType(op) ? ICmpPredicate::sge : ICmpPredicate::uge; + }) + .Case([&](auto op) { return ICmpPredicate::eq; }) + .Case([&](auto op) { return ICmpPredicate::ne; }) + .Case([&](auto op) { return ICmpPredicate::ceq; }) + .Case([&](auto op) { return ICmpPredicate::cne; }) + .Case([&](auto op) { return ICmpPredicate::weq; }) + .Case([&](auto op) { return ICmpPredicate::wne; }); +} + +//===----------------------------------------------------------------------===// +// Structure Conversion +//===----------------------------------------------------------------------===// + +struct SVModuleOpConv : public OpConversionPattern { + SVModuleOpConv(TypeConverter &typeConverter, MLIRContext *ctx, + MoorePortInfoMap &portMap) + : OpConversionPattern(typeConverter, ctx), portMap(portMap) {} LogicalResult - matchAndRewrite(VariableOp op, OpAdaptor adaptor, + matchAndRewrite(SVModuleOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - ImplicitLocOpBuilder builder(op->getLoc(), op.getContext()); - Value value = decl.getValue(op); - if (!value) { - rewriter.eraseOp(op); - return success(); + rewriter.setInsertionPoint(op); + const circt::MoorePortInfo &mp = portMap.at(op.getSymNameAttr()); + + // Create the hw.module to replace svmoduleOp + auto hwModuleOp = rewriter.create( + op.getLoc(), op.getSymNameAttr(), *mp.hwPorts); + rewriter.eraseBlock(hwModuleOp.getBodyBlock()); + rewriter.inlineRegionBefore(op.getBodyRegion(), hwModuleOp.getBodyRegion(), + hwModuleOp.getBodyRegion().end()); + auto *hwBody = hwModuleOp.getBodyBlock(); + + // Replace all relating logic of input port definitions for the input + // block arguments. And update relating uses chain. + for (auto [index, input] : llvm::enumerate(mp.hwPorts->getInputs())) { + BlockArgument newArg; + auto portOp = mp.inputsPort.at(input.name).first; + auto inputTy = mp.inputsPort.at(input.name).second; + rewriter.modifyOpInPlace(hwModuleOp, [&]() { + newArg = hwBody->addArgument(inputTy, portOp.getLoc()); + }); + rewriter.replaceAllUsesWith(portOp.getResult(), newArg); } - Type resultType = typeConverter->convertType(op.getResult().getType()); - op->moveAfter(value.getDefiningOp()); - typeConverter->materializeTargetConversion(builder, op->getLoc(), - resultType, {value}); - rewriter.replaceOpWithNewOp(op, value, op.getNameAttr()); + // Adjust all relating logic of output port definitions for rewriting + // hw.output op. + SmallVector outputValues; + for (auto [index, output] : llvm::enumerate(mp.hwPorts->getOutputs())) { + auto portOp = mp.outputsPort.at(output.name).first; + outputValues.push_back(portOp->getResult(0)); + } + + // Rewrite the hw.output op + rewriter.setInsertionPointToEnd(hwBody); + rewriter.create(op.getLoc(), outputValues); + + // Erase the original op + rewriter.eraseOp(op); return success(); } + MoorePortInfoMap &portMap; }; -struct NetOpConversion : public OpConversionPattern { +struct ProcedureOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(NetOp op, OpAdaptor adaptor, + matchAndRewrite(ProcedureOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - ImplicitLocOpBuilder builder(op->getLoc(), op.getContext()); - Value value = decl.getValue(op); - if (!value) { + switch (adaptor.getKind()) { + case ProcedureKind::AlwaysComb: + rewriter.setInsertionPointAfter(op->getPrevNode()); + rewriter.inlineBlockBefore(&op.getBodyBlock(), + op->getPrevNode()->getBlock(), + rewriter.getInsertionPoint()); rewriter.eraseOp(op); return success(); + case ProcedureKind::Always: + case ProcedureKind::AlwaysFF: + case ProcedureKind::AlwaysLatch: + case ProcedureKind::Initial: + case ProcedureKind::Final: + return emitError(op->getLoc(), "Unsupported procedure operation"); + }; + return success(); + } +}; + +struct InstanceOpConv : public OpConversionPattern { + InstanceOpConv(TypeConverter &typeConverter, MLIRContext *ctx, + MoorePortInfoMap &portMap) + : OpConversionPattern(typeConverter, ctx), portMap(portMap) {} + + LogicalResult + matchAndRewrite(InstanceOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + + auto instName = op.getInstanceNameAttr(); + auto moduleName = op.getModuleNameAttr(); + auto moduleNameAttr = moduleName.getAttr(); + + SmallVector resultTypes; + SmallVector argNames, resNames; + SmallVector operands, convertedOperands; + for (auto operand : op.getInputs()) + operands.push_back(operand); + + for (auto arg : portMap.at(moduleNameAttr).hwPorts->getInputs()) + argNames.push_back(arg.name); + + for (auto res : portMap.at(moduleNameAttr).hwPorts->getOutputs()) { + resultTypes.push_back(res.type); + resNames.push_back(res.name); } - Type resultType = typeConverter->convertType(op.getResult().getType()); - op->moveAfter(value.getDefiningOp()); - typeConverter->materializeTargetConversion(builder, op->getLoc(), - resultType, {value}); - rewriter.replaceOpWithNewOp(op, value, op.getNameAttr()); + rewriter.setInsertionPoint(op); + auto instOp = rewriter.create( + op.getLoc(), resultTypes, instName, moduleName, operands, + rewriter.getArrayAttr(argNames), rewriter.getArrayAttr(resNames), + rewriter.getArrayAttr({}), nullptr); + + // Update uses chain of output ports within instanceOp from original + // moore instance op. + for (auto [index, res] : llvm::enumerate(op.getOutputs())) { + auto *defineOp = res.getDefiningOp(); + defineOp->getResult(0).replaceAllUsesWith(instOp->getResult(index)); + } + rewriter.eraseOp(op); return success(); } + + MoorePortInfoMap &portMap; }; -struct CAssignOpConversion : public OpConversionPattern { +struct PortOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; + LogicalResult - matchAndRewrite(CAssignOp op, OpAdaptor adaptor, + matchAndRewrite(PortOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + switch (op.getDirection()) { + // The users of In / InOut direction port are replaced in the + // HWModuleOp's generation. When it has no users left, erase itself. + case Direction::In: + case Direction::InOut: + if (op->getUsers().empty()) + rewriter.eraseOp(op); + break; + + // Handle the users of Out direction port, if there is no user left, erase + // itself. + case Direction::Out: + if (!op->getUsers().empty()) + op.getResult().replaceAllUsesWith( + decl.getOutputValue(op)->getOperand(1)); - if (decl.getIdentifier(op)) rewriter.eraseOp(op); + break; + + // TODO: Not support handling port operation of Ref direction. + case Direction::Ref: + return op.emitOpError( + "Not support conversion of port (Ref direction) operation."); + } + return success(); + } +}; + +//===----------------------------------------------------------------------===// +// Declaration Conversion +//===----------------------------------------------------------------------===// + +template +struct DeclOpConv : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename OpTy::Adaptor; + LogicalResult + matchAndRewrite(OpTy op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Value value = decl.getValue(op); + if (!value) { + rewriter.eraseOp(op); + return success(); + } + + value.setType(ConversionPattern::typeConverter->convertType( + op.getResult().getType())); + rewriter.replaceOpWithNewOp(op, value, op.getNameAttr()); return success(); } }; @@ -145,7 +347,7 @@ struct ConstantOpConv : public OpConversionPattern { } }; -struct ConcatOpConversion : public OpConversionPattern { +struct ConcatOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult matchAndRewrite(ConcatOp op, OpAdaptor adaptor, @@ -155,62 +357,59 @@ struct ConcatOpConversion : public OpConversionPattern { } }; -/// Lowering the moore::BinaryOp to the comb::UTVariadicOp, which includes -/// the comb::SubOp(UTBinOp). Maybe the DivOp and the ModOp will be supported -/// after properly handling the signed expr. -template -struct BinaryOpConversion : public OpConversionPattern { - using OpConversionPattern::OpConversionPattern; - using OpAdaptor = typename SourceOp::Adaptor; +template +struct UnaryOpConv : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename OpTy::Adaptor; LogicalResult - matchAndRewrite(SourceOp op, OpAdaptor adaptor, + matchAndRewrite(OpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - - rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), - adaptor.getRhs(), false); + rewriter.replaceOpWithNewOp(op, adaptor.getInput()); return success(); } }; -struct DivOpConversion : public OpConversionPattern { +struct NotOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; - LogicalResult - matchAndRewrite(DivOp op, OpAdaptor adaptor, + matchAndRewrite(NotOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - Type resultType = typeConverter->convertType(op.getResult().getType()); - - if (isSignedResultType(op)) - rewriter.replaceOpWithNewOp( - op, resultType, adaptor.getLhs(), adaptor.getRhs(), false); - - rewriter.replaceOpWithNewOp(op, resultType, adaptor.getLhs(), - adaptor.getRhs(), false); + Type resultType = + ConversionPattern::typeConverter->convertType(op.getResult().getType()); + Value max = rewriter.create(op.getLoc(), resultType, -1); + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), max); return success(); } }; -struct ModOpConversion : public OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +template +struct BinaryOpConv : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename SourceOp::Adaptor; LogicalResult - matchAndRewrite(ModOp op, OpAdaptor adaptor, + matchAndRewrite(SourceOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - Type resultType = typeConverter->convertType(op.getResult().getType()); - - if (isSignedResultType(op)) - rewriter.replaceOpWithNewOp( - op, resultType, adaptor.getLhs(), adaptor.getRhs(), false); + if (isa(op) && isSignedType(op)) { + rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), + adaptor.getRhs(), false); + return success(); + } + if (isa(op) && isSignedType(op)) { + rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), + adaptor.getRhs(), false); + return success(); + } - rewriter.replaceOpWithNewOp(op, resultType, adaptor.getLhs(), - adaptor.getRhs(), false); + rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), + adaptor.getRhs(), false); return success(); } }; template -struct ICmpOpConversion : public OpConversionPattern { +struct ICmpOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; using OpAdaptor = typename SourceOp::Adaptor; @@ -219,33 +418,7 @@ struct ICmpOpConversion : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { Type resultType = ConversionPattern::typeConverter->convertType(op.getResult().getType()); - - using comb::ICmpPredicate; - ICmpPredicate pred; - llvm::StringLiteral opName = op.getOperationName(); - if (opName == "moore.lt") - pred = isSignedResultType(op) ? ICmpPredicate::slt : ICmpPredicate::ult; - else if (opName == "moore.le") - pred = isSignedResultType(op) ? ICmpPredicate::sle : ICmpPredicate::ule; - else if (opName == "moore.gt") - pred = isSignedResultType(op) ? ICmpPredicate::sgt : ICmpPredicate::ugt; - else if (opName == "moore.ge") - pred = isSignedResultType(op) ? ICmpPredicate::sge : ICmpPredicate::uge; - else if (opName == "moore.eq") - pred = ICmpPredicate::eq; - else if (opName == "moore.ne") - pred = ICmpPredicate::ne; - else if (opName == "moore.case_eq") - pred = ICmpPredicate::ceq; - else if (opName == "moore.case_ne") - pred = ICmpPredicate::cne; - else if (opName == "moore.wildcard_eq") - pred = ICmpPredicate::weq; - else if (opName == "moore.wildcard_ne") - pred = ICmpPredicate::wne; - else - llvm_unreachable("Missing relation/(wildcard/case)equlity op to " - "comb::ICmpOp conversion"); + comb::ICmpPredicate pred = getCombPredicate(op); rewriter.replaceOpWithNewOp( op, resultType, pred, adapter.getLhs(), adapter.getRhs()); @@ -253,7 +426,7 @@ struct ICmpOpConversion : public OpConversionPattern { } }; -struct ExtractOpConversion : public OpConversionPattern { +struct ExtractOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult @@ -270,7 +443,7 @@ struct ExtractOpConversion : public OpConversionPattern { } }; -struct ConversionOpConversion : public OpConversionPattern { +struct ConversionOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult @@ -289,58 +462,79 @@ struct ConversionOpConversion : public OpConversionPattern { // Statement Conversion //===----------------------------------------------------------------------===// -struct ReturnOpConversion : public OpConversionPattern { - using OpConversionPattern::OpConversionPattern; +/// TODO: The `PAssign`, `PCAssign` ops need to convert. +template +struct AssignOpConv : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename SourceOp::Adaptor; LogicalResult - matchAndRewrite(func::ReturnOp op, OpAdaptor adaptor, + matchAndRewrite(SourceOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); + + rewriter.eraseOp(op); return success(); } }; -struct CondBranchOpConversion : public OpConversionPattern { +struct HWOutputOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(cf::CondBranchOp op, OpAdaptor adaptor, + matchAndRewrite(hw::OutputOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - rewriter.replaceOpWithNewOp( - op, adaptor.getCondition(), adaptor.getTrueDestOperands(), - adaptor.getFalseDestOperands(), op.getTrueDest(), op.getFalseDest()); + rewriter.replaceOpWithNewOp(op, adaptor.getOperands()); return success(); } }; -struct BranchOpConversion : public OpConversionPattern { +struct HWInstanceOpConv : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + LogicalResult + matchAndRewrite(hw::InstanceOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + SmallVector inputs = adaptor.getInputs(); + auto *refeModule = op->getParentOfType().lookupSymbol( + op.getReferencedModuleNameAttr()); + + SmallVector convResTypes; + if (typeConverter->convertTypes(op.getResultTypes(), convResTypes).failed()) + return failure(); + + rewriter.replaceOpWithNewOp( + op, refeModule, adaptor.getInstanceNameAttr(), inputs, + rewriter.getArrayAttr({}), nullptr); + + return success(); + } +}; + +struct CondBranchOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(cf::BranchOp op, OpAdaptor adaptor, + matchAndRewrite(cf::CondBranchOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - rewriter.replaceOpWithNewOp(op, op.getDest(), - adaptor.getDestOperands()); + rewriter.replaceOpWithNewOp( + op, adaptor.getCondition(), adaptor.getTrueDestOperands(), + adaptor.getFalseDestOperands(), op.getTrueDest(), op.getFalseDest()); return success(); } }; -struct CallOpConversion : public OpConversionPattern { +struct BranchOpConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult - matchAndRewrite(func::CallOp op, OpAdaptor adaptor, + matchAndRewrite(cf::BranchOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - SmallVector convResTypes; - if (typeConverter->convertTypes(op.getResultTypes(), convResTypes).failed()) - return failure(); - rewriter.replaceOpWithNewOp( - op, adaptor.getCallee(), convResTypes, adaptor.getOperands()); + rewriter.replaceOpWithNewOp(op, op.getDest(), + adaptor.getDestOperands()); return success(); } }; -struct UnrealizedConversionCastConversion +struct UnrealizedConversionCastConv : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -394,14 +588,25 @@ struct ShrOpConversion : public OpConversionPattern { Value amount = adjustIntegerWidth(rewriter, adaptor.getAmount(), resultType.getIntOrFloatBitWidth(), op->getLoc()); + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getValue(), amount, false); + return success(); + } +}; - if (adaptor.getArithmetic() && isSignedResultType(op)) { - rewriter.replaceOpWithNewOp( - op, resultType, adaptor.getValue(), amount, false); - return success(); - } +struct AShrOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; - rewriter.replaceOpWithNewOp( + LogicalResult + matchAndRewrite(AShrOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type resultType = typeConverter->convertType(op.getResult().getType()); + + // Comb shift operations require the same bit-width for value and amount + Value amount = + adjustIntegerWidth(rewriter, adaptor.getAmount(), + resultType.getIntOrFloatBitWidth(), op->getLoc()); + rewriter.replaceOpWithNewOp( op, resultType, adaptor.getValue(), amount, false); return success(); } @@ -431,32 +636,38 @@ static void addGenericLegality(ConversionTarget &target) { } static void populateLegality(ConversionTarget &target) { - target.addIllegalDialect(); - target.addLegalDialect(); target.addLegalDialect(); - target.addLegalDialect(); target.addLegalDialect(); - + target.addLegalDialect(); + target.addLegalDialect(); addGenericLegality(target); addGenericLegality(target); - addGenericLegality(target); - addGenericLegality(target); addGenericLegality(target); +} - target.addDynamicallyLegalOp([](func::FuncOp op) { - auto argsConverted = llvm::none_of(op.getBlocks(), [](auto &block) { - return hasMooreType(block.getArguments()); +static void populateSelectionalLegality(ConversionTarget &target, + bool isConvertStructure) { + if (isConvertStructure) { + target.addLegalDialect(); + target.addIllegalOp(); + target.addIllegalOp(); + target.addIllegalOp(); + } else { + target.addIllegalDialect(); + target.addDynamicallyLegalOp([](hw::HWModuleOp op) { + return !hasMooreType(op.getInputTypes()) && + !hasMooreType(op.getOutputTypes()) && + !hasMooreType(op.getBody().getArgumentTypes()); }); - auto resultsConverted = !hasMooreType(op.getResultTypes()); - return argsConverted && resultsConverted; - }); + target.addDynamicallyLegalOp( + [](hw::OutputOp op) { return !hasMooreType(op.getOutputs()); }); + target.addDynamicallyLegalOp([](hw::InstanceOp op) { + return !hasMooreType(op.getInputs()) && !hasMooreType(op->getResults()); + }); + } } static void populateTypeConversion(TypeConverter &typeConverter) { - typeConverter.addConversion([&](IntType type) { - return mlir::IntegerType::get(type.getContext(), type.getBitSize()); - }); - // Directly map simple bit vector types to a compact integer type. This needs // to be added after all of the other conversions above, such that SBVs // conversion gets tried first before any of the others. @@ -466,58 +677,77 @@ static void populateTypeConversion(TypeConverter &typeConverter) { return std::nullopt; }); + typeConverter.addConversion([](mlir::IntegerType type) { return type; }); typeConverter.addTargetMaterialization( - [&](OpBuilder &builder, Type type, ValueRange values, Location loc) { - assert(values.size() == 1); - values[0].setType(typeConverter.convertType(type)); - return values[0]; + [&](mlir::OpBuilder &builder, mlir::Type resultType, + mlir::ValueRange inputs, + mlir::Location loc) -> std::optional { + if (inputs.size() != 1) + return std::nullopt; + return inputs[0]; + }); + typeConverter.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]; }); +} - // Valid target types. - typeConverter.addConversion([](mlir::IntegerType type) { return type; }); +void circt::populateMooreStructureConversionPatterns( + TypeConverter &typeConverter, RewritePatternSet &patterns, + circt::MoorePortInfoMap &portInfoMap) { + auto *context = patterns.getContext(); + + patterns.add(typeConverter, context, + portInfoMap); + patterns.add(typeConverter, context); } -static void populateOpConversion(RewritePatternSet &patterns, - TypeConverter &typeConverter) { +void circt::populateMooreToCoreConversionPatterns(TypeConverter &typeConverter, + RewritePatternSet &patterns) { auto *context = patterns.getContext(); // clang-format off patterns.add< - ConstantOpConv, - ConcatOpConversion, - ReturnOpConversion, - CondBranchOpConversion, - BranchOpConversion, - CallOpConversion, - ShlOpConversion, - ShrOpConversion, - BinaryOpConversion, - BinaryOpConversion, - BinaryOpConversion, - BinaryOpConversion, - BinaryOpConversion, - BinaryOpConversion, - DivOpConversion, - ModOpConversion, - ICmpOpConversion, - ICmpOpConversion, - ICmpOpConversion, - ICmpOpConversion, - ICmpOpConversion, - ICmpOpConversion, - ICmpOpConversion, - ICmpOpConversion, - ICmpOpConversion, - ICmpOpConversion, - // ExtractOpConversion, - ConversionOpConversion, - VariableOpConversion, - NetOpConversion, - CAssignOpConversion, - UnrealizedConversionCastConversion - >(typeConverter, context); - // clang-format on - mlir::populateFunctionOpInterfaceTypeConversionPattern( - patterns, typeConverter); + // Patterns of assignment operations. + AssignOpConv, AssignOpConv, + + // Patterns of branch operations. + BranchOpConv, CondBranchOpConv, + + // Patterns of declaration operations. + DeclOpConv, DeclOpConv, + + // Patterns of miscellaneous operations. + ConstantOpConv, ConcatOpConv, ExtractOpConv, ConversionOpConv, + ProcedureOpConv, + + // Patterns of shifting operations. + ShlOpConv, ShrOpConv, AShrOpConv, + + // Patterns of binary operations. + BinaryOpConv, BinaryOpConv, + BinaryOpConv, BinaryOpConv, + BinaryOpConv, BinaryOpConv, + BinaryOpConv, BinaryOpConv, + + // Patterns of relational operations. + ICmpOpConv, ICmpOpConv, ICmpOpConv, ICmpOpConv, + ICmpOpConv, ICmpOpConv, ICmpOpConv, + ICmpOpConv, ICmpOpConv, ICmpOpConv, + + // Patterns of unary operations. + UnaryOpConv, UnaryOpConv, UnaryOpConv, + UnaryOpConv, NotOpConv, + + // Patterns of other operations outside Moore dialect. + HWInstanceOpConv, HWOutputOpConv, UnrealizedConversionCastConv>( + typeConverter, context); + + hw::populateHWModuleLikeTypeConversionPattern( + hw::HWModuleOp::getOperationName(), patterns, typeConverter); } //===----------------------------------------------------------------------===// @@ -540,6 +770,8 @@ void MooreToCorePass::runOnOperation() { MLIRContext &context = getContext(); ModuleOp module = getOperation(); + // This is used to collect the net/variable/port declarations with their + // assigned value and identify the assignment statements that had been used. auto pm = PassManager::on(&context); pm.addPass(moore::createMooreDeclarationsPass()); if (failed(pm.run(module))) @@ -548,9 +780,28 @@ void MooreToCorePass::runOnOperation() { ConversionTarget target(context); TypeConverter typeConverter; RewritePatternSet patterns(&context); - populateLegality(target); + + // Generate moore module signatures. + MoorePortInfoMap portInfoMap; + for (auto svModuleOp : module.getOps()) + portInfoMap.try_emplace(svModuleOp.getSymNameAttr(), + MoorePortInfo(svModuleOp)); + populateTypeConversion(typeConverter); - populateOpConversion(patterns, typeConverter); + populateLegality(target); + + // First to convert structral moore operations to hw. + populateSelectionalLegality(target, true); + populateMooreStructureConversionPatterns(typeConverter, patterns, + portInfoMap); + + if (failed(applyPartialConversion(module, target, std::move(patterns)))) + signalPassFailure(); + patterns.clear(); + + // Second to convert miscellaneous moore operations to core ir. + populateSelectionalLegality(target, false); + populateMooreToCoreConversionPatterns(typeConverter, patterns); if (failed(applyFullConversion(module, target, std::move(patterns)))) signalPassFailure(); diff --git a/lib/Conversion/PassDetail.h b/lib/Conversion/PassDetail.h index e681642a29e8..9ec0885f71bc 100644 --- a/lib/Conversion/PassDetail.h +++ b/lib/Conversion/PassDetail.h @@ -117,6 +117,10 @@ namespace seq { class SeqDialect; } // namespace seq +namespace sim { +class SimDialect; +} // namespace sim + namespace sv { class SVDialect; } // namespace sv diff --git a/lib/Conversion/SCFToCalyx/SCFToCalyx.cpp b/lib/Conversion/SCFToCalyx/SCFToCalyx.cpp index 315154b3f20c..e90c902c3768 100644 --- a/lib/Conversion/SCFToCalyx/SCFToCalyx.cpp +++ b/lib/Conversion/SCFToCalyx/SCFToCalyx.cpp @@ -1059,7 +1059,7 @@ struct FuncOpConversion : public calyx::FuncOpPartialLoweringPattern { funcOp.getLoc(), rewriter.getStringAttr(funcOp.getSymName()), ports); std::string funcName = "func_" + funcOp.getSymName().str(); - rewriter.updateRootInPlace(funcOp, [&]() { funcOp.setSymName(funcName); }); + rewriter.modifyOpInPlace(funcOp, [&]() { funcOp.setSymName(funcName); }); /// Mark this component as the toplevel. compOp->setAttr("toplevel", rewriter.getUnitAttr()); diff --git a/lib/Conversion/SeqToSV/SeqToSV.cpp b/lib/Conversion/SeqToSV/SeqToSV.cpp index d9a8e12a425f..252a9ffee980 100644 --- a/lib/Conversion/SeqToSV/SeqToSV.cpp +++ b/lib/Conversion/SeqToSV/SeqToSV.cpp @@ -22,6 +22,7 @@ #include "circt/Dialect/SV/SVAttributes.h" #include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Support/Naming.h" #include "mlir/IR/Builders.h" #include "mlir/IR/DialectImplementation.h" #include "mlir/IR/ImplicitLocOpBuilder.h" @@ -175,10 +176,30 @@ class ClockGateLowering : public OpConversionPattern { }); // Create the gated clock signal. - Value gclk = rewriter.create( - loc, clk, rewriter.create(loc, enableLatch)); - clockGate.replaceAllUsesWith(gclk); - rewriter.eraseOp(clockGate); + rewriter.replaceOpWithNewOp( + clockGate, clk, rewriter.create(loc, enableLatch)); + return success(); + } +}; + +// Lower seq.clock_inv to a regular inverter. +// +class ClockInverterLowering : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ClockInverterOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto loc = op.getLoc(); + Value clk = adaptor.getInput(); + + StringAttr name = op->getAttrOfType("sv.namehint"); + Value one = rewriter.create(loc, APInt(1, 1)); + auto newOp = rewriter.replaceOpWithNewOp(op, clk, one); + if (name) + rewriter.modifyOpInPlace(newOp, + [&] { newOp->setAttr("sv.namehint", name); }); return success(); } }; @@ -261,6 +282,13 @@ class ClockCastLowering : public OpConversionPattern { LogicalResult matchAndRewrite(T op, typename T::Adaptor adaptor, ConversionPatternRewriter &rewriter) const final { + // If the cast had a better name than its input, propagate it. + if (Operation *inputOp = adaptor.getInput().getDefiningOp()) + if (!isa(inputOp)) + if (auto name = chooseName(op, inputOp)) + rewriter.modifyOpInPlace( + inputOp, [&] { inputOp->setAttr("sv.namehint", name); }); + rewriter.replaceOp(op, adaptor.getInput()); return success(); } @@ -284,13 +312,13 @@ class ClockConstLowering : public OpConversionPattern { /// Lower `seq.clock_div` to a behavioural clock divider /// -class ClockDividerLowering : public OpConversionPattern { +class ClockDividerLowering : public OpConversionPattern { public: - using OpConversionPattern::OpConversionPattern; - using OpConversionPattern::OpAdaptor; + using OpConversionPattern::OpConversionPattern; + using OpConversionPattern::OpAdaptor; LogicalResult - matchAndRewrite(ClockDivider clockDiv, OpAdaptor adaptor, + matchAndRewrite(ClockDividerOp clockDiv, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final { Location loc = clockDiv.getLoc(); @@ -419,6 +447,7 @@ void SeqToSVPass::runOnOperation() { patterns.add>(typeConverter, context); patterns.add>(typeConverter, context); patterns.add(typeConverter, context); + patterns.add(typeConverter, context); patterns.add(typeConverter, context); patterns.add(typeConverter, context); patterns.add(typeConverter, context); diff --git a/lib/Conversion/SimToSV/CMakeLists.txt b/lib/Conversion/SimToSV/CMakeLists.txt new file mode 100644 index 000000000000..12a7773668a7 --- /dev/null +++ b/lib/Conversion/SimToSV/CMakeLists.txt @@ -0,0 +1,13 @@ +add_circt_conversion_library(CIRCTSimToSV + SimToSV.cpp + + DEPENDS + CIRCTConversionPassIncGen + + LINK_COMPONENTS + Core + + LINK_LIBS PUBLIC + CIRCTSim + CIRCTSeq +) diff --git a/lib/Conversion/SimToSV/SimToSV.cpp b/lib/Conversion/SimToSV/SimToSV.cpp new file mode 100644 index 000000000000..a116a0c2495b --- /dev/null +++ b/lib/Conversion/SimToSV/SimToSV.cpp @@ -0,0 +1,167 @@ +//===- LowerSimToSV.cpp - Sim to SV lowering ------------------------------===// +// +// 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 transform translates Sim ops to SV. +// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/SimToSV.h" +#include "../PassDetail.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/Sim/SimOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +#define DEBUG_TYPE "lower-sim-to-sv" + +using namespace circt; +using namespace sim; + +// Lower `sim.plusargs.test` to a standard SV implementation. +// +class PlusArgsTestLowering : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto loc = op.getLoc(); + auto resultType = rewriter.getIntegerType(1); + auto str = rewriter.create(loc, op.getFormatString()); + auto reg = rewriter.create(loc, resultType, + rewriter.getStringAttr("_pargs")); + rewriter.create(loc, [&] { + auto call = rewriter.create( + loc, resultType, "test$plusargs", ArrayRef{str}); + rewriter.create(loc, reg, call); + }); + + rewriter.replaceOpWithNewOp(op, reg); + return success(); + } +}; + +// Lower `sim.plusargs.value` to a standard SV implementation. +// +class PlusArgsValueLowering : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto loc = op.getLoc(); + + auto i1ty = rewriter.getIntegerType(1); + auto type = op.getResult().getType(); + + auto regv = rewriter.create(loc, type, + rewriter.getStringAttr("_pargs_v_")); + auto regf = rewriter.create(loc, i1ty, + rewriter.getStringAttr("_pargs_f")); + + rewriter.create( + loc, "SYNTHESIS", + [&]() { + auto cstFalse = rewriter.create(loc, APInt(1, 0)); + auto cstZ = rewriter.create(loc, type); + auto assignZ = rewriter.create(loc, regv, cstZ); + circt::sv::setSVAttributes( + assignZ, + sv::SVAttributeAttr::get( + rewriter.getContext(), + "This dummy assignment exists to avoid undriven lint " + "warnings (e.g., Verilator UNDRIVEN).", + /*emitAsComment=*/true)); + rewriter.create(loc, regf, cstFalse); + }, + [&]() { + rewriter.create(loc, [&] { + auto zero32 = rewriter.create(loc, APInt(32, 0)); + auto tmpResultType = rewriter.getIntegerType(32); + auto str = + rewriter.create(loc, op.getFormatString()); + auto call = rewriter.create( + loc, tmpResultType, "value$plusargs", + ArrayRef{str, regv}); + auto test = rewriter.create( + loc, comb::ICmpPredicate::ne, call, zero32, true); + rewriter.create(loc, regf, test); + }); + }); + + auto readf = rewriter.create(loc, regf); + auto readv = rewriter.create(loc, regv); + rewriter.replaceOp(op, {readf, readv}); + return success(); + } +}; + +template +class SimulatorStopLowering : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(FromOp op, typename FromOp::Adaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto loc = op.getLoc(); + + Value clockCast = rewriter.create(loc, adaptor.getClk()); + + rewriter.create( + loc, "SYNTHESIS", [&] {}, + [&] { + rewriter.create( + loc, sv::EventControl::AtPosEdge, clockCast, [&] { + rewriter.create(loc, adaptor.getCond(), + [&] { rewriter.create(loc); }); + }); + }); + + rewriter.eraseOp(op); + + return success(); + } +}; + +namespace { +struct SimToSVPass : public LowerSimToSVBase { + void runOnOperation() override { + auto circuit = getOperation(); + MLIRContext *context = &getContext(); + + ConversionTarget target(*context); + target.addIllegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + + RewritePatternSet patterns(context); + patterns.add(context); + patterns.add(context); + patterns.add>(context); + patterns.add>(context); + if (failed(applyPartialConversion(circuit, target, std::move(patterns)))) + signalPassFailure(); + } +}; +} // anonymous namespace + +std::unique_ptr circt::createLowerSimToSVPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/Arc/ArcDialect.cpp b/lib/Dialect/Arc/ArcDialect.cpp index d41d849629d5..cc45118478ac 100644 --- a/lib/Dialect/Arc/ArcDialect.cpp +++ b/lib/Dialect/Arc/ArcDialect.cpp @@ -22,10 +22,6 @@ struct ArcInlinerInterface : public mlir::DialectInlinerInterface { bool isLegalToInline(Operation *call, Operation *callable, bool wouldBeCloned) const override { - if (auto stateOp = dyn_cast(call); - stateOp && stateOp.getLatency() == 0) - return true; - return isa(call); } bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned, diff --git a/lib/Dialect/Arc/ArcOps.cpp b/lib/Dialect/Arc/ArcOps.cpp index 41449d871992..7fdfde1daf36 100644 --- a/lib/Dialect/Arc/ArcOps.cpp +++ b/lib/Dialect/Arc/ArcOps.cpp @@ -150,19 +150,11 @@ LogicalResult StateOp::verifySymbolUses(SymbolTableCollection &symbolTable) { } LogicalResult StateOp::verify() { - if (getLatency() > 0 && !getOperation()->getParentOfType() && - !getClock()) - return emitOpError( - "with non-zero latency outside a clock domain requires a clock"); - - if (getLatency() == 0) { - if (getClock()) - return emitOpError("with zero latency cannot have a clock"); - if (getEnable()) - return emitOpError("with zero latency cannot have an enable"); - if (getReset()) - return emitOpError("with zero latency cannot have a reset"); - } + if (getLatency() < 1) + return emitOpError("latency must be a positive integer"); + + if (!getOperation()->getParentOfType() && !getClock()) + return emitOpError("outside a clock domain requires a clock"); if (getOperation()->getParentOfType() && getClock()) return emitOpError("inside a clock domain cannot have a clock"); @@ -170,8 +162,6 @@ LogicalResult StateOp::verify() { return success(); } -bool StateOp::isClocked() { return getLatency() > 0; } - //===----------------------------------------------------------------------===// // CallOp //===----------------------------------------------------------------------===// @@ -181,6 +171,14 @@ LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) { getResults().getTypes(), symbolTable); } +bool CallOp::isClocked() { return false; } + +Value CallOp::getClock() { return Value{}; } + +void CallOp::eraseClock() {} + +uint32_t CallOp::getLatency() { return 0; } + //===----------------------------------------------------------------------===// // MemoryWritePortOp //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Arc/CMakeLists.txt b/lib/Dialect/Arc/CMakeLists.txt index 8b4d616cdc49..00a58eb30d41 100644 --- a/lib/Dialect/Arc/CMakeLists.txt +++ b/lib/Dialect/Arc/CMakeLists.txt @@ -3,6 +3,7 @@ set(CIRCT_Arc_Sources ArcFolds.cpp ArcOps.cpp ArcTypes.cpp + ModelInfo.cpp ) set(LLVM_OPTIONAL_SOURCES @@ -48,5 +49,6 @@ add_dependencies(circt-headers MLIRArcIncGen ) +add_subdirectory(Export) add_subdirectory(Interfaces) add_subdirectory(Transforms) diff --git a/lib/Dialect/Arc/Export/CMakeLists.txt b/lib/Dialect/Arc/Export/CMakeLists.txt new file mode 100644 index 000000000000..12eaec9268ca --- /dev/null +++ b/lib/Dialect/Arc/Export/CMakeLists.txt @@ -0,0 +1,7 @@ +add_circt_translation_library(CIRCTExportArc + ModelInfoExport.cpp + + LINK_LIBS PUBLIC + CIRCTArc + MLIRTranslateLib +) diff --git a/lib/Dialect/Arc/Export/ModelInfoExport.cpp b/lib/Dialect/Arc/Export/ModelInfoExport.cpp new file mode 100644 index 000000000000..3f0529db6a13 --- /dev/null +++ b/lib/Dialect/Arc/Export/ModelInfoExport.cpp @@ -0,0 +1,41 @@ +//===- ModelInfoExport.cpp - Exports model info to JSON format ------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Register the MLIR translation to export model info to JSON format. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Arc/ModelInfoExport.h" +#include "circt/Dialect/Arc/ArcDialect.h" +#include "circt/Dialect/Arc/ModelInfo.h" +#include "mlir/Tools/mlir-translate/Translation.h" + +using namespace llvm; +using namespace mlir; +using namespace circt; +using namespace arc; + +LogicalResult circt::arc::collectAndExportModelInfo(ModuleOp module, + llvm::raw_ostream &os) { + SmallVector models; + if (failed(collectModels(module, models))) + return failure(); + serializeModelInfoToJson(os, models); + return success(); +} + +void circt::arc::registerArcModelInfoTranslation() { + static mlir::TranslateFromMLIRRegistration modelInfoToJson( + "export-arc-model-info", "export Arc model info in JSON format", + [](ModuleOp module, llvm::raw_ostream &os) { + return arc::collectAndExportModelInfo(module, os); + }, + [](mlir::DialectRegistry ®istry) { + registry.insert(); + }); +} diff --git a/lib/Dialect/Arc/ModelInfo.cpp b/lib/Dialect/Arc/ModelInfo.cpp new file mode 100644 index 000000000000..eb1e41330884 --- /dev/null +++ b/lib/Dialect/Arc/ModelInfo.cpp @@ -0,0 +1,165 @@ +//===- ModelInfo.cpp - Information about Arc models -----------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Defines and computes information about Arc models. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Arc/ModelInfo.h" +#include "circt/Dialect/Arc/ArcOps.h" +#include "llvm/Support/JSON.h" + +using namespace mlir; +using namespace circt; +using namespace arc; + +LogicalResult circt::arc::collectStates(Value storage, unsigned offset, + SmallVector &states) { + struct StateCollectionJob { + mlir::Value::user_iterator nextToProcess; + mlir::Value::user_iterator end; + unsigned offset; + + StateCollectionJob(Value storage, unsigned offset) + : nextToProcess(storage.user_begin()), end(storage.user_end()), + offset(offset) {} + }; + + SmallVector jobStack{{storage, offset}}; + + while (!jobStack.empty()) { + StateCollectionJob &job = jobStack.back(); + + if (job.nextToProcess == job.end) { + jobStack.pop_back(); + continue; + } + + Operation *op = *job.nextToProcess++; + unsigned offset = job.offset; + + if (auto substorage = dyn_cast(op)) { + if (!substorage.getOffset().has_value()) + return substorage.emitOpError( + "without allocated offset; run state allocation first"); + Value substorageOutput = substorage.getOutput(); + jobStack.emplace_back(substorageOutput, offset + *substorage.getOffset()); + continue; + } + + if (!isa(op)) + continue; + + auto opName = op->getAttrOfType("name"); + if (!opName || opName.getValue().empty()) + continue; + + auto opOffset = op->getAttrOfType("offset"); + if (!opOffset) + return op->emitOpError( + "without allocated offset; run state allocation first"); + + if (isa(op)) { + auto result = op->getResult(0); + auto &stateInfo = states.emplace_back(); + stateInfo.type = StateInfo::Register; + if (isa(op)) + stateInfo.type = StateInfo::Input; + else if (isa(op)) + stateInfo.type = StateInfo::Output; + else if (auto alloc = dyn_cast(op)) { + if (alloc.getTap()) + stateInfo.type = StateInfo::Wire; + } + stateInfo.name = opName.getValue(); + stateInfo.offset = opOffset.getValue().getZExtValue() + offset; + stateInfo.numBits = result.getType().cast().getBitWidth(); + continue; + } + + if (auto memOp = dyn_cast(op)) { + auto stride = op->getAttrOfType("stride"); + if (!stride) + return op->emitOpError( + "without allocated stride; run state allocation first"); + auto memType = memOp.getType(); + auto intType = memType.getWordType(); + auto &stateInfo = states.emplace_back(); + stateInfo.type = StateInfo::Memory; + stateInfo.name = opName.getValue(); + stateInfo.offset = opOffset.getValue().getZExtValue() + offset; + stateInfo.numBits = intType.getWidth(); + stateInfo.memoryStride = stride.getValue().getZExtValue(); + stateInfo.memoryDepth = memType.getNumWords(); + continue; + } + } + + return success(); +} + +LogicalResult circt::arc::collectModels(mlir::ModuleOp module, + SmallVector &models) { + for (auto modelOp : module.getOps()) { + auto storageArg = modelOp.getBody().getArgument(0); + auto storageType = storageArg.getType().cast(); + + SmallVector states; + if (failed(collectStates(storageArg, 0, states))) + return failure(); + llvm::sort(states, [](auto &a, auto &b) { return a.offset < b.offset; }); + + models.emplace_back(std::string(modelOp.getName()), storageType.getSize(), + std::move(states)); + } + + return success(); +} + +void circt::arc::serializeModelInfoToJson(llvm::raw_ostream &outputStream, + ArrayRef models) { + llvm::json::OStream json(outputStream, 2); + + json.array([&] { + for (const ModelInfo &model : models) { + json.object([&] { + json.attribute("name", model.name); + json.attribute("numStateBytes", model.numStateBytes); + json.attributeArray("states", [&] { + for (const auto &state : model.states) { + json.object([&] { + json.attribute("name", state.name); + json.attribute("offset", state.offset); + json.attribute("numBits", state.numBits); + auto typeStr = [](StateInfo::Type type) { + switch (type) { + case StateInfo::Input: + return "input"; + case StateInfo::Output: + return "output"; + case StateInfo::Register: + return "register"; + case StateInfo::Memory: + return "memory"; + case StateInfo::Wire: + return "wire"; + } + return ""; + }; + json.attribute("type", typeStr(state.type)); + if (state.type == StateInfo::Memory) { + json.attribute("stride", state.memoryStride); + json.attribute("depth", state.memoryDepth); + } + }); + } + }); + }); + } + }); +} diff --git a/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp b/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp index d244a0c0bc60..0413bff13aa2 100644 --- a/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp +++ b/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp @@ -38,6 +38,8 @@ using namespace arc; // Datastructures //===----------------------------------------------------------------------===// +namespace { + /// A combination of SymbolCache and SymbolUserMap that also allows to add users /// and remove symbols on-demand. class SymbolHandler : public SymbolCache { @@ -105,10 +107,81 @@ class SymbolHandler : public SymbolCache { DenseMap> userMap; }; +/// A Listener keeping the provided SymbolHandler up-to-date. This is especially +/// important for simplifications (e.g. DCE) the rewriter performs automatically +/// that we cannot or do not want to turn off. +class ArcListener : public mlir::RewriterBase::Listener { +public: + explicit ArcListener(SymbolHandler *handler) : Listener(), handler(handler) {} + + void notifyOperationReplaced(Operation *op, Operation *replacement) override { + // If, e.g., a DefineOp is replaced with another DefineOp but with the same + // symbol, we don't want to drop the list of users. + auto symOp = dyn_cast(op); + auto symReplacement = dyn_cast(replacement); + if (symOp && symReplacement && + symOp.getNameAttr() == symReplacement.getNameAttr()) + return; + + remove(op); + // TODO: if an operation is inserted that defines a symbol and the symbol + // already has uses, those users are not added. + add(replacement); + } + + void notifyOperationReplaced(Operation *op, ValueRange replacement) override { + remove(op); + } + + void notifyOperationRemoved(Operation *op) override { remove(op); } + + void notifyOperationInserted(Operation *op, + mlir::IRRewriter::InsertPoint) override { + // TODO: if an operation is inserted that defines a symbol and the symbol + // already has uses, those users are not added. + add(op); + } + +private: + FailureOr maybeGetDefinition(Operation *op) { + if (auto callOp = dyn_cast(op)) { + auto symAttr = + callOp.getCallableForCallee().dyn_cast(); + if (!symAttr) + return failure(); + if (auto *def = handler->getDefinition(symAttr.getLeafReference())) + return def; + } + return failure(); + } + + void remove(Operation *op) { + auto maybeDef = maybeGetDefinition(op); + if (!failed(maybeDef)) + handler->removeUser(*maybeDef, op); + + if (isa(op)) + handler->removeDefinitionAndAllUsers(op); + } + + void add(Operation *op) { + auto maybeDef = maybeGetDefinition(op); + if (!failed(maybeDef)) + handler->addUser(*maybeDef, op); + + if (auto defOp = dyn_cast(op)) + handler->addDefinition(defOp.getNameAttr(), op); + } + + SymbolHandler *handler; +}; + struct PatternStatistics { unsigned removeUnusedArcArgumentsPatternNumArgsRemoved = 0; }; +} // namespace + //===----------------------------------------------------------------------===// // Canonicalization patterns //===----------------------------------------------------------------------===// @@ -155,12 +228,6 @@ struct CallPassthroughArc : public SymOpRewritePattern { PatternRewriter &rewriter) const final; }; -struct StatePassthroughArc : public SymOpRewritePattern { - using SymOpRewritePattern::SymOpRewritePattern; - LogicalResult matchAndRewrite(StateOp op, - PatternRewriter &rewriter) const final; -}; - struct RemoveUnusedArcs : public SymOpRewritePattern { using SymOpRewritePattern::SymOpRewritePattern; LogicalResult matchAndRewrite(DefineOp op, @@ -230,7 +297,7 @@ LogicalResult MemWritePortEnableAndMaskCanonicalizer::matchAndRewrite( if (arcMapping.count(defOp.getNameAttr())) { auto arcWithoutEnable = arcMapping[defOp.getNameAttr()]; // Remove the enable attribute - rewriter.updateRootInPlace(op, [&]() { + rewriter.modifyOpInPlace(op, [&]() { op.setEnable(false); op.setArc(arcWithoutEnable.getValue()); }); @@ -244,7 +311,7 @@ LogicalResult MemWritePortEnableAndMaskCanonicalizer::matchAndRewrite( symbolCache.removeDefinitionAndAllUsers(defOp); // Remove the enable attribute - rewriter.updateRootInPlace(op, [&]() { + rewriter.modifyOpInPlace(op, [&]() { op.setEnable(false); op.setArc(newName); }); @@ -268,9 +335,9 @@ LogicalResult MemWritePortEnableAndMaskCanonicalizer::matchAndRewrite( // Remove the enable output from the current arc auto *terminator = defOp.getBodyBlock().getTerminator(); - rewriter.updateRootInPlace( + rewriter.modifyOpInPlace( terminator, [&]() { terminator->eraseOperand(op.getEnableIdx()); }); - rewriter.updateRootInPlace(defOp, [&]() { + rewriter.modifyOpInPlace(defOp, [&]() { defOp.setName(newName); defOp.setFunctionType( rewriter.getFunctionType(defOp.getArgumentTypes(), newResultTypes)); @@ -296,14 +363,6 @@ CallPassthroughArc::matchAndRewrite(CallOp op, return canonicalizePassthoughCall(op, symbolCache, rewriter); } -LogicalResult -StatePassthroughArc::matchAndRewrite(StateOp op, - PatternRewriter &rewriter) const { - if (op.getLatency() == 0) - return canonicalizePassthoughCall(op, symbolCache, rewriter); - return failure(); -} - LogicalResult RemoveUnusedArcs::matchAndRewrite(DefineOp op, PatternRewriter &rewriter) const { @@ -478,7 +537,7 @@ SinkArcInputsPattern::matchAndRewrite(DefineOp op, else newInputs.push_back(value); } - rewriter.updateRootInPlace( + rewriter.modifyOpInPlace( callOp, [&]() { callOp.getArgOperandsMutable().assign(newInputs); }); for (auto value : maybeUnusedValues) if (value.use_empty()) @@ -513,10 +572,12 @@ void ArcCanonicalizerPass::runOnOperation() { config.enableRegionSimplification = false; config.maxIterations = 10; config.useTopDownTraversal = true; + ArcListener listener(&cache); + config.listener = &listener; PatternStatistics statistics; RewritePatternSet symbolPatterns(&getContext()); - symbolPatterns.add( &getContext(), cache, names, statistics); symbolPatterns.add( diff --git a/lib/Dialect/Arc/Transforms/CMakeLists.txt b/lib/Dialect/Arc/Transforms/CMakeLists.txt index 496b3cc94b4a..337c1d6ee2f4 100644 --- a/lib/Dialect/Arc/Transforms/CMakeLists.txt +++ b/lib/Dialect/Arc/Transforms/CMakeLists.txt @@ -18,7 +18,6 @@ add_circt_dialect_library(CIRCTArcTransforms LowerVectorizations.cpp MakeTables.cpp MuxToControlFlow.cpp - PrintStateInfo.cpp SimplifyVariadicOps.cpp SplitLoops.cpp StripSV.cpp diff --git a/lib/Dialect/Arc/Transforms/Dedup.cpp b/lib/Dialect/Arc/Transforms/Dedup.cpp index ad104709404d..f8dfcd665c18 100644 --- a/lib/Dialect/Arc/Transforms/Dedup.cpp +++ b/lib/Dialect/Arc/Transforms/Dedup.cpp @@ -330,11 +330,11 @@ struct StructuralEquivalence { } // namespace static void addCallSiteOperands( - MutableArrayRef callSites, + SmallSetVector &callSites, ArrayRef> operandMappings) { SmallDenseMap clonedOps; SmallVector newOperands; - for (auto &callOp : callSites) { + for (auto callOp : callSites) { OpBuilder builder(callOp); newOperands.clear(); clonedOps.clear(); @@ -362,12 +362,13 @@ static bool isOutlinable(OpOperand &operand) { namespace { struct DedupPass : public arc::impl::DedupBase { void runOnOperation() override; - void replaceArcWith(DefineOp oldArc, DefineOp newArc); + void replaceArcWith(DefineOp oldArc, DefineOp newArc, + SymbolTableCollection &symbolTable); /// A mapping from arc names to arc definitions. DenseMap arcByName; /// A mapping from arc definitions to call sites. - DenseMap> callSites; + DenseMap> callSites; }; struct ArcHash { @@ -396,10 +397,7 @@ void DedupPass::runOnOperation() { getOperation().walk([&](mlir::CallOpInterface callOp) { if (auto defOp = dyn_cast_or_null(callOp.resolveCallable(&symbolTable))) - callSites[arcByName.lookup(callOp.getCallableForCallee() - .get() - .getLeafReference())] - .push_back(callOp); + callSites[defOp].insert(callOp); }); // Sort the arcs by hash such that arcs with the same hash are next to each @@ -436,7 +434,7 @@ void DedupPass::runOnOperation() { LLVM_DEBUG(llvm::dbgs() << "- Merge " << defineOp.getSymNameAttr() << " <- " << otherDefineOp.getSymNameAttr() << "\n"); - replaceArcWith(otherDefineOp, defineOp); + replaceArcWith(otherDefineOp, defineOp, symbolTable); arcHashes[otherIdx].defineOp = {}; } } @@ -713,13 +711,14 @@ void DedupPass::runOnOperation() { << " - Merged " << defineOp.getSymNameAttr() << " <- " << otherDefineOp.getSymNameAttr() << "\n"); addCallSiteOperands(callSites[otherDefineOp], newOperands); - replaceArcWith(otherDefineOp, defineOp); + replaceArcWith(otherDefineOp, defineOp, symbolTable); arcHashes[otherIdx].defineOp = {}; } } } -void DedupPass::replaceArcWith(DefineOp oldArc, DefineOp newArc) { +void DedupPass::replaceArcWith(DefineOp oldArc, DefineOp newArc, + SymbolTableCollection &symbolTable) { ++dedupPassNumArcsDeduped; auto oldArcOps = oldArc.getOps(); dedupPassTotalOps += std::distance(oldArcOps.begin(), oldArcOps.end()); @@ -728,8 +727,14 @@ void DedupPass::replaceArcWith(DefineOp oldArc, DefineOp newArc) { auto newArcName = SymbolRefAttr::get(newArc.getSymNameAttr()); for (auto callOp : oldUses) { callOp.setCalleeFromCallable(newArcName); - newUses.push_back(callOp); + newUses.insert(callOp); } + + oldArc.walk([&](mlir::CallOpInterface callOp) { + if (auto defOp = + dyn_cast_or_null(callOp.resolveCallable(&symbolTable))) + callSites[defOp].remove(callOp); + }); callSites.erase(oldArc); arcByName.erase(oldArc.getSymNameAttr()); oldArc->erase(); diff --git a/lib/Dialect/Arc/Transforms/GroupResetsAndEnables.cpp b/lib/Dialect/Arc/Transforms/GroupResetsAndEnables.cpp index 693a8af32f5d..e66c834a4b56 100644 --- a/lib/Dialect/Arc/Transforms/GroupResetsAndEnables.cpp +++ b/lib/Dialect/Arc/Transforms/GroupResetsAndEnables.cpp @@ -113,7 +113,7 @@ struct EnableGroupingPattern : public OpRewritePattern { scf::IfOp ifOp = rewriter.create(writeOps[0].getLoc(), enable, false); for (auto writeOp : writeOps) { - rewriter.updateRootInPlace(writeOp, [&]() { + rewriter.modifyOpInPlace(writeOp, [&]() { writeOp->moveBefore(ifOp.thenBlock()->getTerminator()); writeOp.getConditionMutable().erase(0); }); @@ -155,8 +155,8 @@ bool groupInRegion(Block *block, Operation *clockTreeOp, continue; // For some currently unknown reason, just calling moveBefore // directly has the same output but is much slower - rewriter->updateRootInPlace(definition, - [&]() { definition->moveBefore(op); }); + rewriter->modifyOpInPlace(definition, + [&]() { definition->moveBefore(op); }); changed = true; worklist.push_back(definition); } diff --git a/lib/Dialect/Arc/Transforms/InferStateProperties.cpp b/lib/Dialect/Arc/Transforms/InferStateProperties.cpp index 0a75a2ec9286..02472e07176b 100644 --- a/lib/Dialect/Arc/Transforms/InferStateProperties.cpp +++ b/lib/Dialect/Arc/Transforms/InferStateProperties.cpp @@ -405,9 +405,6 @@ void InferStatePropertiesPass::runOnStateOp( arc::StateOp stateOp, arc::DefineOp arc, DenseMap &resetConditionMap) { - if (stateOp.getLatency() < 1) - return; - auto outputOp = cast(arc.getBodyBlock().getTerminator()); static constexpr unsigned visitedNoChange = -1; diff --git a/lib/Dialect/Arc/Transforms/LatencyRetiming.cpp b/lib/Dialect/Arc/Transforms/LatencyRetiming.cpp index f4d13884ca82..0d6f769cb28e 100644 --- a/lib/Dialect/Arc/Transforms/LatencyRetiming.cpp +++ b/lib/Dialect/Arc/Transforms/LatencyRetiming.cpp @@ -38,13 +38,14 @@ struct LatencyRetimingStatistics { /// Absorb the latencies from predecessor states to collapse shift registers and /// reduce the overall amount of latency units in the design. -struct LatencyRetimingPattern : OpRewritePattern { +struct LatencyRetimingPattern + : mlir::OpInterfaceRewritePattern { LatencyRetimingPattern(MLIRContext *context, SymbolCache &symCache, LatencyRetimingStatistics &statistics) - : OpRewritePattern(context), symCache(symCache), - statistics(statistics) {} + : OpInterfaceRewritePattern(context), + symCache(symCache), statistics(statistics) {} - LogicalResult matchAndRewrite(StateOp op, + LogicalResult matchAndRewrite(ClockedOpInterface op, PatternRewriter &rewriter) const final; private: @@ -55,61 +56,123 @@ struct LatencyRetimingPattern : OpRewritePattern { } // namespace LogicalResult -LatencyRetimingPattern::matchAndRewrite(StateOp op, +LatencyRetimingPattern::matchAndRewrite(ClockedOpInterface op, PatternRewriter &rewriter) const { - unsigned minPrevLatency = UINT_MAX; - SetVector predecessors; + uint32_t minPrevLatency = UINT_MAX; + SetVector predecessors; + Value clock; + + auto hasEnableOrReset = [](Operation *op) -> bool { + if (auto stateOp = dyn_cast(op)) + if (stateOp.getReset() || stateOp.getEnable()) + return true; + return false; + }; + + // Restrict this pattern to call and state ops only. In the future we could + // also add support for memory write operations. + if (!isa(op.getOperation())) + return failure(); - if (op.getReset() || op.getEnable()) + // In principle we could support enables and resets but would have to check + // that all involved states have the same. + if (hasEnableOrReset(op)) return failure(); - for (auto input : op.getInputs()) { - auto predState = input.getDefiningOp(); - if (!predState) + assert(isa(op.getOperation()) && + "state and call operations call arcs and thus have to implement the " + "CallOpInterface"); + auto callOp = cast(op.getOperation()); + + for (auto input : callOp.getArgOperands()) { + auto predOp = input.getDefiningOp(); + + // Only support call and state ops for the predecessors as well. + if (!predOp || !isa(predOp.getOperation())) return failure(); - if (predState->hasAttr("name") || predState->hasAttr("names")) + // Conditions for both StateOp and CallOp + if (predOp->hasAttr("name") || predOp->hasAttr("names")) return failure(); - if (predState == op) + // Check for a use-def cycle since we can be in a graph region. + if (predOp == op) return failure(); - if (predState.getLatency() != 0 && op.getLatency() != 0 && - predState.getClock() != op.getClock()) + if (predOp.getClock() && op.getClock() && + predOp.getClock() != op.getClock()) return failure(); - if (predState.getEnable() || predState.getReset()) + if (predOp->getParentRegion() != op->getParentRegion()) return failure(); - if (llvm::any_of(predState->getUsers(), + if (hasEnableOrReset(predOp)) + return failure(); + + // Check that the predecessor state does not have another user since then + // we cannot change its latency attribute without also changing it for the + // other users. This is not supported yet and thus we just fail. + if (llvm::any_of(predOp->getUsers(), [&](auto *user) { return user != op; })) return failure(); - predecessors.insert(predState); - minPrevLatency = std::min(minPrevLatency, predState.getLatency()); + // We check that all clocks are the same if present. Here we remember that + // clock. If none of the involved operations have a clock, they must have + // latency 0 and thus `minPrevLatency = 0` leading to early failure below. + if (!clock) { + if (predOp.getClock()) + clock = predOp.getClock(); + if (auto clockDomain = predOp->getParentOfType()) + clock = clockDomain.getClock(); + } + + predecessors.insert(predOp); + minPrevLatency = std::min(minPrevLatency, predOp.getLatency()); } if (minPrevLatency == 0 || minPrevLatency == UINT_MAX) return failure(); - op.setLatency(op.getLatency() + minPrevLatency); - for (auto prevStateOp : predecessors) { - if (!op.getClock() && !op->getParentOfType()) - op.getClockMutable().assign(prevStateOp.getClock()); - - statistics.latencyUnitsSaved += minPrevLatency; - auto newLatency = prevStateOp.getLatency() - minPrevLatency; - prevStateOp.setLatency(newLatency); + auto setLatency = [&](Operation *op, uint64_t newLatency, Value clock) { + bool validOp = isa(op); + assert(validOp && "must be a state or call op"); + bool isInClockDomain = op->getParentOfType(); + + if (auto stateOp = dyn_cast(op)) { + if (newLatency == 0) { + if (cast(symCache.getDefinition(stateOp.getArcAttr())) + .isPassthrough()) { + rewriter.replaceOp(stateOp, stateOp.getInputs()); + ++statistics.numOpsRemoved; + return; + } + rewriter.setInsertionPoint(op); + rewriter.replaceOpWithNewOp(op, stateOp.getOutputs().getTypes(), + stateOp.getArcAttr(), + stateOp.getInputs()); + return; + } + + rewriter.modifyOpInPlace(op, [&]() { + stateOp.setLatency(newLatency); + if (!stateOp.getClock() && !isInClockDomain) + stateOp.getClockMutable().assign(clock); + }); + return; + } - if (newLatency > 0) - continue; + if (auto callOp = dyn_cast(op); callOp && newLatency > 0) + rewriter.replaceOpWithNewOp( + op, callOp.getArcAttr(), callOp->getResultTypes(), + isInClockDomain ? Value{} : clock, Value{}, newLatency, + callOp.getInputs()); + }; - prevStateOp.getClockMutable().clear(); - if (cast(symCache.getDefinition(prevStateOp.getArcAttr())) - .isPassthrough()) { - rewriter.replaceOp(prevStateOp, prevStateOp.getInputs()); - ++statistics.numOpsRemoved; - } + setLatency(op, op.getLatency() + minPrevLatency, clock); + for (auto prevOp : predecessors) { + statistics.latencyUnitsSaved += minPrevLatency; + auto newLatency = prevOp.getLatency() - minPrevLatency; + setLatency(prevOp, newLatency, {}); } statistics.latencyUnitsSaved -= minPrevLatency; diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index 298160c6086d..87a96863d350 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -136,15 +136,10 @@ static bool shouldMaterialize(Operation *op) { // Don't materialize arc uses with latency >0, since we handle these in a // second pass once all other operations have been moved to their respective // clock trees. - if (auto stateOp = dyn_cast(op); stateOp && stateOp.getLatency() > 0) - return false; - - if (isa(op)) - return false; - - return true; + return !isa(op); } static bool shouldMaterialize(Value value) { @@ -394,12 +389,9 @@ LogicalResult ModuleLowering::lowerPrimaryOutputs() { LogicalResult ModuleLowering::lowerStates() { SmallVector opsToLower; - for (auto &op : *moduleOp.getBodyBlock()) { - auto stateOp = dyn_cast(&op); - if ((stateOp && stateOp.getLatency() > 0) || - isa(&op)) + for (auto &op : *moduleOp.getBodyBlock()) + if (isa(&op)) opsToLower.push_back(&op); - } for (auto *op : opsToLower) { LLVM_DEBUG(llvm::dbgs() << "- Lowering " << *op << "\n"); @@ -414,10 +406,6 @@ LogicalResult ModuleLowering::lowerStates() { } LogicalResult ModuleLowering::lowerState(StateOp stateOp) { - // Latency zero arcs incur no state and remain in the IR unmodified. - if (stateOp.getLatency() == 0) - return success(); - // We don't support arcs beyond latency 1 yet. These should be easy to add in // the future though. if (stateOp.getLatency() > 1) @@ -430,7 +418,6 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { auto stateEnable = stateOp.getEnable(); auto stateReset = stateOp.getReset(); auto stateInputs = SmallVector(stateOp.getInputs()); - stateOp->dropAllReferences(); // Get the clock tree and enable condition for this state's clock. If this arc // carries an explicit enable condition, fold that into the enable provided by @@ -486,9 +473,11 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { nonResetBuilder = ifOp.getElseBodyBuilder(); } - auto newStateOp = nonResetBuilder.create( - stateOp.getLoc(), stateOp.getArcAttr(), stateOp.getResultTypes(), Value{}, - Value{}, 0, materializedOperands); + stateOp->dropAllReferences(); + + auto newStateOp = nonResetBuilder.create( + stateOp.getLoc(), stateOp.getResultTypes(), stateOp.getArcAttr(), + materializedOperands); // Create the write ops that write the result of the transfer function to the // allocated state storage. @@ -664,8 +653,6 @@ LogicalResult ModuleLowering::cleanup() { return true; if (!op->use_empty()) return false; - if (auto stateOp = dyn_cast(op)) - return stateOp.getLatency() == 0; return false; }; for (auto &op : *moduleOp.getBodyBlock()) diff --git a/lib/Dialect/Arc/Transforms/PrintStateInfo.cpp b/lib/Dialect/Arc/Transforms/PrintStateInfo.cpp deleted file mode 100644 index 40651cf67d32..000000000000 --- a/lib/Dialect/Arc/Transforms/PrintStateInfo.cpp +++ /dev/null @@ -1,204 +0,0 @@ -//===- PrintStateInfo.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/Dialect/Arc/ArcOps.h" -#include "circt/Dialect/Arc/ArcPasses.h" -#include "mlir/Pass/Pass.h" -#include "llvm/Support/FileSystem.h" -#include "llvm/Support/JSON.h" -#include "llvm/Support/ToolOutputFile.h" - -#define DEBUG_TYPE "arc-print-state-info" - -namespace circt { -namespace arc { -#define GEN_PASS_DEF_PRINTSTATEINFO -#include "circt/Dialect/Arc/ArcPasses.h.inc" -} // namespace arc -} // namespace circt - -using namespace mlir; -using namespace circt; -using namespace arc; -using namespace hw; - -//===----------------------------------------------------------------------===// -// Pass Implementation -//===----------------------------------------------------------------------===// - -namespace { -struct StateInfo { - enum Type { Input, Output, Register, Memory, Wire } type; - StringAttr name; - unsigned offset; - unsigned numBits; - unsigned memoryStride = 0; // byte separation between memory words - unsigned memoryDepth = 0; // number of words in a memory -}; - -struct ModelInfo { - size_t numStateBytes; - std::vector states; -}; - -struct PrintStateInfoPass - : public arc::impl::PrintStateInfoBase { - void runOnOperation() override; - LogicalResult runOnOperation(llvm::raw_ostream &outputStream); - LogicalResult collectStates(Value storage, unsigned offset, - std::vector &stateInfos); - - using arc::impl::PrintStateInfoBase::stateFile; -}; -} // namespace - -void PrintStateInfoPass::runOnOperation() { - // Print to the output file if one was given, or stdout otherwise. - if (stateFile.empty()) { - auto result = runOnOperation(llvm::outs()); - llvm::outs() << "\n"; - if (failed(result)) - return signalPassFailure(); - } else { - std::error_code ec; - llvm::ToolOutputFile outputFile(stateFile, ec, - llvm::sys::fs::OpenFlags::OF_None); - if (ec) { - mlir::emitError(getOperation().getLoc(), "unable to open state file: ") - << ec.message(); - return signalPassFailure(); - } - if (failed(runOnOperation(outputFile.os()))) - return signalPassFailure(); - outputFile.keep(); - } -} - -LogicalResult -PrintStateInfoPass::runOnOperation(llvm::raw_ostream &outputStream) { - llvm::json::OStream json(outputStream, 2); - bool anyFailed = false; - json.array([&] { - std::vector states; - for (auto modelOp : getOperation().getOps()) { - auto storageArg = modelOp.getBody().getArgument(0); - auto storageType = storageArg.getType().cast(); - states.clear(); - if (failed(collectStates(storageArg, 0, states))) { - anyFailed = true; - return; - } - llvm::sort(states, [](auto &a, auto &b) { return a.offset < b.offset; }); - - json.object([&] { - json.attribute("name", modelOp.getName()); - json.attribute("numStateBytes", storageType.getSize()); - json.attributeArray("states", [&] { - for (const auto &state : states) { - json.object([&] { - json.attribute("name", state.name.getValue()); - json.attribute("offset", state.offset); - json.attribute("numBits", state.numBits); - auto typeStr = [](StateInfo::Type type) { - switch (type) { - case StateInfo::Input: - return "input"; - case StateInfo::Output: - return "output"; - case StateInfo::Register: - return "register"; - case StateInfo::Memory: - return "memory"; - case StateInfo::Wire: - return "wire"; - } - return ""; - }; - json.attribute("type", typeStr(state.type)); - if (state.type == StateInfo::Memory) { - json.attribute("stride", state.memoryStride); - json.attribute("depth", state.memoryDepth); - } - }); - } - }); - }); - } - }); - return failure(anyFailed); -} - -LogicalResult -PrintStateInfoPass::collectStates(Value storage, unsigned offset, - std::vector &stateInfos) { - for (auto *op : storage.getUsers()) { - if (auto substorage = dyn_cast(op)) { - if (!substorage.getOffset().has_value()) { - substorage.emitOpError( - "without allocated offset; run state allocation first"); - return failure(); - } - if (failed(collectStates(substorage.getOutput(), - *substorage.getOffset() + offset, stateInfos))) - return failure(); - continue; - } - if (!isa(op)) - continue; - auto opName = op->getAttrOfType("name"); - if (!opName || opName.getValue().empty()) - continue; - auto opOffset = op->getAttrOfType("offset"); - if (!opOffset) { - op->emitOpError("without allocated offset; run state allocation first"); - return failure(); - } - if (isa(op)) { - auto result = op->getResult(0); - auto &stateInfo = stateInfos.emplace_back(); - stateInfo.type = StateInfo::Register; - if (isa(op)) - stateInfo.type = StateInfo::Input; - else if (isa(op)) - stateInfo.type = StateInfo::Output; - else if (auto alloc = dyn_cast(op)) { - if (alloc.getTap()) - stateInfo.type = StateInfo::Wire; - } - stateInfo.name = opName; - stateInfo.offset = opOffset.getValue().getZExtValue() + offset; - stateInfo.numBits = result.getType().cast().getBitWidth(); - continue; - } - if (auto memOp = dyn_cast(op)) { - auto stride = op->getAttrOfType("stride"); - if (!stride) { - op->emitOpError("without allocated stride; run state allocation first"); - return failure(); - } - auto memType = memOp.getType(); - auto intType = memType.getWordType(); - auto &stateInfo = stateInfos.emplace_back(); - stateInfo.type = StateInfo::Memory; - stateInfo.name = opName; - stateInfo.offset = opOffset.getValue().getZExtValue() + offset; - stateInfo.numBits = intType.getWidth(); - stateInfo.memoryStride = stride.getValue().getZExtValue(); - stateInfo.memoryDepth = memType.getNumWords(); - continue; - } - } - return success(); -} - -std::unique_ptr arc::createPrintStateInfoPass(StringRef stateFile) { - auto pass = std::make_unique(); - if (!stateFile.empty()) - pass->stateFile.assign(stateFile); - return pass; -} diff --git a/lib/Dialect/Arc/Transforms/SplitLoops.cpp b/lib/Dialect/Arc/Transforms/SplitLoops.cpp index 51394f89a2a3..201f4c43e81b 100644 --- a/lib/Dialect/Arc/Transforms/SplitLoops.cpp +++ b/lib/Dialect/Arc/Transforms/SplitLoops.cpp @@ -27,6 +27,7 @@ namespace arc { using namespace circt; using namespace arc; using namespace hw; +using mlir::CallOpInterface; using llvm::SmallSetVector; @@ -184,12 +185,12 @@ namespace { struct SplitLoopsPass : public arc::impl::SplitLoopsBase { void runOnOperation() override; void splitArc(Namespace &arcNamespace, DefineOp defOp, - ArrayRef arcUses); - void replaceArcUse(StateOp arcUse, ArrayRef splitDefs, + ArrayRef arcUses); + void replaceArcUse(CallOpInterface arcUse, ArrayRef splitDefs, ArrayRef splits, ArrayRef outputs); LogicalResult ensureNoLoops(); - DenseSet allArcUses; + DenseSet allArcUses; }; } // namespace @@ -207,18 +208,29 @@ void SplitLoopsPass::runOnOperation() { // Collect all arc uses and determine which arcs we should split. SetVector arcsToSplit; - DenseMap> arcUses; - SetVector allArcUses; - - module.walk([&](StateOp stateOp) { - auto sym = stateOp.getArcAttr().getAttr(); - auto defOp = arcDefs.lookup(sym); - arcUses[defOp].push_back(stateOp); - allArcUses.insert(stateOp); - if (stateOp.getLatency() == 0 && stateOp.getNumResults() > 1) + DenseMap> arcUses; + SetVector allArcUses; + + auto result = module.walk([&](CallOpInterface callOp) -> WalkResult { + auto refSym = callOp.getCallableForCallee().dyn_cast(); + if (!refSym) + return callOp->emitOpError("found unsupported call to an arc"); + + auto defOp = arcDefs.lookup(refSym.getLeafReference()); + arcUses[defOp].push_back(callOp); + allArcUses.insert(callOp); + + auto clockedOp = dyn_cast(callOp.getOperation()); + if ((!clockedOp || clockedOp.getLatency() == 0) && + callOp->getNumResults() > 1) arcsToSplit.insert(defOp); + + return WalkResult::advance(); }); + if (result.wasInterrupted()) + return signalPassFailure(); + // Split all arcs with more than one result. // TODO: This is ugly and we should only split arcs that are truly involved in // a loop. But detecting the minimal split among the arcs is fairly @@ -233,7 +245,7 @@ void SplitLoopsPass::runOnOperation() { /// Split a single arc into a separate arc for each result. void SplitLoopsPass::splitArc(Namespace &arcNamespace, DefineOp defOp, - ArrayRef arcUses) { + ArrayRef arcUses) { LLVM_DEBUG(llvm::dbgs() << "Splitting arc " << defOp.getSymNameAttr() << "\n"); @@ -289,17 +301,18 @@ void SplitLoopsPass::splitArc(Namespace &arcNamespace, DefineOp defOp, } /// Replace a use of the original arc with new uses for the splits. -void SplitLoopsPass::replaceArcUse(StateOp arcUse, ArrayRef splitDefs, +void SplitLoopsPass::replaceArcUse(CallOpInterface arcUse, + ArrayRef splitDefs, ArrayRef splits, ArrayRef outputs) { ImplicitLocOpBuilder builder(arcUse.getLoc(), arcUse); - SmallVector newUses(splits.size()); + SmallVector newUses(splits.size()); // Resolve an `ImportedValue` to either an operand of the original arc or the // result of another split. auto getMappedValue = [&](ImportedValue value) { if (value.isInput) - return arcUse.getInputs()[value.index]; + return arcUse.getArgOperands()[value.index]; return newUses[value.split].getResult(value.index); }; @@ -345,8 +358,7 @@ void SplitLoopsPass::replaceArcUse(StateOp arcUse, ArrayRef splitDefs, if (!getMappedValuesOrSchedule(split->importedValues, operands)) continue; - auto newUse = - builder.create(splitDef, Value{}, Value{}, 0, operands); + auto newUse = builder.create(splitDef, operands); allArcUses.insert(newUse); newUses[split->index] = newUse; @@ -355,7 +367,7 @@ void SplitLoopsPass::replaceArcUse(StateOp arcUse, ArrayRef splitDefs, } // Update the users of the original arc results. - for (auto [result, importedValue] : llvm::zip(arcUse.getResults(), outputs)) + for (auto [result, importedValue] : llvm::zip(arcUse->getResults(), outputs)) result.replaceAllUsesWith(getMappedValue(importedValue)); allArcUses.erase(arcUse); arcUse.erase(); @@ -384,8 +396,8 @@ LogicalResult SplitLoopsPass::ensureNoLoops() { auto *def = operand.getDefiningOp(); if (!def || finished.contains(def)) continue; - if (auto stateOp = dyn_cast(def); - stateOp && stateOp.getLatency() > 0) + if (auto clockedOp = dyn_cast(def); + clockedOp && clockedOp.getLatency() > 0) continue; if (!seen.insert(def).second) { auto d = def->emitError( diff --git a/lib/Dialect/Arc/Transforms/StripSV.cpp b/lib/Dialect/Arc/Transforms/StripSV.cpp index 463489126fb9..0d0f283a1a46 100644 --- a/lib/Dialect/Arc/Transforms/StripSV.cpp +++ b/lib/Dialect/Arc/Transforms/StripSV.cpp @@ -83,7 +83,7 @@ void StripSVPass::runOnOperation() { // Remove `sv.*` operation attributes. mlirModule.walk([](Operation *op) { auto isSVAttr = [](NamedAttribute attr) { - return attr.getName().getValue().startswith("sv."); + return attr.getName().getValue().starts_with("sv."); }; if (llvm::any_of(op->getAttrs(), isSVAttr)) { SmallVector newAttrs; diff --git a/lib/Dialect/CMakeLists.txt b/lib/Dialect/CMakeLists.txt index 885d3ad7fb33..09c8a71a7d52 100644 --- a/lib/Dialect/CMakeLists.txt +++ b/lib/Dialect/CMakeLists.txt @@ -14,6 +14,7 @@ add_subdirectory(Calyx) add_subdirectory(Comb) add_subdirectory(DC) add_subdirectory(Debug) +add_subdirectory(Emit) add_subdirectory(ESI) add_subdirectory(FIRRTL) add_subdirectory(FSM) @@ -30,6 +31,7 @@ add_subdirectory(MSFT) add_subdirectory(OM) add_subdirectory(Pipeline) add_subdirectory(Seq) +add_subdirectory(Sim) add_subdirectory(SSP) add_subdirectory(SV) add_subdirectory(SystemC) diff --git a/lib/Dialect/Calyx/CalyxOps.cpp b/lib/Dialect/Calyx/CalyxOps.cpp index 3cea8cdea0bf..9f45efcb113c 100644 --- a/lib/Dialect/Calyx/CalyxOps.cpp +++ b/lib/Dialect/Calyx/CalyxOps.cpp @@ -472,7 +472,7 @@ parseComponentSignature(OpAsmParser &parser, OperationState &result, SmallVector portNames; auto getPortName = [context](const auto &port) -> StringAttr { StringRef name = port.ssaName.name; - if (name.startswith("%")) + if (name.starts_with("%")) name = name.drop_front(); return StringAttr::get(context, name); }; @@ -643,19 +643,19 @@ ControlOp calyx::ComponentOp::getControlOp() { } Value calyx::ComponentOp::getGoPort() { - return getBlockArgumentWithName("go", *this); + return getBlockArgumentWithName(goPort, *this); } Value calyx::ComponentOp::getDonePort() { - return getBlockArgumentWithName("done", *this); + return getBlockArgumentWithName(donePort, *this); } Value calyx::ComponentOp::getClkPort() { - return getBlockArgumentWithName("clk", *this); + return getBlockArgumentWithName(clkPort, *this); } Value calyx::ComponentOp::getResetPort() { - return getBlockArgumentWithName("reset", *this); + return getBlockArgumentWithName(resetPort, *this); } SmallVector ComponentOp::getPortInfo() { @@ -711,7 +711,7 @@ static LogicalResult hasRequiredPorts(ComponentOp op) { std::sort(identifiers.begin(), identifiers.end()); llvm::SmallVector intersection, - interfacePorts{"clk", "done", "go", "reset"}; + interfacePorts{clkPort, donePort, goPort, resetPort}; // Find the intersection between all identifiers and required ports. std::set_intersection(interfacePorts.begin(), interfacePorts.end(), identifiers.begin(), identifiers.end(), @@ -1955,7 +1955,7 @@ void RegisterOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { } SmallVector RegisterOp::portNames() { - return {"in", "write_en", "clk", "reset", "out", "done"}; + return {"in", "write_en", clkPort, resetPort, "out", donePort}; } SmallVector RegisterOp::portDirections() { @@ -1966,10 +1966,10 @@ SmallVector RegisterOp::portAttributes() { MLIRContext *context = getContext(); IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1); NamedAttrList writeEn, clk, reset, done; - writeEn.append("go", isSet); - clk.append("clk", isSet); - reset.append("reset", isSet); - done.append("done", isSet); + writeEn.append(goPort, isSet); + clk.append(clkPort, isSet); + reset.append(resetPort, isSet); + done.append(donePort, isSet); return { DictionaryAttr::get(context), // In writeEn.getDictionary(context), // Write enable @@ -1998,7 +1998,7 @@ SmallVector MemoryOp::portNames() { StringAttr::get(this->getContext(), "addr" + std::to_string(i)); portNames.push_back(nameAttr.getValue()); } - portNames.append({"write_data", "write_en", "clk", "read_data", "done"}); + portNames.append({"write_data", "write_en", clkPort, "read_data", donePort}); return portNames; } @@ -2019,9 +2019,9 @@ SmallVector MemoryOp::portAttributes() { // Use a boolean to indicate this attribute is used. IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1); NamedAttrList writeEn, clk, reset, done; - writeEn.append("go", isSet); - clk.append("clk", isSet); - done.append("done", isSet); + writeEn.append(goPort, isSet); + clk.append(clkPort, isSet); + done.append(donePort, isSet); portAttributes.append({DictionaryAttr::get(context), // In writeEn.getDictionary(context), // Write enable clk.getDictionary(context), // Clk @@ -2092,8 +2092,8 @@ SmallVector SeqMemoryOp::portNames() { StringAttr::get(this->getContext(), "addr" + std::to_string(i)); portNames.push_back(nameAttr.getValue()); } - portNames.append({"write_data", "write_en", "write_done", "clk", "read_data", - "read_en", "read_done"}); + portNames.append({"write_data", "write_en", "write_done", clkPort, + "read_data", "read_en", "read_done"}); return portNames; } @@ -2116,11 +2116,11 @@ SmallVector SeqMemoryOp::portAttributes() { IntegerAttr isSet = IntegerAttr::get(builder.getIndexType(), 1); IntegerAttr isTwo = IntegerAttr::get(builder.getIndexType(), 2); NamedAttrList writeEn, writeDone, clk, reset, readEn, readDone; - writeEn.append("go", isSet); - writeDone.append("done", isSet); - clk.append("clk", isSet); - readEn.append("go", isTwo); - readDone.append("done", isTwo); + writeEn.append(goPort, isSet); + writeDone.append(donePort, isSet); + clk.append(clkPort, isSet); + readEn.append(goPort, isTwo); + readDone.append(donePort, isTwo); portAttributes.append({DictionaryAttr::get(context), // Write Data writeEn.getDictionary(context), // Write enable writeDone.getDictionary(context), // Write done @@ -2668,7 +2668,7 @@ Value InvokeOp::getInstGoValue() { auto portInfo = op.getReferencedComponent().getPortInfo(); for (auto [portInfo, res] : llvm::zip(portInfo, operation->getResults())) { - if (portInfo.hasAttribute("go")) + if (portInfo.hasAttribute(goPort)) ret = res; } }) @@ -2703,7 +2703,7 @@ Value InvokeOp::getInstDoneValue() { auto portInfo = instanceOp.getReferencedComponent().getPortInfo(); for (auto [portInfo, res] : llvm::zip(portInfo, operation->getResults())) { - if (portInfo.hasAttribute("done")) + if (portInfo.hasAttribute(donePort)) ret = res; } }) @@ -2763,9 +2763,9 @@ LogicalResult InvokeOp::verify() { .Case([&](auto op) { auto portInfo = op.getReferencedComponent().getPortInfo(); for (PortInfo info : portInfo) { - if (info.hasAttribute("go")) + if (info.hasAttribute(goPort)) ++goPortNum; - if (info.hasAttribute("done")) + if (info.hasAttribute(donePort)) ++donePortNum; } }) @@ -2852,7 +2852,7 @@ LogicalResult SliceLibOp::verify() { #define ImplBinPipeOpCellInterface(OpType, outName) \ SmallVector OpType::portNames() { \ - return {"clk", "reset", "go", "left", "right", outName, "done"}; \ + return {clkPort, resetPort, goPort, "left", "right", outName, donePort}; \ } \ \ SmallVector OpType::portDirections() { \ @@ -2867,10 +2867,10 @@ LogicalResult SliceLibOp::verify() { MLIRContext *context = getContext(); \ IntegerAttr isSet = IntegerAttr::get(IntegerType::get(context, 1), 1); \ NamedAttrList go, clk, reset, done; \ - go.append("go", isSet); \ - clk.append("clk", isSet); \ - reset.append("reset", isSet); \ - done.append("done", isSet); \ + go.append(goPort, isSet); \ + clk.append(clkPort, isSet); \ + reset.append(resetPort, isSet); \ + done.append(donePort, isSet); \ return { \ clk.getDictionary(context), /* Clk */ \ reset.getDictionary(context), /* Reset */ \ diff --git a/lib/Dialect/Calyx/Transforms/CalyxHelpers.cpp b/lib/Dialect/Calyx/Transforms/CalyxHelpers.cpp index 85b45f38de9b..1bd5b593958f 100644 --- a/lib/Dialect/Calyx/Transforms/CalyxHelpers.cpp +++ b/lib/Dialect/Calyx/Transforms/CalyxHelpers.cpp @@ -63,28 +63,28 @@ void addMandatoryComponentPorts(PatternRewriter &rewriter, SmallVectorImpl &ports) { MLIRContext *ctx = rewriter.getContext(); ports.push_back({ - rewriter.getStringAttr("clk"), + rewriter.getStringAttr(clkPort), rewriter.getI1Type(), calyx::Direction::Input, - getMandatoryPortAttr(ctx, "clk"), + getMandatoryPortAttr(ctx, clkPort), }); ports.push_back({ - rewriter.getStringAttr("reset"), + rewriter.getStringAttr(resetPort), rewriter.getI1Type(), calyx::Direction::Input, - getMandatoryPortAttr(ctx, "reset"), + getMandatoryPortAttr(ctx, resetPort), }); ports.push_back({ - rewriter.getStringAttr("go"), + rewriter.getStringAttr(goPort), rewriter.getI1Type(), calyx::Direction::Input, - getMandatoryPortAttr(ctx, "go"), + getMandatoryPortAttr(ctx, goPort), }); ports.push_back({ - rewriter.getStringAttr("done"), + rewriter.getStringAttr(donePort), rewriter.getI1Type(), calyx::Direction::Output, - getMandatoryPortAttr(ctx, "done"), + getMandatoryPortAttr(ctx, donePort), }); } diff --git a/lib/Dialect/Calyx/Transforms/CalyxLoweringUtils.cpp b/lib/Dialect/Calyx/Transforms/CalyxLoweringUtils.cpp index a63091fcfcb0..657aa4cf34dc 100644 --- a/lib/Dialect/Calyx/Transforms/CalyxLoweringUtils.cpp +++ b/lib/Dialect/Calyx/Transforms/CalyxLoweringUtils.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "circt/Dialect/Calyx/CalyxLoweringUtils.h" +#include "circt/Conversion/SCFToCalyx.h" #include "circt/Dialect/Calyx/CalyxHelpers.h" #include "circt/Dialect/Calyx/CalyxOps.h" #include "circt/Support/LLVM.h" @@ -791,9 +792,9 @@ BuildCallInstance::partiallyLowerFuncToComp(mlir::func::FuncOp funcOp, auto portInfos = instanceOp.getReferencedComponent().getPortInfo(); auto results = instanceOp.getResults(); for (const auto &[portInfo, result] : llvm::zip(portInfos, results)) { - if (portInfo.hasAttribute("go") || portInfo.hasAttribute("reset")) + if (portInfo.hasAttribute(goPort) || portInfo.hasAttribute(resetPort)) rewriter.create(callOp.getLoc(), result, constantOp); - else if (portInfo.hasAttribute("done")) + else if (portInfo.hasAttribute(donePort)) rewriter.create(callOp.getLoc(), result); } } diff --git a/lib/Dialect/Comb/CombFolds.cpp b/lib/Dialect/Comb/CombFolds.cpp index 0dd0e41fde19..0adcf1916478 100644 --- a/lib/Dialect/Comb/CombFolds.cpp +++ b/lib/Dialect/Comb/CombFolds.cpp @@ -75,8 +75,8 @@ static void replaceOpAndCopyName(PatternRewriter &rewriter, Operation *op, if (auto *newOp = newValue.getDefiningOp()) { auto name = op->getAttrOfType("sv.namehint"); if (name && !newOp->hasAttr("sv.namehint")) - rewriter.updateRootInPlace(newOp, - [&] { newOp->setAttr("sv.namehint", name); }); + rewriter.modifyOpInPlace(newOp, + [&] { newOp->setAttr("sv.namehint", name); }); } rewriter.replaceOp(op, newValue); } @@ -91,8 +91,8 @@ static OpTy replaceOpWithNewOpAndCopyName(PatternRewriter &rewriter, auto newOp = rewriter.replaceOpWithNewOp(op, std::forward(args)...); if (name && !newOp->hasAttr("sv.namehint")) - rewriter.updateRootInPlace(newOp, - [&] { newOp->setAttr("sv.namehint", name); }); + rewriter.modifyOpInPlace(newOp, + [&] { newOp->setAttr("sv.namehint", name); }); return newOp; } @@ -121,25 +121,62 @@ static inline ComplementMatcher m_Complement(const SubType &subExpr) { return ComplementMatcher(subExpr); } +/// Return true if the op will be flattened afterwards. Op will be flattend if +/// it has a single user which has a same op type. +static bool shouldBeFlattened(Operation *op) { + assert((isa(op) && + "must be commutative operations")); + if (op->hasOneUse()) { + auto *user = *op->getUsers().begin(); + return user->getName() == op->getName() && + op->getAttrOfType("twoState") == + user->getAttrOfType("twoState"); + } + return false; +} + /// Flattens a single input in `op` if `hasOneUse` is true and it can be defined /// as an Op. Returns true if successful, and false otherwise. /// /// Example: op(1, 2, op(3, 4), 5) -> op(1, 2, 3, 4, 5) // returns true /// static bool tryFlatteningOperands(Operation *op, PatternRewriter &rewriter) { + // Skip if the operation should be flattened by another operation. + if (shouldBeFlattened(op)) + return false; + auto inputs = op->getOperands(); - for (size_t i = 0, size = inputs.size(); i != size; ++i) { - Operation *flattenOp = inputs[i].getDefiningOp(); - if (!flattenOp || flattenOp->getName() != op->getName()) + SmallVector newOperands; + SmallVector newLocations{op->getLoc()}; + newOperands.reserve(inputs.size()); + struct Element { + decltype(inputs.begin()) current, end; + }; + + SmallVector worklist; + worklist.push_back({inputs.begin(), inputs.end()}); + bool binFlag = op->hasAttrOfType("twoState"); + bool changed = false; + while (!worklist.empty()) { + auto &element = worklist.back(); // Do not pop. Take ref. + + // Pop when we finished traversing the current operand range. + if (element.current == element.end) { + worklist.pop_back(); continue; + } - // Check for loops - if (flattenOp == op) + Value value = *element.current++; + auto *flattenOp = value.getDefiningOp(); + if (!flattenOp || flattenOp->getName() != op->getName() || + flattenOp == op || binFlag != op->hasAttrOfType("twoState")) { + newOperands.push_back(value); continue; + } // Don't duplicate logic when it has multiple uses. - if (!inputs[i].hasOneUse()) { + if (!value.hasOneUse()) { // We can fold a multi-use binary operation into this one if this allows a // constant to fold though. For example, fold // (or a, b, c, (or d, cst1), cst2) --> (or a, b, c, d, cst1, cst2) @@ -149,34 +186,30 @@ static bool tryFlatteningOperands(Operation *op, PatternRewriter &rewriter) { // between the two ops if duplicated. if (flattenOp->getNumOperands() != 2 || !isa(op) || !flattenOp->getOperand(1).getDefiningOp() || - !inputs.back().getDefiningOp()) + !inputs.back().getDefiningOp()) { + newOperands.push_back(value); continue; + } } - // Otherwise, flatten away. - auto flattenOpInputs = flattenOp->getOperands(); - - SmallVector newOperands; - newOperands.reserve(size + flattenOpInputs.size()); + changed = true; - auto flattenOpIndex = inputs.begin() + i; - newOperands.append(inputs.begin(), flattenOpIndex); - newOperands.append(flattenOpInputs.begin(), flattenOpInputs.end()); - newOperands.append(flattenOpIndex + 1, inputs.end()); + // Otherwise, push operands into worklist. + auto flattenOpInputs = flattenOp->getOperands(); + worklist.push_back({flattenOpInputs.begin(), flattenOpInputs.end()}); + newLocations.push_back(flattenOp->getLoc()); + } - Value result = - createGenericOp(op->getLoc(), op->getName(), newOperands, rewriter); + if (!changed) + return false; - // If the original operation and flatten operand have bin flags, propagte - // the flag to new one. - if (op->hasAttrOfType("twoState") && - flattenOp->hasAttrOfType("twoState")) - result.getDefiningOp()->setAttr("twoState", rewriter.getUnitAttr()); + Value result = createGenericOp(FusedLoc::get(op->getContext(), newLocations), + op->getName(), newOperands, rewriter); + if (binFlag) + result.getDefiningOp()->setAttr("twoState", rewriter.getUnitAttr()); - replaceOpAndCopyName(rewriter, op, result); - return true; - } - return false; + replaceOpAndCopyName(rewriter, op, result); + return true; } // Given a range of uses of an operation, find the lowest and highest bits @@ -924,6 +957,10 @@ LogicalResult AndOp::canonicalize(AndOp op, PatternRewriter &rewriter) { auto size = inputs.size(); assert(size > 1 && "expected 2 or more operands, `fold` should handle this"); + // and(x, and(...)) -> and(x, ...) -- flatten + if (tryFlatteningOperands(op, rewriter)) + return success(); + // and(..., x, ..., x) -> and(..., x, ...) -- idempotent // Trivial and(x), and(x, x) cases are handled by [AndOp::fold] above. if (size > 2 && canonicalizeIdempotentInputs(op, rewriter)) @@ -1031,10 +1068,6 @@ LogicalResult AndOp::canonicalize(AndOp op, PatternRewriter &rewriter) { } } - // and(x, and(...)) -> and(x, ...) -- flatten - if (tryFlatteningOperands(op, rewriter)) - return success(); - // extracts only of and(...) -> and(extract()...) if (narrowOperationWidth(op, true, rewriter)) return success(); @@ -1210,6 +1243,10 @@ LogicalResult OrOp::canonicalize(OrOp op, PatternRewriter &rewriter) { auto size = inputs.size(); assert(size > 1 && "expected 2 or more operands"); + // or(x, or(...)) -> or(x, ...) -- flatten + if (tryFlatteningOperands(op, rewriter)) + return success(); + // or(..., x, ..., x, ...) -> or(..., x) -- idempotent // Trivial or(x), or(x, x) cases are handled by [OrOp::fold]. if (size > 2 && canonicalizeIdempotentInputs(op, rewriter)) @@ -1246,10 +1283,6 @@ LogicalResult OrOp::canonicalize(OrOp op, PatternRewriter &rewriter) { } } - // or(x, or(...)) -> or(x, ...) -- flatten - if (tryFlatteningOperands(op, rewriter)) - return success(); - // or(..., concat(x, cst1), concat(cst2, y) // ==> or(..., concat(x, cst3, y)), when x and y don't overlap. for (size_t i = 0; i < size - 1; ++i) { @@ -1583,7 +1616,7 @@ LogicalResult AddOp::canonicalize(AddOp op, PatternRewriter &rewriter) { return success(); } - // add(x, add(...)) -> add(x, ...) -- flatten + // add(a, add(...)) -> add(a, ...) -- flatten if (tryFlatteningOperands(op, rewriter)) return success(); @@ -1956,6 +1989,9 @@ OpFoldResult MuxOp::fold(FoldAdaptor adaptor) { // mux (c, b, b) -> b if (getTrueValue() == getFalseValue()) return getTrueValue(); + if (auto tv = adaptor.getTrueValue()) + if (tv == adaptor.getFalseValue()) + return tv; // mux(0, a, b) -> b // mux(1, a, b) -> a diff --git a/lib/Dialect/Comb/CombOps.cpp b/lib/Dialect/Comb/CombOps.cpp index 49d1219d23dd..67bb84e51940 100644 --- a/lib/Dialect/Comb/CombOps.cpp +++ b/lib/Dialect/Comb/CombOps.cpp @@ -237,7 +237,7 @@ bool XorOp::isBinaryNot() { static unsigned getTotalWidth(ValueRange inputs) { unsigned resultWidth = 0; for (auto input : inputs) { - resultWidth += input.getType().cast().getWidth(); + resultWidth += hw::type_cast(input.getType()).getWidth(); } return resultWidth; } diff --git a/lib/Dialect/Comb/Transforms/LowerComb.cpp b/lib/Dialect/Comb/Transforms/LowerComb.cpp index 5e9fd64cf19b..85a94629769b 100644 --- a/lib/Dialect/Comb/Transforms/LowerComb.cpp +++ b/lib/Dialect/Comb/Transforms/LowerComb.cpp @@ -56,7 +56,7 @@ struct TruthTableToMuxTree : public OpConversionPattern { Value f = b.create(loc, b.getIntegerAttr(b.getI1Type(), 0)); Value tree = getMux(loc, b, t, f, table, op.getInputs()); - b.updateRootInPlace(tree.getDefiningOp(), [&]() { + b.modifyOpInPlace(tree.getDefiningOp(), [&]() { tree.getDefiningOp()->setDialectAttrs(op->getDialectAttrs()); }); b.replaceOp(op, tree); diff --git a/lib/Dialect/ESI/AppID.cpp b/lib/Dialect/ESI/AppID.cpp index 123db9bee56d..51330a376746 100644 --- a/lib/Dialect/ESI/AppID.cpp +++ b/lib/Dialect/ESI/AppID.cpp @@ -119,8 +119,12 @@ LogicalResult AppIDIndex::walk( auto inst = dyn_cast(op); assert(inst && "Search bottomed out. Invalid appid index."); - auto tgtMod = dyn_cast( - symCache.getDefinition(inst.getReferencedModuleNameAttr())); + auto moduleNames = inst.getReferencedModuleNamesAttr(); + if (moduleNames.size() != 1) + return inst.emitError("expected an instance with a single reference"); + + auto tgtMod = + dyn_cast(symCache.getDefinition(moduleNames[0])); assert(tgtMod && "invalid module reference"); ModuleAppIDs *ffModIds = containerAppIDs.at(tgtMod); @@ -147,8 +151,12 @@ LogicalResult AppIDIndex::walk( // We must recurse on an instance. if (auto inst = dyn_cast(op)) { - auto tgtMod = dyn_cast( - symCache.getDefinition(inst.getReferencedModuleNameAttr())); + auto moduleNames = inst.getReferencedModuleNamesAttr(); + if (moduleNames.size() != 1) + return inst.emitError("expected an instance with a single reference"); + + auto tgtMod = + dyn_cast(symCache.getDefinition(moduleNames[0])); assert(tgtMod && "invalid module reference"); if (failed(walk(top, tgtMod, pathStack, opStack, fn))) @@ -198,11 +206,14 @@ FailureOr AppIDIndex::getAppIDPathAttr(hw::HWModuleLike fromMod, if (getAppID(*op)) break; - if (auto inst = dyn_cast(op->getOperation())) - fromMod = cast( - symCache.getDefinition(inst.getReferencedModuleNameAttr())); - else + if (auto inst = dyn_cast(op->getOperation())) { + auto moduleNames = inst.getReferencedModuleNamesAttr(); + if (moduleNames.size() != 1) + return inst.emitError("expected an instance with a single reference"); + fromMod = cast(symCache.getDefinition(moduleNames[0])); + } else { assert(false && "Search bottomed out"); + } } while (true); return ArrayAttr::get(fromMod.getContext(), path); } @@ -228,8 +239,13 @@ AppIDIndex::buildIndexFor(hw::HWModuleLike mod) { // If we encounter an instance op which isn't ID'd... if (auto inst = dyn_cast(op)) { - auto tgtMod = dyn_cast( - symCache.getDefinition(inst.getReferencedModuleNameAttr())); + auto moduleNames = inst.getReferencedModuleNamesAttr(); + if (moduleNames.size() != 1) { + inst.emitError("expected an instance with a single reference"); + return WalkResult::interrupt(); + } + auto tgtMod = + dyn_cast(symCache.getDefinition(moduleNames[0])); // Do the assert here to get a more precise message. assert(tgtMod && "invalid module reference"); diff --git a/lib/Dialect/ESI/CMakeLists.txt b/lib/Dialect/ESI/CMakeLists.txt index 9533448fd809..cbbfed6b6feb 100644 --- a/lib/Dialect/ESI/CMakeLists.txt +++ b/lib/Dialect/ESI/CMakeLists.txt @@ -105,6 +105,9 @@ set(ESI_RUNTIME_SRCS runtime/cosim/Cosim_Manifest.sv runtime/cosim/Cosim_Endpoint.sv runtime/cosim/CosimDpi.capnp + runtime/cosim/driver.cpp + runtime/cosim/driver.sv + runtime/cosim/esi-cosim.py runtime/python/esi/__init__.py runtime/python/esi/accelerator.py runtime/python/esi/esiCppAccel.cpp diff --git a/lib/Dialect/ESI/ESIAttributes.cpp b/lib/Dialect/ESI/ESIAttributes.cpp index 046286b48aae..48009fd3495b 100644 --- a/lib/Dialect/ESI/ESIAttributes.cpp +++ b/lib/Dialect/ESI/ESIAttributes.cpp @@ -36,7 +36,9 @@ Attribute BlobAttr::parse(AsmParser &odsParser, Type odsType) { }); return {}; } - return BlobAttr::get(odsParser.getBuilder().getContext(), data); + ArrayRef unsignedData(reinterpret_cast(data.data()), + data.size()); + return BlobAttr::get(odsParser.getBuilder().getContext(), unsignedData); } void BlobAttr::print(AsmPrinter &odsPrinter) const { odsPrinter << "<\"" << llvm::encodeBase64(getData()) << "\">"; diff --git a/lib/Dialect/ESI/ESIFolds.cpp b/lib/Dialect/ESI/ESIFolds.cpp index ff52c4d61c67..b7cd4ac95c90 100644 --- a/lib/Dialect/ESI/ESIFolds.cpp +++ b/lib/Dialect/ESI/ESIFolds.cpp @@ -19,7 +19,8 @@ LogicalResult WrapValidReadyOp::fold(FoldAdaptor, SmallVectorImpl &results) { if (!getChanOutput().getUsers().empty()) return failure(); - results.push_back(mlir::UnitAttr::get(getContext())); + results.push_back(NullChannelAttr::get( + getContext(), TypeAttr::get(getChanOutput().getType()))); results.push_back(IntegerAttr::get(IntegerType::get(getContext(), 1), 1)); return success(); } diff --git a/lib/Dialect/ESI/ESIOps.cpp b/lib/Dialect/ESI/ESIOps.cpp index d9cf7458e955..f3e42ea3df92 100644 --- a/lib/Dialect/ESI/ESIOps.cpp +++ b/lib/Dialect/ESI/ESIOps.cpp @@ -402,26 +402,14 @@ static LogicalResult checkTypeMatch(Operation *req, return success(); } -LogicalResult RequestToClientConnectionOp::verifySymbolUses( - SymbolTableCollection &symbolTable) { +LogicalResult +RequestConnectionOp::verifySymbolUses(SymbolTableCollection &symbolTable) { auto svcPort = getServicePortInfo(*this, symbolTable, getServicePortAttr()); if (failed(svcPort)) return failure(); - if (svcPort->direction != ServicePortInfo::Direction::toClient) - return emitOpError("Service port is not a to-client port"); return checkTypeMatch(*this, svcPort->type, getToClient().getType(), false); } -LogicalResult RequestToServerConnectionOp::verifySymbolUses( - SymbolTableCollection &symbolTable) { - auto svcPort = getServicePortInfo(*this, symbolTable, getServicePortAttr()); - if (failed(svcPort)) - return failure(); - if (svcPort->direction != ServicePortInfo::Direction::toServer) - return emitOpError("Service port is not a to-server port"); - return checkTypeMatch(*this, svcPort->type, getToServer().getType(), false); -} - LogicalResult ServiceImplementConnReqOp::verifySymbolUses( SymbolTableCollection &symbolTable) { auto svcPort = getServicePortInfo(*this, symbolTable, getServicePortAttr()); @@ -431,14 +419,10 @@ LogicalResult ServiceImplementConnReqOp::verifySymbolUses( } void CustomServiceDeclOp::getPortList(SmallVectorImpl &ports) { - for (auto toServer : getOps()) - ports.push_back(ServicePortInfo{ - hw::InnerRefAttr::get(getSymNameAttr(), toServer.getInnerSymAttr()), - ServicePortInfo::Direction::toServer, toServer.getToServerType()}); - for (auto toClient : getOps()) + for (auto toClient : getOps()) ports.push_back(ServicePortInfo{ hw::InnerRefAttr::get(getSymNameAttr(), toClient.getInnerSymAttr()), - ServicePortInfo::Direction::toClient, toClient.getToClientType()}); + toClient.getToClientType()}); } //===----------------------------------------------------------------------===// @@ -614,7 +598,7 @@ SmallVector ESIPureModuleOp::getAllPortLocs() { return retval; } -void ESIPureModuleOp::setAllPortLocs(ArrayRef locs) { +void ESIPureModuleOp::setAllPortLocsAttrs(ArrayRef locs) { emitError("No ports for port locations"); } @@ -630,10 +614,7 @@ void ESIPureModuleOp::removeAllPortAttrs() { emitError("No ports for port attributes)"); } -SmallVector ESIPureModuleOp::getAllPortAttrs() { - SmallVector retval; - return retval; -} +ArrayRef ESIPureModuleOp::getAllPortAttrs() { return {}; } void ESIPureModuleOp::setHWModuleType(hw::ModuleType type) { emitError("No ports for port types"); @@ -700,9 +681,6 @@ void ServiceRequestRecordOp::getDetails( SmallVectorImpl &results) { auto *ctxt = getContext(); results.emplace_back(StringAttr::get(ctxt, "appID"), getRequestorAttr()); - results.emplace_back( - getDirectionAttrName(), - StringAttr::get(ctxt, stringifyBundleDirection(getDirection()))); results.emplace_back(getBundleTypeAttrName(), getBundleTypeAttr()); results.emplace_back(getServicePortAttrName(), getServicePortAttr()); if (auto stdSvc = getStdServiceAttr()) diff --git a/lib/Dialect/ESI/ESIServices.cpp b/lib/Dialect/ESI/ESIServices.cpp index c1340b5b4e2e..b4b6f15d7722 100644 --- a/lib/Dialect/ESI/ESIServices.cpp +++ b/lib/Dialect/ESI/ESIServices.cpp @@ -321,13 +321,10 @@ struct ESIConnectServicesPass void runOnOperation() override; - /// Bundles allow us to convert to_server requests to to_client requests, - /// which is how the canonical implementation connection request is defined. - /// Convert both to_server and to_client requests into the canonical - /// implementation connection request. This simplifies the rest of the pass - /// and the service implementation code. - void convertReq(RequestToClientConnectionOp); - void convertReq(RequestToServerConnectionOp); + /// Convert connection requests to service implement connection requests, + /// which have a relative appid path instead of just an appid. Leave being a + /// record for the manifest of the original request. + void convertReq(RequestConnectionOp); /// "Bubble up" the specified requests to all of the instantiations of the /// module specified. Create and connect up ports to tunnel the ESI channels @@ -356,12 +353,7 @@ void ESIConnectServicesPass::runOnOperation() { ModuleOp outerMod = getOperation(); topLevelSyms.addDefinitions(outerMod); - outerMod.walk([&](Operation *op) { - if (auto req = dyn_cast(op)) - convertReq(req); - else if (auto req = dyn_cast(op)) - convertReq(req); - }); + outerMod.walk([&](RequestConnectionOp req) { convertReq(req); }); // Get a partially-ordered list of modules based on the instantiation DAG. // It's _very_ important that we process modules before their instantiations @@ -390,72 +382,20 @@ StringAttr ESIConnectServicesPass::getStdService(FlatSymbolRefAttr svcSym) { return {}; } -void ESIConnectServicesPass::convertReq( - RequestToClientConnectionOp toClientReq) { - OpBuilder b(toClientReq); - // to_client requests are already in the canonical form, just the wrong op. +void ESIConnectServicesPass::convertReq(RequestConnectionOp req) { + OpBuilder b(req); auto newReq = b.create( - toClientReq.getLoc(), toClientReq.getToClient().getType(), - toClientReq.getServicePortAttr(), - ArrayAttr::get(&getContext(), {toClientReq.getAppIDAttr()})); - newReq->setDialectAttrs(toClientReq->getDialectAttrs()); - toClientReq.getToClient().replaceAllUsesWith(newReq.getToClient()); - - // Emit a record of the original request. - b.create( - toClientReq.getLoc(), toClientReq.getAppID(), - toClientReq.getServicePortAttr(), - getStdService(toClientReq.getServicePortAttr().getModuleRef()), - BundleDirection::toClient, toClientReq.getToClient().getType()); - toClientReq.erase(); -} - -void ESIConnectServicesPass::convertReq( - RequestToServerConnectionOp toServerReq) { - OpBuilder b(toServerReq); - BackedgeBuilder beb(b, toServerReq.getLoc()); - - // to_server requests need to be converted to bundles with the opposite - // directions. - ChannelBundleType toClientType = - toServerReq.getToServer().getType().getReversed(); - - // Create the new canonical request. It's modeled in the to_client form. - auto toClientReq = b.create( - toServerReq.getLoc(), toClientType, toServerReq.getServicePortAttr(), - ArrayAttr::get(&getContext(), {toServerReq.getAppIDAttr()})); - toClientReq->setDialectAttrs(toServerReq->getDialectAttrs()); - - // Unpack the bundle coming from the new request. - SmallVector unpackToClientFromChannels; - SmallVector unpackToClientFromChannelsBackedges; - for (BundledChannel ch : toClientType.getChannels()) { - if (ch.direction == ChannelDirection::to) - continue; - unpackToClientFromChannelsBackedges.push_back(beb.get(ch.type)); - unpackToClientFromChannels.push_back( - unpackToClientFromChannelsBackedges.back()); - } - auto unpackToClient = - b.create(toServerReq.getLoc(), toClientReq.getToClient(), - unpackToClientFromChannels); - - // Convert the unpacked channels to the original request's bundle type with - // another unpack. - auto unpackToServer = - b.create(toServerReq.getLoc(), toServerReq.getToServer(), - unpackToClient.getToChannels()); - for (auto [v, be] : llvm::zip_equal(unpackToServer.getToChannels(), - unpackToClientFromChannelsBackedges)) - be.setValue(v); + req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(), + ArrayAttr::get(&getContext(), {req.getAppIDAttr()})); + newReq->setDialectAttrs(req->getDialectAttrs()); + req.getToClient().replaceAllUsesWith(newReq.getToClient()); // Emit a record of the original request. b.create( - toServerReq.getLoc(), toServerReq.getAppID(), - toServerReq.getServicePortAttr(), - getStdService(toServerReq.getServicePortAttr().getModuleRef()), - BundleDirection::toServer, toServerReq.getToServer().getType()); - toServerReq.erase(); + req.getLoc(), req.getAppID(), req.getServicePortAttr(), + getStdService(req.getServicePortAttr().getModuleRef()), + req.getToClient().getType()); + req.erase(); } LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) { diff --git a/lib/Dialect/ESI/ESIStdServices.cpp b/lib/Dialect/ESI/ESIStdServices.cpp index d6b05f17f632..d7de4ccfa964 100644 --- a/lib/Dialect/ESI/ESIStdServices.cpp +++ b/lib/Dialect/ESI/ESIStdServices.cpp @@ -29,13 +29,12 @@ static ServicePortInfo createReqResp(StringAttr sym, Twine name, auto *ctxt = reqType ? reqType.getContext() : respType.getContext(); auto bundle = ChannelBundleType::get( ctxt, - {BundledChannel{StringAttr::get(ctxt, reqName), ChannelDirection::to, + {BundledChannel{StringAttr::get(ctxt, reqName), ChannelDirection::from, ChannelType::get(ctxt, reqType)}, - BundledChannel{StringAttr::get(ctxt, respName), ChannelDirection::from, + BundledChannel{StringAttr::get(ctxt, respName), ChannelDirection::to, ChannelType::get(ctxt, respType)}}, /*resettable=false*/ UnitAttr()); - return {hw::InnerRefAttr::get(sym, StringAttr::get(ctxt, name)), - ServicePortInfo::Direction::toServer, bundle}; + return {hw::InnerRefAttr::get(sym, StringAttr::get(ctxt, name)), bundle}; } ServicePortInfo RandomAccessMemoryDeclOp::writePortInfo() { @@ -70,7 +69,6 @@ void FuncServiceDeclOp::getPortList(SmallVectorImpl &ports) { auto *ctxt = getContext(); ports.push_back(ServicePortInfo{ hw::InnerRefAttr::get(getSymNameAttr(), StringAttr::get(ctxt, "call")), - ServicePortInfo::Direction::toClient, ChannelBundleType::get( ctxt, {BundledChannel{StringAttr::get(ctxt, "arg"), ChannelDirection::to, @@ -80,3 +78,27 @@ void FuncServiceDeclOp::getPortList(SmallVectorImpl &ports) { ChannelType::get(ctxt, AnyType::get(ctxt))}}, /*resettable=*/UnitAttr())}); } + +void MMIOServiceDeclOp::getPortList(SmallVectorImpl &ports) { + auto *ctxt = getContext(); + // Read only port. + ports.push_back(ServicePortInfo{ + hw::InnerRefAttr::get(getSymNameAttr(), StringAttr::get(ctxt, "read")), + ChannelBundleType::get( + ctxt, + {BundledChannel{StringAttr::get(ctxt, "offset"), ChannelDirection::to, + ChannelType::get(ctxt, IntegerType::get(ctxt, 32))}, + BundledChannel{StringAttr::get(ctxt, "data"), ChannelDirection::from, + ChannelType::get(ctxt, IntegerType::get(ctxt, 32))}}, + /*resettable=*/UnitAttr())}); + // Write only port. + ports.push_back(ServicePortInfo{ + hw::InnerRefAttr::get(getSymNameAttr(), StringAttr::get(ctxt, "write")), + ChannelBundleType::get( + ctxt, + {BundledChannel{StringAttr::get(ctxt, "offset"), ChannelDirection::to, + ChannelType::get(ctxt, IntegerType::get(ctxt, 32))}, + BundledChannel{StringAttr::get(ctxt, "data"), ChannelDirection::to, + ChannelType::get(ctxt, IntegerType::get(ctxt, 32))}}, + /*resettable=*/UnitAttr())}); +} diff --git a/lib/Dialect/ESI/PassDetails.h b/lib/Dialect/ESI/PassDetails.h index 2e42eba05418..7552aecfc367 100644 --- a/lib/Dialect/ESI/PassDetails.h +++ b/lib/Dialect/ESI/PassDetails.h @@ -29,6 +29,7 @@ #include "mlir/IR/DialectRegistry.h" #include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" namespace circt { namespace esi { @@ -43,6 +44,20 @@ namespace circt { namespace esi { namespace detail { +/// Generic pattern for removing an op during pattern conversion. +template +struct RemoveOpLowering : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename OpConversionPattern::OpAdaptor; + + LogicalResult + matchAndRewrite(OpTy op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + rewriter.eraseOp(op); + return success(); + } +}; + StringAttr getTypeID(Type t); uint64_t getWidth(Type t); diff --git a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp index 190e5bb727d0..12be6f336fbc 100644 --- a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp +++ b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp @@ -115,11 +115,8 @@ void ESIBuildManifestPass::runOnOperation() { ->getRegion(0) .front() .getTerminator()); - b.create( - b.getUnknownLoc(), - BlobAttr::get(ctxt, ArrayRef(reinterpret_cast( - compressedManifest.data()), - compressedManifest.size()))); + b.create(b.getUnknownLoc(), + BlobAttr::get(ctxt, compressedManifest)); } else { mod->emitWarning() << "zlib not available but required for manifest support"; @@ -193,13 +190,15 @@ std::string ESIBuildManifestPass::json() { continue; j.object([&] { j.attribute("symbol", sym.getValue()); + std::optional typeName = svcDecl.getTypeName(); + if (typeName) + j.attribute("type_name", *typeName); llvm::SmallVector ports; svcDecl.getPortList(ports); j.attributeArray("ports", [&]() { for (auto port : ports) { j.object([&] { j.attribute("name", port.port.getName().getValue()); - j.attribute("direction", port.directionAsString()); j.attribute("type", json(svcDecl, TypeAttr::get(port.type))); }); } diff --git a/lib/Dialect/ESI/Passes/ESILowerPhysical.cpp b/lib/Dialect/ESI/Passes/ESILowerPhysical.cpp index fde782fd5417..e2d649215065 100644 --- a/lib/Dialect/ESI/Passes/ESILowerPhysical.cpp +++ b/lib/Dialect/ESI/Passes/ESILowerPhysical.cpp @@ -146,7 +146,7 @@ PureModuleLowering::matchAndRewrite(ESIPureModuleOp pureMod, OpAdaptor adaptor, // Re-wire the inputs and erase them. for (auto input : inputs) { BlockArgument newArg; - rewriter.updateRootInPlace(hwMod, [&]() { + rewriter.modifyOpInPlace(hwMod, [&]() { newArg = body->addArgument(input.getResult().getType(), input.getLoc()); }); rewriter.replaceAllUsesWith(input.getResult(), newArg); diff --git a/lib/Dialect/ESI/Passes/ESILowerPorts.cpp b/lib/Dialect/ESI/Passes/ESILowerPorts.cpp index ffae2e7deef4..c4e4a7bc6317 100644 --- a/lib/Dialect/ESI/Passes/ESILowerPorts.cpp +++ b/lib/Dialect/ESI/Passes/ESILowerPorts.cpp @@ -563,7 +563,7 @@ void ESIPortsPass::updateInstance(HWModuleExternOp mod, InstanceOp inst) { } // Create the new instance! - InstanceOp newInst = instBuilder.create( + auto newInst = instBuilder.create( mod, inst.getInstanceNameAttr(), newOperands, inst.getParameters(), inst.getInnerSymAttr()); diff --git a/lib/Dialect/ESI/Passes/ESILowerToHW.cpp b/lib/Dialect/ESI/Passes/ESILowerToHW.cpp index bb7a2f06b5ff..f48359c072d0 100644 --- a/lib/Dialect/ESI/Passes/ESILowerToHW.cpp +++ b/lib/Dialect/ESI/Passes/ESILowerToHW.cpp @@ -17,6 +17,7 @@ #include "circt/Dialect/ESI/ESIOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Seq/SeqOps.h" #include "circt/Support/BackedgeBuilder.h" #include "circt/Support/LLVM.h" #include "circt/Support/SymCache.h" @@ -85,8 +86,8 @@ LogicalResult PipelineStageLowering::matchAndRewrite( operands.push_back(unwrap.getRawOutput()); operands.push_back(unwrap.getValid()); operands.push_back(stageReady); - auto stageInst = rewriter.create(loc, stageModule, pipeStageName, - operands, stageParams); + auto stageInst = rewriter.create( + loc, stageModule, pipeStageName, operands, stageParams); auto stageInstResults = stageInst.getResults(); // Set a_ready (from the unwrap) back edge correctly to its output from @@ -152,6 +153,13 @@ struct RemoveWrapUnwrap : public ConversionPattern { WrapValidReadyOp wrap = dyn_cast(op); UnwrapValidReadyOp unwrap = dyn_cast(op); if (wrap) { + if (wrap.getChanOutput().getUsers().empty()) { + auto c1 = rewriter.create(wrap.getLoc(), + rewriter.getI1Type(), 1); + rewriter.replaceOp(wrap, {nullptr, c1}); + return success(); + } + if (!wrap.getChanOutput().hasOneUse() || !(unwrap = dyn_cast( wrap.getChanOutput().use_begin()->getOwner()))) @@ -361,7 +369,7 @@ LogicalResult CosimToHostLowering::matchAndRewrite( unwrapSend.getValid(), castedSendData, }; - auto cosimEpModule = rewriter.create( + auto cosimEpModule = rewriter.create( loc, endpoint, ep.getIdAttr(), operands, ArrayAttr::get(ctxt, params)); sendReady.setValue(cosimEpModule.getResult(0)); @@ -419,7 +427,7 @@ LogicalResult CosimFromHostLowering::matchAndRewrite( // Create replacement Cosim_Endpoint instance. Value operands[] = {adaptor.getClk(), adaptor.getRst(), recvReady}; - auto cosimEpModule = rewriter.create( + auto cosimEpModule = rewriter.create( loc, endpoint, ep.getIdAttr(), operands, ArrayAttr::get(ctxt, params)); // Set up the injest path. @@ -444,11 +452,115 @@ LogicalResult CosimFromHostLowering::matchAndRewrite( } namespace { -/// Lower `CompressedManifestOps` ops to a SystemVerilog extern module. -struct CosimManifestLowering - : public OpConversionPattern { +/// Lower `CompressedManifestOps` ops to a module containing an on-chip ROM. +/// Said module has registered input and outputs, so it has two cycles latency +/// between changing the address and the data being reflected on the output. +struct ManifestRomLowering : public OpConversionPattern { public: using OpConversionPattern::OpConversionPattern; + constexpr static StringRef manifestRomName = "__ESI_Manifest_ROM"; + + LogicalResult + matchAndRewrite(CompressedManifestOp, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override; + +protected: + LogicalResult createRomModule(CompressedManifestOp op, + ConversionPatternRewriter &rewriter) const; +}; +} // anonymous namespace + +LogicalResult ManifestRomLowering::createRomModule( + CompressedManifestOp op, ConversionPatternRewriter &rewriter) const { + Location loc = op.getLoc(); + auto mlirModBody = op->getParentOfType(); + rewriter.setInsertionPointToStart(mlirModBody.getBody()); + + // Find possible existing module (which may have been created as a dummy + // module) and erase it. + if (Operation *existingExtern = mlirModBody.lookupSymbol(manifestRomName)) { + if (!isa(existingExtern)) + return rewriter.notifyMatchFailure( + op, + "Found " + manifestRomName + " but it wasn't an HWModuleExternOp"); + rewriter.eraseOp(existingExtern); + } + + // Create the real module. + PortInfo ports[] = { + {{rewriter.getStringAttr("clk"), rewriter.getType(), + ModulePort::Direction::Input}}, + {{rewriter.getStringAttr("address"), rewriter.getIntegerType(30), + ModulePort::Direction::Input}}, + {{rewriter.getStringAttr("data"), rewriter.getI32Type(), + ModulePort::Direction::Output}}, + }; + auto rom = rewriter.create( + loc, rewriter.getStringAttr(manifestRomName), ports); + Block *romBody = rom.getBodyBlock(); + rewriter.setInsertionPointToStart(romBody); + Value clk = romBody->getArgument(0); + Value inputAddress = romBody->getArgument(1); + + // Manifest the compressed manifest into 32-bit words. + ArrayRef maniBytes = op.getCompressedManifest().getData(); + SmallVector words; + words.push_back(maniBytes.size()); + + for (size_t i = 0; i < maniBytes.size() - 3; i += 4) { + uint32_t word = maniBytes[i] | (maniBytes[i + 1] << 8) | + (maniBytes[i + 2] << 16) | (maniBytes[i + 3] << 24); + words.push_back(word); + } + size_t overHang = maniBytes.size() % 4; + if (overHang != 0) { + uint32_t word = 0; + for (size_t i = 0; i < overHang; ++i) + word |= maniBytes[maniBytes.size() - overHang + i] << (i * 8); + words.push_back(word); + } + + // From the words, create an the register which will hold the manifest (and + // hopefully synthized to a ROM). + SmallVector wordAttrs; + for (uint32_t word : words) + wordAttrs.push_back(rewriter.getI32IntegerAttr(word)); + auto manifestConstant = rewriter.create( + loc, hw::UnpackedArrayType::get(rewriter.getI32Type(), words.size()), + rewriter.getArrayAttr(wordAttrs)); + auto manifestReg = + rewriter.create(loc, manifestConstant.getType()); + rewriter.create(loc, manifestReg, manifestConstant); + + // Slim down the address, register it, do the lookup, and register the output. + size_t addrBits = llvm::Log2_64_Ceil(words.size()); + auto slimmedIdx = + rewriter.create(loc, inputAddress, 0, addrBits); + Value inputAddresReg = rewriter.create(loc, slimmedIdx, clk); + auto readIdx = + rewriter.create(loc, manifestReg, inputAddresReg); + auto readData = rewriter.create(loc, readIdx); + Value readDataReg = rewriter.create(loc, readData, clk); + if (auto *term = romBody->getTerminator()) + rewriter.eraseOp(term); + rewriter.create(loc, ValueRange{readDataReg}); + return success(); +} + +LogicalResult ManifestRomLowering::matchAndRewrite( + CompressedManifestOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const { + LogicalResult ret = createRomModule(op, rewriter); + rewriter.eraseOp(op); + return ret; +} + +namespace { +/// Lower `CompressedManifestOps` ops to a SystemVerilog module which sets the +/// Cosim manifest using a DPI support module. +struct CosimManifestLowering : public ManifestRomLowering { +public: + using ManifestRomLowering::ManifestRomLowering; LogicalResult matchAndRewrite(CompressedManifestOp, OpAdaptor adaptor, @@ -462,12 +574,18 @@ LogicalResult CosimManifestLowering::matchAndRewrite( MLIRContext *ctxt = rewriter.getContext(); Location loc = op.getLoc(); + // Cosim can optionally include a manifest simulation, so produce it in case + // the Cosim BSP wants it. + LogicalResult ret = createRomModule(op, rewriter); + if (failed(ret)) + return ret; + // Declare external module. Attribute params[] = { ParamDeclAttr::get("COMPRESSED_MANIFEST_SIZE", rewriter.getI32Type())}; PortInfo ports[] = { {{rewriter.getStringAttr("compressed_manifest"), - rewriter.getType( + rewriter.getType( rewriter.getI8Type(), ParamDeclRefAttr::get( rewriter.getStringAttr("COMPRESSED_MANIFEST_SIZE"), @@ -487,10 +605,10 @@ LogicalResult CosimManifestLowering::matchAndRewrite( [&](OpBuilder &rewriter, const hw::HWModulePortAccessor &) { // Assemble the manifest data into a constant. SmallVector bytes; - for (char b : op.getCompressedManifest().getData()) + for (uint8_t b : op.getCompressedManifest().getData()) bytes.push_back(rewriter.getI8IntegerAttr(b)); auto manifestConstant = rewriter.create( - loc, hw::UnpackedArrayType::get(rewriter.getI8Type(), bytes.size()), + loc, hw::ArrayType::get(rewriter.getI8Type(), bytes.size()), rewriter.getArrayAttr(bytes)); auto manifestLogic = rewriter.create(loc, manifestConstant.getType()); @@ -513,7 +631,6 @@ LogicalResult CosimManifestLowering::matchAndRewrite( rewriter.eraseOp(op); return success(); } - void ESItoHWPass::runOnOperation() { auto top = getOperation(); auto *ctxt = &getContext(); @@ -535,6 +652,7 @@ void ESItoHWPass::runOnOperation() { pass1Target.addLegalDialect(); pass1Target.addLegalDialect(); pass1Target.addLegalDialect(); + pass1Target.addLegalDialect(); pass1Target.addLegalOp(); pass1Target.addIllegalOp(); @@ -551,9 +669,12 @@ void ESItoHWPass::runOnOperation() { pass1Patterns.insert(esiBuilder); pass1Patterns.insert(ctxt); - if (platform == Platform::cosim) { + if (platform == Platform::cosim) pass1Patterns.insert(ctxt); - } + else if (platform == Platform::fpga) + pass1Patterns.insert(ctxt); + else + pass1Patterns.insert>(ctxt); // Run the conversion. if (failed( diff --git a/lib/Dialect/ESI/runtime/CMakeLists.txt b/lib/Dialect/ESI/runtime/CMakeLists.txt index c1b4df0f09db..fbc8fba8cea2 100644 --- a/lib/Dialect/ESI/runtime/CMakeLists.txt +++ b/lib/Dialect/ESI/runtime/CMakeLists.txt @@ -44,6 +44,9 @@ include_directories(cpp/include) set(ESIRuntimeSources cpp/lib/Accelerator.cpp + cpp/lib/Context.cpp + cpp/lib/Common.cpp + cpp/lib/Design.cpp cpp/lib/Manifest.cpp cpp/lib/Services.cpp cpp/lib/Ports.cpp @@ -58,8 +61,13 @@ set(ESIRuntimeLinkLibraries set(ESIPythonRuntimeSources python/esi/__init__.py python/esi/accelerator.py + python/esi/types.py python/esi/esiCppAccel.pyi ) +set(ESIRuntimeIncludeDirs) +set(ESIRuntimeCxxFlags) +set(ESIRuntimeLinkFlags) +set(ESIRuntimeLibDirs) IF(MSVC) set(CMAKE_CXX_FLAGS "/EHa") @@ -103,12 +111,48 @@ if(CapnProto_FOUND) ) endif() +option(XRT_PATH "Path to XRT lib.") +if (XRT_PATH) + message("-- XRT enabled with path ${XRT_PATH}") + + set(ESIRuntimeSources + ${ESIRuntimeSources} + cpp/lib/backends/Xrt.cpp + ) + set(ESIRuntimeIncludeDirs + ${ESIRuntimeIncludeDirs} + ${XRT_PATH}/include + ) + set(ESIRuntimeCxxFlags + ${ESIRuntimeCxxFlags} + -fmessage-length=0 + -Wno-nested-anon-types + -Wno-c++98-compat-extra-semi + ) + set(ESIRuntimeLinkLibraries + ${ESIRuntimeLinkLibraries} + xrt_coreutil + ) + set(ESIRuntimeLinkFlags + ${ESIRuntimeLinkFlags} + -pthread + ) + set(ESIRuntimeLibDirs + ${ESIRuntimeLibDirs} + ${XRT_PATH}/lib + ) +endif() + # The core API. For now, compile the backends into it directly. # TODO: make this a plugin architecture. add_library(ESIRuntime SHARED ${ESIRuntimeSources} ) target_link_libraries(ESIRuntime PRIVATE ${ESIRuntimeLinkLibraries}) +target_include_directories(ESIRuntime PRIVATE ${ESIRuntimeIncludeDirs}) +target_compile_options(ESIRuntime PRIVATE ${ESIRuntimeCxxFlags}) +target_link_directories(ESIRuntime PRIVATE ${ESIRuntimeLibDirs}) +target_link_options(ESIRuntime PRIVATE ${ESIRuntimeLinkFlags}) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") target_compile_options(ESIRuntime PRIVATE -Wno-covered-switch-default) diff --git a/lib/Dialect/ESI/runtime/cosim/Cosim_Manifest.sv b/lib/Dialect/ESI/runtime/cosim/Cosim_Manifest.sv index ccba9bdb258f..30b41b7d539d 100644 --- a/lib/Dialect/ESI/runtime/cosim/Cosim_Manifest.sv +++ b/lib/Dialect/ESI/runtime/cosim/Cosim_Manifest.sv @@ -18,10 +18,15 @@ module Cosim_Manifest parameter int COMPRESSED_MANIFEST_SIZE = 0, parameter int unsigned ESI_VERSION = 1 )( - input byte unsigned compressed_manifest[COMPRESSED_MANIFEST_SIZE] + input logic [COMPRESSED_MANIFEST_SIZE-1:0][7:0] compressed_manifest ); + byte unsigned compressed_manifest_bytes[COMPRESSED_MANIFEST_SIZE-1:0]; + always_comb + for (int i=0; i blob(size); for (int i = 0; i < size; ++i) { - blob[i] = *(char *)svGetArrElemPtr1(compressedManifest, i); + blob[size - i - 1] = *(char *)svGetArrElemPtr1(compressedManifest, i); } printf("[cosim] Setting manifest (esiVersion=%d, size=%d)\n", esiVersion, size); @@ -301,16 +301,28 @@ DPI int sv2cCosimserverMMIOReadTryGet(uint32_t *address) { if (!reqAddress.has_value()) return -1; *address = reqAddress.value(); + server->lowLevelBridge.readsOutstanding++; return 0; } DPI void sv2cCosimserverMMIOReadRespond(uint32_t data, char error) { assert(server); + if (server->lowLevelBridge.readsOutstanding == 0) { + printf("ERROR: More read responses than requests! Not queuing response.\n"); + return; + } + server->lowLevelBridge.readsOutstanding--; server->lowLevelBridge.readResps.push(data, error); } DPI void sv2cCosimserverMMIOWriteRespond(char error) { assert(server); + if (server->lowLevelBridge.writesOutstanding == 0) { + printf( + "ERROR: More write responses than requests! Not queuing response.\n"); + return; + } + server->lowLevelBridge.writesOutstanding--; server->lowLevelBridge.writeResps.push(error); } @@ -321,5 +333,6 @@ DPI int sv2cCosimserverMMIOWriteTryGet(uint32_t *address, uint32_t *data) { return -1; *address = req.value().first; *data = req.value().second; + server->lowLevelBridge.writesOutstanding++; return 0; } diff --git a/lib/Dialect/ESI/runtime/cosim/driver.cpp b/lib/Dialect/ESI/runtime/cosim/driver.cpp new file mode 100644 index 000000000000..a94bd761b6c7 --- /dev/null +++ b/lib/Dialect/ESI/runtime/cosim/driver.cpp @@ -0,0 +1,110 @@ +//===- driver.cpp - ESI Verilator software driver -------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// A fairly standard, boilerplate Verilator C++ simulation driver. Assumes the +// top level exposes just two signals: 'clk' and 'rst'. +// +//===----------------------------------------------------------------------===// + +#ifndef TOP_MODULE +#define TOP_MODULE ESI_Cosim_Top +#endif // TOP_MODULE + +// Macro black magic to get the header file name and class name from the +// TOP_MODULE macro. Need to disable formatting for this section, as +// clang-format messes it up by inserting spaces. + +// clang-format off +#define STRINGIFY_MACRO(x) STR(x) +#define STR(x) #x +#define EXPAND(x)x +#define CONCAT3(n1, n2, n3) STRINGIFY_MACRO(EXPAND(n1)EXPAND(n2)EXPAND(n3)) +#define TOKENPASTE(x, y) x ## y +#define CLASSNAME(x, y) TOKENPASTE(x, y) + +#include CONCAT3(V,TOP_MODULE,.h) +// clang-format on + +#include "verilated_vcd_c.h" + +#include "signal.h" +#include + +vluint64_t timeStamp; + +// Stop the simulation gracefully on ctrl-c. +volatile bool stopSimulation = false; +void handle_sigint(int) { stopSimulation = true; } + +// Called by $time in Verilog. +double sc_time_stamp() { return timeStamp; } + +int main(int argc, char **argv) { + // Register graceful exit handler. + signal(SIGINT, handle_sigint); + + Verilated::commandArgs(argc, argv); + + // Construct the simulated module's C++ model. + auto &dut = *new CLASSNAME(V, TOP_MODULE)(); + char *waveformFile = getenv("SAVE_WAVE"); + + VerilatedVcdC *tfp = nullptr; + if (waveformFile) { +#ifdef TRACE + tfp = new VerilatedVcdC(); + Verilated::traceEverOn(true); + dut.trace(tfp, 99); // Trace 99 levels of hierarchy + tfp->open(waveformFile); + std::cout << "[driver] Writing trace to " << waveformFile << std::endl; +#else + std::cout + << "[driver] Warning: waveform file specified, but not a debug build" + << std::endl; +#endif + } + + std::cout << "[driver] Starting simulation" << std::endl; + + // TODO: Add max speed (cycles per second) option for small, interactive + // simulations to reduce waveform for debugging. Should this be a command line + // option or configurable over the cosim interface? + + // Reset. + dut.rst = 1; + dut.clk = 0; + + // TODO: Support ESI reset handshake in the future. + // Run for a few cycles with reset held. + for (timeStamp = 0; timeStamp < 8 && !Verilated::gotFinish(); timeStamp++) { + dut.eval(); + dut.clk = !dut.clk; + if (tfp) + tfp->dump(timeStamp); + } + + // Take simulation out of reset. + dut.rst = 0; + + // Run for the specified number of cycles out of reset. + for (; !Verilated::gotFinish() && !stopSimulation; timeStamp++) { + dut.eval(); + dut.clk = !dut.clk; + if (tfp) + tfp->dump(timeStamp); + } + + // Tell the simulator that we're going to exit. This flushes the output(s) and + // frees whatever memory may have been allocated. + dut.final(); + if (tfp) + tfp->close(); + + std::cout << "[driver] Ending simulation at tick #" << timeStamp << std::endl; + return 0; +} diff --git a/lib/Dialect/ESI/runtime/cosim/driver.sv b/lib/Dialect/ESI/runtime/cosim/driver.sv new file mode 100644 index 000000000000..0fc24de652fc --- /dev/null +++ b/lib/Dialect/ESI/runtime/cosim/driver.sv @@ -0,0 +1,60 @@ +//===- driver.sv - ESI cosim testbench driver -----------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Contains the top module driver for an ESI cosimulation. It simply provides a +// clock and reset signal. In the future, the reset signal will become a reset +// handshake. +// +//===----------------------------------------------------------------------===// + +`timescale 1ns / 1ps + +`ifndef TOP_MODULE +`define TOP_MODULE ESI_Cosim_Top +`endif + +module driver(); + + logic clk = 0; + logic rst = 0; + + `TOP_MODULE top ( + .clk(clk), + .rst(rst) + ); + + always begin + // A clock period is #4. + clk = ~clk; + #2; + end + + initial begin + int cycles; + + $display("[driver] Starting simulation"); + + rst = 1; + // Hold in reset for 4 cycles. + @(posedge clk); + @(posedge clk); + @(posedge clk); + @(posedge clk); + rst = 0; + + if ($value$plusargs ("cycles=%d", cycles)) begin + int i; + for (i = 0; i < cycles; i++) begin + @(posedge clk); + end + $display("[driver] Ending simulation at tick #%0d", $time); + $finish(); + end + end + +endmodule diff --git a/lib/Dialect/ESI/runtime/cosim/esi-cosim.py b/lib/Dialect/ESI/runtime/cosim/esi-cosim.py new file mode 100755 index 000000000000..cba9b417dca7 --- /dev/null +++ b/lib/Dialect/ESI/runtime/cosim/esi-cosim.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python3 + +# ===- esi-cosim.py - ESI cosimulation launch utility --------*- python -*-===// +# +# 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 +# +# ===----------------------------------------------------------------------===// +# +# Utility script to start a simulation and launch a command to interact with it +# via ESI cosimulation. +# +# ===----------------------------------------------------------------------===// + +import argparse +import os +from pathlib import Path +import re +import signal +import socket +import subprocess +import sys +import textwrap +import time +from typing import List + +CosimCollateralDir = Path(os.path.dirname(os.path.realpath(__file__))) + + +def is_port_open(port) -> bool: + """Check if a TCP port is open locally.""" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.connect_ex(('127.0.0.1', port)) + sock.close() + return True if result == 0 else False + + +class SourceFiles: + + def __init__(self, top: str) -> None: + # User source files. + self.user: List[Path] = [] + # DPI shared objects. + self.dpi_so: List[str] = ["EsiCosimDpiServer"] + # DPI SV files. + self.dpi_sv: List[Path] = [ + CosimCollateralDir / "Cosim_DpiPkg.sv", + CosimCollateralDir / "Cosim_Endpoint.sv", + CosimCollateralDir / "Cosim_Manifest.sv", + CosimCollateralDir / "Cosim_MMIO.sv", + ] + # Name of the top module. + self.top = top + + def add_dir(self, dir: Path): + """Add all the RTL files in a directory to the source list.""" + for file in sorted(dir.iterdir()): + if file.is_file() and (file.suffix == ".sv" or file.suffix == ".v"): + self.user.append(file) + + def dpi_so_paths(self) -> List[Path]: + """Return a list of all the DPI shared object files.""" + + def find_so(name: str) -> Path: + for path in os.environ["LD_LIBRARY_PATH"].split(":"): + if os.name == "nt": + so = Path(path) / f"{name}.dll" + else: + so = Path(path) / f"lib{name}.so" + if so.exists(): + return so + raise FileNotFoundError(f"Could not find {name}.so in LD_LIBRARY_PATH") + + return [find_so(name) for name in self.dpi_so] + + @property + def rtl_sources(self) -> List[Path]: + """Return a list of all the RTL source files.""" + return self.dpi_sv + self.user + + +class Simulator: + + # Some RTL simulators don't use stderr for error messages. Everything goes to + # stdout. Boo! They should feel bad about this. Also, they can specify that + # broken behavior by overriding this. + UsesStderr = True + + def __init__(self, sources: SourceFiles, run_dir: Path, debug: bool): + self.sources = sources + self.run_dir = run_dir + self.debug = debug + + def compile_command(self) -> List[str]: + """Compile the sources. Returns the exit code of the simulation compiler.""" + assert False, "Must be implemented by subclass" + + def compile(self) -> int: + cp = subprocess.run(self.compile_command(), capture_output=True, text=True) + self.run_dir.mkdir(parents=True, exist_ok=True) + open(self.run_dir / "compile_stdout.log", "w").write(cp.stdout) + open(self.run_dir / "compile_stderr.log", "w").write(cp.stderr) + if cp.returncode != 0: + print("====== Compilation failure:") + if self.UsesStderr: + print(cp.stderr) + else: + print(cp.stdout) + return cp.returncode + + def run_command(self) -> List[str]: + """Return the command to run the simulation.""" + assert False, "Must be implemented by subclass" + + def run(self, inner_command: str) -> int: + """Start the simulation then run the command specified. Kill the simulation + when the command exits.""" + + # 'simProc' is accessed in the finally block. Declare it here to avoid + # syntax errors in that block. + simProc = None + try: + # Open log files + self.run_dir.mkdir(parents=True, exist_ok=True) + simStdout = open(self.run_dir / "sim_stdout.log", "w") + simStderr = open(self.run_dir / "sim_stderr.log", "w") + + # Erase the config file if it exists. We don't want to read + # an old config. + portFileName = self.run_dir / "cosim.cfg" + if os.path.exists(portFileName): + os.remove(portFileName) + + # Run the simulation. + simEnv = os.environ.copy() + if self.debug: + simEnv["COSIM_DEBUG_FILE"] = "cosim_debug.log" + simProc = subprocess.Popen(self.run_command(), + stdout=simStdout, + stderr=simStderr, + env=simEnv, + cwd=self.run_dir, + preexec_fn=os.setsid) + simStderr.close() + simStdout.close() + + # Get the port which the simulation RPC selected. + checkCount = 0 + while (not os.path.exists(portFileName)) and \ + simProc.poll() is None: + time.sleep(0.1) + checkCount += 1 + if checkCount > 200: + raise Exception(f"Cosim never wrote cfg file: {portFileName}") + port = -1 + while port < 0: + portFile = open(portFileName, "r") + for line in portFile.readlines(): + m = re.match("port: (\\d+)", line) + if m is not None: + port = int(m.group(1)) + portFile.close() + + # Wait for the simulation to start accepting RPC connections. + checkCount = 0 + while not is_port_open(port): + checkCount += 1 + if checkCount > 200: + raise Exception(f"Cosim RPC port ({port}) never opened") + if simProc.poll() is not None: + raise Exception("Simulation exited early") + time.sleep(0.05) + + # Run the inner command, passing the connection info via environment vars. + testEnv = os.environ.copy() + testEnv["ESI_COSIM_PORT"] = str(port) + testEnv["ESI_COSIM_HOST"] = "localhost" + return subprocess.run(inner_command, cwd=os.getcwd(), + env=testEnv).returncode + finally: + # Make sure to stop the simulation no matter what. + if simProc: + os.killpg(os.getpgid(simProc.pid), signal.SIGINT) + # Allow the simulation time to flush its outputs. + try: + simProc.wait(timeout=1.0) + except subprocess.TimeoutExpired: + # If the simulation doesn't exit of its own free will, kill it. + simProc.kill() + + +class Verilator(Simulator): + """Run and compile funcs for Verilator.""" + + DefaultDriver = CosimCollateralDir / "driver.cpp" + + def __init__(self, sources: SourceFiles, run_dir: Path, debug: bool): + super().__init__(sources, run_dir, debug) + + self.verilator = "verilator" + if "VERILATOR_PATH" in os.environ: + self.verilator = os.environ["VERILATOR_PATH"] + + def compile_command(self) -> List[str]: + cmd: List[str] = [ + self.verilator, + "--cc", + "--top-module", + self.sources.top, + "-DSIMULATION", + "-sv", + "--build", + "--exe", + "--assert", + str(Verilator.DefaultDriver), + ] + cflags = [] + if self.debug: + cmd += ["--trace", "--trace-params", "--trace-structs"] + cflags.append("-DTRACE") + if len(cflags) > 0: + cmd += ["-CFLAGS", " ".join(cflags)] + if len(self.sources.dpi_so) > 0: + cmd += ["-LDFLAGS", " ".join(["-l" + so for so in self.sources.dpi_so])] + cmd += [str(p) for p in self.sources.rtl_sources] + return cmd + + def run_command(self): + exe = Path.cwd() / "obj_dir" / ("V" + self.sources.top) + return [str(exe)] + + +class Questa(Simulator): + """Run and compile funcs for Questasim.""" + + DefaultDriver = CosimCollateralDir / "driver.sv" + + # Questa doesn't use stderr for error messages. Everything goes to stdout. + UsesStderr = False + + def compile_command(self) -> List[str]: + cmd = [ + "vlog", + "-sv", + "+define+TOP_MODULE=" + self.sources.top, + "+define+SIMULATION", + str(Questa.DefaultDriver), + ] + cmd += [str(p) for p in self.sources.rtl_sources] + return cmd + + def run_command(self) -> List[str]: + vsim = "vsim" + # Note: vsim exit codes say nothing about the test run's pass/fail even + # if $fatal is encountered in the simulation. + cmd = [ + vsim, + "driver", + "-batch", + "-do", + "run -all", + ] + for lib in self.sources.dpi_so_paths(): + svLib = os.path.splitext(lib)[0] + cmd.append("-sv_lib") + cmd.append(svLib) + if len(self.sources.dpi_so) > 0: + cmd.append("-cpppath") + cmd.append("/usr/bin/clang++") + return cmd + + def run(self, inner_command: str) -> int: + """Override the Simulator.run() to add a soft link in the run directory (to + the work directory) before running vsim the usual way.""" + + # Create a soft link to the work directory. + workDir = self.run_dir / "work" + if not workDir.exists(): + os.symlink(Path(os.getcwd()) / "work", workDir) + + # Run the simulation. + return super().run(inner_command) + + +def __main__(args): + argparser = argparse.ArgumentParser( + description="Wrap a 'inner_cmd' in an ESI cosimulation environment.", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=textwrap.dedent(""" + Notes: + - For Verilator, libEsiCosimDpiServer.so must be in the dynamic + library runtime search path (LD_LIBRARY_PATH) and link time path + (LIBRARY_PATH). If it is installed to a standard location (e.g. + /usr/lib), this should be handled automatically. + - This script needs to sit in the same directory as the ESI support + SystemVerilog (e.g. Cosim_DpiPkg.sv, Cosim_MMIO.sv, etc.). It can, + however, be soft linked to a different location. + - The simulator executable(s) must be in your PATH. + """)) + + argparser.add_argument( + "--sim", + type=str, + default="verilator", + help="Name of the RTL simulator to use or path to an executable.") + argparser.add_argument("--rundir", + default="run", + help="Directory in which simulation should be run.") + argparser.add_argument( + "--top", + default="ESI_Cosim_Top", + help="Name of the 'top' module to use in the simulation.") + argparser.add_argument("--debug", + action="store_true", + help="Enable debug output.") + argparser.add_argument("--source", + help="Directories containing the source files.", + nargs="+", + default=["hw"]) + + argparser.add_argument("inner_cmd", + nargs=argparse.REMAINDER, + help="Command to run in the simulation environment.") + + if len(args) <= 1: + argparser.print_help() + return + args = argparser.parse_args(args[1:]) + + sources = SourceFiles(args.top) + for src in args.source: + sources.add_dir(Path(src)) + + if args.sim == "verilator": + sim = Verilator(sources, Path(args.rundir), args.debug) + elif args.sim == "questa": + sim = Questa(sources, Path(args.rundir), args.debug) + else: + print("Unknown simulator: " + args.sim) + print("Supported simulators: ") + print(" - verilator") + print(" - questa") + return 1 + + rc = sim.compile() + if rc != 0: + return rc + return sim.run(args.inner_cmd[1:]) + + +if __name__ == '__main__': + sys.exit(__main__(sys.argv)) diff --git a/lib/Dialect/ESI/runtime/cosim/include/cosim/LowLevel.h b/lib/Dialect/ESI/runtime/cosim/include/cosim/LowLevel.h index 90b98f34a2f0..390651fee666 100644 --- a/lib/Dialect/ESI/runtime/cosim/include/cosim/LowLevel.h +++ b/lib/Dialect/ESI/runtime/cosim/include/cosim/LowLevel.h @@ -9,6 +9,8 @@ #ifndef COSIM_LOWLEVEL_H #define COSIM_LOWLEVEL_H +#include + #include "cosim/Utils.h" namespace esi { @@ -26,8 +28,11 @@ class LowLevel { TSQueue readReqs; TSQueue> readResps; + std::atomic readsOutstanding = 0; + TSQueue> writeReqs; TSQueue writeResps; + std::atomic writesOutstanding = 0; }; } // namespace cosim diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h index 1cb7853a23fb..3b540937463d 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h @@ -21,12 +21,12 @@ #ifndef ESI_ACCELERATOR_H #define ESI_ACCELERATOR_H +#include "esi/Context.h" +#include "esi/Design.h" #include "esi/Manifest.h" #include "esi/Ports.h" #include "esi/Services.h" -#include -#include #include #include #include @@ -39,61 +39,20 @@ namespace esi { // Constants used by low-level APIs. //===----------------------------------------------------------------------===// -constexpr uint32_t MagicNumOffset = 16; +constexpr uint32_t MetadataOffset = 8; constexpr uint32_t MagicNumberLo = 0xE5100E51; constexpr uint32_t MagicNumberHi = 0x207D98E5; -constexpr uint32_t VersionNumberOffset = MagicNumOffset + 8; constexpr uint32_t ExpectedVersionNumber = 0; //===----------------------------------------------------------------------===// -// Accelerator design hierarchy. +// Accelerator design hierarchy root. //===----------------------------------------------------------------------===// -class Instance; - -class HWModule { -public: - HWModule(std::optional info, - std::vector> children, - std::vector services, - std::vector ports); - - std::optional getInfo() const { return info; } - const std::vector> &getChildrenOrdered() const { - return children; - } - const std::map &getChildren() const { return childIndex; } - const std::vector &getPortsOrdered() const { return ports; } - const std::map &getPorts() const { - return portIndex; - } - -protected: - const std::optional info; - const std::vector> children; - const std::map childIndex; - const std::vector services; - const std::vector ports; - const std::map portIndex; -}; - -class Instance : public HWModule { -public: - Instance() = delete; - Instance(const Instance &) = delete; - ~Instance() = default; - Instance(AppID id, std::optional info, - std::vector> children, - std::vector services, - std::vector ports) - : HWModule(info, std::move(children), services, ports), id(id) {} - - const AppID getID() const { return id; } - -protected: - const AppID id; -}; - +/// Top level accelerator class. Maintains a shared pointer to the manifest, +/// which owns objects used in the design hierarchy owned by this class. Since +/// this class owns the entire design hierarchy, when it gets destroyed the +/// entire design hierarchy gets destroyed so all of the instances, ports, etc. +/// are no longer valid pointers. class Accelerator : public HWModule { public: Accelerator() = delete; @@ -102,22 +61,28 @@ class Accelerator : public HWModule { Accelerator(std::optional info, std::vector> children, std::vector services, - std::vector ports, - std::shared_ptr manifestImpl) - : HWModule(info, std::move(children), services, ports), - manifestImpl(manifestImpl) {} - -private: - std::shared_ptr manifestImpl; + std::vector> &ports) + : HWModule(info, std::move(children), services, ports) {} }; //===----------------------------------------------------------------------===// // Connection to the accelerator and its services. //===----------------------------------------------------------------------===// +/// Abstract class representing a connection to an accelerator. Actual +/// connections (e.g. to a co-simulation or actual device) are implemented by +/// subclasses. class AcceleratorConnection { public: + AcceleratorConnection(Context &ctxt) : ctxt(ctxt) {} + virtual ~AcceleratorConnection() = default; + Context &getCtxt() const { return ctxt; } + + /// Request the host side channel ports for a particular instance (identified + /// by the AppID path). For convenience, provide the bundle type. + virtual std::map + requestChannelsFor(AppIDPath, const BundleType *) = 0; using Service = services::Service; /// Get a typed reference to a particular service type. Caller does *not* take @@ -151,20 +116,23 @@ class AcceleratorConnection { /// Accelerator objects get deconstructed. using ServiceCacheKey = std::tuple; std::map> serviceCache; + + /// ESI accelerator context. + Context &ctxt; }; namespace registry { // Connect to an ESI accelerator given a backend name and connection specifier. // Alternatively, instantiate the backend directly (if you're using C++). -std::unique_ptr connect(std::string backend, - std::string connection); +std::unique_ptr +connect(Context &ctxt, std::string backend, std::string connection); namespace internal { /// Backends can register themselves to be connected via a connection string. -using BackendCreate = - std::function(std::string)>; +using BackendCreate = std::function( + Context &, std::string)>; void registerBackend(std::string name, BackendCreate create); // Helper struct to diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h index 62667444a8c9..fb31577b65bc 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h @@ -17,8 +17,8 @@ #define ESI_COMMON_H #include +#include #include -#include #include #include #include @@ -52,7 +52,6 @@ class AppIDPath : public std::vector { std::string toStr() const; }; bool operator<(const AppIDPath &a, const AppIDPath &b); -std::ostream &operator<<(std::ostream &, const esi::AppIDPath &); struct ModuleInfo { const std::optional name; @@ -80,9 +79,35 @@ struct HWClientDetail { using HWClientDetails = std::vector; using ServiceImplDetails = std::map; +/// A logical chunk of data representing serialized data. Currently, just a +/// wrapper for a vector of bytes, which is not efficient in terms of memory +/// copying. This will change in the future as will the API. +class MessageData { +public: + /// Adopts the data vector buffer. + MessageData() = default; + MessageData(std::vector &data) : data(std::move(data)) {} + ~MessageData() = default; + + const uint8_t *getBytes() const { return data.data(); } + /// Get the size of the data in bytes. + size_t getSize() const { return data.size(); } + +private: + std::vector data; +}; + } // namespace esi std::ostream &operator<<(std::ostream &, const esi::ModuleInfo &); std::ostream &operator<<(std::ostream &, const esi::AppID &); +//===----------------------------------------------------------------------===// +// Functions which should be in the standard library. +//===----------------------------------------------------------------------===// + +namespace esi { +std::string toHex(uint32_t val); +} // namespace esi + #endif // ESI_COMMON_H diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Context.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Context.h new file mode 100644 index 000000000000..ee2dc022cac7 --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Context.h @@ -0,0 +1,55 @@ +//===- Context.h - Accelerator context --------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// DO NOT EDIT! +// This file is distributed as part of an ESI package. The source for this file +// should always be modified within CIRCT. +// +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE(llvm-header-guard) +#ifndef ESI_CONTEXT_H +#define ESI_CONTEXT_H + +#include "esi/Types.h" + +#include +#include +#include + +namespace esi { +class AcceleratorConnection; + +/// AcceleratorConnections, Accelerators, and Manifests must all share a +/// context. It owns all the types, uniquifying them. +class Context { + +public: + /// Resolve a type id to the type. + std::optional getType(Type::ID id) const { + if (auto f = types.find(id); f != types.end()) + return f->second.get(); + return std::nullopt; + } + + /// Register a type with the context. Takes ownership of the type and returns + /// the pointer which users should use. + void registerType(Type *type); + + /// Connect to an accelerator backend. + std::unique_ptr connect(std::string backend, + std::string connection); + +private: + using TypeCache = std::map>; + TypeCache types; +}; + +} // namespace esi + +#endif // ESI_CONTEXT_H diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h new file mode 100644 index 000000000000..6d1713ce82d4 --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h @@ -0,0 +1,111 @@ +//===- Design.h - Dynamic accelerator API -----------------------*- 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 dynamic API into an accelerator allows access to the accelerator's design +// and communication channels through various stl containers (e.g. std::vector, +// std::map, etc.). This allows runtime reflection against the accelerator and +// can be pybind'd to create a Python API. +// +// The static API, in contrast, is a compile-time API that allows access to the +// design and communication channels symbolically. It will be generated once +// (not here) then compiled into the host software. +// +// Note for hardware designers: the "design hierarchy" from the host API +// perspective is not the same as the hardware module instance hierarchy. +// Rather, it is only the relevant parts as defined by the AppID hierarchy -- +// levels in the hardware module instance hierarchy get skipped. +// +// DO NOT EDIT! +// This file is distributed as part of an ESI package. The source for this file +// should always be modified within CIRCT. +// +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE(llvm-header-guard) +#ifndef ESI_DESIGN_H +#define ESI_DESIGN_H + +#include "esi/Manifest.h" +#include "esi/Ports.h" +#include "esi/Services.h" + +#include + +namespace esi { +// Forward declarations. +class Instance; +namespace services { +class Service; +} // namespace services + +/// Represents either the top level or an instance of a hardware module. +class HWModule { +protected: + HWModule(std::optional info, + std::vector> children, + std::vector services, + std::vector> &ports); + +public: + virtual ~HWModule() = default; + + /// Access the module's metadata, if any. + std::optional getInfo() const { return info; } + /// Get a vector of the module's children in a deterministic order. + std::vector getChildrenOrdered() const { + std::vector ret; + for (const auto &c : children) + ret.push_back(c.get()); + return ret; + } + /// Access the module's children by ID. + const std::map &getChildren() const { return childIndex; } + /// Get the module's ports in a deterministic order. + std::vector> getPortsOrdered() const { + std::vector> ret; + for (const auto &p : ports) + ret.push_back(*p); + return ret; + } + /// Access the module's ports by ID. + const std::map &getPorts() const { + return portIndex; + } + +protected: + const std::optional info; + const std::vector> children; + const std::map childIndex; + const std::vector services; + const std::vector> ports; + const std::map portIndex; +}; + +/// Subclass of `HWModule` which represents a submodule instance. Adds an AppID, +/// which the top level doesn't have or need. +class Instance : public HWModule { +public: + Instance() = delete; + Instance(const Instance &) = delete; + ~Instance() = default; + Instance(AppID id, std::optional info, + std::vector> children, + std::vector services, + std::vector> &ports) + : HWModule(info, std::move(children), services, ports), id(id) {} + + /// Get the instance's ID, which it will always have. + const AppID getID() const { return id; } + +protected: + const AppID id; +}; + +} // namespace esi + +#endif // ESI_DESIGN_H diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h index cf25bd40bdbf..547ecb9dbe94 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h @@ -19,14 +19,12 @@ #define ESI_MANIFEST_H #include "esi/Common.h" +#include "esi/Context.h" #include "esi/Types.h" #include -#include -#include #include #include -#include #include #include @@ -43,7 +41,8 @@ class Manifest { class Impl; Manifest(const Manifest &) = delete; - Manifest(const std::string &jsonManifest); + Manifest(Context &ctxt, const std::string &jsonManifest); + ~Manifest(); uint32_t getApiVersion() const; // Modules which have designer specified metadata. @@ -53,22 +52,20 @@ class Manifest { std::unique_ptr buildAccelerator(AcceleratorConnection &acc) const; - /// Get a Type from the manifest based on its ID. Types are uniqued here. - std::optional> getType(Type::ID id) const; - /// The Type Table is an ordered list of types. The offset can be used to /// compactly and uniquely within a design. It does not include all of the /// types in a design -- just the ones listed in the 'types' section of the /// manifest. - const std::vector> &getTypeTable() const; + const std::vector &getTypeTable() const; private: - std::shared_ptr impl; + Impl *impl; }; } // namespace esi +std::ostream &operator<<(std::ostream &os, const esi::AppID &id); +std::ostream &operator<<(std::ostream &, const esi::AppIDPath &); std::ostream &operator<<(std::ostream &, const esi::ModuleInfo &); -std::ostream &operator<<(std::ostream &, const esi::AppID &); #endif // ESI_MANIFEST_H diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h index 934376438fa3..755bfb21f471 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h @@ -19,6 +19,8 @@ #include "esi/Common.h" #include "esi/Types.h" +#include + namespace esi { /// Unidirectional channels are the basic communication primitive between the @@ -28,16 +30,16 @@ namespace esi { /// but used by higher level APIs which add types. class ChannelPort { public: - ChannelPort(const Type &type) : type(type) {} + ChannelPort(const Type *type) : type(type) {} virtual ~ChannelPort() = default; virtual void connect() {} virtual void disconnect() {} - const Type &getType() const { return type; } + const Type *getType() const { return type; } private: - const Type &type; + const Type *type; }; /// A ChannelPort which sends data to the accelerator. @@ -46,7 +48,7 @@ class WriteChannelPort : public ChannelPort { using ChannelPort::ChannelPort; /// A very basic write API. Will likely change for performance reasons. - virtual void write(const void *data, size_t size) = 0; + virtual void write(const MessageData &) = 0; }; /// A ChannelPort which reads data from the accelerator. @@ -54,10 +56,16 @@ class ReadChannelPort : public ChannelPort { public: using ChannelPort::ChannelPort; - /// Specify a buffer to read into and a maximum size to read. Returns the - /// number of bytes read, or -1 on error. Basic API, will likely change for - /// performance reasons. - virtual std::ptrdiff_t read(void *data, size_t maxSize) = 0; + /// Specify a buffer to read into. Non-blocking. Returns true if message + /// successfully recieved. Basic API, will likely change for performance + /// and functionality reasons. + virtual bool read(MessageData &) = 0; + + /// Asynchronous read. Returns a future which will be set when the message is + /// recieved. Could this subsume the synchronous read API? + /// The default implementation of this is really bad and should be overridden. + /// It simply polls `read` in a loop. + virtual std::future readAsync(); }; /// Services provide connections to 'bundles' -- collections of named, @@ -65,37 +73,32 @@ class ReadChannelPort : public ChannelPort { /// ChannelPorts. class BundlePort { public: - /// Direction of a bundle. This -- combined with the channel direction in the - /// bundle -- can be used to determine if a channel should be writing to or - /// reading from the accelerator. - enum Direction { ToServer, ToClient }; - /// Compute the direction of a channel given the bundle direction and the /// bundle port's direction. - static bool isWrite(BundleType::Direction bundleDir, Direction svcDir) { - if (svcDir == Direction::ToClient) - return bundleDir == BundleType::Direction::To; - return bundleDir == BundleType::Direction::From; + static bool isWrite(BundleType::Direction bundleDir) { + return bundleDir == BundleType::Direction::To; } /// Construct a port. BundlePort(AppID id, std::map channels); + virtual ~BundlePort() = default; /// Get the ID of the port. - AppID getID() const { return _id; } + AppID getID() const { return id; } /// Get access to the raw byte streams of a channel. Intended for internal /// usage and binding to other languages (e.g. Python) which have their own - /// message serialization code. + /// message serialization code. Exposed publicly as an escape hatch, but + /// ordinary users should not use. You have been warned. WriteChannelPort &getRawWrite(const std::string &name) const; ReadChannelPort &getRawRead(const std::string &name) const; const std::map &getChannels() const { - return _channels; + return channels; } private: - AppID _id; - std::map _channels; + AppID id; + std::map channels; }; } // namespace esi diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Services.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Services.h index fed3091d534e..f1b5d15da2d6 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Services.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Services.h @@ -26,8 +26,17 @@ #include namespace esi { +class AcceleratorConnection; namespace services { +/// Add a custom interface to a service client at a particular point in the +/// design hierarchy. +class ServicePort : public BundlePort { +public: + using BundlePort::BundlePort; + virtual ~ServicePort() = default; +}; + /// Parent class of all APIs modeled as 'services'. May or may not map to a /// hardware side 'service'. class Service { @@ -36,6 +45,14 @@ class Service { virtual ~Service() = default; virtual std::string getServiceSymbol() const = 0; + + /// Get specialized port for this service to attach to the given appid path. + /// Null returns mean nothing to attach. + virtual ServicePort *getPort(AppIDPath id, const BundleType *type, + const std::map &, + AcceleratorConnection &) const { + return nullptr; + } }; /// A service for which there are no standard services registered. Requires @@ -51,13 +68,6 @@ class CustomService : public Service { return serviceSymbol; } - /// Request the host side channel ports for a particular instance (identified - /// by the AppID path). For convenience, provide the bundle type and direction - /// of the bundle port. - virtual std::map - requestChannelsFor(AppIDPath, const BundleType &, - BundlePort::Direction portDir) = 0; - protected: std::string serviceSymbol; AppIDPath id; @@ -83,8 +93,8 @@ class SysInfo : public Service { class MMIO : public Service { public: virtual ~MMIO() = default; - virtual uint64_t read(uint32_t addr) const = 0; - virtual void write(uint32_t addr, uint64_t data) = 0; + virtual uint32_t read(uint32_t addr) const = 0; + virtual void write(uint32_t addr, uint32_t data) = 0; virtual std::string getServiceSymbol() const override; }; @@ -103,6 +113,52 @@ class MMIOSysInfo final : public SysInfo { const MMIO *mmio; }; +/// Service for calling functions. +class FuncService : public Service { +public: + FuncService(AcceleratorConnection *acc, AppIDPath id, std::string implName, + ServiceImplDetails details, HWClientDetails clients); + + virtual std::string getServiceSymbol() const override; + virtual ServicePort *getPort(AppIDPath id, const BundleType *type, + const std::map &, + AcceleratorConnection &) const override; + + /// A function call which gets attached to a service port. + class Function : public ServicePort { + friend class FuncService; + Function(AppID id, const std::map &channels); + + public: + void connect(); + std::future call(const MessageData &arg); + + private: + WriteChannelPort &arg; + ReadChannelPort &result; + }; + +private: + std::string symbol; +}; + +/// Registry of services which can be instantiated directly by the Accelerator +/// class if the backend doesn't do anything special with a service. +class ServiceRegistry { +public: + /// Create a service instance from the given details. Returns nullptr if + /// 'svcType' isn't registered. + static Service *createService(AcceleratorConnection *acc, + Service::Type svcType, AppIDPath id, + std::string implName, + ServiceImplDetails details, + HWClientDetails clients); + + /// Resolve a service type from a string. If the string isn't recognized, + /// default to CustomService. + static Service::Type lookupServiceType(const std::string &); +}; + } // namespace services } // namespace esi diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Types.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Types.h index 92e695e05add..ab3c39d0a74c 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Types.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Types.h @@ -16,13 +16,8 @@ #ifndef ESI_TYPES_H #define ESI_TYPES_H -#include -#include -#include #include -#include -#include -#include +#include #include #include @@ -35,7 +30,8 @@ class Type { Type(const ID &id) : id(id) {} virtual ~Type() = default; - ID getID() { return id; } + ID getID() const { return id; } + virtual std::ptrdiff_t getBitWidth() const { return -1; } protected: ID id; @@ -50,12 +46,13 @@ class BundleType : public Type { enum Direction { To, From }; using ChannelVector = - std::vector>; + std::vector>; BundleType(const ID &id, const ChannelVector &channels) : Type(id), channels(channels) {} const ChannelVector &getChannels() const { return channels; } + std::ptrdiff_t getBitWidth() const override { return -1; }; protected: ChannelVector channels; @@ -65,11 +62,20 @@ class BundleType : public Type { /// carry one values of one type. class ChannelType : public Type { public: - ChannelType(const ID &id, const Type &inner) : Type(id), inner(inner) {} - const Type &getInner() const { return inner; } + ChannelType(const ID &id, const Type *inner) : Type(id), inner(inner) {} + const Type *getInner() const { return inner; } + std::ptrdiff_t getBitWidth() const override { return inner->getBitWidth(); }; private: - const Type &inner; + const Type *inner; +}; + +/// The "void" type is a special type which can be used to represent no type. +class VoidType : public Type { +public: + VoidType(const ID &id) : Type(id) {} + // 'void' is 1 bit by convention. + std::ptrdiff_t getBitWidth() const override { return 1; }; }; /// The "any" type is a special type which can be used to represent any type, as @@ -79,6 +85,7 @@ class ChannelType : public Type { class AnyType : public Type { public: AnyType(const ID &id) : Type(id) {} + std::ptrdiff_t getBitWidth() const override { return -1; }; }; /// Bit vectors include signed, unsigned, and signless integers. @@ -87,6 +94,7 @@ class BitVectorType : public Type { BitVectorType(const ID &id, uint64_t width) : Type(id), width(width) {} uint64_t getWidth() const { return width; } + std::ptrdiff_t getBitWidth() const override { return getWidth(); }; private: uint64_t width; @@ -121,12 +129,22 @@ class UIntType : public IntegerType { /// Structs are an ordered collection of fields, each with a name and a type. class StructType : public Type { public: - using FieldVector = std::vector>; + using FieldVector = std::vector>; StructType(const ID &id, const FieldVector &fields) : Type(id), fields(fields) {} const FieldVector &getFields() const { return fields; } + std::ptrdiff_t getBitWidth() const override { + std::ptrdiff_t size = 0; + for (auto [name, ty] : getFields()) { + std::ptrdiff_t fieldSize = ty->getBitWidth(); + if (fieldSize < 0) + return -1; + size += fieldSize; + } + return size; + } private: FieldVector fields; @@ -135,14 +153,20 @@ class StructType : public Type { /// Arrays have a compile time specified (static) size and an element type. class ArrayType : public Type { public: - ArrayType(const ID &id, const Type &elementType, uint64_t size) + ArrayType(const ID &id, const Type *elementType, uint64_t size) : Type(id), elementType(elementType), size(size) {} - const Type &getElementType() const { return elementType; } + const Type *getElementType() const { return elementType; } uint64_t getSize() const { return size; } + std::ptrdiff_t getBitWidth() const override { + std::ptrdiff_t elementSize = elementType->getBitWidth(); + if (elementSize < 0) + return -1; + return elementSize * size; + } private: - const Type &elementType; + const Type *elementType; uint64_t size; }; diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h index d20b204b088f..cca7a5c3bae5 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Cosim.h @@ -33,9 +33,23 @@ class CosimAccelerator : public esi::AcceleratorConnection { public: struct Impl; - CosimAccelerator(std::string hostname, uint16_t port); + CosimAccelerator(Context &, std::string hostname, uint16_t port); static std::unique_ptr - connect(std::string connectionString); + connect(Context &, std::string connectionString); + + // Different ways to retrieve the manifest in Cosimulation. + enum ManifestMethod { + Cosim, // Use the backdoor cosim interface. Default. + MMIO, // Use MMIO emulation. + }; + // Set the way this connection will retrieve the manifest. + void setManifestMethod(ManifestMethod method); + + /// Request the host side channel ports for a particular instance (identified + /// by the AppID path). For convenience, provide the bundle type and direction + /// of the bundle port. + virtual std::map + requestChannelsFor(AppIDPath, const BundleType *) override; protected: virtual Service *createService(Service::Type service, AppIDPath path, @@ -45,6 +59,7 @@ class CosimAccelerator : public esi::AcceleratorConnection { private: std::unique_ptr impl; + ManifestMethod manifestMethod = Cosim; }; } // namespace cosim diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h index 918fba8c05be..6be52ffe6c85 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h @@ -50,17 +50,22 @@ class TraceAccelerator : public esi::AcceleratorConnection { /// \param manifestJson The path to the manifest JSON file. /// \param traceFile The path to the trace file. For 'Write' mode, this file /// is opened for writing. For 'Read' mode, this file is opened for reading. - TraceAccelerator(Mode mode, std::filesystem::path manifestJson, + TraceAccelerator(Context &, Mode mode, std::filesystem::path manifestJson, std::filesystem::path traceFile); /// Parse the connection string and instantiate the accelerator. Format is: /// ":[:]". static std::unique_ptr - connect(std::string connectionString); + connect(Context &, std::string connectionString); /// Internal implementation. struct Impl; + /// Request the host side channel ports for a particular instance (identified + /// by the AppID path). For convenience, provide the bundle type. + std::map + requestChannelsFor(AppIDPath, const BundleType *) override; + protected: virtual Service *createService(Service::Type service, AppIDPath idPath, std::string implName, diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h new file mode 100644 index 000000000000..04454cb3c8a4 --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h @@ -0,0 +1,59 @@ +//===- Xrt.h - ESI XRT device backend ---------------------------*- 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 is a specialization of the ESI C++ API (backend) for connection into +// hardware on an XRT device. Requires XRT C++ library. +// +// DO NOT EDIT! +// This file is distributed as part of an ESI package. The source for this file +// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp). +// +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE(llvm-header-guard) +#ifndef ESI_BACKENDS_XRT_H +#define ESI_BACKENDS_XRT_H + +#include "esi/Accelerator.h" + +#include + +namespace esi { +namespace backends { +namespace xrt { + +/// Connect to an ESI simulation. +class XrtAccelerator : public esi::AcceleratorConnection { +public: + struct Impl; + + XrtAccelerator(Context &, std::string xclbin, std::string kernelName); + static std::unique_ptr + connect(Context &, std::string connectionString); + + /// Request the host side channel ports for a particular instance (identified + /// by the AppID path). For convenience, provide the bundle type and direction + /// of the bundle port. + std::map + requestChannelsFor(AppIDPath, const BundleType *) override; + +protected: + virtual Service *createService(Service::Type service, AppIDPath path, + std::string implName, + const ServiceImplDetails &details, + const HWClientDetails &clients) override; + +private: + std::unique_ptr impl; +}; + +} // namespace xrt +} // namespace backends +} // namespace esi + +#endif // ESI_BACKENDS_XRT_H diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp index d32c2c26916a..a1e14f9c0a04 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp @@ -24,32 +24,6 @@ using namespace esi::services; namespace esi { -/// Build an index of children by AppID. -static map -buildIndex(const vector> &insts) { - map index; - for (auto &item : insts) - index[item->getID()] = item.get(); - return index; -} - -/// Build an index of ports by AppID. -static map -buildIndex(const vector &ports) { - map index; - for (auto &item : ports) - index.emplace(item.getID(), item); - return index; -} - -HWModule::HWModule(std::optional info, - std::vector> children, - std::vector services, - std::vector ports) - : info(info), children(std::move(children)), - childIndex(buildIndex(this->children)), services(services), ports(ports), - portIndex(buildIndex(this->ports)) {} - services::Service *AcceleratorConnection::getService(Service::Type svcType, AppIDPath id, std::string implName, @@ -58,6 +32,9 @@ services::Service *AcceleratorConnection::getService(Service::Type svcType, unique_ptr &cacheEntry = serviceCache[make_tuple(&svcType, id)]; if (cacheEntry == nullptr) { Service *svc = createService(svcType, id, implName, details, clients); + if (!svc) + svc = ServiceRegistry::createService(this, svcType, id, implName, details, + clients); if (!svc) return nullptr; cacheEntry = unique_ptr(svc); @@ -76,11 +53,12 @@ void registerBackend(string name, BackendCreate create) { } } // namespace internal -unique_ptr connect(string backend, string connection) { +unique_ptr connect(Context &ctxt, string backend, + string connection) { auto f = internal::backendRegistry.find(backend); if (f == internal::backendRegistry.end()) throw runtime_error("Backend not found"); - return f->second(connection); + return f->second(ctxt, connection); } } // namespace registry diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Common.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Common.cpp new file mode 100644 index 000000000000..909cf3b8cafc --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/lib/Common.cpp @@ -0,0 +1,24 @@ +//===- Common.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 +// +//===----------------------------------------------------------------------===// +// +// DO NOT EDIT! +// This file is distributed as part of an ESI package. The source for this file +// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp/). +// +//===----------------------------------------------------------------------===// + +#include "esi/Common.h" + +#include +#include + +std::string esi::toHex(uint32_t val) { + std::ostringstream ss; + ss << std::hex << val; + return ss.str(); +} diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Context.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Context.cpp new file mode 100644 index 000000000000..367b727a9456 --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/lib/Context.cpp @@ -0,0 +1,29 @@ +//===- Context.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 +// +//===----------------------------------------------------------------------===// +// +// DO NOT EDIT! +// This file is distributed as part of an ESI package. The source for this file +// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp/). +// +//===----------------------------------------------------------------------===// + +#include "esi/Context.h" +#include "esi/Accelerator.h" + +using namespace esi; + +void Context::registerType(Type *type) { + if (types.count(type->getID())) + throw std::runtime_error("Type already exists in context"); + types.emplace(type->getID(), std::unique_ptr(type)); +} + +std::unique_ptr +Context::connect(std::string backend, std::string connection) { + return registry::connect(*this, backend, connection); +} diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp new file mode 100644 index 000000000000..36c18be3827f --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp @@ -0,0 +1,51 @@ +//===- Design.cpp - ESI design hierarchy implementation -------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// DO NOT EDIT! +// This file is distributed as part of an ESI package. The source for this file +// should always be modified within CIRCT (lib/dialect/ESI/runtime/cpp/). +// +//===----------------------------------------------------------------------===// + +#include "esi/Design.h" + +#include +#include + +using namespace std; +using namespace esi; + +namespace esi { + +/// Build an index of children by AppID. +static map +buildIndex(const vector> &insts) { + map index; + for (auto &item : insts) + index[item->getID()] = item.get(); + return index; +} + +/// Build an index of ports by AppID. +static map +buildIndex(const vector> &ports) { + map index; + for (auto &item : ports) + index.emplace(item->getID(), *item); + return index; +} + +HWModule::HWModule(std::optional info, + std::vector> children, + std::vector services, + std::vector> &ports) + : info(info), children(std::move(children)), + childIndex(buildIndex(this->children)), services(services), + ports(std::move(ports)), portIndex(buildIndex(this->ports)) {} + +} // namespace esi diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp index fdb879727a39..1cd5df83604d 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp @@ -17,6 +17,7 @@ #include "esi/Services.h" #include +#include using namespace std; @@ -38,15 +39,18 @@ class Manifest::Impl { friend class ::esi::Manifest; public: - using TypeCache = map>; - - Impl(const string &jsonManifest); + Impl(Context &ctxt, const string &jsonManifest); auto at(const string &key) const { return manifestJson.at(key); } // Get the module info (if any) for the module instance in 'json'. optional getModInfo(const nlohmann::json &) const; + /// Go through the "service_decls" section of the manifest and populate the + /// services table as appropriate. + void scanServiceDecls(AcceleratorConnection &, const nlohmann::json &, + ServiceTable &) const; + /// Get a Service for the service specified in 'json'. Update the /// activeServices table. services::Service *getService(AppIDPath idPath, AcceleratorConnection &, @@ -62,9 +66,10 @@ class Manifest::Impl { /// Get the bundle ports for the instance at 'idPath' and specified in /// 'instJson'. Look them up in 'activeServies'. - vector getBundlePorts(AppIDPath idPath, - const ServiceTable &activeServices, - const nlohmann::json &instJson) const; + vector> + getBundlePorts(AcceleratorConnection &acc, AppIDPath idPath, + const ServiceTable &activeServices, + const nlohmann::json &instJson) const; /// Build the set of child instances (recursively) for the module instance /// description. @@ -83,28 +88,20 @@ class Manifest::Impl { /// Parse all the types and populate the types table. void populateTypes(const nlohmann::json &typesJson); - // Forwarded from Manifest. - const vector> &getTypeTable() const { - return _typeTable; - } - - // Forwarded from Manifest. - optional> getType(Type::ID id) const { - if (auto f = _types.find(id); f != _types.end()) - return *f->second; - return nullopt; - } + /// Get the ordered list of types from the manifest. + const vector &getTypeTable() const { return _typeTable; } /// Build a dynamic API for the Accelerator connection 'acc' based on the /// manifest stored herein. - unique_ptr buildAccelerator(AcceleratorConnection &acc, - std::shared_ptr me) const; + unique_ptr buildAccelerator(AcceleratorConnection &acc) const; - const Type &parseType(const nlohmann::json &typeJson); + const Type *parseType(const nlohmann::json &typeJson); private: - vector> _typeTable; - TypeCache _types; + Context &ctxt; + vector _typeTable; + + optional getType(Type::ID id) const { return ctxt.getType(id); } // The parsed json. nlohmann::json manifestJson; @@ -178,7 +175,7 @@ static ModuleInfo parseModuleInfo(const nlohmann::json &mod) { for (auto &extra : mod.items()) if (extra.key() != "name" && extra.key() != "summary" && extra.key() != "version" && extra.key() != "repo" && - extra.key() != "commit_hash" && extra.key() != "symbolRef") + extra.key() != "commitHash" && extra.key() != "symbolRef") extras[extra.key()] = getAny(extra.value()); auto value = [&](const string &key) -> optional { @@ -187,15 +184,15 @@ static ModuleInfo parseModuleInfo(const nlohmann::json &mod) { return nullopt; return f.value(); }; - return ModuleInfo{value("name"), value("summary"), value("version"), - value("repo"), value("commit_hash"), extras}; + return ModuleInfo{value("name"), value("summary"), value("version"), + value("repo"), value("commitHash"), extras}; } //===----------------------------------------------------------------------===// // Manifest::Impl class implementation. //===----------------------------------------------------------------------===// -Manifest::Impl::Impl(const string &manifestStr) { +Manifest::Impl::Impl(Context &ctxt, const string &manifestStr) : ctxt(ctxt) { manifestJson = nlohmann::ordered_json::parse(manifestStr); for (auto &mod : manifestJson.at("symbols")) @@ -205,19 +202,24 @@ Manifest::Impl::Impl(const string &manifestStr) { } unique_ptr -Manifest::Impl::buildAccelerator(AcceleratorConnection &acc, - std::shared_ptr me) const { - auto designJson = manifestJson.at("design"); +Manifest::Impl::buildAccelerator(AcceleratorConnection &acc) const { + ServiceTable activeSvcs; // Get the initial active services table. Update it as we descend down. - ServiceTable activeSvcs; + auto svcDecls = manifestJson.at("service_decls"); + scanServiceDecls(acc, svcDecls, activeSvcs); + + // Get the services instantiated at the top level. + auto designJson = manifestJson.at("design"); vector services = getServices({}, acc, designJson, activeSvcs); + // Get the ports at the top level. + auto ports = getBundlePorts(acc, {}, activeSvcs, designJson); + return make_unique( getModInfo(designJson), - getChildInstances({}, acc, activeSvcs, designJson), services, - getBundlePorts({}, activeSvcs, designJson), me); + getChildInstances({}, acc, activeSvcs, designJson), services, ports); } optional @@ -231,6 +233,27 @@ Manifest::Impl::getModInfo(const nlohmann::json &json) const { return nullopt; } +void Manifest::Impl::scanServiceDecls(AcceleratorConnection &acc, + const nlohmann::json &svcDecls, + ServiceTable &activeServices) const { + for (auto &svcDecl : svcDecls) { + if (auto f = svcDecl.find("type_name"); f != svcDecl.end()) { + // Get the implementation details. + ServiceImplDetails svcDetails; + for (auto &detail : svcDecl.items()) + svcDetails[detail.key()] = getAny(detail.value()); + + // Create the service. + services::Service::Type svcId = + services::ServiceRegistry::lookupServiceType(f.value()); + auto svc = acc.getService(svcId, /*id=*/{}, /*implName=*/"", + /*details=*/svcDetails, /*clients=*/{}); + if (svc) + activeServices[svcDecl.at("symbol")] = svc; + } + } +} + vector> Manifest::Impl::getChildInstances(AppIDPath idPath, AcceleratorConnection &acc, const ServiceTable &activeServices, @@ -243,6 +266,7 @@ Manifest::Impl::getChildInstances(AppIDPath idPath, AcceleratorConnection &acc, ret.emplace_back(getChildInstance(idPath, acc, activeServices, child)); return ret; } + unique_ptr Manifest::Impl::getChildInstance(AppIDPath idPath, AcceleratorConnection &acc, ServiceTable activeServices, @@ -254,9 +278,9 @@ Manifest::Impl::getChildInstance(AppIDPath idPath, AcceleratorConnection &acc, getServices(idPath, acc, child, activeServices); auto children = getChildInstances(idPath, acc, activeServices, child); + auto ports = getBundlePorts(acc, idPath, activeServices, child); return make_unique(parseID(child.at("app_id")), getModInfo(child), - move(children), services, - getBundlePorts(idPath, activeServices, child)); + std::move(children), services, ports); } services::Service * @@ -299,11 +323,13 @@ Manifest::Impl::getService(AppIDPath idPath, AcceleratorConnection &acc, // Create the service. // TODO: Add support for 'standard' services. - auto svc = acc.getService(idPath, implName, - svcDetails, clientDetails); - - // Update the active services table. - activeServices[service] = svc; + services::Service::Type svcType = + services::ServiceRegistry::lookupServiceType(service); + services::Service *svc = + acc.getService(svcType, idPath, implName, svcDetails, clientDetails); + if (svc) + // Update the active services table. + activeServices[service] = svc; return svc; } @@ -322,11 +348,11 @@ Manifest::Impl::getServices(AppIDPath idPath, AcceleratorConnection &acc, return ret; } -vector -Manifest::Impl::getBundlePorts(AppIDPath idPath, +vector> +Manifest::Impl::getBundlePorts(AcceleratorConnection &acc, AppIDPath idPath, const ServiceTable &activeServices, const nlohmann::json &instJson) const { - vector ret; + vector> ret; auto contentsIter = instJson.find("contents"); if (contentsIter == instJson.end()) return ret; @@ -339,45 +365,37 @@ Manifest::Impl::getBundlePorts(AppIDPath idPath, std::string serviceName = ""; if (auto f = content.find("servicePort"); f != content.end()) serviceName = parseServicePort(f.value()).name; - auto svc = activeServices.find(serviceName); - if (svc == activeServices.end()) { + auto svcIter = activeServices.find(serviceName); + if (svcIter == activeServices.end()) { // If a specific service isn't found, search for the default service // (typically provided by a BSP). - if (svc = activeServices.find(""); svc == activeServices.end()) + if (svcIter = activeServices.find(""); svcIter == activeServices.end()) throw runtime_error( "Malformed manifest: could not find active service '" + serviceName + "'"); } - - // If the active service is null, then this is a port that is not connected - // externally. Or we don't have an implementation for it. - if (!svc->second) - continue; + services::Service *svc = svcIter->second; string typeName = content.at("bundleType").at("circt_name"); auto type = getType(typeName); if (!type) throw runtime_error("Malformed manifest: could not find port type '" + typeName + "'"); - const BundleType &bundleType = - dynamic_cast(type->get()); - - BundlePort::Direction portDir; - string dirStr = content.at("direction"); - if (dirStr == "toClient") - portDir = BundlePort::Direction::ToClient; - else if (dirStr == "toServer") - portDir = BundlePort::Direction::ToServer; - else - throw runtime_error("Malformed manifest: unknown direction '" + dirStr + - "'"); + const BundleType *bundleType = dynamic_cast(*type); + if (!bundleType) + throw runtime_error("Malformed manifest: type '" + typeName + + "' is not a bundle type"); idPath.push_back(parseID(content.at("appID"))); - map portChannels; - // If we need to have custom ports (because of a custom service), add them. - if (auto *customSvc = dynamic_cast(svc->second)) - portChannels = customSvc->requestChannelsFor(idPath, bundleType, portDir); - ret.emplace_back(idPath.back(), portChannels); + map portChannels = + acc.requestChannelsFor(idPath, bundleType); + + services::ServicePort *svcPort = + svc->getPort(idPath, bundleType, portChannels, acc); + if (svcPort) + ret.emplace_back(svcPort); + else + ret.emplace_back(new BundlePort(idPath.back(), portChannels)); // Since we share idPath between iterations, pop the last element before the // next iteration. idPath.pop_back(); @@ -386,14 +404,12 @@ Manifest::Impl::getBundlePorts(AppIDPath idPath, } namespace { -const Type &parseType(const nlohmann::json &typeJson, - Manifest::Impl::TypeCache &cache); +const Type *parseType(const nlohmann::json &typeJson, Context &ctxt); -BundleType *parseBundleType(const nlohmann::json &typeJson, - Manifest::Impl::TypeCache &cache) { +BundleType *parseBundleType(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "bundle"); - vector> channels; + vector> channels; for (auto &chanJson : typeJson["channels"]) { string dirStr = chanJson.at("direction"); BundleType::Direction dir; @@ -410,15 +426,13 @@ BundleType *parseBundleType(const nlohmann::json &typeJson, return new BundleType(typeJson.at("circt_name"), channels); } -ChannelType *parseChannelType(const nlohmann::json &typeJson, - Manifest::Impl::TypeCache &cache) { +ChannelType *parseChannelType(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "channel"); return new ChannelType(typeJson.at("circt_name"), parseType(typeJson.at("inner"), cache)); } -BitVectorType *parseInt(const nlohmann::json &typeJson, - Manifest::Impl::TypeCache &cache) { +Type *parseInt(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "int"); std::string sign = typeJson.at("signedness"); uint64_t width = typeJson.at("hw_bitwidth"); @@ -428,37 +442,37 @@ BitVectorType *parseInt(const nlohmann::json &typeJson, return new SIntType(id, width); else if (sign == "unsigned") return new UIntType(id, width); - else if (sign == "signless") + else if (sign == "signless" && width == 0) + // By convention, a zero-width signless integer is a void type. + return new VoidType(id); + else if (sign == "signless" && width > 0) return new BitsType(id, width); else throw runtime_error("Malformed manifest: unknown sign '" + sign + "'"); } -StructType *parseStruct(const nlohmann::json &typeJson, - Manifest::Impl::TypeCache &cache) { +StructType *parseStruct(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "struct"); - vector> fields; + vector> fields; for (auto &fieldJson : typeJson["fields"]) fields.emplace_back(fieldJson.at("name"), parseType(fieldJson["type"], cache)); return new StructType(typeJson.at("circt_name"), fields); } -ArrayType *parseArray(const nlohmann::json &typeJson, - Manifest::Impl::TypeCache &cache) { +ArrayType *parseArray(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "array"); uint64_t size = typeJson.at("size"); return new ArrayType(typeJson.at("circt_name"), parseType(typeJson.at("element"), cache), size); } -using TypeParser = - std::function; +using TypeParser = std::function; const std::map typeParsers = { {"bundle", parseBundleType}, {"channel", parseChannelType}, {"any", - [](const nlohmann::json &typeJson, Manifest::Impl::TypeCache &cache) { + [](const nlohmann::json &typeJson, Context &cache) { return new AnyType(typeJson.at("circt_name")); }}, {"int", parseInt}, @@ -468,17 +482,12 @@ const std::map typeParsers = { }; // Parse a type if it doesn't already exist in the cache. -const Type &parseType(const nlohmann::json &typeJson, - Manifest::Impl::TypeCache &cache) { +const Type *parseType(const nlohmann::json &typeJson, Context &cache) { // We use the circt type string as a unique ID. string circt_name = typeJson.at("circt_name"); + if (optional t = cache.getType(circt_name)) + return *t; - // Check the cache. - auto typeF = cache.find(circt_name); - if (typeF != cache.end()) - return *typeF->second; - - // Parse the type. string mnemonic = typeJson.at("mnemonic"); Type *t; auto f = typeParsers.find(mnemonic); @@ -489,13 +498,13 @@ const Type &parseType(const nlohmann::json &typeJson, t = new Type(circt_name); // Insert into the cache. - cache.emplace(circt_name, unique_ptr(t)); - return *t; + cache.registerType(t); + return t; } } // namespace -const Type &Manifest::Impl::parseType(const nlohmann::json &typeJson) { - return ::parseType(typeJson, _types); +const Type *Manifest::Impl::parseType(const nlohmann::json &typeJson) { + return ::parseType(typeJson, ctxt); } void Manifest::Impl::populateTypes(const nlohmann::json &typesJson) { @@ -507,7 +516,10 @@ void Manifest::Impl::populateTypes(const nlohmann::json &typesJson) { // Manifest class implementation. //===----------------------------------------------------------------------===// -Manifest::Manifest(const string &jsonManifest) : impl(new Impl(jsonManifest)) {} +Manifest::Manifest(Context &ctxt, const string &jsonManifest) + : impl(new Impl(ctxt, jsonManifest)) {} + +Manifest::~Manifest() { delete impl; } uint32_t Manifest::getApiVersion() const { return impl->at("api_version").get(); @@ -522,16 +534,10 @@ vector Manifest::getModuleInfos() const { unique_ptr Manifest::buildAccelerator(AcceleratorConnection &acc) const { - return impl->buildAccelerator(acc, impl); -} - -optional> Manifest::getType(Type::ID id) const { - if (auto f = impl->_types.find(id); f != impl->_types.end()) - return *f->second; - return nullopt; + return impl->buildAccelerator(acc); } -const vector> &Manifest::getTypeTable() const { +const vector &Manifest::getTypeTable() const { return impl->getTypeTable(); } @@ -572,7 +578,8 @@ ostream &operator<<(ostream &os, const ModuleInfo &m) { os << ")"; } if (m.summary) - os << ": " << *m.summary << "\n"; + os << ": " << *m.summary; + os << "\n"; if (!m.extra.empty()) { os << " Extra metadata:\n"; @@ -611,6 +618,8 @@ bool operator<(const AppIDPath &a, const AppIDPath &b) { return a[i] < b[i]; return false; } +} // namespace esi + ostream &operator<<(ostream &os, const AppID &id) { os << id.name; if (id.idx) @@ -625,4 +634,3 @@ ostream &operator<<(ostream &os, const AppIDPath &path) { } return os; } -} // namespace esi diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp index 44f79fadd778..f0cdbef34ce7 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp @@ -14,15 +14,18 @@ #include "esi/Ports.h" +#include +#include + using namespace std; using namespace esi; BundlePort::BundlePort(AppID id, map channels) - : _id(id), _channels(channels) {} + : id(id), channels(channels) {} WriteChannelPort &BundlePort::getRawWrite(const string &name) const { - auto f = _channels.find(name); - if (f == _channels.end()) + auto f = channels.find(name); + if (f == channels.end()) throw runtime_error("Channel '" + name + "' not found"); auto *write = dynamic_cast(&f->second); if (!write) @@ -31,11 +34,23 @@ WriteChannelPort &BundlePort::getRawWrite(const string &name) const { } ReadChannelPort &BundlePort::getRawRead(const string &name) const { - auto f = _channels.find(name); - if (f == _channels.end()) + auto f = channels.find(name); + if (f == channels.end()) throw runtime_error("Channel '" + name + "' not found"); auto *read = dynamic_cast(&f->second); if (!read) throw runtime_error("Channel '" + name + "' is not a read channel"); return *read; } + +std::future ReadChannelPort::readAsync() { + // TODO: running this deferred is a horrible idea considering that it blocks! + // It's a hack since Capnp RPC refuses to work with multiple threads. + return std::async(std::launch::deferred, [this]() { + MessageData output; + while (!read(output)) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + return output; + }); +} diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp index d56b2581d20a..c81a871f16d4 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp @@ -47,16 +47,28 @@ string MMIO::getServiceSymbol() const { return "__builtin_MMIO"; } MMIOSysInfo::MMIOSysInfo(const MMIO *mmio) : mmio(mmio) {} uint32_t MMIOSysInfo::getEsiVersion() const { - uint32_t hi = mmio->read(MagicNumOffset); - uint32_t lo = mmio->read(MagicNumOffset + 4); - if (hi != MagicNumberHi || lo != MagicNumberLo) - throw runtime_error("ESI magic number not found"); - return mmio->read(VersionNumberOffset); + uint32_t reg; + if ((reg = mmio->read(MetadataOffset)) != MagicNumberLo) + throw runtime_error("Invalid magic number low bits: " + toHex(reg)); + if ((reg = mmio->read(MetadataOffset + 4)) != MagicNumberHi) + throw runtime_error("Invalid magic number high bits: " + toHex(reg)); + return mmio->read(MetadataOffset + 8); } vector MMIOSysInfo::getCompressedManifest() const { - assert(false && "Not implemented"); - throw runtime_error("Not implemented"); + uint32_t manifestPtr = mmio->read(MetadataOffset + 12); + uint32_t size = mmio->read(manifestPtr); + uint32_t numWords = (size + 3) / 4; + vector manifestWords(numWords); + for (size_t i = 0; i < numWords; ++i) + manifestWords[i] = mmio->read(manifestPtr + 4 + (i * 4)); + + vector manifest; + for (size_t i = 0; i < size; ++i) { + uint32_t word = manifestWords[i / 4]; + manifest.push_back(word >> (8 * (i % 4))); + } + return manifest; } CustomService::CustomService(AppIDPath idPath, @@ -69,3 +81,58 @@ CustomService::CustomService(AppIDPath idPath, serviceSymbol = serviceSymbol.substr(1); } } + +FuncService::FuncService(AcceleratorConnection *acc, AppIDPath idPath, + std::string implName, ServiceImplDetails details, + HWClientDetails clients) { + if (auto f = details.find("service"); f != details.end()) + // Strip off initial '@'. + symbol = any_cast(f->second).substr(1); +} + +std::string FuncService::getServiceSymbol() const { return symbol; } + +ServicePort * +FuncService::getPort(AppIDPath id, const BundleType *type, + const std::map &channels, + AcceleratorConnection &acc) const { + return new Function(id.back(), channels); +} + +FuncService::Function::Function( + AppID id, const std::map &channels) + : ServicePort(id, channels), + arg(dynamic_cast(channels.at("arg"))), + result(dynamic_cast(channels.at("result"))) { + if (channels.size() != 2) + throw runtime_error("FuncService must have exactly two channels"); +} + +void FuncService::Function::connect() { + arg.connect(); + result.connect(); +} + +std::future +FuncService::Function::call(const MessageData &argData) { + arg.write(argData); + return result.readAsync(); +} + +Service *ServiceRegistry::createService(AcceleratorConnection *acc, + Service::Type svcType, AppIDPath id, + std::string implName, + ServiceImplDetails details, + HWClientDetails clients) { + // TODO: Add a proper registration mechanism. + if (svcType == typeid(FuncService)) + return new FuncService(acc, id, implName, details, clients); + return nullptr; +} + +Service::Type ServiceRegistry::lookupServiceType(const std::string &svcName) { + // TODO: Add a proper registration mechanism. + if (svcName == "esi.service.std.func") + return typeid(FuncService); + return typeid(CustomService); +} diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp index 70d876c96a87..8073e48c8e7e 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp @@ -34,7 +34,7 @@ using namespace esi::backends::cosim; /// the cosimulation when it starts (which is useful when it chooses its own /// port). unique_ptr -CosimAccelerator::connect(string connectionString) { +CosimAccelerator::connect(Context &ctxt, string connectionString) { string portStr; string host = "localhost"; @@ -42,7 +42,7 @@ CosimAccelerator::connect(string connectionString) { if ((colon = connectionString.find(':')) != string::npos) { portStr = connectionString.substr(colon + 1); host = connectionString.substr(0, colon); - } else { + } else if (connectionString.ends_with("cosim.cfg")) { ifstream cfg(connectionString); string line, key, value; @@ -58,9 +58,22 @@ CosimAccelerator::connect(string connectionString) { if (portStr.size() == 0) throw runtime_error("port line not found in file"); + } else if (connectionString == "env") { + char *hostEnv = getenv("ESI_COSIM_HOST"); + if (hostEnv) + host = hostEnv; + else + host = "localhost"; + char *portEnv = getenv("ESI_COSIM_PORT"); + if (portEnv) + portStr = portEnv; + else + throw runtime_error("ESI_COSIM_PORT environment variable not set"); + } else { + throw runtime_error("Invalid connection string '" + connectionString + "'"); } uint16_t port = stoul(portStr); - return make_unique(host, port); + return make_unique(ctxt, host, port); } namespace { @@ -77,6 +90,9 @@ struct esi::backends::cosim::CosimAccelerator::Impl { // rpcClient. set> channels; + // Map from client path to channel assignments for that client. + map> clientChannelAssignments; + Impl(string hostname, uint16_t port) : rpcClient(hostname, port), waitScope(rpcClient.getWaitScope()), cosim(rpcClient.getMain()), lowLevel(nullptr) { @@ -85,10 +101,18 @@ struct esi::backends::cosim::CosimAccelerator::Impl { lowLevel = llPromise.wait(waitScope).getLowLevel(); } ~Impl(); + + /// Request the host side channel ports for a particular instance (identified + /// by the AppID path). For convenience, provide the bundle type and direction + /// of the bundle port. + std::map requestChannelsFor(AppIDPath, + const BundleType *); }; /// Construct and connect to a cosim server. -CosimAccelerator::CosimAccelerator(string hostname, uint16_t port) { +CosimAccelerator::CosimAccelerator(Context &ctxt, string hostname, + uint16_t port) + : AcceleratorConnection(ctxt) { impl = make_unique(hostname, port); } @@ -98,12 +122,12 @@ class CosimMMIO : public MMIO { CosimMMIO(EsiLowLevel::Client &llClient, kj::WaitScope &waitScope) : llClient(llClient), waitScope(waitScope) {} - uint64_t read(uint32_t addr) const override { + uint32_t read(uint32_t addr) const override { auto req = llClient.readMMIORequest(); req.setAddress(addr); return req.send().wait(waitScope).getData(); } - void write(uint32_t addr, uint64_t data) override { + void write(uint32_t addr, uint32_t data) override { auto req = llClient.writeMMIORequest(); req.setAddress(addr); req.setData(data); @@ -151,8 +175,8 @@ class CosimChannelPort { void connect(); void disconnect(); - void write(const void *data, size_t size); - std::ptrdiff_t read(void *data, size_t maxSize); + void write(const MessageData &); + bool read(MessageData &); protected: CosimAccelerator::Impl &impl; @@ -162,28 +186,26 @@ class CosimChannelPort { }; } // namespace -void CosimChannelPort::write(const void *data, size_t size) { +void CosimChannelPort::write(const MessageData &data) { if (!isConnected) throw runtime_error("Cannot write to a channel port that is not connected"); auto req = ep.sendFromHostRequest(); - req.setMsg( - capnp::Data::Reader(reinterpret_cast(data), size)); + req.setMsg(capnp::Data::Reader(data.getBytes(), data.getSize())); req.send().wait(impl.waitScope); } -std::ptrdiff_t CosimChannelPort::read(void *data, size_t maxSize) { +bool CosimChannelPort::read(MessageData &data) { auto req = ep.recvToHostRequest(); auto resp = req.send().wait(impl.waitScope); if (!resp.getHasData()) - return 0; + return false; capnp::Data::Reader msg = resp.getResp(); size_t size = msg.size(); - // TODO: buffer data over multiple calls. - if (size > maxSize) - return -1; - memcpy(data, msg.begin(), size); - return size; + std::vector vec(size); + memcpy(vec.data(), msg.begin(), size); + data = MessageData(vec); + return true; } esi::backends::cosim::CosimAccelerator::Impl::~Impl() { @@ -223,7 +245,7 @@ void CosimChannelPort::disconnect() { namespace { class WriteCosimChannelPort : public WriteChannelPort { public: - WriteCosimChannelPort(CosimAccelerator::Impl &impl, const Type &type, + WriteCosimChannelPort(CosimAccelerator::Impl &impl, const Type *type, string name) : WriteChannelPort(type), cosim(make_unique(impl, name)) {} @@ -232,21 +254,21 @@ class WriteCosimChannelPort : public WriteChannelPort { virtual void connect() override { cosim->connect(); } virtual void disconnect() override { cosim->disconnect(); } - virtual void write(const void *data, size_t size) override; + virtual void write(const MessageData &) override; protected: std::unique_ptr cosim; }; } // namespace -void WriteCosimChannelPort::write(const void *data, size_t size) { - cosim->write(data, size); +void WriteCosimChannelPort::write(const MessageData &data) { + cosim->write(data); } namespace { class ReadCosimChannelPort : public ReadChannelPort { public: - ReadCosimChannelPort(CosimAccelerator::Impl &impl, const Type &type, + ReadCosimChannelPort(CosimAccelerator::Impl &impl, const Type *type, string name) : ReadChannelPort(type), cosim(new CosimChannelPort(impl, name)) {} @@ -254,7 +276,7 @@ class ReadCosimChannelPort : public ReadChannelPort { virtual void connect() override { cosim->connect(); } virtual void disconnect() override { cosim->disconnect(); } - virtual std::ptrdiff_t read(void *data, size_t maxSize) override; + virtual bool read(MessageData &) override; protected: std::unique_ptr cosim; @@ -262,25 +284,53 @@ class ReadCosimChannelPort : public ReadChannelPort { } // namespace -std::ptrdiff_t ReadCosimChannelPort::read(void *data, size_t maxSize) { - return cosim->read(data, maxSize); +bool ReadCosimChannelPort::read(MessageData &data) { return cosim->read(data); } + +map +CosimAccelerator::Impl::requestChannelsFor(AppIDPath idPath, + const BundleType *bundleType) { + map channelResults; + + // Find the client details for the port at 'fullPath'. + auto f = clientChannelAssignments.find(idPath); + if (f == clientChannelAssignments.end()) + return channelResults; + const map &channelAssignments = f->second; + + // Each channel in a bundle has a separate cosim endpoint. Find them all. + for (auto [name, dir, type] : bundleType->getChannels()) { + auto f = channelAssignments.find(name); + if (f == channelAssignments.end()) + throw runtime_error("Could not find channel assignment for '" + + idPath.toStr() + "." + name + "'"); + string channelName = f->second; + + ChannelPort *port; + if (BundlePort::isWrite(dir)) + port = new WriteCosimChannelPort(*this, type, channelName); + else + port = new ReadCosimChannelPort(*this, type, channelName); + channels.emplace(port); + channelResults.emplace(name, *port); + } + return channelResults; } -namespace { -class CosimCustomService : public services::CustomService { -public: - CosimCustomService(CosimAccelerator::Impl &impl, AppIDPath idPath, - const ServiceImplDetails &details, - const HWClientDetails &clients) - : CustomService(idPath, details, clients), impl(impl) { - - // Compute our parents id path. - AppIDPath prefix = std::move(idPath); +map +CosimAccelerator::requestChannelsFor(AppIDPath idPath, + const BundleType *bundleType) { + return impl->requestChannelsFor(idPath, bundleType); +} +Service *CosimAccelerator::createService(Service::Type svcType, + AppIDPath idPath, std::string implName, + const ServiceImplDetails &details, + const HWClientDetails &clients) { + // Compute our parents idPath path. + AppIDPath prefix = std::move(idPath); + if (prefix.size() > 0) prefix.pop_back(); - // TODO: Sanity check that the cosim service was actually used. If not, the - // code below will fail. - + if (implName == "cosim") { // Get the channel assignments for each client. for (auto client : clients) { AppIDPath fullClientPath = prefix + client.relPath; @@ -289,59 +339,28 @@ class CosimCustomService : public services::CustomService { client.implOptions.at("channel_assignments"))) channelAssignments[assignment.first] = any_cast(assignment.second); - clientChannelAssignments[fullClientPath] = std::move(channelAssignments); + impl->clientChannelAssignments[fullClientPath] = + std::move(channelAssignments); } } - virtual map - requestChannelsFor(AppIDPath fullPath, const BundleType &bundleType, - BundlePort::Direction svcDir) override { - // Find the client details for the port at 'fullPath'. - auto f = clientChannelAssignments.find(fullPath); - if (f == clientChannelAssignments.end()) - throw runtime_error("Could not find channel assignments for '" + - fullPath.toStr() + "'"); - const map &channelAssignments = f->second; - - // Each channel in a bundle has a separate cosim endpoint. Find them all. - map channels; - for (auto [name, dir, type] : bundleType.getChannels()) { - auto f = channelAssignments.find(name); - if (f == channelAssignments.end()) - throw runtime_error("Could not find channel assignment for '" + - fullPath.toStr() + "." + name + "'"); - string channelName = f->second; - - ChannelPort *port; - if (BundlePort::isWrite(dir, svcDir)) - port = new WriteCosimChannelPort(impl, type, channelName); - else - port = new ReadCosimChannelPort(impl, type, channelName); - impl.channels.emplace(port); - channels.emplace(name, *port); + if (svcType == typeid(services::MMIO)) { + return new CosimMMIO(impl->lowLevel, impl->waitScope); + } else if (svcType == typeid(SysInfo)) { + switch (manifestMethod) { + case ManifestMethod::Cosim: + return new CosimSysInfo(impl->cosim, impl->waitScope); + case ManifestMethod::MMIO: + return new MMIOSysInfo(getService()); } - return channels; + } else if (svcType == typeid(CustomService) && implName == "cosim") { + return new CustomService(idPath, details, clients); } - -private: - // Map from client path to channel assignments for that client. - map> clientChannelAssignments; - CosimAccelerator::Impl &impl; -}; -} // namespace - -Service *CosimAccelerator::createService(Service::Type svcType, AppIDPath id, - std::string implName, - const ServiceImplDetails &details, - const HWClientDetails &clients) { - if (svcType == typeid(MMIO)) - return new CosimMMIO(impl->lowLevel, impl->waitScope); - else if (svcType == typeid(SysInfo)) - // return new MMIOSysInfo(getService()); - return new CosimSysInfo(impl->cosim, impl->waitScope); - else if (svcType == typeid(CustomService) && implName == "cosim") - return new CosimCustomService(*impl, id, details, clients); return nullptr; } +void CosimAccelerator::setManifestMethod(ManifestMethod method) { + manifestMethod = method; +} + REGISTER_ACCELERATOR("cosim", backends::cosim::CosimAccelerator); diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp index f037b8b84c0e..718ad97bcfb1 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp @@ -18,9 +18,11 @@ #include "esi/Services.h" #include "esi/Utils.h" +#include #include #include #include +#include using namespace std; @@ -64,6 +66,12 @@ struct esi::backends::trace::TraceAccelerator::Impl { const ServiceImplDetails &details, const HWClientDetails &clients); + /// Request the host side channel ports for a particular instance (identified + /// by the AppID path). For convenience, provide the bundle type and direction + /// of the bundle port. + std::map requestChannelsFor(AppIDPath, + const BundleType *); + void adoptChannelPort(ChannelPort *port) { channels.emplace_back(port); } void write(const AppIDPath &id, const string &portName, const void *data, @@ -85,10 +93,10 @@ void TraceAccelerator::Impl::write(const AppIDPath &id, const string &portName, } unique_ptr -TraceAccelerator::connect(string connectionString) { +TraceAccelerator::connect(Context &ctxt, string connectionString) { string modeStr; string manifestPath; - string traceFile = "trace.json"; + string traceFile = "trace.log"; // Parse the connection string. // :[:] @@ -111,12 +119,14 @@ TraceAccelerator::connect(string connectionString) { else throw runtime_error("unknown mode '" + modeStr + "'"); - return make_unique(mode, filesystem::path(manifestPath), - filesystem::path(traceFile)); + return make_unique( + ctxt, mode, filesystem::path(manifestPath), filesystem::path(traceFile)); } -TraceAccelerator::TraceAccelerator(Mode mode, filesystem::path manifestJson, - filesystem::path traceFile) { +TraceAccelerator::TraceAccelerator(Context &ctxt, Mode mode, + filesystem::path manifestJson, + filesystem::path traceFile) + : AcceleratorConnection(ctxt) { impl = make_unique(mode, manifestJson, traceFile); } @@ -157,12 +167,12 @@ class TraceSysInfo : public SysInfo { namespace { class WriteTraceChannelPort : public WriteChannelPort { public: - WriteTraceChannelPort(TraceAccelerator::Impl &impl, const Type &type, + WriteTraceChannelPort(TraceAccelerator::Impl &impl, const Type *type, const AppIDPath &id, const string &portName) : WriteChannelPort(type), impl(impl), id(id), portName(portName) {} - virtual void write(const void *data, size_t size) override { - impl.write(id, portName, data, size); + virtual void write(const MessageData &data) override { + impl.write(id, portName, data.getBytes(), data.getSize()); } protected: @@ -175,18 +185,31 @@ class WriteTraceChannelPort : public WriteChannelPort { namespace { class ReadTraceChannelPort : public ReadChannelPort { public: - ReadTraceChannelPort(TraceAccelerator::Impl &impl, const Type &type) + ReadTraceChannelPort(TraceAccelerator::Impl &impl, const Type *type) : ReadChannelPort(type) {} - virtual std::ptrdiff_t read(void *data, size_t maxSize) override; + virtual bool read(MessageData &data) override; + +private: + size_t numReads = 0; }; } // namespace -std::ptrdiff_t ReadTraceChannelPort::read(void *data, size_t maxSize) { - uint8_t *dataPtr = reinterpret_cast(data); - for (size_t i = 0; i < maxSize; ++i) - dataPtr[i] = rand() % 256; - return maxSize; +bool ReadTraceChannelPort::read(MessageData &data) { + if ((++numReads & 0x1) == 1) + return false; + + std::ptrdiff_t numBits = getType()->getBitWidth(); + if (numBits < 0) + // TODO: support other types. + throw runtime_error("unsupported type for read: " + getType()->getID()); + + std::ptrdiff_t size = (numBits + 7) / 8; + std::vector bytes(size); + for (std::ptrdiff_t i = 0; i < size; ++i) + bytes[i] = rand() % 256; + data = MessageData(bytes); + return true; } namespace { @@ -195,29 +218,32 @@ class TraceCustomService : public CustomService { TraceCustomService(TraceAccelerator::Impl &impl, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients) - : CustomService(idPath, details, clients), impl(impl) {} - - virtual map - requestChannelsFor(AppIDPath idPath, const BundleType &bundleType, - BundlePort::Direction svcDir) override { - map channels; - for (auto [name, dir, type] : bundleType.getChannels()) { - ChannelPort *port; - if (BundlePort::isWrite(dir, svcDir)) - port = new WriteTraceChannelPort(impl, type, idPath, name); - else - port = new ReadTraceChannelPort(impl, type); - channels.emplace(name, *port); - impl.adoptChannelPort(port); - } - return channels; - } - -private: - TraceAccelerator::Impl &impl; + : CustomService(idPath, details, clients) {} }; } // namespace +map +TraceAccelerator::Impl::requestChannelsFor(AppIDPath idPath, + const BundleType *bundleType) { + map channels; + for (auto [name, dir, type] : bundleType->getChannels()) { + ChannelPort *port; + if (BundlePort::isWrite(dir)) + port = new WriteTraceChannelPort(*this, type, idPath, name); + else + port = new ReadTraceChannelPort(*this, type); + channels.emplace(name, *port); + adoptChannelPort(port); + } + return channels; +} + +map +TraceAccelerator::requestChannelsFor(AppIDPath idPath, + const BundleType *bundleType) { + return impl->requestChannelsFor(idPath, bundleType); +} + Service * TraceAccelerator::Impl::createService(Service::Type svcType, AppIDPath idPath, const ServiceImplDetails &details, diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp new file mode 100644 index 000000000000..d93df7020c2e --- /dev/null +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp @@ -0,0 +1,115 @@ +//===- Xrt.cpp - ESI XRT device backend -------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// DO NOT EDIT! +// This file is distributed as part of an ESI package. The source for this file +// should always be modified within CIRCT +// (lib/dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp). +// +//===----------------------------------------------------------------------===// + +#include "esi/backends/Xrt.h" +#include "esi/Services.h" + +// XRT includes +#include "experimental/xrt_bo.h" +#include "experimental/xrt_device.h" +#include "experimental/xrt_ip.h" +#include "experimental/xrt_xclbin.h" + +#include +#include +#include + +using namespace std; + +using namespace esi; +using namespace esi::services; +using namespace esi::backends::xrt; + +/// Parse the connection string and instantiate the accelerator. Connection +/// string format: +/// [:] +/// wherein is in BDF format. +unique_ptr +XrtAccelerator::connect(Context &ctxt, string connectionString) { + string xclbin; + string device_id; + + size_t colon = connectionString.find(':'); + if (colon == string::npos) { + xclbin = connectionString; + } else { + xclbin = connectionString.substr(0, colon); + device_id = connectionString.substr(colon + 1); + } + + return make_unique(ctxt, xclbin, device_id); +} + +struct esi::backends::xrt::XrtAccelerator::Impl { + constexpr static char kernel[] = "esi_kernel"; + + Impl(string xclbin, string device_id) { + if (device_id.empty()) + device = ::xrt::device(0); + else + device = ::xrt::device(device_id); + + auto uuid = device.load_xclbin(xclbin); + ip = ::xrt::ip(device, uuid, kernel); + } + + std::map requestChannelsFor(AppIDPath, + const BundleType *) { + throw runtime_error("XRT does not support channel communication yet"); + } + + ::xrt::device device; + ::xrt::ip ip; +}; + +/// Construct and connect to a cosim server. +XrtAccelerator::XrtAccelerator(Context &ctxt, string xclbin, string device_id) + : AcceleratorConnection(ctxt) { + impl = make_unique(xclbin, device_id); +} + +namespace { +class XrtMMIO : public MMIO { +public: + XrtMMIO(::xrt::ip &ip) : ip(ip) {} + + uint32_t read(uint32_t addr) const override { return ip.read_register(addr); } + void write(uint32_t addr, uint32_t data) override { + ip.write_register(addr, data); + } + +private: + ::xrt::ip &ip; +}; +} // namespace + +map +XrtAccelerator::requestChannelsFor(AppIDPath idPath, + const BundleType *bundleType) { + return impl->requestChannelsFor(idPath, bundleType); +} + +Service *XrtAccelerator::createService(Service::Type svcType, AppIDPath id, + std::string implName, + const ServiceImplDetails &details, + const HWClientDetails &clients) { + if (svcType == typeid(MMIO)) + return new XrtMMIO(impl->ip); + else if (svcType == typeid(SysInfo)) + return new MMIOSysInfo(getService()); + return nullptr; +} + +REGISTER_ACCELERATOR("xrt", backends::xrt::XrtAccelerator); diff --git a/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp b/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp index d29ac033fb9d..af595104caa3 100644 --- a/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp +++ b/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp @@ -26,6 +26,7 @@ using namespace std; using namespace esi; void printInfo(ostream &os, AcceleratorConnection &acc); +void printHier(ostream &os, AcceleratorConnection &acc); int main(int argc, const char *argv[]) { // TODO: find a command line parser library rather than doing this by hand. @@ -35,6 +36,8 @@ int main(int argc, const char *argv[]) { return -1; } + Context ctxt; + const char *backend = argv[1]; const char *conn = argv[2]; string cmd; @@ -42,7 +45,7 @@ int main(int argc, const char *argv[]) { cmd = argv[3]; try { - unique_ptr acc = registry::connect(backend, conn); + unique_ptr acc = ctxt.connect(backend, conn); const auto &info = *acc->getService(); if (cmd == "version") @@ -51,7 +54,8 @@ int main(int argc, const char *argv[]) { cout << info.getJsonManifest() << endl; else if (cmd == "info") printInfo(cout, *acc); - // TODO: add a command to print out the instance hierarchy. + else if (cmd == "hier") + printHier(cout, *acc); else { cout << "Connection successful." << endl; if (!cmd.empty()) { @@ -68,20 +72,57 @@ int main(int argc, const char *argv[]) { void printInfo(ostream &os, AcceleratorConnection &acc) { string jsonManifest = acc.getService()->getJsonManifest(); - Manifest m(jsonManifest); + Manifest m(acc.getCtxt(), jsonManifest); os << "API version: " << m.getApiVersion() << endl << endl; os << "********************************" << endl; - os << "* Design information" << endl; + os << "* Module information" << endl; os << "********************************" << endl; os << endl; for (ModuleInfo mod : m.getModuleInfos()) - os << mod << endl; + os << "- " << mod; + os << endl; os << "********************************" << endl; os << "* Type table" << endl; os << "********************************" << endl; os << endl; size_t i = 0; - for (Type t : m.getTypeTable()) - os << " " << i++ << ": " << t.getID() << endl; + for (const Type *t : m.getTypeTable()) + os << " " << i++ << ": " << t->getID() << endl; +} + +void printPort(ostream &os, const BundlePort &port, string indent = "") { + os << indent << " " << port.getID() << ":" << endl; + for (const auto &[name, chan] : port.getChannels()) { + os << indent << " " << name << ": " << chan.getType()->getID() << endl; + } +} + +void printInstance(ostream &os, const HWModule *d, string indent = "") { + os << indent << "* Instance:"; + if (auto inst = dynamic_cast(d)) + os << inst->getID() << endl; + else + os << "top" << endl; + os << indent << "* Ports:" << endl; + for (const BundlePort &port : d->getPortsOrdered()) + printPort(os, port, indent + " "); + std::vector children = d->getChildrenOrdered(); + if (!children.empty()) { + os << indent << "* Children:" << endl; + for (const Instance *child : d->getChildrenOrdered()) + printInstance(os, child, indent + " "); + } + os << endl; +} + +void printHier(ostream &os, AcceleratorConnection &acc) { + Manifest manifest(acc.getCtxt(), + acc.getService()->getJsonManifest()); + std::unique_ptr design = manifest.buildAccelerator(acc); + os << "********************************" << endl; + os << "* Design hierarchy" << endl; + os << "********************************" << endl; + os << endl; + printInstance(os, design.get()); } diff --git a/lib/Dialect/ESI/runtime/python/esi/accelerator.py b/lib/Dialect/ESI/runtime/python/esi/accelerator.py index 968152400f32..5fbcd07b65a8 100644 --- a/lib/Dialect/ESI/runtime/python/esi/accelerator.py +++ b/lib/Dialect/ESI/runtime/python/esi/accelerator.py @@ -1,13 +1,82 @@ +# ===-----------------------------------------------------------------------===# # 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 structure of the Python classes and hierarchy roughly mirrors the C++ +# side, but wraps the C++ objects. The wrapper classes sometimes add convenience +# functionality and serve to return wrapped versions of the returned objects. +# +# ===-----------------------------------------------------------------------===# +from typing import Dict, Optional + +from .types import BundlePort from . import esiCppAccel as cpp +# Global context for the C++ side. +ctxt = cpp.Context() + + +class AcceleratorConnection: + """A connection to an ESI accelerator.""" -class AcceleratorConnection(cpp.AcceleratorConnection): - """An ESI accelerator.""" + def __init__(self, platform: str, connection_str: str): + self.cpp_accel = cpp.AcceleratorConnection(ctxt, platform, connection_str) def manifest(self) -> cpp.Manifest: - """Returns the accelerator's manifest.""" - return cpp.Manifest(self.sysinfo().json_manifest()) + """Get and parse the accelerator manifest.""" + return cpp.Manifest(ctxt, self.cpp_accel.sysinfo().json_manifest()) + + def sysinfo(self) -> cpp.SysInfo: + return self.cpp_accel.sysinfo() + + def build_accelerator(self) -> "Accelerator": + return Accelerator(self.manifest().build_accelerator(self.cpp_accel)) + + def get_service_mmio(self) -> cpp.MMIO: + return self.cpp_accel.get_service_mmio() + + +class HWModule: + """Represents either the top level or an instance of a hardware module.""" + + def __init__(self, parent: Optional["HWModule"], cpp_hwmodule: cpp.HWModule): + self.parent = parent + self.cpp_hwmodule = cpp_hwmodule + + @property + def children(self) -> Dict[cpp.AppID, "Instance"]: + return { + name: Instance(self, inst) + for name, inst in self.cpp_hwmodule.children.items() + } + + @property + def ports(self) -> Dict[cpp.AppID, BundlePort]: + return { + name: BundlePort(self, port) + for name, port in self.cpp_hwmodule.ports.items() + } + + +class Instance(HWModule): + """Subclass of `HWModule` which represents a submodule instance. Adds an + AppID, which the top level doesn't have or need.""" + + def __init__(self, parent: Optional["HWModule"], cpp_instance: cpp.Instance): + super().__init__(parent, cpp_instance) + self.cpp_hwmodule: cpp.Instance = cpp_instance + + @property + def id(self) -> cpp.AppID: + return self.cpp_hwmodule.id + + +class Accelerator(HWModule): + """Root of the accelerator design hierarchy.""" + + def __init__(self, cpp_accelerator: cpp.Accelerator): + super().__init__(None, cpp_accelerator) + self.cpp_hwmodule = cpp_accelerator diff --git a/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.cpp b/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.cpp index 267508129813..ca8926a0fe31 100644 --- a/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.cpp +++ b/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.cpp @@ -13,6 +13,8 @@ #include "esi/Accelerator.h" #include "esi/Services.h" +#include "esi/backends/Cosim.h" + #include // pybind11 includes @@ -49,14 +51,15 @@ PYBIND11_MODULE(esiCppAccel, m) { .def("__repr__", [](Type &t) { return "<" + t.getID() + ">"; }); py::class_(m, "ChannelType") .def_property_readonly("inner", &ChannelType::getInner, - py::return_value_policy::reference_internal); + py::return_value_policy::reference); py::enum_(m, "Direction") .value("To", BundleType::Direction::To) .value("From", BundleType::Direction::From) .export_values(); py::class_(m, "BundleType") .def_property_readonly("channels", &BundleType::getChannels, - py::return_value_policy::reference_internal); + py::return_value_policy::reference); + py::class_(m, "VoidType"); py::class_(m, "AnyType"); py::class_(m, "BitVectorType") .def_property_readonly("width", &BitVectorType::getWidth); @@ -66,12 +69,16 @@ PYBIND11_MODULE(esiCppAccel, m) { py::class_(m, "UIntType"); py::class_(m, "StructType") .def_property_readonly("fields", &StructType::getFields, - py::return_value_policy::reference_internal); + py::return_value_policy::reference); py::class_(m, "ArrayType") .def_property_readonly("element", &ArrayType::getElementType, - py::return_value_policy::reference_internal) + py::return_value_policy::reference) .def_property_readonly("size", &ArrayType::getSize); + py::class_(m, "Context") + .def(py::init<>()) + .def("connect", &Context::connect); + py::class_(m, "ModuleInfo") .def_property_readonly("name", [](ModuleInfo &info) { return info.name; }) .def_property_readonly("summary", @@ -122,24 +129,36 @@ PYBIND11_MODULE(esiCppAccel, m) { (std::hash{}(id.idx.value_or(-1)) << 1); }); + py::class_>(m, "MessageDataFuture") + .def("valid", &std::future::valid) + .def("wait", &std::future::wait) + .def("get", [](std::future &f) { + MessageData data = f.get(); + return py::bytearray((const char *)data.getBytes(), data.getSize()); + }); + py::class_(m, "ChannelPort") .def("connect", &ChannelPort::connect) .def_property_readonly("type", &ChannelPort::getType, - py::return_value_policy::reference_internal); + py::return_value_policy::reference); py::class_(m, "WriteChannelPort") - .def("write", [](WriteChannelPort &p, std::vector data) { - p.write(data.data(), data.size()); + .def("write", [](WriteChannelPort &p, py::bytearray &data) { + py::buffer_info info(py::buffer(data).request()); + std::vector dataVec((uint8_t *)info.ptr, + (uint8_t *)info.ptr + info.size); + p.write(dataVec); }); py::class_(m, "ReadChannelPort") - .def("read", [](ReadChannelPort &p, size_t maxSize) { - std::vector data(maxSize); - std::ptrdiff_t size = p.read(data.data(), data.size()); - if (size < 0) - throw std::runtime_error("read failed"); - data.resize(size); - return data; - }); + .def("read", + [](ReadChannelPort &p) -> py::object { + MessageData data; + if (!p.read(data)) + return py::none(); + return py::bytearray((const char *)data.getBytes(), + data.getSize()); + }) + .def("read_async", &ReadChannelPort::readAsync); py::class_(m, "BundlePort") .def_property_readonly("id", &BundlePort::getID) @@ -150,15 +169,30 @@ PYBIND11_MODULE(esiCppAccel, m) { .def("getRead", &BundlePort::getRawRead, py::return_value_policy::reference); + py::class_(m, "ServicePort"); + py::class_(m, "Function") + .def( + "call", + [](FuncService::Function &self, + py::bytearray msg) -> std::future { + py::buffer_info info(py::buffer(msg).request()); + std::vector dataVec((uint8_t *)info.ptr, + (uint8_t *)info.ptr + info.size); + MessageData data(dataVec); + return self.call(data); + }, + py::return_value_policy::take_ownership) + .def("connect", &FuncService::Function::connect); + // Store this variable (not commonly done) as the "children" method needs for // "Instance" to be defined first. auto hwmodule = py::class_(m, "HWModule") .def_property_readonly("info", &HWModule::getInfo) .def_property_readonly("ports", &HWModule::getPorts, - py::return_value_policy::reference_internal); + py::return_value_policy::reference); - // In order to inherit methods from "Design", it needs to be defined first. + // In order to inherit methods from "HWModule", it needs to be defined first. py::class_(m, "Instance") .def_property_readonly("id", &Instance::getID); @@ -167,7 +201,15 @@ PYBIND11_MODULE(esiCppAccel, m) { // Since this returns a vector of Instance*, we need to define Instance first // or else pybind11-stubgen complains. hwmodule.def_property_readonly("children", &HWModule::getChildren, - py::return_value_policy::reference_internal); + py::return_value_policy::reference); + + py::enum_( + m, "CosimManifestMethod") + .value("ManifestCosim", + backends::cosim::CosimAccelerator::ManifestMethod::Cosim) + .value("ManifestMMIO", + backends::cosim::CosimAccelerator::ManifestMethod::MMIO) + .export_values(); py::class_(m, "AcceleratorConnection") .def(py::init(®istry::connect)) @@ -176,17 +218,31 @@ PYBIND11_MODULE(esiCppAccel, m) { [](AcceleratorConnection &acc) { return acc.getService({}); }, - py::return_value_policy::reference_internal) + py::return_value_policy::reference) .def( "get_service_mmio", [](AcceleratorConnection &acc) { return acc.getService({}); }, - py::return_value_policy::reference_internal); + py::return_value_policy::reference) + // This is a bit of a hack to test. Come up with a more generic way to set + // accelerator-specific properties/configurations. + .def("set_manifest_method", + [](AcceleratorConnection &acc, + backends::cosim::CosimAccelerator::ManifestMethod method) { + auto cosim = + dynamic_cast(&acc); + if (!cosim) + throw std::runtime_error( + "set_manifest_method only supported for cosim connections"); + cosim->setManifestMethod(method); + }); py::class_(m, "Manifest") - .def(py::init()) + .def(py::init()) .def_property_readonly("api_version", &Manifest::getApiVersion) - .def("build_accelerator", &Manifest::buildAccelerator) - .def_property_readonly("type_table", &Manifest::getTypeTable); + .def("build_accelerator", &Manifest::buildAccelerator, + py::return_value_policy::take_ownership) + .def_property_readonly("type_table", &Manifest::getTypeTable) + .def_property_readonly("module_infos", &Manifest::getModuleInfos); } diff --git a/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.pyi b/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.pyi index 71d85c62bafa..d0cbd8a379e1 100644 --- a/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.pyi +++ b/lib/Dialect/ESI/runtime/python/esi/esiCppAccel.pyi @@ -4,14 +4,16 @@ # None yet. Though we're assuming that we will have some at some point. from __future__ import annotations +from ast import Mod import typing __all__ = [ 'Accelerator', 'AcceleratorConnection', 'AnyType', 'AppID', 'ArrayType', 'BitVectorType', 'BitsType', 'BundlePort', 'BundleType', 'ChannelPort', - 'ChannelType', 'Direction', 'From', 'HWModule', 'Instance', 'IntegerType', - 'MMIO', 'Manifest', 'ModuleInfo', 'ReadChannelPort', 'SIntType', - 'StructType', 'SysInfo', 'To', 'Type', 'UIntType', 'WriteChannelPort' + 'ChannelType', 'CosimManifestMethod', 'Direction', 'From', 'HWModule', + 'Instance', 'IntegerType', 'MMIO', 'Manifest', 'ManifestCosim', + 'ManifestMMIO', 'ModuleInfo', 'ReadChannelPort', 'SIntType', 'StructType', + 'SysInfo', 'To', 'Type', 'UIntType', 'VoidType', 'WriteChannelPort' ] @@ -27,6 +29,9 @@ class AcceleratorConnection: def get_service_mmio(self) -> MMIO: ... + def set_manifest_method(self, arg0: CosimManifestMethod) -> None: + ... + def sysinfo(self) -> SysInfo: ... @@ -121,6 +126,61 @@ class ChannelType(Type): ... +class CosimManifestMethod: + """ + Members: + + ManifestCosim + + ManifestMMIO + """ + ManifestCosim: typing.ClassVar[ + CosimManifestMethod] # value = + ManifestMMIO: typing.ClassVar[ + CosimManifestMethod] # value = + __members__: typing.ClassVar[dict[ + str, + CosimManifestMethod]] # value = {'ManifestCosim': , 'ManifestMMIO': } + + def __eq__(self, other: typing.Any) -> bool: + ... + + def __getstate__(self) -> int: + ... + + def __hash__(self) -> int: + ... + + def __index__(self) -> int: + ... + + def __init__(self, value: int) -> None: + ... + + def __int__(self) -> int: + ... + + def __ne__(self, other: typing.Any) -> bool: + ... + + def __repr__(self) -> str: + ... + + def __setstate__(self, state: int) -> None: + ... + + def __str__(self) -> str: + ... + + @property + def name(self) -> str: + ... + + @property + def value(self) -> int: + ... + + class Direction: """ Members: @@ -225,6 +285,10 @@ class Manifest: def type_table(self) -> list[Type]: ... + @property + def module_infos(self) -> list[ModuleInfo]: + ... + class ModuleInfo: @@ -254,7 +318,7 @@ class ModuleInfo: class ReadChannelPort(ChannelPort): - def read(self, arg0: int) -> list[int]: + def read(self, arg0: int) -> bytearray: ... @@ -292,11 +356,17 @@ class UIntType(IntegerType): pass +class VoidType(Type): + pass + + class WriteChannelPort(ChannelPort): - def write(self, arg0: list[int]) -> None: + def write(self, arg0: bytearray) -> None: ... From: Direction # value = +ManifestCosim: CosimManifestMethod # value = +ManifestMMIO: CosimManifestMethod # value = To: Direction # value = diff --git a/lib/Dialect/ESI/runtime/python/esi/types.py b/lib/Dialect/ESI/runtime/python/esi/types.py new file mode 100644 index 000000000000..594545769939 --- /dev/null +++ b/lib/Dialect/ESI/runtime/python/esi/types.py @@ -0,0 +1,424 @@ +# ===-----------------------------------------------------------------------===# +# 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 structure of the Python classes and hierarchy roughly mirrors the C++ +# side, but wraps the C++ objects. The wrapper classes sometimes add convenience +# functionality and serve to return wrapped versions of the returned objects. +# +# ===-----------------------------------------------------------------------===# + +from __future__ import annotations + +from . import esiCppAccel as cpp + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .accelerator import HWModule + +from concurrent.futures import Future +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union + + +def _get_esi_type(cpp_type: cpp.Type): + """Get the wrapper class for a C++ type.""" + for cpp_type_cls, fn in __esi_mapping.items(): + if isinstance(cpp_type, cpp_type_cls): + return fn(cpp_type) + return ESIType(cpp_type) + + +# Mapping from C++ types to functions constructing the Python object +# corresponding to that type. +__esi_mapping: Dict[Type, Callable] = { + cpp.ChannelType: lambda cpp_type: _get_esi_type(cpp_type.inner) +} + + +class ESIType: + + def __init__(self, cpp_type: cpp.Type): + self.cpp_type = cpp_type + + @property + def supports_host(self) -> Tuple[bool, Optional[str]]: + """Does this type support host communication via Python? Returns either + '(True, None)' if it is, or '(False, reason)' if it is not.""" + + if self.bit_width % 8 != 0: + return (False, "runtime only supports types with multiple of 8 bits") + return (True, None) + + def is_valid(self, obj) -> Tuple[bool, Optional[str]]: + """Is a Python object compatible with HW type? Returns either '(True, + None)' if it is, or '(False, reason)' if it is not.""" + assert False, "unimplemented" + + @property + def bit_width(self) -> int: + """Size of this type, in bits. Negative for unbounded types.""" + assert False, "unimplemented" + + @property + def max_size(self) -> int: + """Maximum size of a value of this type, in bytes.""" + bitwidth = int((self.bit_width + 7) / 8) + if bitwidth < 0: + return bitwidth + return bitwidth + + def serialize(self, obj) -> bytearray: + """Convert a Python object to a bytearray.""" + assert False, "unimplemented" + + def deserialize(self, data: bytearray) -> Tuple[object, bytearray]: + """Convert a bytearray to a Python object. Return the object and the + leftover bytes.""" + assert False, "unimplemented" + + def __str__(self) -> str: + return str(self.cpp_type) + + +class VoidType(ESIType): + + def is_valid(self, obj) -> Tuple[bool, Optional[str]]: + if obj is not None: + return (False, f"void type cannot must represented by None, not {obj}") + return (True, None) + + @property + def bit_width(self) -> int: + return 8 + + def serialize(self, obj) -> bytearray: + # By convention, void is represented by a single byte of value 0. + return bytearray([0]) + + def deserialize(self, data: bytearray) -> Tuple[object, bytearray]: + if len(data) == 0: + raise ValueError(f"void type cannot be represented by {data}") + return (None, data[1:]) + + +__esi_mapping[cpp.VoidType] = VoidType + + +class BitsType(ESIType): + + def __init__(self, cpp_type: cpp.BitsType): + self.cpp_type: cpp.BitsType = cpp_type + + def is_valid(self, obj) -> Tuple[bool, Optional[str]]: + if not isinstance(obj, (bytearray, bytes, list)): + return (False, f"invalid type: {type(obj)}") + if isinstance(obj, list) and not all( + [isinstance(b, int) and b.bit_length() <= 8 for b in obj]): + return (False, f"list item too large: {obj}") + if len(obj) != self.max_size: + return (False, f"wrong size: {len(obj)}") + return (True, None) + + @property + def bit_width(self) -> int: + return self.cpp_type.width + + def serialize(self, obj: Union[bytearray, bytes, List[int]]) -> bytearray: + if isinstance(obj, bytearray): + return obj + if isinstance(obj, bytes) or isinstance(obj, list): + return bytearray(obj) + raise ValueError(f"cannot convert {obj} to bytearray") + + def deserialize(self, data: bytearray) -> Tuple[bytearray, bytearray]: + return (data[0:self.max_size], data[self.max_size:]) + + +__esi_mapping[cpp.BitsType] = BitsType + + +class IntType(ESIType): + + def __init__(self, cpp_type: cpp.IntegerType): + self.cpp_type: cpp.IntegerType = cpp_type + + @property + def bit_width(self) -> int: + return self.cpp_type.width + + +class UIntType(IntType): + + def is_valid(self, obj) -> Tuple[bool, Optional[str]]: + if not isinstance(obj, int): + return (False, f"must be an int, not {type(obj)}") + if obj < 0 or obj.bit_length() > self.bit_width: + return (False, f"out of range: {obj}") + return (True, None) + + def __str__(self) -> str: + return f"uint{self.bit_width}" + + def serialize(self, obj: int) -> bytearray: + return bytearray(int.to_bytes(obj, self.max_size, "little")) + + def deserialize(self, data: bytearray) -> Tuple[int, bytearray]: + return (int.from_bytes(data[0:self.max_size], + "little"), data[self.max_size:]) + + +__esi_mapping[cpp.UIntType] = UIntType + + +class SIntType(IntType): + + def is_valid(self, obj) -> Tuple[bool, Optional[str]]: + if not isinstance(obj, int): + return (False, f"must be an int, not {type(obj)}") + if obj < 0: + if (-1 * obj) > 2**(self.bit_width - 1): + return (False, f"out of range: {obj}") + elif obj < 0: + if obj >= 2**(self.bit_width - 1) - 1: + return (False, f"out of range: {obj}") + return (True, None) + + def __str__(self) -> str: + return f"sint{self.bit_width}" + + def serialize(self, obj: int) -> bytearray: + return bytearray(int.to_bytes(obj, self.max_size, "little", signed=True)) + + def deserialize(self, data: bytearray) -> Tuple[int, bytearray]: + return (int.from_bytes(data[0:self.max_size], "little", + signed=True), data[self.max_size:]) + + +__esi_mapping[cpp.SIntType] = SIntType + + +class StructType(ESIType): + + def __init__(self, cpp_type: cpp.StructType): + self.cpp_type = cpp_type + self.fields: List[Tuple[str, ESIType]] = [ + (name, _get_esi_type(ty)) for (name, ty) in cpp_type.fields + ] + + @property + def bit_width(self) -> int: + widths = [ty.bit_width for (_, ty) in self.fields] + if any([w < 0 for w in widths]): + return -1 + return sum(widths) + + def is_valid(self, obj) -> Tuple[bool, Optional[str]]: + fields_count = 0 + if not isinstance(obj, dict): + obj = obj.__dict__ + + for (fname, ftype) in self.fields: + if fname not in obj: + return (False, f"missing field '{fname}'") + fvalid, reason = ftype.is_valid(obj[fname]) + if not fvalid: + return (False, f"invalid field '{fname}': {reason}") + fields_count += 1 + if fields_count != len(obj): + return (False, "missing fields") + return (True, None) + + def serialize(self, obj) -> bytearray: + ret = bytearray() + for (fname, ftype) in reversed(self.fields): + fval = obj[fname] + ret.extend(ftype.serialize(fval)) + return ret + + def deserialize(self, data: bytearray) -> Tuple[Dict[str, Any], bytearray]: + ret = {} + for (fname, ftype) in reversed(self.fields): + (fval, data) = ftype.deserialize(data) + ret[fname] = fval + return (ret, data) + + +__esi_mapping[cpp.StructType] = StructType + + +class ArrayType(ESIType): + + def __init__(self, cpp_type: cpp.ArrayType): + self.cpp_type = cpp_type + self.element_type = _get_esi_type(cpp_type.element) + self.size = cpp_type.size + + @property + def bit_width(self) -> int: + return self.element_type.bit_width * self.size + + def is_valid(self, obj) -> Tuple[bool, Optional[str]]: + if not isinstance(obj, list): + return (False, f"must be a list, not {type(obj)}") + if len(obj) != self.size: + return (False, f"wrong size: expected {self.size} not {len(obj)}") + for (idx, e) in enumerate(obj): + evalid, reason = self.element_type.is_valid(e) + if not evalid: + return (False, f"invalid element {idx}: {reason}") + return (True, None) + + def serialize(self, lst: list) -> bytearray: + ret = bytearray() + for e in reversed(lst): + ret.extend(self.element_type.serialize(e)) + return ret + + def deserialize(self, data: bytearray) -> Tuple[List[Any], bytearray]: + ret = [] + for _ in range(self.size): + (obj, data) = self.element_type.deserialize(data) + ret.append(obj) + ret.reverse() + return (ret, data) + + +__esi_mapping[cpp.ArrayType] = ArrayType + + +class Port: + """A unidirectional communication channel. This is the basic communication + method with an accelerator.""" + + def __init__(self, owner: BundlePort, cpp_port: cpp.ChannelPort): + self.owner = owner + self.cpp_port = cpp_port + self.type = _get_esi_type(cpp_port.type) + (supports_host, reason) = self.type.supports_host + if not supports_host: + raise TypeError(f"unsupported type: {reason}") + + def connect(self): + self.cpp_port.connect() + return self + + +class WritePort(Port): + """A unidirectional communication channel from the host to the accelerator.""" + + def __init__(self, owner: BundlePort, cpp_port: cpp.WriteChannelPort): + super().__init__(owner, cpp_port) + self.cpp_port: cpp.WriteChannelPort = cpp_port + + def write(self, msg=None) -> bool: + """Write a typed message to the channel. Attempts to serialize 'msg' to what + the accelerator expects, but will fail if the object is not convertible to + the port type.""" + + valid, reason = self.type.is_valid(msg) + if not valid: + raise ValueError( + f"'{msg}' cannot be converted to '{self.type}': {reason}") + msg_bytes: bytearray = self.type.serialize(msg) + self.cpp_port.write(msg_bytes) + return True + + +class ReadPort(Port): + """A unidirectional communication channel from the accelerator to the host.""" + + def __init__(self, owner: BundlePort, cpp_port: cpp.ReadChannelPort): + super().__init__(owner, cpp_port) + self.cpp_port: cpp.ReadChannelPort = cpp_port + + def read(self) -> Tuple[bool, Optional[object]]: + """Read a typed message from the channel. Returns a deserialized object of a + type defined by the port type.""" + + buffer = self.cpp_port.read() + if buffer is None: + return (False, None) + (msg, leftover) = self.type.deserialize(buffer) + if len(leftover) != 0: + raise ValueError(f"leftover bytes: {leftover}") + return (True, msg) + + +class BundlePort: + """A collections of named, unidirectional communication channels.""" + + # When creating a new port, we need to determine if it is a service port and + # instantiate it correctly. + def __new__(cls, owner: HWModule, cpp_port: cpp.BundlePort): + # TODO: add a proper registration mechanism for service ports. + if isinstance(cpp_port, cpp.Function): + return super().__new__(FunctionPort) + return super().__new__(cls) + + def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort): + self.owner = owner + self.cpp_port = cpp_port + + def write_port(self, channel_name: str) -> WritePort: + return WritePort(self, self.cpp_port.getWrite(channel_name)) + + def read_port(self, channel_name: str) -> ReadPort: + return ReadPort(self, self.cpp_port.getRead(channel_name)) + + +class MessageFuture(Future): + """A specialization of `Future` for ESI messages. Wraps the cpp object and + deserializes the result. Hopefully overrides all the methods necessary for + proper operation, which is assumed to be not all of them.""" + + def __init__(self, result_type: Type, cpp_future: cpp.MessageDataFuture): + self.result_type = result_type + self.cpp_future = cpp_future + + def running(self) -> bool: + return True + + def done(self) -> bool: + return self.cpp_future.valid() + + def result(self, timeout: Optional[Union[int, float]] = None) -> Any: + # TODO: respect timeout + self.cpp_future.wait() + result_bytes = self.cpp_future.get() + (msg, leftover) = self.result_type.deserialize(result_bytes) + if len(leftover) != 0: + raise ValueError(f"leftover bytes: {leftover}") + return msg + + def add_done_callback(self, fn: Callable[[Future], object]) -> None: + raise NotImplementedError("add_done_callback is not implemented") + + +class FunctionPort(BundlePort): + """A pair of channels which carry the input and output of a function.""" + + def __init__(self, owner: HWModule, cpp_port: cpp.BundlePort): + super().__init__(owner, cpp_port) + self.arg_type = self.write_port("arg").type + self.result_type = self.read_port("result").type + self.connected = False + + def connect(self): + self.cpp_port.connect() + self.connected = True + + def call(self, **kwargs: Any) -> Future: + """Call the function with the given argument and returns a future of the + result.""" + valid, reason = self.arg_type.is_valid(kwargs) + if not valid: + raise ValueError( + f"'{kwargs}' cannot be converted to '{self.arg_type}': {reason}") + arg_bytes: bytearray = self.arg_type.serialize(kwargs) + cpp_future = self.cpp_port.call(arg_bytes) + return MessageFuture(self.result_type, cpp_future) + + def __call__(self, *args: Any, **kwds: Any) -> Future: + return self.call(*args, **kwds) diff --git a/lib/Dialect/Emit/CMakeLists.txt b/lib/Dialect/Emit/CMakeLists.txt new file mode 100644 index 000000000000..37089dc80f39 --- /dev/null +++ b/lib/Dialect/Emit/CMakeLists.txt @@ -0,0 +1,30 @@ +##===- CMakeLists.txt - build definitions for Emit ------------*- cmake -*-===// +## +## 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 +## +##===----------------------------------------------------------------------===// +## +## +##===----------------------------------------------------------------------===// + +add_circt_dialect_library(CIRCTEmit + EmitDialect.cpp + EmitOps.cpp + + ADDITIONAL_HEADER_DIRS + ${CIRCT_MAIN_INCLUDE_DIR}/circt/Dialect/Emit + + DEPENDS + MLIREmitIncGen + + LINK_COMPONENTS + Support + + LINK_LIBS PUBLIC + CIRCTHW + MLIRIR + MLIRPass + MLIRTransforms +) diff --git a/lib/Dialect/Emit/EmitDialect.cpp b/lib/Dialect/Emit/EmitDialect.cpp new file mode 100644 index 000000000000..8b5f98063437 --- /dev/null +++ b/lib/Dialect/Emit/EmitDialect.cpp @@ -0,0 +1,34 @@ +//===- EmitDialect.cpp - Implement the Emit 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 file implements the Emit dialect. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Emit/EmitDialect.h" +#include "circt/Dialect/Emit/EmitOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/DialectImplementation.h" + +using namespace circt; +using namespace emit; + +//===----------------------------------------------------------------------===// +// Dialect specification. +//===----------------------------------------------------------------------===// + +void EmitDialect::initialize() { + addOperations< +#define GET_OP_LIST +#include "circt/Dialect/Emit/Emit.cpp.inc" + >(); +} + +#include "circt/Dialect/Emit/EmitDialect.cpp.inc" diff --git a/lib/Dialect/Emit/EmitOps.cpp b/lib/Dialect/Emit/EmitOps.cpp new file mode 100644 index 000000000000..f899ccadd3c0 --- /dev/null +++ b/lib/Dialect/Emit/EmitOps.cpp @@ -0,0 +1,74 @@ +//===- EmitOps.cpp - Implement the Emit 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements `sim` dialect ops. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Emit/EmitOps.h" + +using namespace mlir; +using namespace circt; +using namespace emit; + +//===----------------------------------------------------------------------===// +// FileOp +//===----------------------------------------------------------------------===// + +void FileOp::build(OpBuilder &builder, OperationState &result, + StringRef fileName, StringRef symName, + llvm::function_ref bodyCtor) { + MLIRContext *context = builder.getContext(); + OpBuilder::InsertionGuard guard(builder); + + auto &props = result.getOrAddProperties(); + props.sym_name = StringAttr::get(context, symName); + props.file_name = StringAttr::get(context, fileName); + + builder.createBlock(result.addRegion()); + if (bodyCtor) + bodyCtor(); +} + +void FileOp::build(OpBuilder &builder, OperationState &result, + StringRef fileName, llvm::function_ref bodyCtor) { + MLIRContext *context = builder.getContext(); + OpBuilder::InsertionGuard guard(builder); + + auto &props = result.getOrAddProperties(); + props.file_name = StringAttr::get(context, fileName); + + builder.createBlock(result.addRegion()); + if (bodyCtor) + bodyCtor(); +} + +//===----------------------------------------------------------------------===// +// FileListOp +//===----------------------------------------------------------------------===// + +LogicalResult FileListOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + for (auto sym : getFiles()) { + Operation *op = symbolTable.lookupNearestSymbolFrom( + getOperation(), sym.cast()); + if (!op) + return emitError("invalid symbol reference: ") << sym; + + if (!isa(op)) + return emitError("referenced operation is not a file: ") << sym; + } + return success(); +} + +//===----------------------------------------------------------------------===// +// TableGen generated logic. +//===----------------------------------------------------------------------===// + +// Provide the autogenerated implementation guts for the Op classes. +#define GET_OP_CLASSES +#include "circt/Dialect/Emit/Emit.cpp.inc" diff --git a/lib/Dialect/FIRRTL/Export/FIREmitter.cpp b/lib/Dialect/FIRRTL/Export/FIREmitter.cpp index 82b00f6f9f18..0b298bee5e5e 100644 --- a/lib/Dialect/FIRRTL/Export/FIREmitter.cpp +++ b/lib/Dialect/FIRRTL/Export/FIREmitter.cpp @@ -60,6 +60,7 @@ struct Emitter { void emitModuleParameters(Operation *op, ArrayAttr parameters); void emitDeclaration(LayerOp op); void emitDeclaration(OptionOp op); + void emitEnabledLayers(ArrayRef layers); // Statement emission void emitStatementsInBlock(Block &block); @@ -253,6 +254,18 @@ struct Emitter { [&](Value v) { emitSubExprIBox2(v); }); } + /// Emit a (potentially nested) symbol reference as `A.B.C`. + void emitSymbol(SymbolRefAttr symbol) { + ps.ibox(2, IndentStyle::Block); + ps << symbol.getRootReference(); + for (auto nested : symbol.getNestedReferences()) { + ps.zerobreak(); + ps << "."; + ps << nested.getAttr(); + } + ps.end(); + } + private: /// Emit an error and remark that emission failed. InFlightDiagnostic emitError(Operation *op, const Twine &message) { @@ -396,11 +409,27 @@ void Emitter::emitCircuit(CircuitOp op) { symInfos = std::nullopt; } +void Emitter::emitEnabledLayers(ArrayRef layers) { + for (auto layer : layers) { + ps << PP::space; + ps.cbox(2, IndentStyle::Block); + ps << "enablelayer" << PP::space; + emitSymbol(cast(layer)); + ps << PP::end; + } +} + /// Emit an entire module. void Emitter::emitModule(FModuleOp op) { startStatement(); - ps << "module " << PPExtString(legalize(op.getNameAttr())) << " :"; + ps.cbox(4, IndentStyle::Block); + if (op.isPublic()) + ps << "public" << PP::nbsp; + ps << "module " << PPExtString(legalize(op.getNameAttr())); + emitEnabledLayers(op.getLayers()); + ps << PP::nbsp << ":" << PP::end; emitLocation(op); + ps.scopedBox(PP::bbox2, [&]() { setPendingNewline(); @@ -420,8 +449,12 @@ void Emitter::emitModule(FModuleOp op) { /// Emit an external module. void Emitter::emitModule(FExtModuleOp op) { startStatement(); - ps << "extmodule " << PPExtString(legalize(op.getNameAttr())) << " :"; + ps.cbox(4, IndentStyle::Block); + ps << "extmodule " << PPExtString(legalize(op.getNameAttr())); + emitEnabledLayers(op.getLayers()); + ps << PP::nbsp << ":" << PP::end; emitLocation(op); + ps.scopedBox(PP::bbox2, [&]() { setPendingNewline(); @@ -444,8 +477,12 @@ void Emitter::emitModule(FExtModuleOp op) { /// Emit an intrinsic module void Emitter::emitModule(FIntModuleOp op) { startStatement(); - ps << "intmodule " << PPExtString(legalize(op.getNameAttr())) << " :"; + ps.cbox(4, IndentStyle::Block); + ps << "intmodule " << PPExtString(legalize(op.getNameAttr())); + emitEnabledLayers(op.getLayers()); + ps << PP::nbsp << ":" << PP::end; emitLocation(op); + ps.scopedBox(PP::bbox2, [&]() { setPendingNewline(); @@ -1377,8 +1414,16 @@ void Emitter::emitType(Type type, bool includeConst) { if (type.getForceable()) ps << "RW"; ps << "Probe<"; + ps.cbox(2, IndentStyle::Block); + ps.zerobreak(); emitType(type.getType()); - ps << ">"; + if (auto layer = type.getLayer()) { + ps << ","; + ps.space(); + emitSymbol(type.getLayer()); + } + ps << BreakToken(0, -2) << ">"; + ps.end(); }) .Case([&](AnyRefType type) { ps << "AnyRef"; }) .Case([&](StringType type) { ps << "String"; }) diff --git a/lib/Dialect/FIRRTL/FIRRTLFieldSource.cpp b/lib/Dialect/FIRRTL/FIRRTLFieldSource.cpp index 28afcf6c7f17..66cea410d295 100644 --- a/lib/Dialect/FIRRTL/FIRRTLFieldSource.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLFieldSource.cpp @@ -38,17 +38,26 @@ FieldSource::FieldSource(Operation *operation) { void FieldSource::visitOp(Operation *op) { if (auto sf = dyn_cast(op)) return visitSubfield(sf); + if (auto sf = dyn_cast(op)) + return visitOpenSubfield(sf); + if (auto si = dyn_cast(op)) return visitSubindex(si); + if (auto si = dyn_cast(op)) + return visitOpenSubindex(si); + if (auto sa = dyn_cast(op)) return visitSubaccess(sa); - if (isa(op)) + + if (isa(op)) return makeNodeForValue(op->getResult(0), op->getResult(0), {}, foldFlow(op->getResult(0))); if (auto mem = dyn_cast(op)) return visitMem(mem); if (auto inst = dyn_cast(op)) return visitInst(inst); + if (auto inst = dyn_cast(op)) + return visitInstChoice(inst); // Track all other definitions of aggregates. if (op->getNumResults()) { @@ -69,6 +78,15 @@ void FieldSource::visitSubfield(SubfieldOp sf) { makeNodeForValue(sf.getResult(), node->src, sv, foldFlow(sf)); } +void FieldSource::visitOpenSubfield(OpenSubfieldOp sf) { + auto value = sf.getInput(); + const auto *node = nodeForValue(value); + assert(node && "node should be in the map"); + auto sv = node->path; + sv.push_back(sf.getFieldIndex()); + makeNodeForValue(sf.getResult(), node->src, sv, foldFlow(sf)); +} + void FieldSource::visitSubindex(SubindexOp si) { auto value = si.getInput(); const auto *node = nodeForValue(value); @@ -78,6 +96,15 @@ void FieldSource::visitSubindex(SubindexOp si) { makeNodeForValue(si.getResult(), node->src, sv, foldFlow(si)); } +void FieldSource::visitOpenSubindex(OpenSubindexOp si) { + auto value = si.getInput(); + const auto *node = nodeForValue(value); + assert(node && "node should be in the map"); + auto sv = node->path; + sv.push_back(si.getIndex()); + makeNodeForValue(si.getResult(), node->src, sv, foldFlow(si)); +} + void FieldSource::visitSubaccess(SubaccessOp sa) { auto value = sa.getInput(); const auto *node = nodeForValue(value); @@ -97,6 +124,11 @@ void FieldSource::visitInst(InstanceOp inst) { makeNodeForValue(r, r, {}, foldFlow(r)); } +void FieldSource::visitInstChoice(InstanceChoiceOp inst) { + for (auto r : inst.getResults()) + makeNodeForValue(r, r, {}, foldFlow(r)); +} + const FieldSource::PathNode *FieldSource::nodeForValue(Value v) const { auto ii = paths.find(v); if (ii == paths.end()) diff --git a/lib/Dialect/FIRRTL/FIRRTLFolds.cpp b/lib/Dialect/FIRRTL/FIRRTLFolds.cpp index 5dfd9ad31bfe..6a208c3854cc 100644 --- a/lib/Dialect/FIRRTL/FIRRTLFolds.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLFolds.cpp @@ -16,6 +16,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Support/APInt.h" #include "circt/Support/LLVM.h" +#include "circt/Support/Naming.h" #include "mlir/IR/Matchers.h" #include "mlir/IR/PatternMatch.h" #include "llvm/ADT/APSInt.h" @@ -85,32 +86,6 @@ static bool isUInt1(Type type) { return true; } -// Heuristic to pick the best name. -// Good names are not useless, don't start with an underscore, minimize -// underscores in them, and are short. This function deterministically favors -// the second name on ties. -static StringRef chooseName(StringRef a, StringRef b) { - if (a.empty()) - return b; - if (b.empty()) - return a; - if (isUselessName(a)) - return b; - if (isUselessName(b)) - return a; - if (a.starts_with("_")) - return b; - if (b.starts_with("_")) - return a; - if (b.count('_') < a.count('_')) - return b; - if (b.count('_') > a.count('_')) - return a; - if (a.size() > b.size()) - return b; - return a; -} - /// Set the name of an op based on the best of two names: The current name, and /// the name passed in. static void updateName(PatternRewriter &rewriter, Operation *op, @@ -126,7 +101,7 @@ static void updateName(PatternRewriter &rewriter, Operation *op, newName = chooseName(newOpName.getValue(), name.getValue()); // Only update if needed if (!newOpName || newOpName.getValue() != newName) - rewriter.updateRootInPlace( + rewriter.modifyOpInPlace( op, [&] { op->setAttr("name", rewriter.getStringAttr(newName)); }); } @@ -155,15 +130,6 @@ static OpTy replaceOpWithNewOpAndCopyName(PatternRewriter &rewriter, return newOp; } -/// Return true if this is a useless temporary name produced by FIRRTL. We -/// drop these as they don't convey semantic meaning. -bool circt::firrtl::isUselessName(StringRef name) { - if (name.empty()) - return true; - // Ignore _.* - return name.startswith("_T") || name.startswith("_WIRE"); -} - /// Return true if the name is droppable. Note that this is different from /// `isUselessName` because non-useless names may be also droppable. bool circt::firrtl::hasDroppableName(Operation *op) { @@ -477,7 +443,7 @@ OpFoldResult DivPrimOp::fold(FoldAdaptor adaptor) { /// supersede any division with invalid or zero. Division of invalid by /// invalid should be one. if (getLhs() == getRhs()) { - auto width = getType().get().getWidthOrSentinel(); + auto width = getType().base().getWidthOrSentinel(); if (width == -1) width = 2; // Only fold if we have at least 1 bit of width to represent the `1` value. @@ -556,8 +522,8 @@ OpFoldResult DShrPrimOp::fold(FoldAdaptor adaptor) { return constFoldFIRRTLBinaryOp( *this, adaptor.getOperands(), BinOpKind::DivideOrShift, [=](const APSInt &a, const APSInt &b) -> APInt { - return getType().get().isUnsigned() || !a.getBitWidth() ? a.lshr(b) - : a.ashr(b); + return getType().base().isUnsigned() || !a.getBitWidth() ? a.lshr(b) + : a.ashr(b); }); } @@ -657,7 +623,8 @@ OpFoldResult XorPrimOp::fold(FoldAdaptor adaptor) { /// xor(x, x) -> 0 if (getLhs() == getRhs()) return getIntAttr( - getType(), APInt(std::max(getType().get().getWidthOrSentinel(), 0), 0)); + getType(), + APInt(std::max(getType().base().getWidthOrSentinel(), 0), 0)); return constFoldFIRRTLBinaryOp( *this, adaptor.getOperands(), BinOpKind::Normal, @@ -677,14 +644,14 @@ void LEQPrimOp::getCanonicalizationPatterns(RewritePatternSet &results, } OpFoldResult LEQPrimOp::fold(FoldAdaptor adaptor) { - bool isUnsigned = getLhs().getType().get().isUnsigned(); + bool isUnsigned = getLhs().getType().base().isUnsigned(); // leq(x, x) -> 1 if (getLhs() == getRhs()) return getIntAttr(getType(), APInt(1, 1)); // Comparison against constant outside type bounds. - if (auto width = getLhs().getType().get().getWidth()) { + if (auto width = getLhs().getType().base().getWidth()) { if (auto rhsCst = getConstant(adaptor.getRhs())) { auto commonWidth = std::max(*width, rhsCst->getBitWidth()); commonWidth = std::max(commonWidth, 1); @@ -968,6 +935,24 @@ LogicalResult NEQPrimOp::canonicalize(NEQPrimOp op, PatternRewriter &rewriter) { }); } +OpFoldResult IntegerAddOp::fold(FoldAdaptor adaptor) { + // TODO: implement constant folding, etc. + // Tracked in https://github.com/llvm/circt/issues/6696. + return {}; +} + +OpFoldResult IntegerMulOp::fold(FoldAdaptor adaptor) { + // TODO: implement constant folding, etc. + // Tracked in https://github.com/llvm/circt/issues/6724. + return {}; +} + +OpFoldResult IntegerShrOp::fold(FoldAdaptor adaptor) { + // TODO: implement constant folding, etc. + // Tracked in https://github.com/llvm/circt/issues/6725. + return {}; +} + //===----------------------------------------------------------------------===// // Unary Operators //===----------------------------------------------------------------------===// @@ -995,7 +980,7 @@ OpFoldResult AsSIntPrimOp::fold(FoldAdaptor adaptor) { // Be careful to only fold the cast into the constant if the size is known. // Otherwise width inference may produce differently-sized constants if the // sign changes. - if (getType().get().hasWidth()) + if (getType().base().hasWidth()) if (auto cst = getConstant(adaptor.getInput())) return getIntAttr(getType(), *cst); @@ -1015,7 +1000,7 @@ OpFoldResult AsUIntPrimOp::fold(FoldAdaptor adaptor) { // Be careful to only fold the cast into the constant if the size is known. // Otherwise width inference may produce differently-sized constants if the // sign changes. - if (getType().get().hasWidth()) + if (getType().base().hasWidth()) if (auto cst = getConstant(adaptor.getInput())) return getIntAttr(getType(), *cst); @@ -1057,7 +1042,7 @@ OpFoldResult CvtPrimOp::fold(FoldAdaptor adaptor) { // Signed to signed is a noop, unsigned operands prepend a zero bit. if (auto cst = getExtendedConstant(getOperand(), adaptor.getInput(), - getType().get().getWidthOrSentinel())) + getType().base().getWidthOrSentinel())) return getIntAttr(getType(), *cst); return {}; @@ -1075,7 +1060,7 @@ OpFoldResult NegPrimOp::fold(FoldAdaptor adaptor) { // FIRRTL negate always adds a bit. // -x ---> 0-sext(x) or 0-zext(x) if (auto cst = getExtendedConstant(getOperand(), adaptor.getInput(), - getType().get().getWidthOrSentinel())) + getType().base().getWidthOrSentinel())) return getIntAttr(getType(), APInt((*cst).getBitWidth(), 0) - *cst); return {}; @@ -1086,7 +1071,7 @@ OpFoldResult NotPrimOp::fold(FoldAdaptor adaptor) { return {}; if (auto cst = getExtendedConstant(getOperand(), adaptor.getInput(), - getType().get().getWidthOrSentinel())) + getType().base().getWidthOrSentinel())) return getIntAttr(getType(), ~*cst); return {}; @@ -1408,7 +1393,7 @@ class MuxSharedCond : public mlir::RewritePattern { mlir::PatternRewriter &rewriter, bool updateInPlace) const { if (updateInPlace) { - rewriter.updateRootInPlace(mux, [&] { + rewriter.modifyOpInPlace(mux, [&] { mux.setOperand(1, high); mux.setOperand(2, low); }); @@ -1475,12 +1460,12 @@ class MuxSharedCond : public mlir::RewritePattern { return failure(); if (Value v = tryCondTrue(mux.getHigh(), mux.getSel(), rewriter, true, 0)) { - rewriter.updateRootInPlace(mux, [&] { mux.setOperand(1, v); }); + rewriter.modifyOpInPlace(mux, [&] { mux.setOperand(1, v); }); return success(); } if (Value v = tryCondFalse(mux.getLow(), mux.getSel(), rewriter, true, 0)) { - rewriter.updateRootInPlace(mux, [&] { mux.setOperand(2, v); }); + rewriter.modifyOpInPlace(mux, [&] { mux.setOperand(2, v); }); return success(); } @@ -1505,14 +1490,14 @@ OpFoldResult PadPrimOp::fold(FoldAdaptor adaptor) { return input; // Need to know the input width. - auto inputType = input.getType().get(); + auto inputType = input.getType().base(); int32_t width = inputType.getWidthOrSentinel(); if (width == -1) return {}; // Constant fold. if (auto cst = getConstant(adaptor.getInput())) { - auto destWidth = getType().get().getWidthOrSentinel(); + auto destWidth = getType().base().getWidthOrSentinel(); if (destWidth == -1) return {}; @@ -1549,12 +1534,14 @@ OpFoldResult ShrPrimOp::fold(FoldAdaptor adaptor) { auto input = this->getInput(); IntType inputType = input.getType(); int shiftAmount = getAmount(); + auto inputWidth = inputType.getWidthOrSentinel(); // shr(x, 0) -> x - if (shiftAmount == 0) + // Once the shr width changes, do this: shiftAmount == 0 && + // (!inputType.isSigned() || inputWidth > 0) + if (shiftAmount == 0 && inputWidth > 0) return input; - auto inputWidth = inputType.getWidthOrSentinel(); if (inputWidth == -1) return {}; if (inputWidth == 0) @@ -1563,7 +1550,7 @@ OpFoldResult ShrPrimOp::fold(FoldAdaptor adaptor) { // shr(x, cst) where cst is all of x's bits and x is unsigned is 0. // If x is signed, it is the sign bit. if (shiftAmount >= inputWidth && inputType.isUnsigned()) - return getIntAttr(getType(), APInt(1, 0)); + return getIntAttr(getType(), APInt(0, 0, false)); // Constant fold. if (auto cst = getConstant(adaptor.getInput())) { @@ -1579,7 +1566,7 @@ OpFoldResult ShrPrimOp::fold(FoldAdaptor adaptor) { } LogicalResult ShrPrimOp::canonicalize(ShrPrimOp op, PatternRewriter &rewriter) { - auto inputWidth = op.getInput().getType().get().getWidthOrSentinel(); + auto inputWidth = op.getInput().getType().base().getWidthOrSentinel(); if (inputWidth <= 0) return failure(); @@ -1587,7 +1574,7 @@ LogicalResult ShrPrimOp::canonicalize(ShrPrimOp op, PatternRewriter &rewriter) { unsigned shiftAmount = op.getAmount(); if (int(shiftAmount) >= inputWidth) { // shift(x, 32) => 0 when x has 32 bits. This is handled by fold(). - if (op.getType().get().isUnsigned()) + if (op.getType().base().isUnsigned()) return failure(); // Shifting a signed value by the full width is actually taking the @@ -1602,7 +1589,7 @@ LogicalResult ShrPrimOp::canonicalize(ShrPrimOp op, PatternRewriter &rewriter) { LogicalResult HeadPrimOp::canonicalize(HeadPrimOp op, PatternRewriter &rewriter) { - auto inputWidth = op.getInput().getType().get().getWidthOrSentinel(); + auto inputWidth = op.getInput().getType().base().getWidthOrSentinel(); if (inputWidth <= 0) return failure(); @@ -1618,7 +1605,7 @@ OpFoldResult HeadPrimOp::fold(FoldAdaptor adaptor) { if (hasKnownWidthIntTypes(*this)) if (auto cst = getConstant(adaptor.getInput())) { int shiftAmount = - getInput().getType().get().getWidthOrSentinel() - getAmount(); + getInput().getType().base().getWidthOrSentinel() - getAmount(); return getIntAttr(getType(), cst->lshr(shiftAmount).trunc(getAmount())); } @@ -1629,13 +1616,13 @@ OpFoldResult TailPrimOp::fold(FoldAdaptor adaptor) { if (hasKnownWidthIntTypes(*this)) if (auto cst = getConstant(adaptor.getInput())) return getIntAttr(getType(), - cst->trunc(getType().get().getWidthOrSentinel())); + cst->trunc(getType().base().getWidthOrSentinel())); return {}; } LogicalResult TailPrimOp::canonicalize(TailPrimOp op, PatternRewriter &rewriter) { - auto inputWidth = op.getInput().getType().get().getWidthOrSentinel(); + auto inputWidth = op.getInput().getType().base().getWidthOrSentinel(); if (inputWidth <= 0) return failure(); @@ -1955,9 +1942,9 @@ struct NodeBypass : public mlir::RewritePattern { if (node.getInnerSym() || !AnnotationSet(node).canBeDeleted() || node.use_empty() || node.isForceable()) return failure(); - rewriter.startRootUpdate(node); + rewriter.startOpModification(node); node.getResult().replaceAllUsesWith(node.getInput()); - rewriter.finalizeRootUpdate(node); + rewriter.finalizeOpModification(node); return success(); } }; @@ -3232,8 +3219,8 @@ LogicalResult ClockGateIntrinsicOp::canonicalize(ClockGateIntrinsicOp op, if (auto testEnable = op.getTestEnable()) { if (auto constOp = testEnable.getDefiningOp()) { if (constOp.getValue().isZero()) { - rewriter.updateRootInPlace(op, - [&] { op.getTestEnableMutable().clear(); }); + rewriter.modifyOpInPlace(op, + [&] { op.getTestEnableMutable().clear(); }); return success(); } } @@ -3322,3 +3309,34 @@ OpFoldResult HasBeenResetIntrinsicOp::fold(FoldAdaptor adaptor) { return {}; } + +//===----------------------------------------------------------------------===// +// FPGAProbeIntrinsicOp +//===----------------------------------------------------------------------===// + +static bool isTypeEmpty(FIRRTLType type) { + return FIRRTLTypeSwitch(type) + .Case( + [&](auto ty) -> bool { return isTypeEmpty(ty.getElementType()); }) + .Case([&](auto ty) -> bool { + for (auto elem : ty.getElements()) + if (!isTypeEmpty(elem.type)) + return false; + return true; + }) + .Case([&](auto ty) { return ty.getWidth() == 0; }) + .Default([](auto) -> bool { return false; }); +} + +LogicalResult FPGAProbeIntrinsicOp::canonicalize(FPGAProbeIntrinsicOp op, + PatternRewriter &rewriter) { + auto firrtlTy = type_dyn_cast(op.getInput().getType()); + if (!firrtlTy) + return failure(); + + if (!isTypeEmpty(firrtlTy)) + return failure(); + + rewriter.eraseOp(op); + return success(); +} diff --git a/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp b/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp index d24ad40da82f..5c53322632bd 100644 --- a/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp @@ -133,5 +133,13 @@ instance_like_impl::verifyReferencedModule(Operation *instanceOp, llvm_unreachable("should have found something wrong"); } + // Check that the instance op lists the correct layer requirements. + auto instanceLayers = instanceOp->getAttrOfType("layers"); + auto moduleLayers = referencedModule.getLayersAttr(); + if (instanceLayers != moduleLayers) + return emitNote(instanceOp->emitOpError() + << "layers must be " << moduleLayers << ", but got " + << instanceLayers); + return success(); } diff --git a/lib/Dialect/FIRRTL/FIRRTLIntrinsics.cpp b/lib/Dialect/FIRRTL/FIRRTLIntrinsics.cpp index 466e49a5479d..dab90071e925 100644 --- a/lib/Dialect/FIRRTL/FIRRTLIntrinsics.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLIntrinsics.cpp @@ -109,11 +109,13 @@ LogicalResult IntrinsicLowerings::doLowering(FModuleLike mod, if (conv.check()) return failure(); for (auto *use : graph.lookup(mod)->uses()) - conv.convert(use->getInstance()); + if (failed(conv.convert(use->getInstance()))) + return failure(); return success(); } -LogicalResult IntrinsicLowerings::lower(CircuitOp circuit) { +LogicalResult IntrinsicLowerings::lower(CircuitOp circuit, + bool allowUnknownIntrinsics) { unsigned numFailures = 0; for (auto op : llvm::make_early_inc_range(circuit.getOps())) { StringAttr intname; @@ -123,6 +125,7 @@ LogicalResult IntrinsicLowerings::lower(CircuitOp circuit) { if (it != extmods.end()) { if (succeeded(it->second(op))) { op.erase(); + ++numConverted; } else { ++numFailures; } @@ -152,12 +155,16 @@ LogicalResult IntrinsicLowerings::lower(CircuitOp circuit) { // Find the converter and apply it. auto it = intmods.find(intname); - if (it == intmods.end()) + if (it == intmods.end()) { + if (allowUnknownIntrinsics) + continue; return op.emitError() << "intrinsic not recognized"; + } if (failed(it->second(op))) { ++numFailures; continue; } + ++numConverted; op.erase(); } diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index a917a5bcc047..82635f0f8ca6 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -31,6 +31,7 @@ #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/FormatVariadic.h" @@ -320,6 +321,128 @@ void getAsmBlockArgumentNamesImpl(Operation *op, mlir::Region ®ion, static ParseResult parseNameKind(OpAsmParser &parser, firrtl::NameKindEnumAttr &result); +//===----------------------------------------------------------------------===// +// Layer Verification Utilities +//===----------------------------------------------------------------------===// + +namespace { +struct CompareSymbolRefAttr { + // True if lhs is lexicographically less than rhs. + bool operator()(SymbolRefAttr lhs, SymbolRefAttr rhs) const { + auto cmp = lhs.getRootReference().compare(rhs.getRootReference()); + if (cmp == -1) + return true; + if (cmp == 1) + return false; + auto lhsNested = lhs.getNestedReferences(); + auto rhsNested = rhs.getNestedReferences(); + auto lhsNestedSize = lhsNested.size(); + auto rhsNestedSize = rhsNested.size(); + auto e = std::min(lhsNestedSize, rhsNestedSize); + for (unsigned i = 0; i < e; ++i) { + auto cmp = lhsNested[i].getAttr().compare(rhsNested[i].getAttr()); + if (cmp == -1) + return true; + if (cmp == 1) + return false; + } + return lhsNestedSize < rhsNestedSize; + } +}; +} // namespace + +using LayerSet = SmallSet; + +/// Get the ambient layers active at the given op. +static LayerSet getAmbientLayersAt(Operation *op) { + // Crawl through the parent ops, accumulating all ambient layers at the given + // operation. + LayerSet result; + for (; op != nullptr; op = op->getParentOp()) { + if (auto module = dyn_cast(op)) { + auto layers = module.getLayersAttr().getAsRange(); + result.insert(layers.begin(), layers.end()); + break; + } + if (auto layerblock = dyn_cast(op)) { + result.insert(layerblock.getLayerName()); + continue; + } + } + return result; +} + +/// Get the ambient layer requirements at the definition site of the value. +static LayerSet getAmbientLayersFor(Value value) { + return getAmbientLayersAt(getFieldRefFromValue(value).getDefiningOp()); +} + +/// Get the effective layer requirements for the given value. +/// The effective layers for a value is the union of +/// - the ambient layers for the cannonical storage location. +/// - any explicit layer annotations in the value's type. +static LayerSet getLayersFor(Value value) { + auto result = getAmbientLayersFor(value); + if (auto type = dyn_cast(value.getType())) + if (auto layer = type.getLayer()) + result.insert(type.getLayer()); + return result; +} + +/// Check that the source layer is compatible with the destination layer. +/// Either the source and destination are identical, or the source-layer +/// is a parent of the destination. For example `A` is compatible with `A.B.C`, +/// because any definition valid in `A` is also valid in `A.B.C`. +static bool isLayerCompatibleWith(mlir::SymbolRefAttr srcLayer, + mlir::SymbolRefAttr dstLayer) { + // A non-colored probe may be cast to any colored probe. + if (!srcLayer) + return true; + + // A colored probe cannot be cast to an uncolored probe. + if (!dstLayer) + return false; + + // Return true if the srcLayer is a prefix of the dstLayer. + if (srcLayer.getRootReference() != dstLayer.getRootReference()) + return false; + + auto srcNames = srcLayer.getNestedReferences(); + auto dstNames = dstLayer.getNestedReferences(); + if (dstNames.size() < srcNames.size()) + return false; + + return llvm::all_of(llvm::zip_first(srcNames, dstNames), + [](auto x) { return std::get<0>(x) == std::get<1>(x); }); +} + +/// Check that the source layer is present in the destination layers. +static bool isLayerCompatibleWith(SymbolRefAttr srcLayer, + const LayerSet &dstLayers) { + // fast path: the required layer is directly listed in the provided layers. + if (dstLayers.contains(srcLayer)) + return true; + + // Slow path: the required layer is not directly listed in the provided + // layers, but the layer may still be provided by a nested layer. + return any_of(dstLayers, [=](SymbolRefAttr dstLayer) { + return isLayerCompatibleWith(srcLayer, dstLayer); + }); +} + +/// Check that the source layers are all present in the destination layers. +/// True if all source layers are present in the destination. +/// Outputs the set of source layers that are missing in the destination. +static bool isLayerSetCompatibleWith(const LayerSet &src, const LayerSet &dst, + SmallVectorImpl &missing) { + for (auto srcLayer : src) + if (!isLayerCompatibleWith(srcLayer, dst)) + missing.push_back(srcLayer); + + llvm::sort(missing, CompareSymbolRefAttr()); + return missing.empty(); +} + //===----------------------------------------------------------------------===// // CircuitOp //===----------------------------------------------------------------------===// @@ -796,9 +919,10 @@ void FMemModuleOp::insertPorts(ArrayRef> ports) { ::insertPorts(cast((Operation *)*this), ports); } -static void buildModule(OpBuilder &builder, OperationState &result, - StringAttr name, ArrayRef ports, - ArrayAttr annotations, bool withAnnotations = true) { +static void buildModuleLike(OpBuilder &builder, OperationState &result, + StringAttr name, ArrayRef ports, + ArrayAttr annotations, ArrayAttr layers, + bool withAnnotations, bool withLayers) { // Add an attribute for the name. result.addAttribute(::mlir::SymbolTable::getSymbolAttrName(), name); @@ -843,19 +967,34 @@ static void buildModule(OpBuilder &builder, OperationState &result, builder.getArrayAttr(portAnnotations)); } + if (withLayers) { + if (!layers) + layers = builder.getArrayAttr({}); + result.addAttribute("layers", layers); + } + result.addRegion(); } -static void buildModuleWithoutAnnos(OpBuilder &builder, OperationState &result, - StringAttr name, ArrayRef ports) { - return buildModule(builder, result, name, ports, {}, - /*withAnnotations=*/false); +static void buildModule(OpBuilder &builder, OperationState &result, + StringAttr name, ArrayRef ports, + ArrayAttr annotations, ArrayAttr layers) { + buildModuleLike(builder, result, name, ports, annotations, layers, + /*withAnnotations=*/true, /*withLayers=*/true); +} + +static void buildClass(OpBuilder &builder, OperationState &result, + StringAttr name, ArrayRef ports) { + return buildModuleLike(builder, result, name, ports, {}, {}, + /*withAnnotations=*/false, + /*withLayers=*/false); } void FModuleOp::build(OpBuilder &builder, OperationState &result, StringAttr name, ConventionAttr convention, - ArrayRef ports, ArrayAttr annotations) { - buildModule(builder, result, name, ports, annotations); + ArrayRef ports, ArrayAttr annotations, + ArrayAttr layers) { + buildModule(builder, result, name, ports, annotations, layers); result.addAttribute("convention", convention); // Create a region and a block for the body. @@ -872,8 +1011,8 @@ void FExtModuleOp::build(OpBuilder &builder, OperationState &result, StringAttr name, ConventionAttr convention, ArrayRef ports, StringRef defnameAttr, ArrayAttr annotations, ArrayAttr parameters, - ArrayAttr internalPaths) { - buildModule(builder, result, name, ports, annotations); + ArrayAttr internalPaths, ArrayAttr layers) { + buildModule(builder, result, name, ports, annotations, layers); result.addAttribute("convention", convention); if (!defnameAttr.empty()) result.addAttribute("defname", builder.getStringAttr(defnameAttr)); @@ -887,8 +1026,9 @@ void FExtModuleOp::build(OpBuilder &builder, OperationState &result, void FIntModuleOp::build(OpBuilder &builder, OperationState &result, StringAttr name, ArrayRef ports, StringRef intrinsicNameAttr, ArrayAttr annotations, - ArrayAttr parameters, ArrayAttr internalPaths) { - buildModule(builder, result, name, ports, annotations); + ArrayAttr parameters, ArrayAttr internalPaths, + ArrayAttr layers) { + buildModule(builder, result, name, ports, annotations, layers); result.addAttribute("intrinsic", builder.getStringAttr(intrinsicNameAttr)); if (!parameters) parameters = builder.getArrayAttr({}); @@ -903,9 +1043,9 @@ void FMemModuleOp::build(OpBuilder &builder, OperationState &result, uint32_t numReadWritePorts, uint32_t dataWidth, uint32_t maskBits, uint32_t readLatency, uint32_t writeLatency, uint64_t depth, - ArrayAttr annotations) { + ArrayAttr annotations, ArrayAttr layers) { auto *context = builder.getContext(); - buildModule(builder, result, name, ports, annotations); + buildModule(builder, result, name, ports, annotations, layers); auto ui32Type = IntegerType::get(context, 32, IntegerType::Unsigned); auto ui64Type = IntegerType::get(context, 64, IntegerType::Unsigned); result.addAttribute("numReadPorts", IntegerAttr::get(ui32Type, numReadPorts)); @@ -1170,6 +1310,11 @@ static void printFModuleLikeOp(OpAsmPrinter &p, FModuleLike op) { if (op->getAttrOfType("annotations").empty()) omittedAttrs.push_back("annotations"); + // If there are no enabled layers, then omit the empty array. + if (auto layers = op->getAttrOfType("layers")) + if (layers.empty()) + omittedAttrs.push_back("layers"); + p.printOptionalAttrDictWithKeyword(op->getAttrs(), omittedAttrs); } @@ -1328,6 +1473,8 @@ ParseResult FModuleOp::parse(OpAsmParser &parser, OperationState &result) { result.addAttribute( "convention", ConventionAttr::get(result.getContext(), Convention::Internal)); + if (!result.attributes.get("layers")) + result.addAttribute("layers", ArrayAttr::get(parser.getContext(), {})); return success(); } @@ -1448,35 +1595,72 @@ LogicalResult FIntModuleOp::verify() { return success(); } +static LogicalResult verifyProbeType(RefType refType, Location loc, + CircuitOp circuitOp, + SymbolTableCollection &symbolTable, + Twine start) { + auto layer = refType.getLayer(); + if (!layer) + return success(); + auto *layerOp = symbolTable.lookupSymbolIn(circuitOp, layer); + if (!layerOp) + return emitError(loc) << start << " associated with layer '" << layer + << "', but this layer was not defined"; + if (!isa(layerOp)) { + auto diag = emitError(loc) + << start << " associated with layer '" << layer + << "', but symbol '" << layer << "' does not refer to a '" + << LayerOp::getOperationName() << "' op"; + return diag.attachNote(layerOp->getLoc()) << "symbol refers to this op"; + } + return success(); +} + static LogicalResult verifyPortSymbolUses(FModuleLike module, SymbolTableCollection &symbolTable) { - auto circuitOp = module->getParentOfType(); - // verify types in ports. + auto circuitOp = module->getParentOfType(); for (size_t i = 0, e = module.getNumPorts(); i < e; ++i) { auto type = module.getPortType(i); - auto classType = dyn_cast(type); - if (!classType) + + if (auto refType = type_dyn_cast(type)) { + if (failed(verifyProbeType( + refType, module.getPortLocation(i), circuitOp, symbolTable, + Twine("probe port '") + module.getPortName(i) + "' is"))) + return failure(); continue; + } - // verify that the class exists. - auto className = classType.getNameAttr(); - auto classOp = dyn_cast_or_null( - symbolTable.lookupSymbolIn(circuitOp, className)); - if (!classOp) - return module.emitOpError() << "references unknown class " << className; + if (auto classType = dyn_cast(type)) { + auto className = classType.getNameAttr(); + auto classOp = dyn_cast_or_null( + symbolTable.lookupSymbolIn(circuitOp, className)); + if (!classOp) + return module.emitOpError() << "references unknown class " << className; - // verify that the result type agrees with the class definition. - if (failed(classOp.verifyType(classType, - [&]() { return module.emitOpError(); }))) - return failure(); + // verify that the result type agrees with the class definition. + if (failed(classOp.verifyType(classType, + [&]() { return module.emitOpError(); }))) + return failure(); + continue; + } } return success(); } LogicalResult FModuleOp::verifySymbolUses(SymbolTableCollection &symbolTable) { - return verifyPortSymbolUses(cast(getOperation()), symbolTable); + if (failed( + verifyPortSymbolUses(cast(getOperation()), symbolTable))) + return failure(); + + auto circuitOp = (*this)->getParentOfType(); + for (auto layer : getLayers()) { + if (!symbolTable.lookupSymbolIn(circuitOp, cast(layer))) + return emitOpError() << "enables unknown layer '" << layer << "'"; + } + + return success(); } LogicalResult @@ -1729,7 +1913,7 @@ void ClassOp::build(OpBuilder &builder, OperationState &result, StringAttr name, [](const auto &port) { return port.annotations.empty(); }) && "class ports may not have annotations"); - buildModuleWithoutAnnos(builder, result, name, ports); + buildClass(builder, result, name, ports); // Create a region and a block for the body. auto *bodyRegion = result.regions[0].get(); @@ -1797,6 +1981,10 @@ ArrayAttr ClassOp::getPortAnnotationsAttr() { return ArrayAttr::get(getContext(), {}); } +ArrayAttr ClassOp::getLayersAttr() { return ArrayAttr::get(getContext(), {}); } + +ArrayRef ClassOp::getLayers() { return getLayersAttr(); } + SmallVector<::circt::hw::PortInfo> ClassOp::getPortList() { return ::getPortListImpl(*this); } @@ -1827,7 +2015,7 @@ void ExtClassOp::build(OpBuilder &builder, OperationState &result, [](const auto &port) { return port.annotations.empty(); }) && "class ports may not have annotations"); - buildModuleWithoutAnnos(builder, result, name, ports); + buildClass(builder, result, name, ports); } void ExtClassOp::print(OpAsmPrinter &p) { @@ -1867,6 +2055,12 @@ ConventionAttr ExtClassOp::getConventionAttr() { return ConventionAttr::get(getContext(), getConvention()); } +ArrayAttr ExtClassOp::getLayersAttr() { + return ArrayAttr::get(getContext(), {}); +} + +ArrayRef ExtClassOp::getLayers() { return getLayersAttr(); } + ArrayAttr ExtClassOp::getParameters() { return {}; } ArrayAttr ExtClassOp::getPortAnnotationsAttr() { @@ -1899,27 +2093,24 @@ SmallVector<::circt::hw::PortInfo> InstanceOp::getPortList() { return circuit.lookupSymbol(getModuleNameAttr()).getPortList(); } -void InstanceOp::build(OpBuilder &builder, OperationState &result, - TypeRange resultTypes, StringRef moduleName, - StringRef name, NameKindEnum nameKind, - ArrayRef portDirections, - ArrayRef portNames, - ArrayRef annotations, - ArrayRef portAnnotations, bool lowerToBind, - StringAttr innerSym) { +void InstanceOp::build( + OpBuilder &builder, OperationState &result, TypeRange resultTypes, + StringRef moduleName, StringRef name, NameKindEnum nameKind, + ArrayRef portDirections, ArrayRef portNames, + ArrayRef annotations, ArrayRef portAnnotations, + ArrayRef layers, bool lowerToBind, StringAttr innerSym) { build(builder, result, resultTypes, moduleName, name, nameKind, - portDirections, portNames, annotations, portAnnotations, lowerToBind, + portDirections, portNames, annotations, portAnnotations, layers, + lowerToBind, innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr()); } -void InstanceOp::build(OpBuilder &builder, OperationState &result, - TypeRange resultTypes, StringRef moduleName, - StringRef name, NameKindEnum nameKind, - ArrayRef portDirections, - ArrayRef portNames, - ArrayRef annotations, - ArrayRef portAnnotations, bool lowerToBind, - hw::InnerSymAttr innerSym) { +void InstanceOp::build( + OpBuilder &builder, OperationState &result, TypeRange resultTypes, + StringRef moduleName, StringRef name, NameKindEnum nameKind, + ArrayRef portDirections, ArrayRef portNames, + ArrayRef annotations, ArrayRef portAnnotations, + ArrayRef layers, bool lowerToBind, hw::InnerSymAttr innerSym) { result.addTypes(resultTypes); result.addAttribute("moduleName", SymbolRefAttr::get(builder.getContext(), moduleName)); @@ -1929,6 +2120,7 @@ void InstanceOp::build(OpBuilder &builder, OperationState &result, direction::packAttribute(builder.getContext(), portDirections)); result.addAttribute("portNames", builder.getArrayAttr(portNames)); result.addAttribute("annotations", builder.getArrayAttr(annotations)); + result.addAttribute("layers", builder.getArrayAttr(layers)); if (lowerToBind) result.addAttribute("lowerToBind", builder.getUnitAttr()); if (innerSym) @@ -1977,15 +2169,16 @@ void InstanceOp::build(OpBuilder &builder, OperationState &result, NameKindEnumAttr::get(builder.getContext(), nameKind), module.getPortDirectionsAttr(), module.getPortNamesAttr(), builder.getArrayAttr(annotations), portAnnotationsAttr, - lowerToBind ? builder.getUnitAttr() : UnitAttr(), innerSym); + module.getLayersAttr(), lowerToBind ? builder.getUnitAttr() : UnitAttr(), + innerSym); } void InstanceOp::build(OpBuilder &builder, OperationState &odsState, ArrayRef ports, StringRef moduleName, StringRef name, NameKindEnum nameKind, - ArrayRef annotations, bool lowerToBind, + ArrayRef annotations, + ArrayRef layers, bool lowerToBind, hw::InnerSymAttr innerSym) { - // Gather the result types. SmallVector newResultTypes; SmallVector newPortDirections; @@ -2000,7 +2193,26 @@ void InstanceOp::build(OpBuilder &builder, OperationState &odsState, return build(builder, odsState, newResultTypes, moduleName, name, nameKind, newPortDirections, newPortNames, annotations, newPortAnnotations, - lowerToBind, innerSym); + layers, lowerToBind, innerSym); +} + +LogicalResult InstanceOp::verify() { + // The instance may only be instantiated under its required layers. + auto ambientLayers = getAmbientLayersAt(getOperation()); + SmallVector missingLayers; + for (auto layer : getLayersAttr().getAsRange()) + if (!isLayerCompatibleWith(layer, ambientLayers)) + missingLayers.push_back(layer); + + if (missingLayers.empty()) + return success(); + + auto diag = + emitOpError("ambient layers are insufficient to instantiate module"); + auto ¬e = diag.attachNote(); + note << "missing layer requirements: "; + interleaveComma(missingLayers, note); + return failure(); } /// Builds a new `InstanceOp` with the ports listed in `portIndices` erased, and @@ -2025,7 +2237,7 @@ InstanceOp InstanceOp::erasePorts(OpBuilder &builder, auto newOp = builder.create( getLoc(), newResultTypes, getModuleName(), getName(), getNameKind(), newPortDirections, newPortNames, getAnnotations().getValue(), - newPortAnnotations, getLowerToBind(), getInnerSymAttr()); + newPortAnnotations, getLayers(), getLowerToBind(), getInnerSymAttr()); for (unsigned oldIdx = 0, newIdx = 0, numOldPorts = getNumResults(); oldIdx != numOldPorts; ++oldIdx) { @@ -2098,7 +2310,7 @@ InstanceOp::cloneAndInsertPorts(ArrayRef> ports) { return OpBuilder(*this).create( getLoc(), newPortTypes, getModuleName(), getName(), getNameKind(), newPortDirections, newPortNames, getAnnotations().getValue(), - newPortAnnos, getLowerToBind(), getInnerSymAttr()); + newPortAnnos, getLayers(), getLowerToBind(), getInnerSymAttr()); } LogicalResult InstanceOp::verifySymbolUses(SymbolTableCollection &symbolTable) { @@ -2122,12 +2334,14 @@ void InstanceOp::print(OpAsmPrinter &p) { p << ' ' << stringifyNameKindEnum(getNameKindAttr().getValue()); // Print the attr-dict. - SmallVector omittedAttrs = {"moduleName", "name", - "portDirections", "portNames", - "portTypes", "portAnnotations", - "inner_sym", "nameKind"}; + SmallVector omittedAttrs = { + "moduleName", "name", "portDirections", + "portNames", "portTypes", "portAnnotations", + "inner_sym", "nameKind"}; if (getAnnotations().empty()) omittedAttrs.push_back("annotations"); + if (getLayers().empty()) + omittedAttrs.push_back("layers"); p.printOptionalAttrDict((*this)->getAttrs(), omittedAttrs); // Print the module name. @@ -2196,10 +2410,12 @@ ParseResult InstanceOp::parse(OpAsmParser &parser, OperationState &result) { result.addAttribute("portAnnotations", ArrayAttr::get(context, portAnnotations)); - // Annotations and LowerToBind are omitted in the printed format if they are - // empty and false, respectively. + // Annotations, layers, and LowerToBind are omitted in the printed format + // if they are empty, empty, and false (respectively). if (!resultAttrs.get("annotations")) resultAttrs.append("annotations", parser.getBuilder().getArrayAttr({})); + if (!resultAttrs.get("layers")) + resultAttrs.append("layers", parser.getBuilder().getArrayAttr({})); // Add result types. result.types.reserve(portTypes.size()); @@ -2264,6 +2480,7 @@ void InstanceChoiceOp::build( defaultModule.getPortDirectionsAttr(), defaultModule.getPortNamesAttr(), builder.getArrayAttr(annotations), portAnnotationsAttr, + defaultModule.getLayersAttr(), innerSym ? hw::InnerSymAttr::get(innerSym) : hw::InnerSymAttr()); } @@ -2283,12 +2500,14 @@ void InstanceChoiceOp::print(OpAsmPrinter &p) { p << ' ' << stringifyNameKindEnum(getNameKindAttr().getValue()); // Print the attr-dict. - SmallVector omittedAttrs = { + SmallVector omittedAttrs = { "moduleNames", "caseNames", "name", "portDirections", "portNames", "portTypes", "portAnnotations", "inner_sym", "nameKind"}; if (getAnnotations().empty()) omittedAttrs.push_back("annotations"); + if (getLayers().empty()) + omittedAttrs.push_back("layers"); p.printOptionalAttrDict((*this)->getAttrs(), omittedAttrs); // Print the module name. @@ -2409,10 +2628,12 @@ ParseResult InstanceChoiceOp::parse(OpAsmParser &parser, result.addAttribute("portAnnotations", ArrayAttr::get(context, portAnnotations)); - // Annotations and LowerToBind are omitted in the printed format if they are - // empty and false, respectively. + // Annotations, layers, and LowerToBind are omitted in the printed format if + // they are empty, empty, and false (respectively). if (!resultAttrs.get("annotations")) resultAttrs.append("annotations", parser.getBuilder().getArrayAttr({})); + if (!resultAttrs.get("layers")) + resultAttrs.append("layers", parser.getBuilder().getArrayAttr({})); // Add result types. result.types.reserve(portTypes.size()); @@ -2435,7 +2656,24 @@ LogicalResult InstanceChoiceOp::verify() { if (getModuleNamesAttr().size() != getCaseNamesAttr().size() + 1) return emitOpError() << "number of referenced modules does not match the " "number of options"; - return success(); + + // The modules may only be instantiated under their required layers (which + // are the same for all modules). + auto ambientLayers = getAmbientLayersAt(getOperation()); + SmallVector missingLayers; + for (auto layer : getLayersAttr().getAsRange()) + if (!isLayerCompatibleWith(layer, ambientLayers)) + missingLayers.push_back(layer); + + if (missingLayers.empty()) + return success(); + + auto diag = + emitOpError("ambient layers are insufficient to instantiate module"); + auto ¬e = diag.attachNote(); + note << "missing layer requirements: "; + interleaveComma(missingLayers, note); + return failure(); } LogicalResult @@ -3045,6 +3283,16 @@ void WireOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { std::optional WireOp::getTargetResultIndex() { return 0; } +LogicalResult WireOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + auto refType = type_dyn_cast(getType(0)); + if (!refType) + return success(); + + return verifyProbeType( + refType, getLoc(), getOperation()->getParentOfType(), + symbolTable, Twine("'") + getOperationName() + "' op is"); +} + void ObjectOp::build(OpBuilder &builder, OperationState &state, ClassLike klass, StringRef name) { build(builder, state, klass.getInstanceType(), @@ -3209,7 +3457,7 @@ static LogicalResult checkConnectConditionality(FConnectLike connect) { .Case([&](SubaccessOp op) { if (op.getInput() .getType() - .get() + .base() .getElementTypePreservingConst() .isConst()) originalFieldType = originalFieldType.getConstType(true); @@ -3366,6 +3614,20 @@ LogicalResult RefDefineOp::verify() { "destination reference cannot be a cast of another reference"); } + // This define is only enabled when its ambient layers are active. Check + // that whenever the destination's layer requirements are met, that this + // op is enabled. + auto ambientLayers = getAmbientLayersAt(getOperation()); + auto dstLayers = getLayersFor(getDest()); + SmallVector missingLayers; + if (!isLayerSetCompatibleWith(ambientLayers, dstLayers, missingLayers)) { + auto diag = emitOpError("has more layer requirements than destination"); + auto ¬e = diag.attachNote(); + note << "additional layers required: "; + interleaveComma(missingLayers, note); + return failure(); + } + return success(); } @@ -3572,12 +3834,20 @@ LogicalResult impl::inferReturnTypes( return failure(); } -/// Get an attribute by name from a list of named attributes. Aborts if the -/// attribute does not exist. -static Attribute getAttr(ArrayRef attrs, StringRef name) { +/// Get an attribute by name from a list of named attributes. Return null if no +/// attribute is found with that name. +static Attribute maybeGetAttr(ArrayRef attrs, StringRef name) { for (auto attr : attrs) if (attr.getName() == name) return attr.getValue(); + return {}; +} + +/// Get an attribute by name from a list of named attributes. Aborts if the +/// attribute does not exist. +static Attribute getAttr(ArrayRef attrs, StringRef name) { + if (auto attr = maybeGetAttr(attrs, name)) + return attr; llvm::report_fatal_error("attribute '" + name + "' not found"); } @@ -4017,7 +4287,7 @@ ParseResult FEnumCreateOp::parse(OpAsmParser &parser, OperationState &result) { //===----------------------------------------------------------------------===// LogicalResult IsTagOp::verify() { - if (getFieldIndex() >= getInput().getType().get().getNumElements()) + if (getFieldIndex() >= getInput().getType().base().getNumElements()) return emitOpError("element index is greater than the number of fields in " "the bundle type"); return success(); @@ -4206,7 +4476,7 @@ LogicalResult OpenSubfieldOp::verify() { } LogicalResult SubtagOp::verify() { - if (getFieldIndex() >= getInput().getType().get().getNumElements()) + if (getFieldIndex() >= getInput().getType().base().getNumElements()) return emitOpError("subfield element index is greater than the number " "of fields in the bundle type"); return success(); @@ -5089,8 +5359,11 @@ FIRRTLType ShrPrimOp::inferReturnType(ValueRange operands, loc, "shr input must be integer and amount must be >= 0"); int32_t width = inputi.getWidthOrSentinel(); - if (width != -1) - width = std::max(1, width - amount); + if (width != -1) { + // UInt saturates at 0 bits, SInt at 1 bit + int32_t minWidth = inputi.isUnsigned() ? 0 : 1; + width = std::max(minWidth, width - amount); + } return IntType::get(input.getContext(), inputi.isSigned(), width, inputi.isConst()); @@ -5130,7 +5403,7 @@ void VerbatimExprOp::getAsmResultNames( auto isOkCharacter = [](char c) { return llvm::isAlnum(c) || c == '_'; }; auto name = getText(); // Ignore a leading ` in macro name. - if (name.startswith("`")) + if (name.starts_with("`")) name = name.drop_front(); name = name.take_while(isOkCharacter); if (!name.empty()) @@ -5149,7 +5422,7 @@ void VerbatimWireOp::getAsmResultNames( auto isOkCharacter = [](char c) { return llvm::isAlnum(c) || c == '_'; }; auto name = getText(); // Ignore a leading ` in macro name. - if (name.startswith("`")) + if (name.starts_with("`")) name = name.drop_front(); name = name.take_while(isOkCharacter); if (!name.empty()) @@ -5480,6 +5753,15 @@ void GTPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { void HeadPrimOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { genericAsmResultNames(*this, setNameFn); } +void IntegerAddOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + genericAsmResultNames(*this, setNameFn); +} +void IntegerMulOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + genericAsmResultNames(*this, setNameFn); +} +void IntegerShrOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + genericAsmResultNames(*this, setNameFn); +} void IsTagOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { genericAsmResultNames(*this, setNameFn); } @@ -5670,7 +5952,7 @@ FIRRTLType RefSubOp::inferReturnType(ValueRange operands, return RefType::get( vectorType.getElementType().getConstType( vectorType.isConst() || vectorType.getElementType().isConst()), - refType.getForceable()); + refType.getForceable(), refType.getLayer()); return emitInferRetTypeError(loc, "out of range index '", fieldIdx, "' in RefType of vector type ", refType); } @@ -5683,13 +5965,43 @@ FIRRTLType RefSubOp::inferReturnType(ValueRange operands, return RefType::get(bundleType.getElement(fieldIdx).type.getConstType( bundleType.isConst() || bundleType.getElement(fieldIdx).type.isConst()), - refType.getForceable()); + refType.getForceable(), refType.getLayer()); } return emitInferRetTypeError( loc, "ref.sub op requires a RefType of vector or bundle base type"); } +LogicalResult RefCastOp::verify() { + auto srcLayers = getLayersFor(getInput()); + auto dstLayers = getLayersFor(getResult()); + SmallVector missingLayers; + if (!isLayerSetCompatibleWith(srcLayers, dstLayers, missingLayers)) { + auto diag = + emitOpError("cannot discard layer requirements of input reference"); + auto ¬e = diag.attachNote(); + note << "discarding layer requirements: "; + llvm::interleaveComma(missingLayers, note); + return failure(); + } + return success(); +} + +LogicalResult RefResolveOp::verify() { + auto srcLayers = getLayersFor(getRef()); + auto dstLayers = getAmbientLayersAt(getOperation()); + SmallVector missingLayers; + if (!isLayerSetCompatibleWith(srcLayers, dstLayers, missingLayers)) { + auto diag = + emitOpError("ambient layers are insufficient to resolve reference"); + auto ¬e = diag.attachNote(); + note << "missing layer requirements: "; + interleaveComma(missingLayers, note); + return failure(); + } + return success(); +} + LogicalResult RWProbeOp::verifyInnerRefs(hw::InnerRefNamespace &ns) { auto targetRef = getTarget(); if (targetRef.getModule() != @@ -5773,56 +6085,67 @@ LogicalResult LayerBlockOp::verify() { } // Verify the body of the region. - Block *body = getBody(0); - bool failed = false; - body->walk([&](Operation *op) { - // Skip nested layer blocks. Those will be verified separately. - if (isa(op)) - return WalkResult::skip(); - // Check all the operands of each op to make sure that only legal things are - // captured. - for (auto operand : op->getOperands()) { - // Any value captured from the current layer block is fine. - if (operand.getParentBlock() == body) - continue; - // Capture of a non-base type, e.g., reference is illegal. - FIRRTLBaseType baseType = dyn_cast(operand.getType()); - if (!baseType) { - auto diag = emitOpError() - << "captures an operand which is not a FIRRTL base type"; - diag.attachNote(operand.getLoc()) << "operand is defined here"; - diag.attachNote(op->getLoc()) << "operand is used here"; - failed = true; - return WalkResult::advance(); - } - // Capturing a non-passive type is illegal. - if (!baseType.isPassive()) { - auto diag = emitOpError() - << "captures an operand which is not a passive type"; - diag.attachNote(operand.getLoc()) << "operand is defined here"; - diag.attachNote(op->getLoc()) << "operand is used here"; - failed = true; - return WalkResult::advance(); - } - } - // Ensure that the layer block does not drive any sinks. - if (auto connect = dyn_cast(op)) { - auto dest = getFieldRefFromValue(connect.getDest()).getValue(); - if (dest.getParentBlock() == body) + auto result = getBody(0)->walk( + [&](Operation *op) -> WalkResult { + // Skip nested layer blocks. Those will be verified separately. + if (isa(op)) + return WalkResult::skip(); + + // Check all the operands of each op to make sure that only legal things + // are captured. + for (auto operand : op->getOperands()) { + // Any value captured from the current layer block is fine. + if (auto *definingOp = operand.getDefiningOp()) + if (getOperation()->isAncestor(definingOp)) + continue; + + auto type = operand.getType(); + + // Capture of a non-base type, e.g., reference, is allowed. + if (isa(type)) { + auto diag = emitOpError() << "captures a property operand"; + diag.attachNote(operand.getLoc()) << "operand is defined here"; + diag.attachNote(op->getLoc()) << "operand is used here"; + return WalkResult::interrupt(); + } + + // Capturing a non-passive type is illegal. + if (auto baseType = type_dyn_cast(type)) { + if (!baseType.isPassive()) { + auto diag = emitOpError() + << "captures an operand which is not a passive type"; + diag.attachNote(operand.getLoc()) << "operand is defined here"; + diag.attachNote(op->getLoc()) << "operand is used here"; + return WalkResult::interrupt(); + } + } + } + + // Ensure that the layer block does not drive any sinks outside. + if (auto connect = dyn_cast(op)) { + // ref.define is allowed to drive probes outside the layerblock. + if (isa(connect)) + return WalkResult::advance(); + + // We can drive any destination inside the current layerblock. + auto dest = getFieldRefFromValue(connect.getDest()).getValue(); + if (auto *destOp = dest.getDefiningOp()) + if (getOperation()->isAncestor(destOp)) + return WalkResult::advance(); + + auto diag = + connect.emitOpError() + << "connects to a destination which is defined outside its " + "enclosing layer block"; + diag.attachNote(getLoc()) << "enclosing layer block is defined here"; + diag.attachNote(dest.getLoc()) << "destination is defined here"; + return WalkResult::interrupt(); + } + return WalkResult::advance(); - auto diag = connect.emitOpError() - << "connects to a destination which is defined outside its " - "enclosing layer block"; - diag.attachNote(getLoc()) << "enclosing layer block is defined here"; - diag.attachNote(dest.getLoc()) << "destination is defined here"; - failed = true; - } - return WalkResult::advance(); - }); - if (failed) - return failure(); + }); - return success(); + return failure(result.wasInterrupted()); } LogicalResult diff --git a/lib/Dialect/FIRRTL/FIRRTLTypes.cpp b/lib/Dialect/FIRRTL/FIRRTLTypes.cpp index 483fa58cef20..0db07f3424fc 100644 --- a/lib/Dialect/FIRRTL/FIRRTLTypes.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLTypes.cpp @@ -105,11 +105,13 @@ static LogicalResult customTypePrinter(Type type, AsmPrinter &os) { printNestedType(vectorType.getElementType(), os); os << ", " << vectorType.getNumElements() << '>'; }) - .Case([&](auto refType) { + .Case([&](RefType refType) { if (refType.getForceable()) os << "rw"; os << "probe<"; printNestedType(refType.getType(), os); + if (auto layer = refType.getLayer()) + os << ", " << layer; os << '>'; }) .Case([&](auto stringType) { os << "string"; }) @@ -329,31 +331,43 @@ static OptionalParseResult customTypeParser(AsmParser &parser, StringRef name, // For now, support both firrtl.ref and firrtl.probe. if (name.equals("ref") || name.equals("probe")) { FIRRTLBaseType type; + SymbolRefAttr layer; // Don't pass `isConst` to `parseNestedBaseType since `ref` can point to // either `const` or non-`const` types - if (parser.parseLess() || parseNestedBaseType(type, parser) || - parser.parseGreater()) + if (parser.parseLess() || parseNestedBaseType(type, parser)) + return failure(); + if (parser.parseOptionalComma().succeeded()) + if (parser.parseOptionalAttribute(layer).value()) + return parser.emitError(parser.getNameLoc(), + "expected symbol reference"); + if (parser.parseGreater()) return failure(); if (failed(RefType::verify( [&]() { return parser.emitError(parser.getNameLoc()); }, type, - false))) + false, layer))) return failure(); - return result = RefType::get(type, false), success(); + return result = RefType::get(type, false, layer), success(); } if (name.equals("rwprobe")) { FIRRTLBaseType type; - if (parser.parseLess() || parseNestedBaseType(type, parser) || - parser.parseGreater()) + SymbolRefAttr layer; + if (parser.parseLess() || parseNestedBaseType(type, parser)) + return failure(); + if (parser.parseOptionalComma().succeeded()) + if (parser.parseOptionalAttribute(layer).value()) + return parser.emitError(parser.getNameLoc(), + "expected symbol reference"); + if (parser.parseGreater()) return failure(); if (failed(RefType::verify( - [&]() { return parser.emitError(parser.getNameLoc()); }, type, - true))) + [&]() { return parser.emitError(parser.getNameLoc()); }, type, true, + layer))) return failure(); - return result = RefType::get(type, true), success(); + return result = RefType::get(type, true, layer), success(); } if (name.equals("class")) { if (isConst) @@ -817,6 +831,30 @@ bool firrtl::containsConst(Type type) { .Default(false); } +// NOLINTBEGIN(misc-no-recursion) +bool firrtl::hasZeroBitWidth(FIRRTLType type) { + return FIRRTLTypeSwitch(type) + .Case([&](auto bundle) { + for (size_t i = 0, e = bundle.getNumElements(); i < e; ++i) { + auto elt = bundle.getElement(i); + if (hasZeroBitWidth(elt.type)) + return true; + } + return bundle.getNumElements() == 0; + }) + .Case([&](auto vector) { + if (vector.getNumElements() == 0) + return true; + return hasZeroBitWidth(vector.getElementType()); + }) + .Case([](auto groundType) { + return firrtl::getBitWidth(groundType).value_or(0) == 0; + }) + .Case([](auto ref) { return hasZeroBitWidth(ref.getType()); }) + .Default([](auto) { return false; }); +} +// NOLINTEND(misc-no-recursion) + /// Helper to implement the equivalence logic for a pair of bundle elements. /// Note that the FIRRTL spec requires bundle elements to have the same /// orientation, but this only compares their passive types. The FIRRTL dialect @@ -2400,12 +2438,14 @@ BaseTypeAliasType::getIndexAndSubfieldID(uint64_t fieldID) const { // RefType //===----------------------------------------------------------------------===// -auto RefType::get(FIRRTLBaseType type, bool forceable) -> RefType { - return Base::get(type.getContext(), type, forceable); +auto RefType::get(FIRRTLBaseType type, bool forceable, SymbolRefAttr layer) + -> RefType { + return Base::get(type.getContext(), type, forceable, layer); } auto RefType::verify(function_ref emitErrorFn, - FIRRTLBaseType base, bool forceable) -> LogicalResult { + FIRRTLBaseType base, bool forceable, SymbolRefAttr layer) + -> LogicalResult { if (!base.isPassive()) return emitErrorFn() << "reference base type must be passive"; if (forceable && base.containsConst()) diff --git a/lib/Dialect/FIRRTL/FIRRTLUtils.cpp b/lib/Dialect/FIRRTL/FIRRTLUtils.cpp index 0c02fe024374..b7da0a82d8fc 100644 --- a/lib/Dialect/FIRRTL/FIRRTLUtils.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLUtils.cpp @@ -14,6 +14,7 @@ #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/InnerSymbolNamespace.h" #include "circt/Dialect/Seq/SeqTypes.h" +#include "circt/Support/Naming.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "llvm/ADT/TypeSwitch.h" @@ -42,8 +43,14 @@ void circt::firrtl::emitConnect(ImplicitLocOpBuilder &builder, Value dst, if (dstFType != srcFType) src = builder.create(dstFType, src); builder.create(dst, src); - } else // Other types, give up and leave a connect + } else if (type_isa(dstFType) && + type_isa(srcFType)) { + // Properties use propassign. + builder.create(dst, src); + } else { + // Other types, give up and leave a connect builder.create(dst, src); + } return; } @@ -836,7 +843,7 @@ circt::firrtl::maybeStringToLocation(StringRef spelling, bool skipParsing, FileLineColLoc &fileLineColLocCache, MLIRContext *context) { // The spelling of the token looks something like "@[Decoupled.scala 221:8]". - if (!spelling.startswith("@[") || !spelling.endswith("]")) + if (!spelling.starts_with("@[") || !spelling.ends_with("]")) return {false, std::nullopt}; spelling = spelling.drop_front(2).drop_back(1); diff --git a/lib/Dialect/FIRRTL/Import/FIRAnnotations.cpp b/lib/Dialect/FIRRTL/Import/FIRAnnotations.cpp index e4cb1cf6326d..7f20e007ec25 100644 --- a/lib/Dialect/FIRRTL/Import/FIRAnnotations.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRAnnotations.cpp @@ -15,6 +15,7 @@ #include "circt/Dialect/FIRRTL/AnnotationDetails.h" #include "circt/Dialect/FIRRTL/FIRParser.h" #include "circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h" +#include "circt/Dialect/FIRRTL/Import/FIRAnnotations.h" #include "circt/Support/JSON.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/BuiltinTypes.h" @@ -53,9 +54,9 @@ bool circt::firrtl::fromOMIRJSON(json::Value &value, /// represented as a Target-keyed arrays of attributes. The input JSON value is /// checked, at runtime, to be an array of objects. Returns true if successful, /// false if unsuccessful. -bool circt::firrtl::fromJSONRaw(json::Value &value, - SmallVectorImpl &annotations, - json::Path path, MLIRContext *context) { +bool circt::firrtl::importAnnotationsFromJSONRaw( + json::Value &value, SmallVectorImpl &annotations, + json::Path path, MLIRContext *context) { // The JSON value must be an array of objects. Anything else is reported as // invalid. diff --git a/lib/Dialect/FIRRTL/Import/FIRAnnotations.h b/lib/Dialect/FIRRTL/Import/FIRAnnotations.h index b49f06f1eb08..3ffdb20d1e5b 100644 --- a/lib/Dialect/FIRRTL/Import/FIRAnnotations.h +++ b/lib/Dialect/FIRRTL/Import/FIRAnnotations.h @@ -39,10 +39,6 @@ bool fromOMIRJSON(llvm::json::Value &value, SmallVectorImpl &annotations, llvm::json::Path path, MLIRContext *context); -bool fromJSONRaw(llvm::json::Value &value, - SmallVectorImpl &annotations, llvm::json::Path path, - MLIRContext *context); - ParseResult foldWhenEncodedVerifOp(PrintFOp printOp); } // namespace firrtl diff --git a/lib/Dialect/FIRRTL/Import/FIRParser.cpp b/lib/Dialect/FIRRTL/Import/FIRParser.cpp index c47bc0d9448e..c11b11c422cb 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParser.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParser.cpp @@ -18,6 +18,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLAttributes.h" #include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" +#include "circt/Dialect/FIRRTL/Import/FIRAnnotations.h" #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/InnerSymbolNamespace.h" #include "circt/Support/LLVM.h" @@ -25,6 +26,7 @@ #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/IR/PatternMatch.h" #include "mlir/IR/Threading.h" #include "mlir/IR/Verifier.h" #include "mlir/Support/Timing.h" @@ -167,19 +169,27 @@ struct FIRParser { //===--------------------------------------------------------------------===// ParseResult requireFeature(FIRVersion minimum, StringRef feature) { + return requireFeature(minimum, feature, getToken().getLoc()); + } + + ParseResult requireFeature(FIRVersion minimum, StringRef feature, SMLoc loc) { if (version < minimum) - return emitError() << feature << " are a FIRRTL " << minimum - << "+ feature, but the specified FIRRTL version was " - << version; + return emitError(loc) + << feature << " are a FIRRTL " << minimum + << "+ feature, but the specified FIRRTL version was " << version; return success(); } ParseResult removedFeature(FIRVersion removedVersion, StringRef feature) { + return removedFeature(removedVersion, feature, getToken().getLoc()); + } + + ParseResult removedFeature(FIRVersion removedVersion, StringRef feature, + SMLoc loc) { if (version >= removedVersion) - return emitError() << feature << " were removed in FIRRTL " - << removedVersion - << ", but the specified FIRRTL version was " - << version; + return emitError(loc) + << feature << " were removed in FIRRTL " << removedVersion + << ", but the specified FIRRTL version was " << version; return success(); } @@ -934,18 +944,35 @@ ParseResult FIRParser::parseType(FIRRTLType &result, const Twine &message) { auto kind = getToken().getKind(); auto loc = getToken().getLoc(); consumeToken(); - FIRRTLType type; + // Inner Type + FIRRTLType type; if (parseToken(FIRToken::less, "expected '<' in reference type") || - parseType(type, "expected probe data type") || - parseToken(FIRToken::greater, "expected '>' in reference type")) + parseType(type, "expected probe data type")) return failure(); + // Probe Color + SmallVector layers; + if (getToken().getKind() == FIRToken::identifier) { + if (requireFeature({3, 2, 0}, "colored probes")) + return failure(); + do { + StringRef layer; + loc = getToken().getLoc(); + if (parseId(layer, "expected layer name")) + return failure(); + layers.push_back(layer); + } while (consumeIf(FIRToken::period)); + } + + if (!consumeIf(FIRToken::greater)) + return emitError(loc, "expected '>' to end reference type"); + bool forceable = kind == FIRToken::kw_RWProbe; auto innerType = type_dyn_cast(type); - if (!innerType || innerType.containsReference()) - return emitError(loc, "cannot nest reference types"); + if (!innerType) + return emitError(loc, "invalid probe inner type, must be base-type"); if (!innerType.isPassive()) return emitError(loc, "probe inner type must be passive"); @@ -953,7 +980,17 @@ ParseResult FIRParser::parseType(FIRRTLType &result, const Twine &message) { if (forceable && innerType.containsConst()) return emitError(loc, "rwprobe cannot contain const"); - result = RefType::get(innerType, forceable); + SymbolRefAttr layer; + if (!layers.empty()) { + auto nestedLayers = + llvm::map_range(ArrayRef(layers).drop_front(), [&](StringRef a) { + return FlatSymbolRefAttr::get(getContext(), a); + }); + layer = SymbolRefAttr::get(getContext(), layers.front(), + llvm::to_vector(nestedLayers)); + } + + result = RefType::get(innerType, forceable, layer); break; } @@ -1466,7 +1503,8 @@ struct LazyLocationListener : public OpBuilder::Listener { // Notification handler for when an operation is inserted into the builder. /// `op` is the operation that was inserted. - void notifyOperationInserted(Operation *op) override { + void notifyOperationInserted(Operation *op, + mlir::IRRewriter::InsertPoint) override { assert(currentSMLoc != SMLoc() && "No .fir file location specified"); assert(isActive && "Not parsing a statement"); subOps.push_back({op, currentSMLoc}); @@ -2215,6 +2253,12 @@ ParseResult FIRStmtParser::parsePrimExp(Value &result) { case FIRToken::lp_tail: attrNames.push_back(getConstants().amountIdentifier); break; + case FIRToken::lp_integer_add: + case FIRToken::lp_integer_mul: + case FIRToken::lp_integer_shr: + if (requireFeature({4, 0, 0}, "Integer arithmetic expressions", loc)) + return failure(); + break; } if (operands.size() != numOperandsExpected) { @@ -2249,12 +2293,23 @@ ParseResult FIRStmtParser::parsePrimExp(Value &result) { return failure(); \ } \ result = builder.create(resultTy, operands, attrs); \ - return success(); \ + break; \ } #include "FIRTokenKinds.def" } - - llvm_unreachable("all cases should return"); + // Don't add code here, the common cases of these switch statements will be + // merged. This allows for fixing up primops after they have been created. + switch (kind) { + default: + break; + case FIRToken::lp_shr: + // For FIRRTL versions earlier than 4.0.0, insert pad(_, 1) around any + // unsigned shr This ensures the minimum width is 1 (but can be greater) + if (version < FIRVersion(4, 0, 0) && type_isa(result.getType())) + result = builder.create(result, 1); + break; + } + return success(); } /// integer-literal-exp ::= 'UInt' optional-width '(' intLit ')' @@ -2747,49 +2802,65 @@ ParseResult FIRStmtParser::parseStop() { return success(); } -/// assert ::= 'assert(' exp exp exp StringLit ')' info? +/// assert ::= 'assert(' exp exp exp StringLit exp*')' info? ParseResult FIRStmtParser::parseAssert() { auto startTok = consumeToken(FIRToken::lp_assert); Value clock, predicate, enable; - StringRef message; + StringRef formatString; StringAttr name; if (parseExp(clock, "expected clock expression in 'assert'") || parseExp(predicate, "expected predicate in 'assert'") || parseExp(enable, "expected enable in 'assert'") || - parseGetSpelling(message) || - parseToken(FIRToken::string, "expected message in 'assert'") || - parseToken(FIRToken::r_paren, "expected ')' in 'assert'") || - parseOptionalName(name) || parseOptionalInfo()) + parseGetSpelling(formatString) || + parseToken(FIRToken::string, "expected format string in 'assert'")) + return failure(); + + SmallVector operands; + while (!consumeIf(FIRToken::r_paren)) { + operands.push_back({}); + if (parseExp(operands.back(), "expected operand in 'assert'")) + return failure(); + } + + if (parseOptionalName(name) || parseOptionalInfo()) return failure(); locationProcessor.setLoc(startTok.getLoc()); - auto messageUnescaped = FIRToken::getStringValue(message); - builder.create(clock, predicate, enable, messageUnescaped, - ValueRange{}, name.getValue()); + auto formatStrUnescaped = FIRToken::getStringValue(formatString); + builder.create(clock, predicate, enable, formatStrUnescaped, + operands, name.getValue()); return success(); } -/// assume ::= 'assume(' exp exp exp StringLit ')' info? +/// assume ::= 'assume(' exp exp exp StringLit exp* ')' info? ParseResult FIRStmtParser::parseAssume() { auto startTok = consumeToken(FIRToken::lp_assume); Value clock, predicate, enable; - StringRef message; + StringRef formatString; StringAttr name; if (parseExp(clock, "expected clock expression in 'assume'") || parseExp(predicate, "expected predicate in 'assume'") || parseExp(enable, "expected enable in 'assume'") || - parseGetSpelling(message) || - parseToken(FIRToken::string, "expected message in 'assume'") || - parseToken(FIRToken::r_paren, "expected ')' in 'assume'") || - parseOptionalName(name) || parseOptionalInfo()) + parseGetSpelling(formatString) || + parseToken(FIRToken::string, "expected format string in 'assume'")) + return failure(); + + SmallVector operands; + while (!consumeIf(FIRToken::r_paren)) { + operands.push_back({}); + if (parseExp(operands.back(), "expected operand in 'assume'")) + return failure(); + } + + if (parseOptionalName(name) || parseOptionalInfo()) return failure(); locationProcessor.setLoc(startTok.getLoc()); - auto messageUnescaped = FIRToken::getStringValue(message); - builder.create(clock, predicate, enable, messageUnescaped, - ValueRange{}, name.getValue()); + auto formatStrUnescaped = FIRToken::getStringValue(formatString); + builder.create(clock, predicate, enable, formatStrUnescaped, + operands, name.getValue()); return success(); } @@ -2883,6 +2954,7 @@ ParseResult FIRStmtParser::parseWhen(unsigned whenIndent) { /// enum-exp ::= enum-type '(' Id ( ',' exp )? ')' ParseResult FIRStmtParser::parseEnumExp(Value &value) { auto startLoc = getToken().getLoc(); + locationProcessor.setLoc(startLoc); FIRRTLType type; if (parseEnumType(type)) return failure(); @@ -2902,7 +2974,7 @@ ParseResult FIRStmtParser::parseEnumExp(Value &value) { if (consumeIf(FIRToken::r_paren)) { // If the payload is not specified, we create a 0 bit unsigned integer // constant. - auto type = IntType::get(builder.getContext(), false, 0); + auto type = IntType::get(builder.getContext(), false, 0, true); Type attrType = IntegerType::get(getContext(), 0, IntegerType::Unsigned); auto attr = builder.getIntegerAttr(attrType, APInt(0, 0, false)); input = builder.create(type, attr); @@ -2913,7 +2985,6 @@ ParseResult FIRStmtParser::parseEnumExp(Value &value) { return failure(); } - locationProcessor.setLoc(startLoc); value = builder.create(enumType, tag, input); return success(); } @@ -4389,6 +4460,8 @@ struct FIRCircuitParser : public FIRParser { ParseResult parseIntModule(CircuitOp circuit, unsigned indent); ParseResult parseModule(CircuitOp circuit, bool isPublic, unsigned indent); + ParseResult parseLayerName(SymbolRefAttr &result); + ParseResult parseOptionalEnabledLayers(ArrayAttr &result); ParseResult parsePortList(SmallVectorImpl &resultPorts, SmallVectorImpl &resultPortLocs, unsigned indent); @@ -4435,7 +4508,8 @@ FIRCircuitParser::importAnnotationsRaw(SMLoc loc, StringRef annotationsStr, json::Path::Root root; llvm::StringMap thisAnnotationMap; - if (!fromJSONRaw(annotations.get(), attrs, root, getContext())) { + if (!importAnnotationsFromJSONRaw(annotations.get(), attrs, root, + getContext())) { auto diag = emitError(loc, "Invalid/unsupported annotation format"); std::string jsonErrorMessage = "See inline comments for problem area in JSON:\n"; @@ -4475,6 +4549,47 @@ ParseResult FIRCircuitParser::importOMIR(CircuitOp circuit, SMLoc loc, return success(); } +ParseResult FIRCircuitParser::parseLayerName(SymbolRefAttr &result) { + auto *context = getContext(); + SmallVector strings; + do { + StringRef name; + if (parseId(name, "expected layer name")) + return failure(); + strings.push_back(name); + } while (consumeIf(FIRToken::period)); + + SmallVector nested; + nested.reserve(strings.size() - 1); + for (unsigned i = 1, e = strings.size(); i < e; ++i) + nested.push_back(FlatSymbolRefAttr::get(context, strings[i])); + + result = SymbolRefAttr::get(context, strings[0], nested); + return success(); +} + +ParseResult FIRCircuitParser::parseOptionalEnabledLayers(ArrayAttr &result) { + if (getToken().getKind() != FIRToken::kw_enablelayer) { + result = ArrayAttr::get(getContext(), {}); + return success(); + } + + if (requireFeature({4, 0, 0}, "modules with layers enabled")) + return failure(); + + SmallVector layers; + do { + SymbolRefAttr layer; + consumeToken(); + if (parseLayerName(layer)) + return failure(); + layers.push_back(layer); + } while (getToken().getKind() == FIRToken::kw_enablelayer); + + result = ArrayAttr::get(getContext(), layers); + return success(); +} + /// portlist ::= port* /// port ::= dir id ':' type info? NEWLINE /// dir ::= 'input' | 'output' @@ -4828,11 +4943,13 @@ ParseResult FIRCircuitParser::parseExtClass(CircuitOp circuit, ParseResult FIRCircuitParser::parseExtModule(CircuitOp circuit, unsigned indent) { StringAttr name; + ArrayAttr layers; SmallVector portList; SmallVector portLocs; LocWithInfo info(getToken().getLoc(), this); consumeToken(FIRToken::kw_extmodule); if (parseId(name, "expected extmodule name") || + parseOptionalEnabledLayers(layers) || parseToken(FIRToken::colon, "expected ':' in extmodule definition") || info.parseOptionalInfo() || parsePortList(portList, portLocs, indent)) return failure(); @@ -4849,6 +4966,15 @@ ParseResult FIRCircuitParser::parseExtModule(CircuitOp circuit, if (parseParameterList(parameters) || parseRefList(portList, internalPaths)) return failure(); + if (version >= FIRVersion{4, 0, 0}) { + for (auto [pi, loc] : llvm::zip_equal(portList, portLocs)) { + if (auto ftype = type_dyn_cast(pi.type)) { + if (ftype.hasUninferredWidth()) + return emitError(loc, "extmodule port must have known width"); + } + } + } + auto builder = circuit.getBodyBuilder(); auto isMainModule = (name == circuit.getName()); auto convention = @@ -4860,7 +4986,7 @@ ParseResult FIRCircuitParser::parseExtModule(CircuitOp circuit, auto annotations = ArrayAttr::get(getContext(), {}); auto extModuleOp = builder.create( info.getLoc(), name, conventionAttr, portList, defName, annotations, - parameters, internalPaths); + parameters, internalPaths, layers); auto visibility = isMainModule ? SymbolTable::Visibility::Public : SymbolTable::Visibility::Private; SymbolTable::setSymbolVisibility(extModuleOp, visibility); @@ -4874,11 +5000,13 @@ ParseResult FIRCircuitParser::parseExtModule(CircuitOp circuit, ParseResult FIRCircuitParser::parseIntModule(CircuitOp circuit, unsigned indent) { StringAttr name; + ArrayAttr layers; SmallVector portList; SmallVector portLocs; LocWithInfo info(getToken().getLoc(), this); consumeToken(FIRToken::kw_intmodule); if (parseId(name, "expected intmodule name") || + parseOptionalEnabledLayers(layers) || parseToken(FIRToken::colon, "expected ':' in intmodule definition") || info.parseOptionalInfo() || parsePortList(portList, portLocs, indent)) return failure(); @@ -4899,7 +5027,7 @@ ParseResult FIRCircuitParser::parseIntModule(CircuitOp circuit, auto builder = circuit.getBodyBuilder(); builder .create(info.getLoc(), name, portList, intName, annotations, - parameters, internalPaths) + parameters, internalPaths, layers) .setPrivate(); return success(); } @@ -4910,9 +5038,11 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit, bool isPublic, StringAttr name; SmallVector portList; SmallVector portLocs; + ArrayAttr layers; LocWithInfo info(getToken().getLoc(), this); consumeToken(FIRToken::kw_module); if (parseId(name, "expected module name") || + parseOptionalEnabledLayers(layers) || parseToken(FIRToken::colon, "expected ':' in module definition") || info.parseOptionalInfo() || parsePortList(portList, portLocs, indent)) return failure(); @@ -4920,6 +5050,18 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit, bool isPublic, // The main module is implicitly public. isPublic |= name == circuit.getName(); + if (isPublic && version >= FIRVersion{4, 0, 0}) { + for (auto [pi, loc] : llvm::zip_equal(portList, portLocs)) { + if (auto ftype = type_dyn_cast(pi.type)) { + if (ftype.hasUninferredWidth()) + return emitError(loc, "public module port must have known width"); + if (ftype.hasUninferredReset()) + return emitError(loc, + "public module port must have concrete reset type"); + } + } + } + ArrayAttr annotations = getConstants().emptyArrayAttr; auto convention = Convention::Internal; if (isPublic && getConstants().options.scalarizePublicModules) @@ -4927,7 +5069,7 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit, bool isPublic, auto conventionAttr = ConventionAttr::get(getContext(), convention); auto builder = circuit.getBodyBuilder(); auto moduleOp = builder.create(info.getLoc(), name, conventionAttr, - portList, annotations); + portList, annotations, layers); auto visibility = isPublic ? SymbolTable::Visibility::Public : SymbolTable::Visibility::Private; diff --git a/lib/Dialect/FIRRTL/Import/FIRParserAsserts.cpp b/lib/Dialect/FIRRTL/Import/FIRParserAsserts.cpp index f5deca251eef..baaf4051d7b4 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParserAsserts.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParserAsserts.cpp @@ -276,7 +276,7 @@ ParseResult circt::firrtl::foldWhenEncodedVerifOp(PrintFOp printOp) { flavor = VerifFlavor::Cover; else if (fmt.consume_front("assertNotX:")) flavor = VerifFlavor::AssertNotX; - else if (fmt.startswith("Assertion failed")) + else if (fmt.starts_with("Assertion failed")) flavor = VerifFlavor::ChiselAssert; else return success(); @@ -406,7 +406,7 @@ ParseResult circt::firrtl::foldWhenEncodedVerifOp(PrintFOp printOp) { printOp.emitError("printf-encoded assertNotX requires one operand"); return failure(); } - // Construct a `!whenCond | (value !== 1'bx)` predicate. + // Construct a `!whenCond | (^value !== 1'bx)` predicate. Value notCond = predicate; predicate = builder.create(printOp.getSubstitutions()[0]); predicate = builder.create(predicate); @@ -523,10 +523,9 @@ ParseResult circt::firrtl::foldWhenEncodedVerifOp(PrintFOp printOp) { break; case PredicateModifier::TrueOrIsX: // Construct a `predicate | (^predicate === 1'bx)`. - Value orX = builder.create(predicate); - orX = builder.create(UIntType::get(context, 1), - "{{0}} === 1'bx", orX); - predicate = builder.create(predicate, orX); + Value isX = + builder.create(builder.create(predicate)); + predicate = builder.create(predicate, isX); break; } diff --git a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def index a8447769116f..e927e8bf76a1 100644 --- a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def +++ b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def @@ -112,6 +112,7 @@ TOK_KEYWORD(declgroup) TOK_KEYWORD(define) TOK_KEYWORD(defname) TOK_KEYWORD(else) +TOK_KEYWORD(enablelayer) TOK_KEYWORD(extclass) TOK_KEYWORD(extmodule) TOK_KEYWORD(false) @@ -214,6 +215,9 @@ TOK_LPKEYWORD_PRIM(sub, SubPrimOp, 2) TOK_LPKEYWORD_PRIM(tail, TailPrimOp, 1) TOK_LPKEYWORD_PRIM(xor, XorPrimOp, 2) TOK_LPKEYWORD_PRIM(xorr, XorRPrimOp, 1) +TOK_LPKEYWORD_PRIM(integer_add, IntegerAddOp, 2) +TOK_LPKEYWORD_PRIM(integer_mul, IntegerMulOp, 2) +TOK_LPKEYWORD_PRIM(integer_shr, IntegerShrOp, 2) #undef TOK_MARKER #undef TOK_IDENTIFIER diff --git a/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp b/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp index 9f653c139ba0..276135471616 100644 --- a/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp +++ b/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp @@ -14,9 +14,11 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" +#include "circt/Dialect/Emit/EmitOps.h" #include "circt/Dialect/FIRRTL/AnnotationDetails.h" #include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h" #include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h" +#include "circt/Dialect/FIRRTL/Namespace.h" #include "circt/Dialect/FIRRTL/Passes.h" #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWDialect.h" @@ -36,9 +38,6 @@ using namespace circt; using namespace firrtl; -using hw::OutputFileAttr; -using sv::VerbatimOp; - //===----------------------------------------------------------------------===// // Pass Implementation //===----------------------------------------------------------------------===// @@ -55,7 +54,7 @@ struct AnnotationInfo { /// The name of the file that should be created for this BlackBox. StringAttr name; /// The output directory where this annotation should be written. - OutputFileAttr outputFile; + StringAttr outputFile; /// The body of the BlackBox. (This should be Verilog text.) StringAttr inlineText; /// The priority of this annotation. In the even that multiple annotations @@ -63,6 +62,8 @@ struct AnnotationInfo { /// external module is instantiated multiple times and those multiple /// instantiations disagree on where the module should go. Priority priority = Priority::Unset; + /// Indicates whether the file is included in the file list. + bool excludeFromFileList = false; #if !defined(NDEBUG) /// Pretty print the AnnotationInfo in a YAML-esque format. @@ -73,23 +74,28 @@ struct AnnotationInfo { } os << llvm::formatv("name: {1}\n" "{0}outputFile: {2}\n" - "{0}priority: {3}\n", - llvm::fmt_pad("", indent, 0), name, - outputFile.getFilename(), (unsigned)priority); + "{0}priority: {3}\n" + "{0}exclude: {4}\n", + llvm::fmt_pad("", indent, 0), name, outputFile, + (unsigned)priority, excludeFromFileList); }; #endif }; +/// Collects the attributes representing an output file. +struct OutputFileInfo { + StringAttr fileName; + Priority priority; + bool excludeFromFileList; +}; + struct BlackBoxReaderPass : public BlackBoxReaderBase { void runOnOperation() override; bool runOnAnnotation(Operation *op, Annotation anno, OpBuilder &builder, bool isCover, AnnotationInfo &annotationInfo); StringAttr loadFile(Operation *op, StringRef inputPath, OpBuilder &builder); - std::pair getOutputFile(Operation *origOp, - StringAttr fileNameAttr, - bool isCover = false); - void setOutputFile(VerbatimOp op, OutputFileAttr outputFile, - StringAttr fileNameAttr); + OutputFileInfo getOutputFile(Operation *origOp, StringAttr fileNameAttr, + bool isCover = false); // Check if module or any of its parents in the InstanceGraph is a DUT. bool isDut(Operation *module); @@ -98,7 +104,7 @@ struct BlackBoxReaderPass : public BlackBoxReaderBase { private: /// A list of all files which will be included in the file list. This is /// subset of all emitted files. - SmallVector fileListFiles; + SmallVector fileListFiles; /// The target directory to output black boxes into. Can be changed /// through `firrtl.transforms.BlackBoxTargetDirAnno` annotations. @@ -135,13 +141,15 @@ struct BlackBoxReaderPass : public BlackBoxReaderBase { /// appropriate annotation is found (e.g., which will cause the file to be /// written to the DUT directory and not the TestHarness directory), then this /// will map will be updated. - llvm::MapVector emittedFileMap; + llvm::MapVector emittedFileMap; }; } // end anonymous namespace /// Emit the annotated source code for black boxes in a circuit. void BlackBoxReaderPass::runOnOperation() { CircuitOp circuitOp = getOperation(); + CircuitNamespace ns(circuitOp); + instanceGraph = &getAnalysis(); auto context = &getContext(); @@ -260,6 +268,7 @@ void BlackBoxReaderPass::runOnOperation() { } LLVM_DEBUG(llvm::dbgs() << "emittedFiles:\n"); + Location loc = builder.getUnknownLoc(); for (auto &[verilogName, annotationInfo] : emittedFileMap) { LLVM_DEBUG({ llvm::dbgs().indent(2) << "verilogName: " << verilogName << "\n"; @@ -267,9 +276,16 @@ void BlackBoxReaderPass::runOnOperation() { annotationInfo.print(llvm::dbgs().indent(4) << "- ", 6); }); - auto verbatim = builder.create(builder.getUnknownLoc(), - annotationInfo.inlineText); - setOutputFile(verbatim, annotationInfo.outputFile, annotationInfo.name); + auto fileName = ns.newName("blackbox_" + verilogName.getValue()); + + auto fileOp = builder.create( + loc, annotationInfo.outputFile, fileName, + [&, text = annotationInfo.inlineText] { + builder.create(loc, text); + }); + + if (!annotationInfo.excludeFromFileList) + fileListFiles.push_back(fileOp); } // If we have emitted any files, generate a file list operation that @@ -277,31 +293,20 @@ void BlackBoxReaderPass::runOnOperation() { // created. if (!fileListFiles.empty()) { // Output the file list in sorted order. - llvm::sort(fileListFiles.begin(), fileListFiles.end()); - - // Create the file list contents by prepending the file name with the target - // directory, and putting each file on its own line. - std::string output; - llvm::raw_string_ostream os(output); - llvm::interleave( - fileListFiles, os, - [&](StringRef fileName) { - SmallString<32> filePath(targetDir); - llvm::sys::path::append(filePath, fileName); - llvm::sys::path::remove_dots(filePath); - os << filePath; - }, - "\n"); - - // Put the file list in to a verbatim op. Use "unknown location" so that no - // file info will unnecessarily print. - auto op = - builder.create(builder.getUnknownLoc(), std::move(output)); - - // Attach the output file information to the verbatim op. - op->setAttr("output_file", hw::OutputFileAttr::getFromFilename( - context, resourceFileName, - /*excludeFromFileList=*/true)); + llvm::sort(fileListFiles.begin(), fileListFiles.end(), + [](emit::FileOp fileA, emit::FileOp fileB) { + return fileA.getFileName() < fileB.getFileName(); + }); + + // Create the file list contents by enumerating the symbols to the files. + SmallVector symbols; + for (emit::FileOp file : fileListFiles) + symbols.push_back(FlatSymbolRefAttr::get(file.getSymNameAttr())); + + builder.create( + loc, builder.getStringAttr(resourceFileName), + builder.getArrayAttr(symbols), + builder.getStringAttr(ns.newName("blackbox_filelist"))); } // If nothing has changed we can preserve the analysis. @@ -332,10 +337,11 @@ bool BlackBoxReaderPass::runOnAnnotation(Operation *op, Annotation anno, } auto outputFile = getOutputFile(op, name, isCover); - annotationInfo.outputFile = outputFile.first; + annotationInfo.outputFile = outputFile.fileName; annotationInfo.name = name; annotationInfo.inlineText = text; - annotationInfo.priority = outputFile.second; + annotationInfo.priority = outputFile.priority; + annotationInfo.excludeFromFileList = outputFile.excludeFromFileList; return true; } @@ -358,10 +364,11 @@ bool BlackBoxReaderPass::runOnAnnotation(Operation *op, Annotation anno, } auto name = builder.getStringAttr(llvm::sys::path::filename(path)); auto outputFile = getOutputFile(op, name, isCover); - annotationInfo.outputFile = outputFile.first; + annotationInfo.outputFile = outputFile.fileName; annotationInfo.name = name; annotationInfo.inlineText = text; - annotationInfo.priority = outputFile.second; + annotationInfo.priority = outputFile.priority; + annotationInfo.excludeFromFileList = outputFile.excludeFromFileList; return true; } @@ -388,14 +395,16 @@ StringAttr BlackBoxReaderPass::loadFile(Operation *op, StringRef inputPath, } /// Determine the output file for some operation. -std::pair -BlackBoxReaderPass::getOutputFile(Operation *origOp, StringAttr fileNameAttr, - bool isCover) { +OutputFileInfo BlackBoxReaderPass::getOutputFile(Operation *origOp, + StringAttr fileNameAttr, + bool isCover) { // If the original operation has a specified output file that is not a // directory, then just use that. - auto outputFile = origOp->getAttrOfType("output_file"); - if (outputFile && !outputFile.isDirectory()) - return {outputFile, Priority::TargetDir}; + auto outputFile = origOp->getAttrOfType("output_file"); + if (outputFile && !outputFile.isDirectory()) { + return {outputFile.getFilename(), Priority::TargetDir, + outputFile.getExcludeFromFilelist().getValue()}; + } // Exclude Verilog header files since we expect them to be included // explicitly by compiler directives in other source files. @@ -417,24 +426,9 @@ BlackBoxReaderPass::getOutputFile(Operation *origOp, StringAttr fileNameAttr, // If targetDir is not set explicitly and this is a testbench module, then // update the targetDir to be the "../testbench". - auto outFileAttr = OutputFileAttr::getFromDirectoryAndFilename( - context, outDir.first, fileName, - /*excludeFromFileList=*/exclude); - return {outFileAttr, outDir.second}; -} - -/// This function is called for every file generated. It does the following -/// things: -/// 1. Attaches the output file attribute to the VerbatimOp. -/// 2. Record that the file has been generated to avoid duplicates. -/// 3. Add each file name to the generated "file list" file. -void BlackBoxReaderPass::setOutputFile(VerbatimOp op, OutputFileAttr outputFile, - StringAttr fileNameAttr) { - op->setAttr("output_file", outputFile); - - // Append this file to the file list if its not excluded. - if (!outputFile.getExcludeFromFilelist().getValue()) - fileListFiles.push_back(outputFile.getFilename()); + SmallString<128> outputFilePath(outDir.first); + llvm::sys::path::append(outputFilePath, fileName); + return {StringAttr::get(context, outputFilePath), outDir.second, exclude}; } /// Return true if module is in the DUT hierarchy. diff --git a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt index 11ce8fca312d..6a9979b11765 100755 --- a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt +++ b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt @@ -65,6 +65,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms LINK_LIBS PUBLIC CIRCTFIRRTL CIRCTDebug + CIRCTEmit CIRCTHW CIRCTOM CIRCTSV diff --git a/lib/Dialect/FIRRTL/Transforms/CheckCombLoops.cpp b/lib/Dialect/FIRRTL/Transforms/CheckCombLoops.cpp index 2dd2dab3f7c0..b952add43c0b 100644 --- a/lib/Dialect/FIRRTL/Transforms/CheckCombLoops.cpp +++ b/lib/Dialect/FIRRTL/Transforms/CheckCombLoops.cpp @@ -6,448 +6,592 @@ // //===----------------------------------------------------------------------===// // -// This file implements the FIRRTL combinational cycles detection pass. The -// algorithm handles aggregates and sub-index/field/access ops. +// This file implements the FIRRTL combinational cycles detection pass. +// Terminology: +// In the context of the circt that the MLIR represents, a Value is called the +// driver of another Value, if the driver actively sets the the other Value. The +// driver Value is responsible for determining the logic level of the driven +// Value. +// This pass is a dataflow analysis that interprets the operations of the +// circt to build a connectivity graph, which represents the driver +// relationships. Each node in this connectivity graph is a FieldRef, and an +// edge exists from a source node to a desination node if the source drives the +// destination.. +// Algorithm to construct the connectivity graph. // 1. Traverse each module in the Instance Graph bottom up. -// 2. Preprocess step: Gather all the Value which serve as the root for the -// DFS traversal. The input arguments and wire ops and Instance results -// are the roots. -// Then populate the map for Value to all the FieldRefs it can refer to, -// and another map of FieldRef to all the Values that refer to it. -// (A single Value can refer to multiple FieldRefs, if the Value is the -// result of a SubAccess op. Multiple values can refer to the same -// FieldRef, since multiple SubIndex/Field ops with the same fieldIndex -// can exist in the IR). We also maintain an aliasingValuesMap that maps -// each Value to the set of Values that can refer to the same FieldRef. -// 3. Start from DFS traversal from the root. Push the root to the DFS stack. -// 4. Pop a Value from the DFS stack, add all the Values that alias with it -// to the Visiting set. Add all the unvisited children of the Values in the -// alias set to the DFS stack. -// 5. If any child is already present in the Visiting set, then a cycle is -// found. -// +// 2. Preprocess step: Construct the circt connectivity directed graph. +// Perform a dataflow analysis, on the domain of FieldRefs. Interpret each +// operation, to record the values that can potentially drive another value. +// Each node in the graph is a fieldRef. Each edge represents a dataflow from +// the source to the sink. +// 3. Perform a DFS traversal on the graph, to detect combinational loops and +// paths between ports. +// 4. Inline the combinational paths discovered in each module to its instance +// and continue the analysis through the instance graph. //===----------------------------------------------------------------------===// #include "PassDetails.h" +#include "circt/Dialect/FIRRTL/CHIRRTLDialect.h" #include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h" +#include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/FIRRTLVisitors.h" #include "circt/Dialect/FIRRTL/Passes.h" #include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/DepthFirstIterator.h" +#include "llvm/ADT/EquivalenceClasses.h" #include "llvm/ADT/PostOrderIterator.h" -#include "llvm/ADT/SCCIterator.h" -#include "llvm/ADT/SmallSet.h" -#include #define DEBUG_TYPE "check-comb-loops" using namespace circt; using namespace firrtl; -using SetOfFieldRefs = DenseSet; - -/// A value is in VisitingSet if its subtree is still being traversed. That is, -/// all its children have not yet been visited. If any Value is visited while -/// its still in the `VisitingSet`, that implies a back edge and a cycle. -struct VisitingSet { -private: - /// The stack is maintained to keep track of the cycle, if one is found. This - /// is required for an iterative DFS traversal, its implicitly recorded for a - /// recursive version of this algorithm. Each entry in the stack is a list of - /// aliasing Values, which were visited at the same time. - SmallVector> visitingStack; - /// This map of the Visiting values, is for faster query, to check if a Value - /// is in VisitingSet. It also records the corresponding index into the - /// visitingStack, for faster pop until the Value. - DenseMap valToStackMap; - -public: - void appendEmpty() { visitingStack.push_back({}); } - void appendToEnd(SmallVector &values) { - auto stackSize = visitingStack.size() - 1; - visitingStack.back().append(values.begin(), values.end()); - // Record the stack location where this Value is pushed. - llvm::for_each(values, [&](Value v) { valToStackMap[v] = stackSize; }); - } - bool contains(Value v) { - return valToStackMap.find(v) != valToStackMap.end(); - } - // Pop all the Values which were visited after v. Then invoke f (if present) - // on a popped value for each index. - void popUntilVal(Value v, - const llvm::function_ref f = {}) { - auto valPos = valToStackMap[v]; - while (visitingStack.size() != valPos) { - auto poppedVals = visitingStack.pop_back_val(); - Value poppedVal; - llvm::for_each(poppedVals, [&](Value pv) { - if (!poppedVal) - poppedVal = pv; - valToStackMap.erase(pv); - }); - if (f && poppedVal) - f(poppedVal); - } - } -}; +using DrivenBysMapType = DenseMap>; class DiscoverLoops { + /// Adjacency list representation. + /// Each entry is a pair, the first element is the FieldRef corresponding to + /// the graph vertex. The second element is the list of vertices, that have an + /// edge to this vertex. + /// The directed edges represent a connectivity relation, of a source that + /// drives the sink. + using DrivenByGraphType = + SmallVector>, 64>; + public: - DiscoverLoops(FModuleOp module, InstanceGraph &instanceGraph, - DenseMap &portPaths) - : module(module), instanceGraph(instanceGraph), portPaths(portPaths) {} + DiscoverLoops( + FModuleOp module, InstanceGraph &instanceGraph, + const DenseMap &otherModulePortPaths, + DrivenBysMapType &thisModulePortPaths) + : module(module), instanceGraph(instanceGraph), + modulePortPaths(otherModulePortPaths), portPaths(thisModulePortPaths) {} LogicalResult processModule() { LLVM_DEBUG(llvm::dbgs() << "\n processing module :" << module.getName()); - SmallVector worklist; - // Traverse over ports and ops, to populate the worklist and get the - // FieldRef corresponding to every Value. Also process the InstanceOps and - // get the paths that exist between the ports of the referenced module. - preprocess(worklist); - - llvm::DenseSet visited; - VisitingSet visiting; - SmallVector dfsStack; - SmallVector inputArgFields; - // Record all the children of Value being visited. - SmallVector children; - // If this is an input port field, then record it. This is used to - // discover paths from input to output ports. Only the last input port - // that is visited on the DFS traversal is recorded. - SmallVector inputArgFieldsTemp; - SmallVector aliasingValues; - - // worklist is the list of roots, to begin the traversal from. - for (auto root : worklist) { - dfsStack = {root}; - inputArgFields.clear(); - LLVM_DEBUG(llvm::dbgs() << "\n Starting traversal from root :" - << getFieldName(FieldRef(root, 0)).first); - if (auto inArg = dyn_cast(root)) { - if (module.getPortDirection(inArg.getArgNumber()) == Direction::In) - // This is required, such that paths to output port can be discovered. - // If there is an overlapping path from two input ports to an output - // port, then the already visited nodes must be re-visited to discover - // the comb paths to the output port. - visited.clear(); - } - while (!dfsStack.empty()) { - auto dfsVal = dfsStack.back(); - if (!visiting.contains(dfsVal)) { - unsigned dfsSize = dfsStack.size(); - - LLVM_DEBUG(llvm::dbgs() << "\n Stack pop :" - << getFieldName(FieldRef(dfsVal, 0)).first - << "," << dfsVal;); - - // Visiting set will contain all the values which alias with the - // dfsVal, this is required to detect back edges to aliasing Values. - // That is fieldRefs that can refer to the same memory location. - visiting.appendEmpty(); - children.clear(); - inputArgFieldsTemp.clear(); - // All the Values that refer to the same FieldRef are added to the - // aliasingValues. - aliasingValues = {dfsVal}; - auto aToVIter = aliasingValuesMap.find(dfsVal); - if (aToVIter != aliasingValuesMap.end()) { - aliasingValues.append(aToVIter->getSecond().begin(), - aToVIter->getSecond().end()); - } - // If `dfsVal` is a subfield, then get all the FieldRefs that it - // refers to and then get all the values that alias with it. - forallRefersTo(dfsVal, [&](FieldRef ref) { - // If this subfield refers to instance/mem results(input port), then - // add the output port FieldRefs that exist in the referenced module - // comb paths to the children. - handlePorts(ref, children); - // Get all the values that refer to this FieldRef, and add them to - // the aliasing values. - if (auto arg = dyn_cast(ref.getValue())) - if (module.getPortDirection(arg.getArgNumber()) == Direction::In) - inputArgFieldsTemp.push_back(ref); - - return success(); - }); - if (!inputArgFieldsTemp.empty()) - inputArgFields = std::move(inputArgFieldsTemp); - - visiting.appendToEnd(aliasingValues); - visited.insert(aliasingValues.begin(), aliasingValues.end()); - // Add the Value to `children`, to which a path exists from `dfsVal`. - for (auto dfsFromVal : aliasingValues) { - - for (auto &use : dfsFromVal.getUses()) { - auto childVal = - TypeSwitch(use.getOwner()) - // Registers stop walk for comb loops. - .Case([](auto _) { return Value(); }) - // For non-register declarations, look at data result. - .Case([](auto op) { return op.getDataRaw(); }) - // Handle connect ops specially. - .Case([&](FConnectLike connect) -> Value { - if (use.getOperandNumber() == 1) { - auto dst = connect.getDest(); - if (handleConnects(dst, inputArgFields).succeeded()) - return dst; - } - return {}; - }) - // For everything else (e.g., expressions), if has single - // result use that. - .Default([](auto op) -> Value { - if (op->getNumResults() == 1) - return op->getResult(0); - return {}; - }); - if (childVal && type_isa(childVal.getType())) - children.push_back(childVal); - } - } - for (auto childVal : children) { - // This childVal can be ignored, if - // It is a Register or a subfield of a register. - if (!visited.contains(childVal)) - dfsStack.push_back(childVal); - // If the childVal is a sub, then check if it aliases with any of - // the predecessors (the visiting set). - if (visiting.contains(childVal)) { - // Comb Cycle Detected !! - reportLoopFound(childVal, visiting); - return failure(); - } - } - // child nodes added, continue the DFS - if (dfsSize != dfsStack.size()) - continue; - } - // FieldRef is an SCC root node, pop the visiting stack to remove the - // nodes that are no longer active predecessors, that is their sub-tree - // is already explored. All the Values reachable from `dfsVal` have been - // explored, remove it and its children from the visiting stack. - visiting.popUntilVal(dfsVal); - - auto popped = dfsStack.pop_back_val(); - (void)popped; - LLVM_DEBUG({ - llvm::dbgs() << "\n dfs popped :" - << getFieldName(FieldRef(popped, 0)).first; - dump(); - }); - } - } - - return success(); + constructConnectivityGraph(module); + return dfsTraverse(drivenBy); } - // Preprocess the module ops to get the - // 1. roots for DFS traversal, - // 2. FieldRef corresponding to each Value. - void preprocess(SmallVector &worklist) { - // All the input ports are added to the worklist. - for (BlockArgument arg : module.getArguments()) { - auto argType = type_cast(arg.getType()); - if (type_isa(argType)) + void constructConnectivityGraph(FModuleOp module) { + LLVM_DEBUG(llvm::dbgs() << "\n Module :" << module.getName()); + + // ALl the module output ports must be added as the initial nodes. + for (auto port : module.getArguments()) { + if (module.getPortDirection(port.getArgNumber()) != Direction::Out) continue; - if (module.getPortDirection(arg.getArgNumber()) == Direction::In) - worklist.push_back(arg); - if (!argType.isGround()) - setValRefsTo(arg, FieldRef(arg, 0)); + walkGroundTypes(port.getType().cast(), + [&](uint64_t index, FIRRTLBaseType t, auto isFlip) { + getOrAddNode(FieldRef(port, index)); + }); } - DenseSet memPorts; - - for (auto &op : module.getOps()) { - TypeSwitch(&op) - // Wire is added to the worklist - .Case([&](WireOp wire) { - worklist.push_back(wire.getResult()); - auto ty = type_dyn_cast(wire.getResult().getType()); - if (ty && !ty.isGround()) - setValRefsTo(wire.getResult(), FieldRef(wire.getResult(), 0)); + + bool foreignOps = false; + walk(module, [&](Operation *op) { + llvm::TypeSwitch(op) + .Case([&](auto) {}) + .Case([&](Forceable forceableOp) { + // Any declaration that can be forced. + if (auto node = dyn_cast(op)) + recordDataflow(node.getData(), node.getInput()); + if (!forceableOp.isForceable() || + forceableOp.getDataRef().use_empty()) + return; + auto data = forceableOp.getData(); + auto ref = forceableOp.getDataRef(); + // Record dataflow from data to the probe. + recordDataflow(ref, data); + recordProbe(data, ref); }) - // All sub elements are added to the worklist. - .Case([&](SubfieldOp sub) { - auto res = sub.getResult(); - bool isValid = false; - auto fieldIndex = sub.getAccessedField().getFieldID(); - if (memPorts.contains(sub.getInput())) { - auto memPort = sub.getInput(); - BundleType type = memPort.getType(); - auto enableFieldId = - type.getFieldID((unsigned)ReadPortSubfield::en); - auto dataFieldId = - type.getFieldID((unsigned)ReadPortSubfield::data); - auto addressFieldId = - type.getFieldID((unsigned)ReadPortSubfield::addr); - if (fieldIndex == enableFieldId || fieldIndex == dataFieldId || - fieldIndex == addressFieldId) { - setValRefsTo(memPort, FieldRef(memPort, 0)); - } else - return; - } - SmallVector fields; - forallRefersTo( - sub.getInput(), - [&](FieldRef subBase) { - isValid = true; - fields.push_back(subBase.getSubField(fieldIndex)); - return success(); - }, - false); - if (isValid) { - for (auto f : fields) - setValRefsTo(res, f); - } + .Case([&](MemOp mem) { handleMemory(mem); }) + .Case([&](RefSendOp send) { + recordDataflow(send.getResult(), send.getBase()); }) + .Case([&](RefDefineOp def) { + // Dataflow from src to dst. + recordDataflow(def.getDest(), def.getSrc()); + if (!def.getDest().getType().getForceable()) + return; + // Dataflow from dst to src, for RWProbe. + probesReferToSameData(def.getSrc(), def.getDest()); + }) + .Case( + [&](auto ref) { handleRefForce(ref.getDest(), ref.getSrc()); }) + .Case([&](auto inst) { handleInstanceOp(inst); }) .Case([&](SubindexOp sub) { - auto res = sub.getResult(); - bool isValid = false; - auto index = sub.getAccessedField().getFieldID(); - SmallVector fields; - forallRefersTo( + recordValueRefersToFieldRef( sub.getInput(), - [&](FieldRef subBase) { - isValid = true; - fields.push_back(subBase.getSubField(index)); - return success(); - }, - false); - if (isValid) { - for (auto f : fields) - setValRefsTo(res, f); - } + sub.getInput().getType().base().getFieldID(sub.getIndex()), + sub.getResult()); }) - .Case([&](SubaccessOp sub) { - FVectorType vecType = sub.getInput().getType(); - auto res = sub.getResult(); - bool isValid = false; - SmallVector fields; - forallRefersTo( + .Case([&](SubfieldOp sub) { + recordValueRefersToFieldRef( sub.getInput(), - [&](FieldRef subBase) { - isValid = true; - // The result of a subaccess can refer to multiple storage - // locations corresponding to all the possible indices. - for (size_t index = 0; index < vecType.getNumElements(); - ++index) - fields.push_back(subBase.getSubField( - 1 + index * (hw::FieldIdImpl::getMaxFieldID( - vecType.getElementType()) + - 1))); - return success(); - }, - false); - if (isValid) { - for (auto f : fields) - setValRefsTo(res, f); - } + sub.getInput().getType().base().getFieldID(sub.getFieldIndex()), + sub.getResult()); + }) + .Case([&](SubaccessOp sub) { + auto vecType = sub.getInput().getType().base(); + auto input = sub.getInput(); + auto result = sub.getResult(); + // Flatten the subaccess. The result can refer to any of the + // elements. + for (size_t index = 0; index < vecType.getNumElements(); ++index) + recordValueRefersToFieldRef(input, vecType.getFieldID(index), + result); + }) + .Case([&](RefSubOp sub) { + size_t fieldID = TypeSwitch( + sub.getInput().getType().getType()) + .Case([&](auto type) { + return type.getFieldID(sub.getIndex()); + }); + recordValueRefersToFieldRef(sub.getInput(), fieldID, + sub.getResult()); + }) + .Case([&](auto op) { + auto type = op.getType(); + auto res = op.getResult(); + auto getFieldId = [&](unsigned index) { + size_t fieldID = + TypeSwitch(type) + .Case( + [&](auto type) { return type.getFieldID(index); }); + return fieldID; + }; + for (auto [index, v] : llvm::enumerate(op.getOperands())) + recordValueRefersToFieldRef(res, getFieldId(index), v); + }) + .Case([&](FConnectLike connect) { + recordDataflow(connect.getDest(), connect.getSrc()); + }) + .Case([&](auto) { + // Casts are cast-like regardless of source. + // UnrealizedConversionCastOp doesn't implement CastOpInterace, + // otherwise we would use it here. + for (auto res : op->getResults()) + for (auto src : op->getOperands()) + recordDataflow(res, src); }) - .Case( - [&](InstanceOp ins) { handleInstanceOp(ins, worklist); }) - .Case([&](MemOp mem) { - if (!(mem.getReadLatency() == 0)) { + .Default([&](Operation *op) { + // Non FIRRTL ops are not checked + if (!op->getDialect() || + !isa( + op->getDialect())) { + if (!foreignOps && op->getNumResults() > 0 && + op->getNumOperands() > 0) { + op->emitRemark("Non-firrtl operations detected, combinatorial " + "loop checking may miss some loops."); + foreignOps = true; + } return; } - for (auto memPort : mem.getResults()) { - if (!type_isa(memPort.getType())) - continue; - memPorts.insert(memPort); + // All other expressions. + if (op->getNumResults() == 1) { + auto res = op->getResult(0); + for (auto src : op->getOperands()) + recordDataflow(res, src); } - }) - .Default([&](auto) {}); - } + }); + }); } - void handleInstanceOp(InstanceOp ins, SmallVector &worklist) { - for (auto port : ins.getResults()) { - if (auto type = type_dyn_cast(port.getType())) { - worklist.push_back(port); - if (!type.isGround()) - setValRefsTo(port, FieldRef(port, 0)); - } else if (auto type = type_dyn_cast(port.getType())) { - worklist.push_back(port); - } - } + static std::string getName(FieldRef v) { return getFieldName(v).first; }; + + unsigned getOrAddNode(Value v) { + auto iter = valToFieldRefs.find(v); + if (iter == valToFieldRefs.end()) + return getOrAddNode({v, 0}); + return getOrAddNode(*iter->second.begin()); } - void handlePorts(FieldRef ref, SmallVectorImpl &children) { - if (auto inst = dyn_cast_or_null(ref.getDefiningOp())) { - auto res = cast(ref.getValue()); - auto portNum = res.getResultNumber(); - auto refMod = inst.getReferencedModule(instanceGraph); - if (!refMod) - return; - FieldRef modArg(refMod.getArgument(portNum), ref.getFieldID()); - auto pathIter = portPaths.find(modArg); - if (pathIter == portPaths.end()) + // Get the node id if it exists, else add it to the graph. + unsigned getOrAddNode(FieldRef f) { + auto iter = nodes.find(f); + if (iter != nodes.end()) + return iter->second; + // Add the fieldRef to the graph. + auto id = drivenBy.size(); + // The node id can be used to index into the graph. The entry is a pair, + // first element is the corresponding FieldRef, and the second entry is a + // list of adjacent nodes. + drivenBy.push_back({f, {}}); + nodes[f] = id; + return id; + } + + // Construct the connectivity graph, by adding `dst` and `src` as new nodes, + // if not already existing. Then add an edge from `src` to `dst`. + void addDrivenBy(FieldRef dst, FieldRef src) { + auto srcNode = getOrAddNode(src); + auto dstNode = getOrAddNode(dst); + drivenBy[dstNode].second.push_back(srcNode); + } + + // Add `dstVal` as being driven by `srcVal`. + void recordDataflow(Value dstVal, Value srcVal) { + // Ignore connectivity from constants. + if (auto *def = srcVal.getDefiningOp()) + if (def->hasTrait()) return; - for (auto modOutPort : pathIter->second) { - auto outPortNum = - cast(modOutPort.getValue()).getArgNumber(); - if (modOutPort.getFieldID() == 0) { - children.push_back(inst.getResult(outPortNum)); + // Check if srcVal/dstVal is a fieldRef to an aggregate. Then, there may + // exist other values, that refer to the same fieldRef. Add a connectivity + // from all such "aliasing" values. + auto dstIt = valToFieldRefs.find(dstVal); + auto srcIt = valToFieldRefs.find(srcVal); + + // Block the dataflow through registers. + // dstVal refers to a register, If dstVal is not recorded as the fieldref of + // an aggregate, and its either a register, or result of a sub op. + if (dstIt == valToFieldRefs.end()) + if (auto *def = dstVal.getDefiningOp()) + if (isa(def)) + return; + + auto dstValType = getBaseType(dstVal.getType()); + auto srcValType = getBaseType(srcVal.getType()); + + // Handle Properties. + if (!(srcValType && dstValType)) + return addDrivenBy({dstVal, 0}, {srcVal, 0}); + + auto addDef = [&](FieldRef dst, FieldRef src) { + // If the dstVal and srcVal are aggregate types, then record the dataflow + // between each individual ground type. This is equivalent to flattening + // the type to ensure all the contained FieldRefs are also recorded. + if (dstValType && !dstValType.isGround()) + walkGroundTypes(dstValType, [&](uint64_t dstIndex, FIRRTLBaseType t, + bool dstIsFlip) { + // Handle the case when the dst and src are not of the same type. + // For each dst ground type, and for each src ground type. + if (srcValType == dstValType) { + // This is the only case when the flip is valid. Flip is relevant + // only for connect ops, and src and dst of a connect op must be + // type equivalent! + if (dstIsFlip) + std::swap(dst, src); + addDrivenBy(dst.getSubField(dstIndex), src.getSubField(dstIndex)); + } else if (srcValType && !srcValType.isGround()) + walkGroundTypes(srcValType, [&](uint64_t srcIndex, FIRRTLBaseType t, + auto) { + addDrivenBy(dst.getSubField(dstIndex), src.getSubField(srcIndex)); + }); + // Else, the src is ground type. + else + addDrivenBy(dst.getSubField(dstIndex), src); + }); + + addDrivenBy(dst, src); + }; + + // Both the dstVal and srcVal, can refer to multiple FieldRefs, ensure that + // we capture the dataflow between each pair. Occurs when val result of + // subaccess op. + + // Case 1: None of src and dst are fields of an aggregate. But they can be + // aggregate values. + if (dstIt == valToFieldRefs.end() && srcIt == valToFieldRefs.end()) + return addDef({dstVal, 0}, {srcVal, 0}); + // Case 2: Only src is the field of an aggregate. Get all the fields that + // the src refers to. + if (dstIt == valToFieldRefs.end() && srcIt != valToFieldRefs.end()) { + llvm::for_each(srcIt->getSecond(), [&](const FieldRef &srcField) { + addDef(FieldRef(dstVal, 0), srcField); + }); + return; + } + // Case 3: Only dst is the field of an aggregate. Get all the fields that + // the dst refers to. + if (dstIt != valToFieldRefs.end() && srcIt == valToFieldRefs.end()) { + llvm::for_each(dstIt->getSecond(), [&](const FieldRef &dstField) { + addDef(dstField, FieldRef(srcVal, 0)); + }); + return; + } + // Case 4: Both src and dst are the fields of an aggregate. Get all the + // fields that they refers to. + llvm::for_each(srcIt->second, [&](const FieldRef &srcField) { + llvm::for_each(dstIt->second, [&](const FieldRef &dstField) { + addDef(dstField, srcField); + }); + }); + } + + // Record srcVal as driving the original data value that the probe refers to. + void handleRefForce(Value dstProbe, Value srcVal) { + recordDataflow(dstProbe, srcVal); + auto dstNode = getOrAddNode(dstProbe); + // Now add srcVal as driving the data that dstProbe refers to. + auto leader = rwProbeClasses.findLeader(dstNode); + if (leader == rwProbeClasses.member_end()) + return; + auto iter = rwProbeRefersTo.find(*leader); + assert(iter != rwProbeRefersTo.end()); + if (iter->second != dstNode) + drivenBy[iter->second].second.push_back(getOrAddNode(srcVal)); + } + + // Check the referenced module paths and add input ports as the drivers for + // the corresponding output port. The granularity of the connectivity + // relations is per field. + void handleInstanceOp(InstanceOp inst) { + auto refMod = inst.getReferencedModule(instanceGraph); + // TODO: External modules not handled !! + if (!refMod) + return; + auto modulePaths = modulePortPaths.find(refMod); + if (modulePaths == modulePortPaths.end()) + return; + // Note: Handling RWProbes. + // 1. For RWProbes, output ports can be source of dataflow. + // 2. All the RWProbes that refer to the same base value form a strongly + // connected component. Each has a dataflow from the other, including + // itself. + // Steps to add the instance ports to the connectivity graph: + // 1. Find the set of RWProbes that refer to the same base value. + // 2. Add them to the same rwProbeClasses. + // 3. Choose the first RWProbe port from this set as a representative base + // value. And add it as the source driver for every other RWProbe + // port in the set. + // 4. This will ensure we can detect cycles involving different RWProbes to + // the same base value. + for (auto &path : modulePaths->second) { + auto modSinkPortField = path.first; + auto sinkArgNum = + cast(modSinkPortField.getValue()).getArgNumber(); + FieldRef sinkPort(inst.getResult(sinkArgNum), + modSinkPortField.getFieldID()); + auto sinkNode = getOrAddNode(sinkPort); + bool sinkPortIsForceable = false; + if (auto refResultType = + type_dyn_cast(inst.getResult(sinkArgNum).getType())) + sinkPortIsForceable = refResultType.getForceable(); + + DenseSet setOfEquivalentRWProbes; + unsigned minArgNum = sinkArgNum; + unsigned basePortNode = sinkNode; + for (auto &modSrcPortField : path.second) { + auto srcArgNum = + cast(modSrcPortField.getValue()).getArgNumber(); + // Self loop will exist for RWProbes, ignore them. + if (modSrcPortField == modSinkPortField) continue; + + FieldRef srcPort(inst.getResult(srcArgNum), + modSrcPortField.getFieldID()); + bool srcPortIsForceable = false; + if (auto refResultType = + type_dyn_cast(inst.getResult(srcArgNum).getType())) + srcPortIsForceable = refResultType.getForceable(); + // RWProbes can potentially refer to the same base value. Such ports + // have a path from each other, a false loop, detect such cases. + if (sinkPortIsForceable && srcPortIsForceable) { + auto srcNode = getOrAddNode(srcPort); + // If a path is already recorded, continue. + if (rwProbeClasses.findLeader(srcNode) != + rwProbeClasses.member_end() && + rwProbeClasses.findLeader(sinkNode) == + rwProbeClasses.findLeader(srcNode)) + continue; + // Check if sinkPort is a driver of sourcePort. + auto drivenBysToSrcPort = modulePaths->second.find(modSrcPortField); + if (drivenBysToSrcPort != modulePaths->second.end()) + if (llvm::find(drivenBysToSrcPort->second, modSinkPortField) != + drivenBysToSrcPort->second.end()) { + // This case can occur when there are multiple RWProbes on the + // port, which refer to the same base value. So, each of such + // probes are drivers of each other. Hence the false + // loops. Instead of recording this in the drivenByGraph, + // record it separately with the rwProbeClasses. + setOfEquivalentRWProbes.insert(srcNode); + if (minArgNum > srcArgNum) { + // Make one of the RWProbe port the base node. Use the first + // port for deterministic error messages. + minArgNum = srcArgNum; + basePortNode = srcNode; + } + continue; + } } - FieldRef instanceOutPort(inst.getResult(outPortNum), - modOutPort.getFieldID()); - llvm::append_range(children, fieldToVals[instanceOutPort]); + addDrivenBy(sinkPort, srcPort); } - } else if (auto mem = dyn_cast(ref.getDefiningOp())) { - if (mem.getReadLatency() > 0) + if (setOfEquivalentRWProbes.empty()) + continue; + + // Add all the rwprobes to the same class. + for (auto probe : setOfEquivalentRWProbes) + rwProbeClasses.unionSets(probe, sinkNode); + + // Make the first port as the base value. + // Note: this is a port and the actual reference base exists in another + // module. + auto leader = rwProbeClasses.getLeaderValue(sinkNode); + rwProbeRefersTo[leader] = basePortNode; + + setOfEquivalentRWProbes.insert(sinkNode); + // Add the base RWProbe port as a driver to all other RWProbe ports. + for (auto probe : setOfEquivalentRWProbes) + if (probe != basePortNode) + drivenBy[probe].second.push_back(basePortNode); + } + } + + // Record the FieldRef, corresponding to the result of the sub op + // `result = base[index]` + void recordValueRefersToFieldRef(Value base, unsigned fieldID, Value result) { + + // Check if base is itself field of an aggregate. + auto it = valToFieldRefs.find(base); + if (it != valToFieldRefs.end()) { + // Rebase it to the original aggregate. + // Because of subaccess op, each value can refer to multiple FieldRefs. + SmallVector entry; + for (auto &sub : it->second) + entry.emplace_back(sub.getValue(), sub.getFieldID() + fieldID); + // Update the map at the end, to avoid invaliding the iterator. + valToFieldRefs[result].append(entry.begin(), entry.end()); + return; + } + // Break cycles from registers. + if (auto *def = base.getDefiningOp()) { + if (isa(def)) return; - auto memPort = ref.getValue(); - auto type = type_cast(memPort.getType()); - auto enableFieldId = type.getFieldID((unsigned)ReadPortSubfield::en); - auto dataFieldId = type.getFieldID((unsigned)ReadPortSubfield::data); - auto addressFieldId = type.getFieldID((unsigned)ReadPortSubfield::addr); - if (ref.getFieldID() == enableFieldId || - ref.getFieldID() == addressFieldId) { - for (auto dataField : fieldToVals[FieldRef(memPort, dataFieldId)]) - children.push_back(dataField); - } } + valToFieldRefs[result].emplace_back(base, fieldID); } - void reportLoopFound(Value childVal, VisitingSet visiting) { - // TODO: Work harder to provide best information possible to user, - // especially across instances or when we trace through aliasing values. - // We're about to exit, and can afford to do some slower work here. - auto getName = [&](Value v) { - if (isa_and_nonnull( - v.getDefiningOp())) { - assert(!valRefersTo[v].empty()); - // Pick representative of the "alias set", not deterministic. - return getFieldName(*valRefersTo[v].begin()).first; + void handleMemory(MemOp mem) { + if (mem.getReadLatency() > 0) + return; + // Add the enable and address fields as the drivers of the data field. + for (auto memPort : mem.getResults()) + // TODO: Can reftype ports create cycle ? + if (auto type = type_dyn_cast(memPort.getType())) { + auto enableFieldId = type.getFieldID((unsigned)ReadPortSubfield::en); + auto addressFieldId = type.getFieldID((unsigned)ReadPortSubfield::addr); + auto dataFieldId = type.getFieldID((unsigned)ReadPortSubfield::data); + addDrivenBy({memPort, static_cast(dataFieldId)}, + {memPort, static_cast(enableFieldId)}); + addDrivenBy({memPort, static_cast(dataFieldId)}, + {memPort, static_cast(addressFieldId)}); } - return getFieldName(FieldRef(v, 0)).first; + } + + // Perform an iterative DFS traversal of the given graph. Record paths between + // the ports and detect and report any cycles in the graph. + LogicalResult dfsTraverse(const DrivenByGraphType &graph) { + auto numNodes = graph.size(); + SmallVector onStack(numNodes, false); + SmallVector dfsStack; + + auto hasCycle = [&](unsigned rootNode, DenseSet &visited, + bool recordPortPaths = false) { + if (visited.contains(rootNode)) + return success(); + dfsStack.push_back(rootNode); + + while (!dfsStack.empty()) { + auto currentNode = dfsStack.back(); + + if (!visited.contains(currentNode)) { + visited.insert(currentNode); + onStack[currentNode] = true; + LLVM_DEBUG(llvm::dbgs() + << "\n visiting :" + << drivenBy[currentNode].first.getValue().getType() + << drivenBy[currentNode].first.getValue() << "," + << drivenBy[currentNode].first.getFieldID() << "\n" + << getName(drivenBy[currentNode].first)); + + FieldRef currentF = drivenBy[currentNode].first; + if (recordPortPaths && currentNode != rootNode) { + if (isa(currentF.getValue())) + portPaths[drivenBy[rootNode].first].insert(currentF); + // Even if the current node is not a port, there can be RWProbes of + // the current node at the port. + addToPortPathsIfRWProbe(currentNode, + portPaths[drivenBy[rootNode].first]); + } + } else { + onStack[currentNode] = false; + dfsStack.pop_back(); + } + + for (auto neighbor : graph[currentNode].second) { + if (!visited.contains(neighbor)) { + dfsStack.push_back(neighbor); + } else if (onStack[neighbor]) { + // Cycle found !! + SmallVector path; + auto loopNode = neighbor; + // Construct the cyclic path. + do { + SmallVector::iterator it = + llvm::find_if(drivenBy[loopNode].second, + [&](unsigned node) { return onStack[node]; }); + if (it == drivenBy[loopNode].second.end()) + break; + + path.push_back(drivenBy[loopNode].first); + loopNode = *it; + } while (loopNode != neighbor); + + reportLoopFound(path, drivenBy[neighbor].first.getLoc()); + return failure(); + } + } + } + return success(); }; - auto errorDiag = mlir::emitError( - module.getLoc(), "detected combinational cycle in a FIRRTL module"); - SmallVector path; - path.push_back(childVal); - visiting.popUntilVal( - childVal, [&](Value visitingVal) { path.push_back(visitingVal); }); - assert(path.back() == childVal); - path.pop_back(); + DenseSet visited; + for (unsigned node = 0; node < graph.size(); ++node) { + bool isPort = false; + if (auto arg = dyn_cast(drivenBy[node].first.getValue())) + if (module.getPortDirection(arg.getArgNumber()) == Direction::Out) { + // For output ports, reset the visited. Required to revisit the entire + // graph, to discover all the paths that exist from any input port. + visited.clear(); + isPort = true; + } + if (hasCycle(node, visited, isPort).failed()) + return failure(); + } + return success(); + } + + void reportLoopFound(SmallVectorImpl &path, Location loc) { + auto errorDiag = mlir::emitError( + module.getLoc(), "detected combinational cycle in a FIRRTL module"); // Find a value we can name - auto *it = - llvm::find_if(path, [&](Value v) { return !getName(v).empty(); }); + std::string firstName; + FieldRef *it = llvm::find_if(path, [&](FieldRef v) { + firstName = getName(v); + return !firstName.empty(); + }); if (it == path.end()) { errorDiag.append(", but unable to find names for any involved values."); - errorDiag.attachNote(childVal.getLoc()) << "cycle detected here"; + errorDiag.attachNote(loc) << "cycle detected here"; return; } + // Begin the path from the "smallest string". + for (circt::FieldRef *findStartIt = it; findStartIt != path.end(); + ++findStartIt) { + auto n = getName(*findStartIt); + if (!n.empty() && n < firstName) { + firstName = n; + it = findStartIt; + } + } errorDiag.append(", sample path: "); bool lastWasDots = false; errorDiag << module.getName() << ".{" << getName(*it); - for (auto v : - llvm::concat(llvm::make_range(std::next(it), path.end()), - llvm::make_range(path.begin(), std::next(it)))) { + for (auto v : llvm::concat( + llvm::make_range(std::next(it), path.end()), + llvm::make_range(path.begin(), std::next(it)))) { auto name = getName(v); if (!name.empty()) { errorDiag << " <- " << name; @@ -461,110 +605,136 @@ class DiscoverLoops { errorDiag << "}"; } - LogicalResult handleConnects(Value dst, - SmallVector &inputArgFields) { + void dumpMap() { + LLVM_DEBUG({ + llvm::dbgs() << "\n Connectivity Graph ==>"; + for (const auto &[index, i] : llvm::enumerate(drivenBy)) { + llvm::dbgs() << "\n ===>dst:" << getName(i.first) + << "::" << i.first.getValue(); + for (auto s : i.second) + llvm::dbgs() << "<---" << getName(drivenBy[s].first) + << "::" << drivenBy[s].first.getValue(); + } - bool onlyFieldZero = true; - auto pathsToOutPort = [&](FieldRef dstFieldRef) { - if (dstFieldRef.getFieldID() != 0) - onlyFieldZero = false; - if (!isa(dstFieldRef.getValue())) { - return failure(); + llvm::dbgs() << "\n Value to FieldRef :"; + for (const auto &fields : valToFieldRefs) { + llvm::dbgs() << "\n Val:" << fields.first; + for (auto f : fields.second) + llvm::dbgs() << ", FieldRef:" << getName(f) << "::" << f.getFieldID(); } - onlyFieldZero = false; - for (auto inArg : inputArgFields) { - portPaths[inArg].insert(dstFieldRef); + llvm::dbgs() << "\n Port paths:"; + for (const auto &p : portPaths) { + llvm::dbgs() << "\n Output :" << getName(p.first); + for (auto i : p.second) + llvm::dbgs() << "\n Input :" << getName(i); } - return success(); - }; - forallRefersTo(dst, pathsToOutPort); - - if (onlyFieldZero) { - if (isa( - dst.getDefiningOp())) - return failure(); - } - return success(); + llvm::dbgs() << "\n rwprobes:"; + for (auto node : rwProbeRefersTo) { + llvm::dbgs() << "\n node:" << getName(drivenBy[node.first].first) + << "=> probe:" << getName(drivenBy[node.second].first); + } + for (auto i = rwProbeClasses.begin(), e = rwProbeClasses.end(); i != e; + ++i) { // Iterate over all of the equivalence sets. + if (!i->isLeader()) + continue; // Ignore non-leader sets. + // Print members in this set. + llvm::interleave(llvm::make_range(rwProbeClasses.member_begin(i), + rwProbeClasses.member_end()), + llvm::dbgs(), "\n"); + llvm::dbgs() << "\n dataflow at leader::" << i->getData() << "\n =>" + << rwProbeRefersTo[i->getData()]; + llvm::dbgs() << "\n Done\n"; // Finish set. + } + }); } - void setValRefsTo(Value val, FieldRef ref) { - assert(val && ref && " Value and Ref cannot be null"); - valRefersTo[val].insert(ref); - auto fToVIter = fieldToVals.find(ref); - if (fToVIter != fieldToVals.end()) { - for (auto aliasingVal : fToVIter->second) { - aliasingValuesMap[val].insert(aliasingVal); - aliasingValuesMap[aliasingVal].insert(val); - } - fToVIter->getSecond().insert(val); - } else - fieldToVals[ref].insert(val); + void recordProbe(Value data, Value ref) { + auto refNode = getOrAddNode(ref); + auto dataNode = getOrAddNode(data); + rwProbeRefersTo[rwProbeClasses.getOrInsertLeaderValue(refNode)] = dataNode; } - void - forallRefersTo(Value val, - const llvm::function_ref f, - bool baseCase = true) { - auto refersToIter = valRefersTo.find(val); - if (refersToIter != valRefersTo.end()) { - for (auto ref : refersToIter->second) - if (f(ref).failed()) - return; - } else if (baseCase) { - FieldRef base(val, 0); - if (f(base).failed()) - return; - } + // Add both the probes to the same equivalence class, to record that they + // refer to the same data value. + void probesReferToSameData(Value probe1, Value probe2) { + auto p1Node = getOrAddNode(probe1); + auto p2Node = getOrAddNode(probe2); + rwProbeClasses.unionSets(p1Node, p2Node); } - void dump() { - for (const auto &valRef : valRefersTo) { - llvm::dbgs() << "\n val :" << valRef.first; - for (auto node : valRef.second) - llvm::dbgs() << "\n Refers to :" << getFieldName(node).first; - } - for (const auto &dtv : fieldToVals) { - llvm::dbgs() << "\n Field :" << getFieldName(dtv.first).first - << " ::" << dtv.first.getValue(); - for (auto val : dtv.second) - llvm::dbgs() << "\n val :" << val; - } - for (const auto &p : portPaths) { - llvm::dbgs() << "\n Output port : " << getFieldName(p.first).first - << " has comb path from :"; - for (const auto &src : p.second) - llvm::dbgs() << "\n Input port : " << getFieldName(src).first; - } + void addToPortPathsIfRWProbe(unsigned srcNode, + DenseSet &inputPortPaths) { + // Check if there exists any RWProbe for the srcNode. + auto baseFieldRef = drivenBy[srcNode].first; + if (auto defOp = dyn_cast_or_null(baseFieldRef.getDefiningOp())) + if (defOp.isForceable() && !defOp.getDataRef().use_empty()) { + // Assumption, the probe must exist in the equivalence classes. + auto rwProbeNode = + rwProbeClasses.getLeaderValue(getOrAddNode(defOp.getDataRef())); + // For all the probes, that are in the same eqv class, i.e., refer to + // the same value. + for (auto probe : + llvm::make_range(rwProbeClasses.member_begin( + rwProbeClasses.findValue(rwProbeNode)), + rwProbeClasses.member_end())) { + auto probeVal = drivenBy[probe].first; + // If the probe is a port, then record the path from the probe to the + // input port. + if (probeVal.getValue().isa()) { + inputPortPaths.insert(probeVal); + } + } + } } +private: FModuleOp module; InstanceGraph &instanceGraph; - /// Map of a Value to all the FieldRefs that it refers to. - DenseMap valRefersTo; - DenseMap> aliasingValuesMap; - - DenseMap> fieldToVals; + /// Map of values to the set of all FieldRefs (same base) that this may be + /// directly derived from through indexing operations. + DenseMap> valToFieldRefs; /// Comb paths that exist between module ports. This is maintained across /// modules. - DenseMap &portPaths; + const DenseMap &modulePortPaths; + /// The comb paths between the ports of this module. This is the final + /// output of this intra-procedural analysis, that is used to construct the + /// inter-procedural dataflow. + DrivenBysMapType &portPaths; + + /// This is an adjacency list representation of the connectivity graph. This + /// can be indexed by the graph node id, and each entry is the list of graph + /// nodes that has an edge to it. Each graph node represents a FieldRef and + /// each edge represents a source that directly drives the sink node. + DrivenByGraphType drivenBy; + /// Map of FieldRef to its corresponding graph node. + DenseMap nodes; + + /// The base value that the RWProbe refers to. Used to add an edge to the base + /// value, when the probe is forced. + DenseMap rwProbeRefersTo; + + /// An eqv class of all the RWProbes that refer to the same base value. + llvm::EquivalenceClasses rwProbeClasses; }; -/// This pass constructs a local graph for each module to detect combinational -/// cycles. To capture the cross-module combinational cycles, this pass inlines -/// the combinational paths between IOs of its subinstances into a subgraph and -/// encodes them in a `combPathsMap`. +/// This pass constructs a local graph for each module to detect +/// combinational cycles. To capture the cross-module combinational cycles, +/// this pass inlines the combinational paths between IOs of its +/// subinstances into a subgraph and encodes them in `modulePortPaths`. class CheckCombLoopsPass : public CheckCombLoopsBase { public: void runOnOperation() override { auto &instanceGraph = getAnalysis(); - DenseMap portPaths; + DenseMap modulePortPaths; + // Traverse modules in a post order to make sure the combinational paths - // between IOs of a module have been detected and recorded in `portPaths` - // before we handle its parent modules. + // between IOs of a module have been detected and recorded in + // `modulePortPaths` before we handle its parent modules. for (auto *igNode : llvm::post_order(&instanceGraph)) { if (auto module = dyn_cast(*igNode->getModule())) { - DiscoverLoops rdf(module, instanceGraph, portPaths); + DiscoverLoops rdf(module, instanceGraph, modulePortPaths, + modulePortPaths[module]); if (rdf.processModule().failed()) { return signalPassFailure(); } diff --git a/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp b/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp index 587e4a7e3d6b..e35c510ac073 100644 --- a/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp +++ b/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp @@ -252,7 +252,9 @@ CreateSiFiveMetadataPass::emitMemoryMetadata(ObjectModelIR &omir) { seqMemSymbols.push_back(memExtSym); // Compute the mask granularity. auto isMasked = mem.isMasked(); - auto maskGran = width / mem.getMaskBits(); + auto maskGran = width; + if (isMasked) + maskGran /= mem.getMaskBits(); // Now create the config string for the memory. std::string portStr; for (uint32_t i = 0; i < mem.getNumWritePorts(); ++i) { diff --git a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp index dc244a26dd3b..84777ec5ba6c 100644 --- a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp +++ b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp @@ -84,6 +84,7 @@ struct StructuralHasherSharedConstants { moduleNameAttr = StringAttr::get(context, "moduleName"); innerSymAttr = StringAttr::get(context, "inner_sym"); portSymsAttr = StringAttr::get(context, "portSyms"); + portNamesAttr = StringAttr::get(context, "portNames"); nonessentialAttributes.insert(StringAttr::get(context, "annotations")); nonessentialAttributes.insert(StringAttr::get(context, "name")); nonessentialAttributes.insert(StringAttr::get(context, "portAnnotations")); @@ -104,6 +105,9 @@ struct StructuralHasherSharedConstants { // This is a cached "portSyms" string attr. StringAttr portSymsAttr; + // This is a cached "portNames" string attr. + StringAttr portNamesAttr; + // This is a set of every attribute we should ignore. DenseSet nonessentialAttributes; }; @@ -159,6 +163,15 @@ struct StructuralHasher { void update(OpResult result) { record(result.getAsOpaquePointer()); + + // Like instance ops, don't use object ops' result types since they might be + // replaced by dedup. Record the class names and lazily combine their hashes + // using the same mechanism as instances and modules. + if (auto objectOp = dyn_cast(result.getOwner())) { + referredModuleNames.push_back(objectOp.getType().getNameAttr().getAttr()); + return; + } + update(result.getType()); } @@ -201,8 +214,11 @@ struct StructuralHasher { for (auto namedAttr : dict) { auto name = namedAttr.getName(); auto value = namedAttr.getValue(); - // Skip names and annotations. - if (constants.nonessentialAttributes.contains(name)) + // Skip names and annotations, except in certain cases. + // Names of ports are load bearing for classes, so we do hash those. + bool isClassPortNames = + isa(op) && name == constants.portNamesAttr; + if (constants.nonessentialAttributes.contains(name) && !isClassPortNames) continue; // Hash the port types. @@ -247,6 +263,11 @@ struct StructuralHasher { // Hash the interned pointer. update(name.getAsOpaquePointer()); + // TODO: properly handle DistinctAttr, including its use in paths. + // See https://github.com/llvm/circt/issues/6583. + if (isa(value)) + continue; + // If this is an symbol reference, we need to perform name erasure. if (auto innerRef = dyn_cast(value)) update(innerRef); @@ -588,6 +609,9 @@ struct Equivalence { if (failed(check(diag, a, cast(aAttr), b, cast(bAttr)))) return failure(); + } else if (isa(aAttr) && isa(bAttr)) { + // TODO: properly handle DistinctAttr, including its use in paths. + // See https://github.com/llvm/circt/issues/6583 } else if (aAttr != bAttr) { diag.attachNote(a->getLoc()) << "first operation has attribute '" << attrName.getValue() @@ -617,21 +641,22 @@ struct Equivalence { } // NOLINTNEXTLINE(misc-no-recursion) - LogicalResult check(InFlightDiagnostic &diag, InstanceOp a, InstanceOp b) { - auto aName = a.getModuleNameAttr().getAttr(); - auto bName = b.getModuleNameAttr().getAttr(); + LogicalResult check(InFlightDiagnostic &diag, FInstanceLike a, + FInstanceLike b) { + auto aName = a.getReferencedModuleNameAttr(); + auto bName = b.getReferencedModuleNameAttr(); if (aName == bName) return success(); // If the modules instantiate are different we will want to know why the // sub module did not dedupliate. This code recursively checks the child // module. - auto aModule = a.getReferencedModule(instanceGraph); - auto bModule = b.getReferencedModule(instanceGraph); + auto aModule = instanceGraph.lookup(aName)->getModule(); + auto bModule = instanceGraph.lookup(bName)->getModule(); // Create a new error for the submodule. diag.attachNote(std::nullopt) - << "in instance " << a.getNameAttr() << " of " << aName - << ", and instance " << b.getNameAttr() << " of " << bName; + << "in instance " << a.getInstanceNameAttr() << " of " << aName + << ", and instance " << b.getInstanceNameAttr() << " of " << bName; check(diag, aModule, bModule); return failure(); } @@ -648,8 +673,8 @@ struct Equivalence { // If its an instance operaiton, perform some checking and possibly // recurse. - if (auto aInst = dyn_cast(a)) { - auto bInst = cast(b); + if (auto aInst = dyn_cast(a)) { + auto bInst = cast(b); if (failed(check(diag, aInst, bInst))) return failure(); } @@ -800,7 +825,7 @@ static Location mergeLoc(MLIRContext *context, Location to, Location from) { // simply add all of the internal locations. for (auto loc : fusedLoc.getLocations()) { if (FileLineColLoc fileLoc = dyn_cast(loc)) { - if (fileLoc.getFilename().strref().endswith(".fir")) { + if (fileLoc.getFilename().strref().ends_with(".fir")) { ++seenFIR; if (seenFIR > 8) continue; @@ -813,7 +838,7 @@ static Location mergeLoc(MLIRContext *context, Location to, Location from) { // Might need to skip this fir. if (FileLineColLoc fileLoc = dyn_cast(loc)) { - if (fileLoc.getFilename().strref().endswith(".fir")) { + if (fileLoc.getFilename().strref().ends_with(".fir")) { ++seenFIR; if (seenFIR > 8) continue; @@ -940,9 +965,15 @@ struct Deduper { auto *toNode = instanceGraph[toModule]; auto toModuleRef = FlatSymbolRefAttr::get(toModule.getModuleNameAttr()); for (auto *oldInstRec : llvm::make_early_inc_range(fromNode->uses())) { - auto inst = ::cast(*oldInstRec->getInstance()); - inst.setModuleNameAttr(toModuleRef); - inst.setPortNamesAttr(toModule.getPortNamesAttr()); + auto inst = oldInstRec->getInstance(); + if (auto instOp = dyn_cast(*inst)) { + instOp.setModuleNameAttr(toModuleRef); + instOp.setPortNamesAttr(toModule.getPortNamesAttr()); + } else if (auto objectOp = dyn_cast(*inst)) { + auto classLike = cast(*toNode->getModule()); + ClassType classType = detail::getInstanceTypeForClassLike(classLike); + objectOp.getResult().setType(classType); + } oldInstRec->getParent()->addInstance(inst, toNode); oldInstRec->erase(); } @@ -1399,7 +1430,7 @@ void fixupAllModules(InstanceGraph &instanceGraph) { continue; ImplicitLocOpBuilder builder(inst.getLoc(), inst->getContext()); builder.setInsertionPointAfter(inst); - for (unsigned i = 0, e = getNumPorts(module); i < e; ++i) { + for (size_t i = 0, e = getNumPorts(module); i < e; ++i) { auto result = inst.getResult(i); auto newType = module.getPortType(i); auto oldType = result.getType(); @@ -1523,14 +1554,8 @@ class DedupPass : public DedupBase { // If module has symbol (name) that must be preserved even if unused, // skip it. All symbol uses must be supported, which is not true if // non-private. - if (!module.isPrivate() || !module.canDiscardOnUseEmpty()) { - return success(); - } - - // Explicitly skip class-like modules. This is presently unreachable - // due to above and current implementation but check anyway as dedup - // code does not handle these or object operations. - if (isa(*module)) { + if (!module.isPrivate() || + (!module.canDiscardOnUseEmpty() && !isa(*module))) { return success(); } diff --git a/lib/Dialect/FIRRTL/Transforms/DropConst.cpp b/lib/Dialect/FIRRTL/Transforms/DropConst.cpp index 2f1b5466f6fd..24a811bfff22 100644 --- a/lib/Dialect/FIRRTL/Transforms/DropConst.cpp +++ b/lib/Dialect/FIRRTL/Transforms/DropConst.cpp @@ -35,7 +35,8 @@ static Type convertType(Type type) { if (auto refType = type_dyn_cast(type)) { if (auto converted = convertType(refType.getType())) - return RefType::get(converted, refType.getForceable()); + return RefType::get(converted, refType.getForceable(), + refType.getLayer()); } return {}; diff --git a/lib/Dialect/FIRRTL/Transforms/DropName.cpp b/lib/Dialect/FIRRTL/Transforms/DropName.cpp index c9850b05605c..b8ef80e1f282 100644 --- a/lib/Dialect/FIRRTL/Transforms/DropName.cpp +++ b/lib/Dialect/FIRRTL/Transforms/DropName.cpp @@ -14,6 +14,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Support/Naming.h" using namespace circt; using namespace firrtl; diff --git a/lib/Dialect/FIRRTL/Transforms/ExtractInstances.cpp b/lib/Dialect/FIRRTL/Transforms/ExtractInstances.cpp index d370f0493b66..be28fe331c3e 100644 --- a/lib/Dialect/FIRRTL/Transforms/ExtractInstances.cpp +++ b/lib/Dialect/FIRRTL/Transforms/ExtractInstances.cpp @@ -147,6 +147,8 @@ void ExtractInstancesPass::runOnOperation() { annotatedModules.clear(); dutRootModules.clear(); dutModules.clear(); + dutModuleNames.clear(); + dutPrefix = ""; extractionWorklist.clear(); extractionPaths.clear(); originalInstanceParents.clear(); diff --git a/lib/Dialect/FIRRTL/Transforms/GrandCentral.cpp b/lib/Dialect/FIRRTL/Transforms/GrandCentral.cpp index 970c59fc1ba1..d32b5820aeca 100644 --- a/lib/Dialect/FIRRTL/Transforms/GrandCentral.cpp +++ b/lib/Dialect/FIRRTL/Transforms/GrandCentral.cpp @@ -23,6 +23,7 @@ #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/InnerSymbolNamespace.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Support/Debug.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/TypeSwitch.h" @@ -1538,8 +1539,7 @@ GrandCentralPass::getEnclosingModule(Value value, FlatSymbolRefAttr sym) { /// This method contains the business logic of this pass. void GrandCentralPass::runOnOperation() { - LLVM_DEBUG(llvm::dbgs() << "===- Running Grand Central Views/Interface Pass " - "-----------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n"); CircuitOp circuitOp = getOperation(); @@ -1839,7 +1839,7 @@ void GrandCentralPass::runOnOperation() { // Handle annotations on the module. AnnotationSet::removeAnnotations(op, [&](Annotation annotation) { - if (!annotation.getClass().startswith(viewAnnoClass)) + if (!annotation.getClass().starts_with(viewAnnoClass)) return false; auto isNonlocal = annotation.getMember( "circt.nonlocal") != nullptr; diff --git a/lib/Dialect/FIRRTL/Transforms/HoistPassthrough.cpp b/lib/Dialect/FIRRTL/Transforms/HoistPassthrough.cpp index 5bac2bef37d9..6a0ce7ff64e0 100644 --- a/lib/Dialect/FIRRTL/Transforms/HoistPassthrough.cpp +++ b/lib/Dialect/FIRRTL/Transforms/HoistPassthrough.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" + #include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h" #include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/FIRRTLTypes.h" @@ -19,6 +20,7 @@ #include "circt/Dialect/FIRRTL/FieldRefCache.h" #include "circt/Dialect/FIRRTL/Passes.h" #include "circt/Dialect/HW/HWTypeInterfaces.h" +#include "circt/Support/Debug.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "llvm/ADT/PostOrderIterator.h" @@ -465,8 +467,7 @@ struct HoistPassthroughPass } // end anonymous namespace void HoistPassthroughPass::runOnOperation() { - LLVM_DEBUG(llvm::dbgs() << "===- Running HoistPassthrough Pass " - "------------------------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n"); auto &instanceGraph = getAnalysis(); SmallVector modules(llvm::make_filter_range( diff --git a/lib/Dialect/FIRRTL/Transforms/IMConstProp.cpp b/lib/Dialect/FIRRTL/Transforms/IMConstProp.cpp index bc19ce417e1d..7efec82dc048 100644 --- a/lib/Dialect/FIRRTL/Transforms/IMConstProp.cpp +++ b/lib/Dialect/FIRRTL/Transforms/IMConstProp.cpp @@ -974,7 +974,7 @@ void IMConstPropPass::rewriteModuleBody(FModuleOp module) { }; // TODO: Replace entire aggregate. - auto it = latticeValues.find(getOrCacheFieldRefFromValue(value)); + auto it = latticeValues.find(getFieldRefFromValue(value)); if (it == latticeValues.end() || it->second.isOverdefined() || it->second.isUnknown()) return false; diff --git a/lib/Dialect/FIRRTL/Transforms/IMDeadCodeElim.cpp b/lib/Dialect/FIRRTL/Transforms/IMDeadCodeElim.cpp index a00dbbae206b..5e88cf2915fa 100644 --- a/lib/Dialect/FIRRTL/Transforms/IMDeadCodeElim.cpp +++ b/lib/Dialect/FIRRTL/Transforms/IMDeadCodeElim.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/FIRRTL/Passes.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/InnerSymbolTable.h" +#include "circt/Support/Debug.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "mlir/IR/Threading.h" #include "mlir/Interfaces/SideEffectInterfaces.h" @@ -298,9 +299,7 @@ void IMDeadCodeElimPass::forwardConstantOutputPort(FModuleOp module) { } void IMDeadCodeElimPass::runOnOperation() { - LLVM_DEBUG( - llvm::dbgs() << "===----- Inter-module Dead Code Elimination -----===" - << "\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n";); auto circuits = getOperation().getOps(); if (circuits.empty()) return; diff --git a/lib/Dialect/FIRRTL/Transforms/InferReadWrite.cpp b/lib/Dialect/FIRRTL/Transforms/InferReadWrite.cpp index 704f96f35fca..39087e5f3ad6 100644 --- a/lib/Dialect/FIRRTL/Transforms/InferReadWrite.cpp +++ b/lib/Dialect/FIRRTL/Transforms/InferReadWrite.cpp @@ -80,7 +80,7 @@ struct InferReadWritePass : public InferReadWriteBase { for (Operation *u : portVal.getUsers()) if (auto sf = dyn_cast(u)) { // Get the field name. - auto fName = sf.getInput().getType().get().getElementName( + auto fName = sf.getInput().getType().base().getElementName( sf.getFieldIndex()); // If this is the enable field, record the product terms(the And // expression tree). @@ -188,7 +188,7 @@ struct InferReadWritePass : public InferReadWriteBase { // replace them. for (Operation *u : portVal.getUsers()) if (auto sf = dyn_cast(u)) { - StringRef fName = sf.getInput().getType().get().getElementName( + StringRef fName = sf.getInput().getType().base().getElementName( sf.getFieldIndex()); Value repl; if (isReadPort) @@ -328,7 +328,7 @@ struct InferReadWritePass : public InferReadWriteBase { if (auto sf = dyn_cast(u)) { // Get the field name. auto fName = - sf.getInput().getType().get().getElementName(sf.getFieldIndex()); + sf.getInput().getType().base().getElementName(sf.getFieldIndex()); // Check if this is the mask field. if (fName.contains("mask")) { // Already 1 bit, nothing to do. @@ -379,7 +379,7 @@ struct InferReadWritePass : public InferReadWriteBase { auto sf = builder.create(newPortVal, oldRes.getFieldIndex()); auto fName = - sf.getInput().getType().get().getElementName(sf.getFieldIndex()); + sf.getInput().getType().base().getElementName(sf.getFieldIndex()); // Replace all mask fields with a one bit constant 1. // Replace all other fields with the new port. if (fName.contains("mask")) { diff --git a/lib/Dialect/FIRRTL/Transforms/InferResets.cpp b/lib/Dialect/FIRRTL/Transforms/InferResets.cpp index b958f8356df7..b283ba7a321d 100644 --- a/lib/Dialect/FIRRTL/Transforms/InferResets.cpp +++ b/lib/Dialect/FIRRTL/Transforms/InferResets.cpp @@ -17,6 +17,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLTypes.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Support/Debug.h" #include "circt/Support/FieldRef.h" #include "circt/Support/InstanceGraph.h" #include "circt/Support/InstanceGraphInterface.h" @@ -724,8 +725,10 @@ static bool typeContainsReset(Type type) { /// populated with the reset networks in the circuit, alongside information on /// drivers and their types that contribute to the reset. void InferResetsPass::traceResets(CircuitOp circuit) { - LLVM_DEBUG( - llvm::dbgs() << "\n===----- Tracing uninferred resets -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Tracing uninferred resets") << "\n\n"; + }); SmallVector>> moduleToOps; @@ -978,7 +981,10 @@ void InferResetsPass::traceResets(Type dstType, Value dst, unsigned dstID, //===----------------------------------------------------------------------===// LogicalResult InferResetsPass::inferAndUpdateResets() { - LLVM_DEBUG(llvm::dbgs() << "\n===----- Infer reset types -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Infer reset types") << "\n\n"; + }); for (auto it = resetClasses.begin(), end = resetClasses.end(); it != end; ++it) { if (!it->isLeader()) @@ -1234,8 +1240,10 @@ bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) { //===----------------------------------------------------------------------===// LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) { - LLVM_DEBUG( - llvm::dbgs() << "\n===----- Gather async reset annotations -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Gather async reset annotations") << "\n\n"; + }); SmallVector>> results; for (auto module : circuit.getOps()) results.push_back({module, {}}); @@ -1291,8 +1299,16 @@ InferResetsPass::collectAnnos(FModuleOp module) { Annotation anno) { Value arg = module.getArgument(argNum); if (anno.isClass(fullAsyncResetAnnoClass)) { + if (!isa(arg.getType())) { + mlir::emitError(arg.getLoc(), "'IgnoreFullAsyncResetAnnotation' must " + "target async reset, but targets ") + << arg.getType(); + anyFailed = true; + return true; + } reset = arg; conflictingAnnos.insert({anno, reset.getLoc()}); + return true; } if (anno.isClass(ignoreFullAsyncResetAnnoClass)) { @@ -1324,7 +1340,15 @@ InferResetsPass::collectAnnos(FModuleOp module) { // At this point we know that we have a WireOp/NodeOp. Process the reset // annotations. + auto resultType = op->getResult(0).getType(); if (anno.isClass(fullAsyncResetAnnoClass)) { + if (!isa(resultType)) { + mlir::emitError(op->getLoc(), "'IgnoreFullAsyncResetAnnotation' must " + "target async reset, but targets ") + << resultType; + anyFailed = true; + return true; + } reset = op->getResult(0); conflictingAnnos.insert({anno, reset.getLoc()}); return true; @@ -1389,8 +1413,10 @@ InferResetsPass::collectAnnos(FModuleOp module) { /// wrong in some cases, mainly when a module is instantiated multiple times /// within different reset domains. LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) { - LLVM_DEBUG( - llvm::dbgs() << "\n===----- Build async reset domains -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Build async reset domains") << "\n\n"; + }); // Gather the domains. auto &instGraph = getAnalysis(); @@ -1497,8 +1523,10 @@ void InferResetsPass::buildDomains(FModuleOp module, /// Determine how the reset for each module shall be implemented. void InferResetsPass::determineImpl() { - LLVM_DEBUG( - llvm::dbgs() << "\n===----- Determine implementation -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Determine implementation") << "\n\n"; + }); for (auto &it : domains) { auto module = cast(it.first); auto &domain = it.second.back().first; @@ -1591,7 +1619,10 @@ void InferResetsPass::determineImpl(FModuleOp module, ResetDomain &domain) { /// Implement the async resets gathered in the pass' `domains` map. LogicalResult InferResetsPass::implementAsyncReset() { - LLVM_DEBUG(llvm::dbgs() << "\n===----- Implement async resets -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Implement async resets") << "\n\n"; + }); for (auto &it : domains) if (failed(implementAsyncReset(cast(it.first), it.second.back().first))) diff --git a/lib/Dialect/FIRRTL/Transforms/InferWidths.cpp b/lib/Dialect/FIRRTL/Transforms/InferWidths.cpp index 004548986223..2267e656fa3c 100644 --- a/lib/Dialect/FIRRTL/Transforms/InferWidths.cpp +++ b/lib/Dialect/FIRRTL/Transforms/InferWidths.cpp @@ -11,11 +11,13 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" + #include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/FIRRTLTypes.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/FIRRTLVisitors.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Support/Debug.h" #include "circt/Support/FieldRef.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "mlir/IR/Threading.h" @@ -1008,13 +1010,16 @@ static ExprSolution solveExpr(Expr *expr, SmallPtrSetImpl &seenVars, /// present. LogicalResult ConstraintSolver::solve() { LLVM_DEBUG({ - llvm::dbgs() << "\n===----- Constraints -----===\n\n"; + llvm::dbgs() << "\n"; + debugHeader("Constraints") << "\n\n"; dumpConstraints(llvm::dbgs()); }); // Ensure that there are no adverse cycles around. - LLVM_DEBUG( - llvm::dbgs() << "\n===----- Checking for unbreakable loops -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Checking for unbreakable loops") << "\n\n"; + }); SmallPtrSet seenVars; bool anyFailed = false; @@ -1073,7 +1078,10 @@ LogicalResult ConstraintSolver::solve() { return failure(); // Iterate over the constraint variables and solve each. - LLVM_DEBUG(llvm::dbgs() << "\n===----- Solving constraints -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Solving constraints") << "\n\n"; + }); unsigned defaultWorklistSize = exprs.size() / 2; for (auto *expr : exprs) { // Only work on variables. @@ -1386,7 +1394,7 @@ LogicalResult InferenceMapping::mapOperation(Operation *op) { // If the constant has a known width, use that. Otherwise pick the // smallest number of bits necessary to represent the constant. Expr *e; - if (auto width = op.getType().get().getWidth()) + if (auto width = op.getType().base().getWidth()) e = solver.known(*width); else { auto v = op.getValue(); @@ -1488,7 +1496,7 @@ LogicalResult InferenceMapping::mapOperation(Operation *op) { .Case([&](auto op) { auto lhs = getExpr(op.getLhs()); Expr *e; - if (op.getType().get().isSigned()) { + if (op.getType().base().isSigned()) { e = solver.add(lhs, solver.known(1)); } else { e = lhs; @@ -1534,7 +1542,7 @@ LogicalResult InferenceMapping::mapOperation(Operation *op) { }) .Case([&](auto op) { auto input = getExpr(op.getInput()); - auto e = op.getInput().getType().get().isSigned() + auto e = op.getInput().getType().base().isSigned() ? input : solver.add(input, solver.known(1)); setExpr(op.getResult(), e); @@ -1564,8 +1572,10 @@ LogicalResult InferenceMapping::mapOperation(Operation *op) { }) .Case([&](auto op) { auto input = getExpr(op.getInput()); + // UInt saturates at 0 bits, SInt at 1 bit + auto minWidth = op.getInput().getType().base().isUnsigned() ? 0 : 1; auto e = solver.max(solver.add(input, solver.known(-op.getAmount())), - solver.known(1)); + solver.known(minWidth)); setExpr(op.getResult(), e); }) @@ -2079,7 +2089,10 @@ class InferenceTypeUpdate { /// Update the types throughout a circuit. LogicalResult InferenceTypeUpdate::update(CircuitOp op) { - LLVM_DEBUG(llvm::dbgs() << "\n===----- Update types -----===\n\n"); + LLVM_DEBUG({ + llvm::dbgs() << "\n"; + debugHeader("Update types") << "\n\n"; + }); return mlir::failableParallelForEach( op.getContext(), op.getOps(), [&](FModuleOp op) { // Skip this module if it had no widths to be diff --git a/lib/Dialect/FIRRTL/Transforms/InjectDUTHierarchy.cpp b/lib/Dialect/FIRRTL/Transforms/InjectDUTHierarchy.cpp index e5eadd7084b2..cbbd97c2103d 100644 --- a/lib/Dialect/FIRRTL/Transforms/InjectDUTHierarchy.cpp +++ b/lib/Dialect/FIRRTL/Transforms/InjectDUTHierarchy.cpp @@ -21,6 +21,7 @@ #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/InnerSymbolNamespace.h" +#include "circt/Support/Debug.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "firrtl-inject-dut-hier" @@ -69,8 +70,7 @@ static void addHierarchy(hw::HierPathOp path, FModuleOp dut, } void InjectDUTHierarchy::runOnOperation() { - LLVM_DEBUG(llvm::dbgs() << "===- Running InjectDUTHierarchyPass " - "-----------------------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n";); CircuitOp circuit = getOperation(); diff --git a/lib/Dialect/FIRRTL/Transforms/LayerSink.cpp b/lib/Dialect/FIRRTL/Transforms/LayerSink.cpp index 764a1f21ea4c..39b2ff0fa679 100644 --- a/lib/Dialect/FIRRTL/Transforms/LayerSink.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LayerSink.cpp @@ -12,6 +12,7 @@ #include "PassDetails.h" +#include "circt/Support/Debug.h" #include "mlir/IR/Dominance.h" #include "mlir/Interfaces/ControlFlowInterfaces.h" #include "mlir/Interfaces/SideEffectInterfaces.h" @@ -30,10 +31,9 @@ struct LayerSink : public LayerSinkBase { } // end anonymous namespace void LayerSink::runOnOperation() { - LLVM_DEBUG( - llvm::dbgs() << "==----- Running LayerSink " - "---------------------------------------------------===\n" - << "Module: '" << getOperation().getName() << "'\n";); + LLVM_DEBUG(debugPassHeader(this) + << "\n" + << "Module: '" << getOperation().getName() << "'\n";); auto &domInfo = getAnalysis(); getOperation()->walk([&](LayerBlockOp layerBlock) { SmallVector regionsToSink({&layerBlock.getRegion()}); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp b/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp index e1e6632562c4..442de73a8601 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" + #include "circt/Dialect/FIRRTL/AnnotationDetails.h" #include "circt/Dialect/FIRRTL/CHIRRTLDialect.h" #include "circt/Dialect/FIRRTL/FIRRTLAnnotationHelper.h" @@ -28,6 +29,7 @@ #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/SV/SVAttributes.h" +#include "circt/Support/Debug.h" #include "mlir/IR/Diagnostics.h" #include "llvm/ADT/APSInt.h" #include "llvm/ADT/PostOrderIterator.h" @@ -1004,8 +1006,7 @@ void LowerAnnotationsPass::runOnOperation() { CircuitOp circuit = getOperation(); SymbolTable modules(circuit); - LLVM_DEBUG(llvm::dbgs() << "===- Running LowerAnnotations Pass " - "------------------------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n"); // Grab the annotations from a non-standard attribute called "rawAnnotations". // This is a temporary location for all annotations that are earmarked for diff --git a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp index e70babee765c..98d555859b9f 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp @@ -204,19 +204,16 @@ LogicalResult LowerClassesPass::processPaths( auto moduleName = target.getModule().getModuleNameAttr(); - // Copy the middle part from the annotation's NLA. + // Verify a nonlocal annotation refers to a HierPathOp. + hw::HierPathOp hierPathOp; if (auto hierName = anno.getMember("circt.nonlocal")) { - auto hierPathOp = + hierPathOp = dyn_cast(symbolTable.lookup(hierName.getAttr())); if (!hierPathOp) { op->emitError("annotation does not point at a HierPathOp"); error = true; return false; } - // Copy the old path, dropping the module name. - auto oldPath = hierPathOp.getNamepath().getValue(); - llvm::append_range(path, llvm::reverse(oldPath.drop_back())); - moduleName = cast(oldPath.front()).getModule(); } auto [it, inserted] = pathInfoTable.try_emplace(id); @@ -236,6 +233,34 @@ LogicalResult LowerClassesPass::processPaths( if (!owningModule) return true; + // Copy the middle part from the annotation's NLA. + if (hierPathOp) { + // Copy the old path, dropping the module name. + auto oldPath = hierPathOp.getNamepath().getValue(); + llvm::append_range(path, llvm::reverse(oldPath.drop_back())); + + // Set the moduleName based on the hierarchical path. If the + // owningModule is in the hierarichal path, set the moduleName to the + // owning module. Otherwise use the top of the hierarchical path. + bool pathContainsOwningModule = + llvm::any_of(oldPath, [&](auto pathFragment) { + return llvm::TypeSwitch(pathFragment) + .Case([&](hw::InnerRefAttr innerRef) { + return innerRef.getModule() == + owningModule.getModuleNameAttr(); + }) + .Case([&](FlatSymbolRefAttr symRef) { + return symRef.getAttr() == owningModule.getModuleNameAttr(); + }) + .Default([](auto attr) { return false; }); + }); + if (pathContainsOwningModule) { + moduleName = owningModule.getModuleNameAttr(); + } else { + moduleName = cast(oldPath.front()).getModule(); + } + } + // Copy the leading part of the hierarchical path from the owning module // to the start of the annotation's NLA. auto *node = instanceGraph.lookup(moduleName); @@ -368,10 +393,15 @@ void LowerClassesPass::runOnOperation() { lowerClassLike(state.moduleLike, classLike); }); - // Completely erase Class module-likes + // Completely erase Class module-likes, and remove from the InstanceGraph. for (auto &[omClass, state] : loweringState.classLoweringStateTable) { - if (isa(state.moduleLike.getOperation())) + if (isa(state.moduleLike.getOperation())) { + InstanceGraphNode *node = instanceGraph.lookup(state.moduleLike); + for (auto *use : llvm::make_early_inc_range(node->uses())) + use->erase(); + instanceGraph.erase(node); state.moduleLike.erase(); + } } // Collect ops where Objects can be instantiated. @@ -411,9 +441,23 @@ bool LowerClassesPass::shouldCreateClass(FModuleLike moduleLike) { if (moduleLike.isPublic()) return true; - return llvm::any_of(moduleLike.getPorts(), [](PortInfo port) { + // Create a class for modules with property ports. + bool hasClassPorts = llvm::any_of(moduleLike.getPorts(), [](PortInfo port) { return isa(port.type); }); + + if (hasClassPorts) + return true; + + // Create a class for modules that instantiate classes or modules with + // property ports. + for (auto op : + moduleLike.getOperation()->getRegion(0).getOps()) + for (auto result : op->getResults()) + if (type_isa(result.getType())) + return true; + + return false; } // Create an OM Class op from a FIRRTL Class op or Module op with properties. @@ -716,7 +760,7 @@ updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath, // Value as an actual parameter to the Object instance. auto propertyAssignment = getPropertyAssignment(propertyResult); assert(propertyAssignment && "properties require single assignment"); - actualParameters.push_back(propertyAssignment.getSrc()); + actualParameters.push_back(propertyAssignment.getSrcMutable().get()); // Erase the property assignment. opsToErase.push_back(propertyAssignment); @@ -940,6 +984,45 @@ struct ListCreateOpConversion } }; +struct IntegerAddOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), + adaptor.getRhs()); + return success(); + } +}; + +struct IntegerMulOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), + adaptor.getRhs()); + return success(); + } +}; + +struct IntegerShrOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getLhs(), + adaptor.getRhs()); + return success(); + } +}; + struct PathOpConversion : public OpConversionPattern { PathOpConversion(TypeConverter &typeConverter, MLIRContext *context, @@ -1097,7 +1180,7 @@ struct ClassFieldOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ClassFieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - rewriter.replaceOpWithNewOp(op, adaptor.getSymNameAttr(), + rewriter.replaceOpWithNewOp(op, adaptor.getNameAttr(), adaptor.getValue()); return success(); } @@ -1113,8 +1196,8 @@ struct ClassExternFieldOpConversion auto type = typeConverter->convertType(adaptor.getType()); if (!type) return failure(); - rewriter.replaceOpWithNewOp( - op, adaptor.getSymNameAttr(), type); + rewriter.replaceOpWithNewOp(op, adaptor.getNameAttr(), + type); return success(); } }; @@ -1153,7 +1236,7 @@ struct ClassOpSignatureConversion : public OpConversionPattern { &result))) return failure(); - rewriter.updateRootInPlace(classOp, []() {}); + rewriter.modifyOpInPlace(classOp, []() {}); return success(); } @@ -1179,7 +1262,7 @@ struct ClassExternOpSignatureConversion &result))) return failure(); - rewriter.updateRootInPlace(classOp, []() {}); + rewriter.modifyOpInPlace(classOp, []() {}); return success(); } @@ -1353,6 +1436,9 @@ static void populateRewritePatterns( patterns.add(converter, patterns.getContext()); patterns.add(converter, patterns.getContext()); patterns.add(converter, patterns.getContext()); + patterns.add(converter, patterns.getContext()); + patterns.add(converter, patterns.getContext()); + patterns.add(converter, patterns.getContext()); } // Convert to OM ops and types in Classes or Modules. diff --git a/lib/Dialect/FIRRTL/Transforms/LowerIntrinsics.cpp b/lib/Dialect/FIRRTL/Transforms/LowerIntrinsics.cpp index 5b8741e59adc..ef1d54771d1f 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerIntrinsics.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerIntrinsics.cpp @@ -41,7 +41,7 @@ class CirctSizeofConverter : public IntrinsicConverter { sizedPort(1, 32) || hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto inputTy = inst.getResult(0).getType(); auto inputWire = builder.create(inputTy).getResult(); @@ -49,6 +49,7 @@ class CirctSizeofConverter : public IntrinsicConverter { auto size = builder.create(inputWire); inst.getResult(1).replaceAllUsesWith(size); inst.erase(); + return success(); } }; @@ -61,7 +62,7 @@ class CirctIsXConverter : public IntrinsicConverter { sizedPort(1, 1) || hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto inputTy = inst.getResult(0).getType(); auto inputWire = builder.create(inputTy).getResult(); @@ -69,6 +70,7 @@ class CirctIsXConverter : public IntrinsicConverter { auto size = builder.create(inputWire); inst.getResult(1).replaceAllUsesWith(size); inst.erase(); + return success(); } }; @@ -81,13 +83,14 @@ class CirctPlusArgTestConverter : public IntrinsicConverter { hasNParam(1) || namedParam("FORMAT"); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { auto param = cast(mod.getParameters()[0]); ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto newop = builder.create( cast(param.getValue())); inst.getResult(0).replaceAllUsesWith(newop); inst.erase(); + return success(); } }; @@ -100,8 +103,7 @@ class CirctPlusArgValueConverter : public IntrinsicConverter { sizedPort(0, 1) || hasNParam(1) || namedParam("FORMAT"); } - void convert(InstanceOp inst) override { - + LogicalResult convert(InstanceOp inst) override { auto param = cast(mod.getParameters()[0]); ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto newop = builder.create( @@ -109,6 +111,7 @@ class CirctPlusArgValueConverter : public IntrinsicConverter { inst.getResult(0).replaceAllUsesWith(newop.getFound()); inst.getResult(1).replaceAllUsesWith(newop.getResult()); inst.erase(); + return success(); } }; @@ -122,7 +125,7 @@ class CirctClockGateConverter : public IntrinsicConverter { sizedPort(1, 1) || typedPort(2) || hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto in = builder.create(inst.getResult(0).getType()).getResult(); auto en = builder.create(inst.getResult(1).getType()).getResult(); @@ -131,32 +134,84 @@ class CirctClockGateConverter : public IntrinsicConverter { auto out = builder.create(in, en, Value{}); inst.getResult(2).replaceAllUsesWith(out); inst.erase(); + return success(); } }; -class EICGWrapperToClockGateConverter : public IntrinsicConverter { +class CirctClockInverterConverter : public IntrinsicConverter { public: using IntrinsicConverter::IntrinsicConverter; bool check() override { - return hasNPorts(4) || namedPort(0, "in") || namedPort(1, "test_en") || - namedPort(2, "en") || namedPort(3, "out") || - typedPort(0) || sizedPort(1, 1) || - sizedPort(2, 1) || typedPort(3) || hasNParam(0); + return hasNPorts(2) || namedPort(0, "in") || namedPort(1, "out") || + typedPort(0) || typedPort(1) || hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto in = builder.create(inst.getResult(0).getType()).getResult(); - auto testEn = - builder.create(inst.getResult(1).getType()).getResult(); - auto en = builder.create(inst.getResult(2).getType()).getResult(); inst.getResult(0).replaceAllUsesWith(in); - inst.getResult(1).replaceAllUsesWith(testEn); - inst.getResult(2).replaceAllUsesWith(en); - auto out = builder.create(in, en, testEn); - inst.getResult(3).replaceAllUsesWith(out); + auto out = builder.create(in); + inst.getResult(1).replaceAllUsesWith(out); + inst.erase(); + return success(); + } +}; + +class EICGWrapperToClockGateConverter : public IntrinsicConverter { +public: + using IntrinsicConverter::IntrinsicConverter; + + bool check() override { + if (!AnnotationSet(mod).empty()) { + mod.emitError(name) + << " cannot have annotations since it is an intrinsic"; + return true; + } + if (mod.getPorts().size() == 4) { + return namedPort(0, "in") || namedPort(1, "test_en") || + namedPort(2, "en") || namedPort(3, "out") || + typedPort(0) || sizedPort(1, 1) || + sizedPort(2, 1) || typedPort(3) || + hasNParam(0); + } + if (mod.getPorts().size() == 3) { + return namedPort(0, "in") || namedPort(1, "en") || namedPort(2, "out") || + typedPort(0) || sizedPort(1, 1) || + typedPort(2) || hasNParam(0); + } + mod.emitError(name) << " has " << mod.getPorts().size() + << " ports instead of 3 or 4"; + return true; + } + + LogicalResult convert(InstanceOp inst) override { + if (!AnnotationSet(inst).empty()) + return inst.emitError(name) + << " instance cannot have annotations since it is an intrinsic"; + ImplicitLocOpBuilder builder(inst.getLoc(), inst); + if (mod.getPorts().size() == 4) { + // Ports: in, test_en, en, out + auto in = builder.create(inst.getResult(0).getType()).getResult(); + auto testEn = + builder.create(inst.getResult(1).getType()).getResult(); + auto en = builder.create(inst.getResult(2).getType()).getResult(); + auto out = builder.create(in, en, testEn); + inst.getResult(0).replaceAllUsesWith(in); + inst.getResult(1).replaceAllUsesWith(testEn); + inst.getResult(2).replaceAllUsesWith(en); + inst.getResult(3).replaceAllUsesWith(out); + } else { + // Ports: in, en, out + auto in = builder.create(inst.getResult(0).getType()).getResult(); + auto en = builder.create(inst.getResult(1).getType()).getResult(); + auto out = builder.create(in, en, Value{}); + inst.getResult(0).replaceAllUsesWith(in); + inst.getResult(1).replaceAllUsesWith(en); + inst.getResult(2).replaceAllUsesWith(out); + } inst.erase(); + return success(); } }; @@ -183,7 +238,7 @@ class CirctMuxCellConverter : public IntrinsicConverter { return false; } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); SmallVector operands; operands.reserve(portNum - 1); @@ -199,6 +254,7 @@ class CirctMuxCellConverter : public IntrinsicConverter { out = builder.create(operands); inst.getResult(portNum - 1).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -213,7 +269,7 @@ class CirctLTLAndConverter : public IntrinsicConverter { hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto lhs = builder.create(inst.getResult(0).getType()).getResult(); auto rhs = builder.create(inst.getResult(1).getType()).getResult(); @@ -222,6 +278,7 @@ class CirctLTLAndConverter : public IntrinsicConverter { auto out = builder.create(lhs.getType(), lhs, rhs); inst.getResult(2).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -236,7 +293,7 @@ class CirctLTLOrConverter : public IntrinsicConverter { hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto lhs = builder.create(inst.getResult(0).getType()).getResult(); auto rhs = builder.create(inst.getResult(1).getType()).getResult(); @@ -245,6 +302,7 @@ class CirctLTLOrConverter : public IntrinsicConverter { auto out = builder.create(lhs.getType(), lhs, rhs); inst.getResult(2).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -277,7 +335,7 @@ class CirctLTLDelayConverter : public IntrinsicConverter { namedIntParam("length", true); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto in = builder.create(inst.getResult(0).getType()).getResult(); inst.getResult(0).replaceAllUsesWith(in); @@ -285,6 +343,7 @@ class CirctLTLDelayConverter : public IntrinsicConverter { builder.create(in.getType(), in, delay, length); inst.getResult(1).replaceAllUsesWith(out); inst.erase(); + return success(); } private: @@ -303,7 +362,7 @@ class CirctLTLConcatConverter : public IntrinsicConverter { hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto lhs = builder.create(inst.getResult(0).getType()).getResult(); auto rhs = builder.create(inst.getResult(1).getType()).getResult(); @@ -312,6 +371,7 @@ class CirctLTLConcatConverter : public IntrinsicConverter { auto out = builder.create(lhs.getType(), lhs, rhs); inst.getResult(2).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -325,7 +385,7 @@ class CirctLTLNotConverter : public IntrinsicConverter { hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto input = builder.create(inst.getResult(0).getType()).getResult(); @@ -333,6 +393,7 @@ class CirctLTLNotConverter : public IntrinsicConverter { auto out = builder.create(input.getType(), input); inst.getResult(1).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -347,7 +408,7 @@ class CirctLTLImplicationConverter : public IntrinsicConverter { hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto lhs = builder.create(inst.getResult(0).getType()).getResult(); auto rhs = builder.create(inst.getResult(1).getType()).getResult(); @@ -357,6 +418,7 @@ class CirctLTLImplicationConverter : public IntrinsicConverter { builder.create(lhs.getType(), lhs, rhs); inst.getResult(2).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -370,7 +432,7 @@ class CirctLTLEventuallyConverter : public IntrinsicConverter { hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto input = builder.create(inst.getResult(0).getType()).getResult(); @@ -378,6 +440,7 @@ class CirctLTLEventuallyConverter : public IntrinsicConverter { auto out = builder.create(input.getType(), input); inst.getResult(1).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -391,7 +454,7 @@ class CirctLTLClockConverter : public IntrinsicConverter { typedPort(1) || sizedPort(2, 1) || hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto in = builder.create(inst.getResult(0).getType()).getResult(); auto clock = @@ -401,6 +464,7 @@ class CirctLTLClockConverter : public IntrinsicConverter { auto out = builder.create(in.getType(), in, clock); inst.getResult(2).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -415,7 +479,7 @@ class CirctLTLDisableConverter : public IntrinsicConverter { hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto in = builder.create(inst.getResult(0).getType()).getResult(); auto condition = @@ -426,6 +490,7 @@ class CirctLTLDisableConverter : public IntrinsicConverter { builder.create(in.getType(), in, condition); inst.getResult(2).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -440,7 +505,7 @@ class CirctVerifConverter : public IntrinsicConverter { namedParam("label", true); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { auto params = mod.getParameters(); StringAttr label; if (!params.empty()) @@ -453,6 +518,7 @@ class CirctVerifConverter : public IntrinsicConverter { inst.getResult(0).replaceAllUsesWith(property); builder.create(property, label); inst.erase(); + return success(); } }; @@ -466,7 +532,7 @@ class CirctHasBeenResetConverter : public IntrinsicConverter { sizedPort(2, 1) || hasNParam(0); } - void convert(InstanceOp inst) override { + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto clock = builder.create(inst.getResult(0).getType()).getResult(); @@ -477,6 +543,7 @@ class CirctHasBeenResetConverter : public IntrinsicConverter { auto out = builder.create(clock, reset); inst.getResult(2).replaceAllUsesWith(out); inst.erase(); + return success(); } }; @@ -484,7 +551,12 @@ class CirctProbeConverter : public IntrinsicConverter { public: using IntrinsicConverter::IntrinsicConverter; - void convert(InstanceOp inst) override { + bool check() override { + return hasNPorts(2) || namedPort(0, "data") || namedPort(1, "clock") || + typedPort(1) || hasNParam(0); + } + + LogicalResult convert(InstanceOp inst) override { ImplicitLocOpBuilder builder(inst.getLoc(), inst); auto clock = builder.create(inst.getResult(0).getType()).getResult(); @@ -494,6 +566,135 @@ class CirctProbeConverter : public IntrinsicConverter { inst.getResult(1).replaceAllUsesWith(input); builder.create(clock, input); inst.erase(); + return success(); + } +}; + +} // namespace + +// Replace range of values with new wires and return them. +template +static SmallVector replaceResults(OpBuilder &b, R &&range) { + return llvm::map_to_vector(range, [&b](auto v) { + auto w = b.create(v.getLoc(), v.getType()).getResult(); + v.replaceAllUsesWith(w); + return w; + }); +} + +// Check ports are all inputs, emit diagnostic if not. +static ParseResult allInputs(ArrayRef ports) { + for (auto &p : ports) { + if (p.direction != Direction::In) + return mlir::emitError(p.loc, "expected input port"); + } + return success(); +} + +// Get parameter by the given name. Null if not found. +static ParamDeclAttr getNamedParam(ArrayAttr params, StringRef name) { + for (auto param : params.getAsRange()) + if (param.getName().getValue().equals(name)) + return param; + return {}; +} + +namespace { + +template +class CirctAssertAssumeConverter : public IntrinsicConverter { +public: + using IntrinsicConverter::IntrinsicConverter; + + bool check() override { + return namedPort(0, "clock") || typedPort(0) || + namedPort(1, "predicate") || sizedPort(1, 1) || + namedPort(2, "enable") || sizedPort(2, 1) || + namedParam("format", /*optional=*/true) || + namedParam("label", /*optional=*/true) || + namedParam("guards", /*optional=*/true) || allInputs(mod.getPorts()); + // TODO: Check all parameters accounted for. + } + + LogicalResult convert(InstanceOp inst) override { + ImplicitLocOpBuilder builder(inst.getLoc(), inst); + auto params = mod.getParameters(); + auto format = getNamedParam(params, "format"); + auto label = getNamedParam(params, "label"); + auto guards = getNamedParam(params, "guards"); + + auto wires = replaceResults(builder, inst.getResults()); + + auto clock = wires[0]; + auto predicate = wires[1]; + auto enable = wires[2]; + + auto substitutions = ArrayRef(wires).drop_front(3); + auto name = label ? cast(label.getValue()).strref() : ""; + // Message is not optional, so provide empty string if not present. + auto message = format ? cast(format.getValue()) + : builder.getStringAttr(""); + auto op = builder.template create(clock, predicate, enable, message, + substitutions, name, + /*isConcurrent=*/true); + if (guards) { + SmallVector guardStrings; + cast(guards.getValue()).strref().split(guardStrings, ';'); + // TODO: Legalize / sanity-check? + op->setAttr("guards", builder.getStrArrayAttr(guardStrings)); + } + + if constexpr (ifElseFatal) + op->setAttr("format", builder.getStringAttr("ifElseFatal")); + + inst.erase(); + return success(); + } + +private: +}; + +class CirctCoverConverter : public IntrinsicConverter { +public: + using IntrinsicConverter::IntrinsicConverter; + + bool check() override { + return namedPort(0, "clock") || typedPort(0) || + namedPort(1, "predicate") || sizedPort(1, 1) || + namedPort(2, "enable") || sizedPort(2, 1) || + hasNPorts(3) || allInputs(mod.getPorts()) || + namedParam("label", /*optional=*/true) || + namedParam("guards", /*optional=*/true); + // TODO: Check all parameters accounted for. + } + + LogicalResult convert(InstanceOp inst) override { + ImplicitLocOpBuilder builder(inst.getLoc(), inst); + auto params = mod.getParameters(); + auto label = getNamedParam(params, "label"); + auto guards = getNamedParam(params, "guards"); + + auto wires = replaceResults(builder, inst.getResults()); + + auto clock = wires[0]; + auto predicate = wires[1]; + auto enable = wires[2]; + + auto name = label ? cast(label.getValue()).strref() : ""; + + // Empty message string for cover, only 'name' / label. + auto op = builder.create(clock, predicate, enable, + builder.getStringAttr(""), ValueRange{}, + name, /*isConcurrent=*/true); + if (guards) { + SmallVector guardStrings; + cast(guards.getValue()).strref().split(guardStrings, ';'); + // TODO: Legalize / sanity-check? + op->setAttr("guards", builder.getStrArrayAttr(guardStrings)); + } + + inst.erase(); + return success(); } }; @@ -520,6 +721,8 @@ void LowerIntrinsicsPass::runOnOperation() { lowering.add("circt.plusargs.value", "circt_plusargs_value"); lowering.add("circt.clock_gate", "circt_clock_gate"); + lowering.add("circt.clock_inv", + "circt_clock_inv"); lowering.add("circt.ltl.and", "circt_ltl_and"); lowering.add("circt.ltl.or", "circt_ltl_or"); lowering.add("circt.ltl.delay", "circt_ltl_delay"); @@ -544,6 +747,13 @@ void LowerIntrinsicsPass::runOnOperation() { lowering.add("circt.has_been_reset", "circt_has_been_reset"); lowering.add("circt.fpga_probe", "circt_fpga_probe"); + lowering.add>( + "circt.chisel_assert_assume", "circt_chisel_assert_assume"); + lowering.add>( + "circt.chisel_ifelsefatal", "circt_chisel_ifelsefatal"); + lowering.add>("circt.chisel_assume", + "circt_chisel_assume"); + lowering.add("circt.chisel_cover", "circt_chisel_cover"); // Remove this once `EICG_wrapper` is no longer special-cased by firtool. if (fixupEICGWrapper) diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index 4d5b57d8e1ba..1b88ef0e425c 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -13,6 +13,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/Namespace.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Dialect/HW/InnerSymbolNamespace.h" #include "circt/Dialect/SV/SVOps.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Mutex.h" @@ -23,14 +24,67 @@ using namespace circt; using namespace firrtl; +//===----------------------------------------------------------------------===// +// Helpers +//===----------------------------------------------------------------------===// + +static bool isAncestor(Operation *op, Value value) { + if (auto result = dyn_cast(value)) + return op->isAncestor(result.getOwner()); + auto argument = cast(value); + return op->isAncestor(argument.getOwner()->getParentOp()); +} + +//===----------------------------------------------------------------------===// +// Type Conversion +//===----------------------------------------------------------------------===// + +namespace { + +/// Indicates the kind of reference that was captured. +enum class ConnectKind { + /// A normal captured value. This is a read of a value outside the + /// layerblock. + NonRef, + /// A reference. This is a destination of a ref define. + Ref +}; + +struct ConnectInfo { + Value value; + ConnectKind kind; +}; + +} // namespace + +// A mapping of an old InnerRefAttr to the new inner symbol and module name that +// need to be spliced into the old InnerRefAttr. This is used to fix +// hierarchical path operations after layers are converted to modules. +using InnerRefMap = + DenseMap>; + class LowerLayersPass : public LowerLayersBase { /// Safely build a new module with a given namehint. This handles geting a /// lock to modify the top-level circuit. FModuleOp buildNewModule(OpBuilder &builder, Location location, Twine namehint, SmallVectorImpl &ports); - /// Function to process each module. - void runOnModule(FModuleOp moduleOp); + /// Strip layer colors from the module's interface. + InnerRefMap runOnModuleLike(FModuleLike moduleLike); + + /// Extract layerblocks and strip probe colors from all ops under the module. + void runOnModuleBody(FModuleOp moduleOp, InnerRefMap &innerRefMap); + + /// Update the module's port types to remove any explicit layer requirements + /// from any probe types. + void removeLayersFromPorts(FModuleLike moduleLike); + + /// Update the value's type to remove any layers from any probe types. + void removeLayersFromValue(Value value); + + /// Remove any layers from the result of the cast. If the cast becomes a nop, + /// remove the cast itself from the IR. + void removeLayersFromRefCast(RefCastOp cast); /// Entry point for the function. void runOnOperation() override; @@ -58,15 +112,86 @@ FModuleOp LowerLayersPass::buildNewModule(OpBuilder &builder, Location location, return newModule; } -/// Process a module to remove any layer blocks it has. -void LowerLayersPass::runOnModule(FModuleOp moduleOp) { +void LowerLayersPass::removeLayersFromValue(Value value) { + auto type = dyn_cast(value.getType()); + if (!type || !type.getLayer()) + return; + value.setType(type.removeLayer()); +} + +void LowerLayersPass::removeLayersFromRefCast(RefCastOp cast) { + auto result = cast.getResult(); + auto oldType = result.getType(); + if (oldType.getLayer()) { + auto input = cast.getInput(); + auto srcType = input.getType(); + auto newType = oldType.removeLayer(); + if (newType == srcType) { + result.replaceAllUsesWith(input); + cast->erase(); + return; + } + result.setType(newType); + } +} + +void LowerLayersPass::removeLayersFromPorts(FModuleLike moduleLike) { + auto oldTypeAttrs = moduleLike.getPortTypesAttr(); + SmallVector newTypeAttrs; + newTypeAttrs.reserve(oldTypeAttrs.size()); + bool changed = false; + + for (auto typeAttr : oldTypeAttrs.getAsRange()) { + if (auto refType = dyn_cast(typeAttr.getValue())) { + if (refType.getLayer()) { + typeAttr = TypeAttr::get(refType.removeLayer()); + changed = true; + } + } + newTypeAttrs.push_back(typeAttr); + } + + if (!changed) + return; + + moduleLike->setAttr(FModuleLike::getPortTypesAttrName(), + ArrayAttr::get(moduleLike.getContext(), newTypeAttrs)); + + if (auto moduleOp = dyn_cast(moduleLike.getOperation())) { + for (auto arg : moduleOp.getBodyBlock()->getArguments()) + removeLayersFromValue(arg); + } +} + +InnerRefMap LowerLayersPass::runOnModuleLike(FModuleLike moduleLike) { LLVM_DEBUG({ - llvm::dbgs() << "Module: " << moduleOp.getModuleName() << "\n"; + llvm::dbgs() << "Module: " << moduleLike.getModuleName() << "\n"; llvm::dbgs() << " Examining Layer Blocks:\n"; }); + // Strip away layers from the interface of the module-like op. + InnerRefMap innerRefMap; + TypeSwitch(moduleLike.getOperation()) + .Case([&](auto op) { + op.setLayers({}); + removeLayersFromPorts(op); + runOnModuleBody(op, innerRefMap); + }) + .Case([&](auto op) { + op.setLayers({}); + removeLayersFromPorts(op); + }) + .Case([](auto) {}) + .Default([](auto) { assert(0 && "unknown module-like op"); }); + + return innerRefMap; +} + +void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, + InnerRefMap &innerRefMap) { CircuitOp circuitOp = moduleOp->getParentOfType(); StringRef circuitName = circuitOp.getName(); + hw::InnerSymbolNamespace ns(moduleOp); // A map of instance ops to modules that this pass creates. This is used to // check if this was an instance that we created and to do fast module @@ -85,6 +210,26 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { // 4. Instantiate the new module outside the layer block and hook it up. // 5. Erase the layer block. moduleOp.walk([&](Operation *op) { + // Strip layer requirements from any op that might represent a probe. + if (auto wire = dyn_cast(op)) { + removeLayersFromValue(wire.getResult()); + return WalkResult::advance(); + } + if (auto sub = dyn_cast(op)) { + removeLayersFromValue(sub.getResult()); + return WalkResult::advance(); + } + if (auto instance = dyn_cast(op)) { + instance.setLayers({}); + for (auto result : instance.getResults()) + removeLayersFromValue(result); + return WalkResult::advance(); + } + if (auto cast = dyn_cast(op)) { + removeLayersFromRefCast(cast); + return WalkResult::advance(); + } + auto layerBlock = dyn_cast(op); if (!layerBlock) return WalkResult::advance(); @@ -104,29 +249,44 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { // block. SmallVector ports; - // Connectsion that need to be made to the instance of the derived module. - SmallVector connectValues; + // Connection that need to be made to the instance of the derived module. + SmallVector connectValues; // Create an input port for an operand that is captured from outside. - // - // TODO: If we allow capturing reference types, this will need to be - // updated. auto createInputPort = [&](Value operand, Location loc) { - auto portNum = ports.size(); auto operandName = getFieldName(FieldRef(operand, 0), true); - ports.push_back({builder.getStringAttr("_" + operandName.first), - operand.getType(), Direction::In, /*sym=*/{}, + // If the value is a ref, we must resolve the ref inside the parent, + // passing the input as a value instead of a ref. Inside the layer, we + // convert (ref.send) the value back into a ref. + auto type = operand.getType(); + if (auto refType = dyn_cast(type)) + type = refType.getType(); + + ports.push_back({builder.getStringAttr("_" + operandName.first), type, + Direction::In, /*sym=*/{}, /*loc=*/loc}); // Update the layer block's body with arguments as we will swap this body - // into the module when we create it. - body->addArgument(operand.getType(), loc); - operand.replaceUsesWithIf(body->getArgument(portNum), - [&](OpOperand &operand) { - return operand.getOwner()->getBlock() == body; - }); - - connectValues.push_back(operand); + // into the module when we create it. If this is a ref type, then add a + // refsend to convert from the non-ref type input port. + Value replacement = body->addArgument(type, loc); + if (isa(operand.getType())) { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(body); + replacement = builder.create(loc, replacement); + } + operand.replaceUsesWithIf(replacement, [&](OpOperand &use) { + auto *user = use.getOwner(); + if (!layerBlock->isAncestor(user)) + return false; + if (auto connectLike = dyn_cast(user)) { + if (use.getOperandNumber() == 0) + return false; + } + return true; + }); + + connectValues.push_back({operand, ConnectKind::NonRef}); }; // Set the location intelligently. Use the location of the capture if this @@ -145,21 +305,39 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { return loc; }; - // Create an output probe port port and adds the ref.define/ref.send to - // drive the port. + // Create an output probe port port and adds a ref.define/ref.send to + // drive the port if this was not already capturing a ref type. auto createOutputPort = [&](Value dest, Value src) { auto loc = getPortLoc(dest); auto portNum = ports.size(); auto operandName = getFieldName(FieldRef(dest, 0), true); - auto refType = RefType::get( - type_cast(dest.getType()).getPassiveType(), - /*forceable=*/false); + RefType refType; + if (auto oldRef = dyn_cast(dest.getType())) + refType = oldRef; + else + refType = RefType::get( + type_cast(dest.getType()).getPassiveType(), + /*forceable=*/false); ports.push_back({builder.getStringAttr("_" + operandName.first), refType, Direction::Out, /*sym=*/{}, /*loc=*/loc}); - body->addArgument(refType, loc); - connectValues.push_back(dest); + Value replacement = body->addArgument(refType, loc); + if (isa(dest.getType())) { + dest.replaceUsesWithIf(replacement, [&](OpOperand &use) { + auto *user = use.getOwner(); + if (!layerBlock->isAncestor(user)) + return false; + if (auto connectLike = dyn_cast(user)) { + if (use.getOperandNumber() == 0) + return true; + } + return false; + }); + connectValues.push_back({dest, ConnectKind::Ref}); + return; + } + connectValues.push_back({dest, ConnectKind::NonRef}); OpBuilder::InsertionGuard guard(builder); builder.setInsertionPointAfterValue(src); builder.create( @@ -167,7 +345,14 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { builder.create(loc, src)->getResult(0)); }; + SmallVector innerSyms; for (auto &op : llvm::make_early_inc_range(*body)) { + // Record any operations inside the layer block which have inner symbols. + // Theses may have symbol users which need to be updated. + if (auto symOp = dyn_cast(op)) + if (auto innerSym = symOp.getInnerSymAttr()) + innerSyms.push_back(innerSym); + // Handle instance ops that were created from nested layer blocks. These // ops need to be moved outside the layer block to avoid nested binds. // Nested binds are illegal in the SystemVerilog specification (and @@ -179,11 +364,10 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { // outside the layer block. We will hook it up later once we replace the // layer block with an instance. if (auto instOp = dyn_cast(op)) { - // Ignore any instance which this pass did not create from a nested - // layer block. Instances which are not marked lowerToBind do not need - // to be split out. + // Ignore instances which this pass did not create. if (!createdInstances.contains(instOp)) continue; + LLVM_DEBUG({ llvm::dbgs() << " Found instance created from nested layer block:\n" @@ -194,31 +378,51 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { continue; } + if (auto refSend = dyn_cast(op)) { + auto src = refSend.getBase(); + if (!isAncestor(layerBlock, src)) + createInputPort(src, op.getLoc()); + continue; + } + + if (auto refCast = dyn_cast(op)) { + if (!isAncestor(layerBlock, refCast)) + createInputPort(refCast.getInput(), op.getLoc()); + continue; + } + if (auto connect = dyn_cast(op)) { - auto srcInLayerBlock = connect.getSrc().getParentBlock() == body; - auto destInLayerBlock = connect.getDest().getParentBlock() == body; - if (!srcInLayerBlock && !destInLayerBlock) { + auto src = connect.getSrc(); + auto dst = connect.getDest(); + auto srcInLayerBlock = isAncestor(layerBlock, src); + auto dstInLayerBlock = isAncestor(layerBlock, dst); + if (!srcInLayerBlock && !dstInLayerBlock) { connect->moveBefore(layerBlock); continue; } // Create an input port. if (!srcInLayerBlock) { - createInputPort(connect.getSrc(), op.getLoc()); + createInputPort(src, op.getLoc()); continue; } // Create an output port. - if (!destInLayerBlock) { - createOutputPort(connect.getDest(), connect.getSrc()); - connect.erase(); + if (!dstInLayerBlock) { + createOutputPort(dst, src); + if (!dst.getType().isa()) + connect.erase(); continue; } // Source and destination in layer block. Nothing to do. continue; } - // Pattern match the following structure. Move the ref.resolve outside - // the layer block. The strictconnect will be moved outside in the next - // loop iteration: + // Pre-emptively de-squiggle connections that we are creating. This will + // later be cleaned up by the de-squiggling pass. However, there is no + // point in creating deeply squiggled connections if we don't have to. + // + // This pattern matches the following structure. Move the ref.resolve + // outside the layer block. The strictconnect will be moved outside in + // the next loop iteration: // %0 = ... // %1 = ... // firrtl.layerblock { @@ -226,7 +430,8 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { // firrtl.strictconnect %1, %2 // } if (auto refResolve = dyn_cast(op)) - if (refResolve.getResult().hasOneUse()) + if (refResolve.getResult().hasOneUse() && + refResolve.getRef().getParentBlock() != body) if (auto connect = dyn_cast( *refResolve.getResult().getUsers().begin())) if (connect.getDest().getParentBlock() != body) { @@ -235,9 +440,10 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { } // For any other ops, create input ports for any captured operands. - for (auto operand : op.getOperands()) - if (operand.getParentBlock() != body) + for (auto operand : op.getOperands()) { + if (!isAncestor(layerBlock, operand)) createInputPort(operand, op.getLoc()); + } } // Create the new module. This grabs a lock to modify the circuit. @@ -257,29 +463,52 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { llvm::dbgs() << " - name: " << port.getName() << "\n" << " type: " << port.type << "\n" << " direction: " << port.direction << "\n" - << " value: " << value << "\n"; + << " value: " << value.value << "\n" + << " kind: " + << (value.kind == ConnectKind::NonRef ? "NonRef" : "Ref") + << "\n"; } }); // Replace the original layer block with an instance. Hook up the instance. + // Intentionally create instance with probe ports which do not have an + // associated layer. This is illegal IR that will be made legal by the end + // of the pass. This is done to avoid having to revisit and rewrite each + // instance everytime it is moved into a parent layer. builder.setInsertionPointAfter(layerBlock); auto moduleName = newModule.getModuleName(); + auto instanceName = + (Twine((char)tolower(moduleName[0])) + moduleName.drop_front()).str(); auto instanceOp = builder.create( layerBlock.getLoc(), /*moduleName=*/newModule, /*name=*/ - (Twine((char)tolower(moduleName[0])) + moduleName.drop_front()).str(), - NameKindEnum::DroppableName, + instanceName, NameKindEnum::DroppableName, /*annotations=*/ArrayRef{}, - /*portAnnotations=*/ArrayRef{}, /*lowerToBind=*/true); - // TODO: Change this to "layers_" once we switch to FIRRTL 4.0.0+. + /*portAnnotations=*/ArrayRef{}, /*lowerToBind=*/true, + /*innerSym=*/ + (innerSyms.empty() ? hw::InnerSymAttr{} + : hw::InnerSymAttr::get(builder.getStringAttr( + ns.newName(instanceName))))); instanceOp->setAttr("output_file", hw::OutputFileAttr::getFromFilename( builder.getContext(), - "groups_" + circuitName + "_" + layerName + ".sv", + "layers_" + circuitName + "_" + layerName + ".sv", /*excludeFromFileList=*/true)); createdInstances.try_emplace(instanceOp, newModule); + LLVM_DEBUG(llvm::dbgs() << " moved inner refs:\n"); + for (hw::InnerSymAttr innerSym : innerSyms) { + auto oldInnerRef = hw::InnerRefAttr::get(moduleOp.getModuleNameAttr(), + innerSym.getSymName()); + auto splice = std::make_pair(instanceOp.getInnerSymAttr(), + newModule.getModuleNameAttr()); + innerRefMap.insert({oldInnerRef, splice}); + LLVM_DEBUG(llvm::dbgs() << " - ref: " << oldInnerRef << "\n" + << " splice: " << splice.first << ", " + << splice.second << "\n";); + } + // Connect instance ports to values. assert(ports.size() == connectValues.size() && "the number of instance ports and values to connect to them must be " @@ -288,13 +517,22 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { ++portNum) { OpBuilder::InsertionGuard guard(builder); builder.setInsertionPointAfterValue(instanceOp.getResult(portNum)); - if (instanceOp.getPortDirection(portNum) == Direction::In) + if (instanceOp.getPortDirection(portNum) == Direction::In) { + auto src = connectValues[portNum].value; + if (isa(src.getType())) + src = builder.create( + newModule.getPortLocationAttr(portNum), src); builder.create(newModule.getPortLocationAttr(portNum), - instanceOp.getResult(portNum), - connectValues[portNum]); + instanceOp.getResult(portNum), src); + } else if (instanceOp.getResult(portNum).getType().isa() && + connectValues[portNum].kind == ConnectKind::Ref) + builder.create(getPortLoc(connectValues[portNum].value), + connectValues[portNum].value, + instanceOp.getResult(portNum)); else builder.create( - getPortLoc(connectValues[portNum]), connectValues[portNum], + getPortLoc(connectValues[portNum].value), + connectValues[portNum].value, builder.create(newModule.getPortLocationAttr(portNum), instanceOp.getResult(portNum))); } @@ -331,27 +569,63 @@ void LowerLayersPass::runOnOperation() { }); }); - // Early exit if no work to do. - if (moduleNames.empty()) - return markAllAnalysesPreserved(); + auto mergeMaps = [](auto &&a, auto &&b) { + for (auto bb : b) + a.insert(bb); + return std::forward(a); + }; // Lower the layer blocks of each module. - SmallVector modules(circuitOp.getBodyBlock()->getOps()); - llvm::parallelForEach(modules, - [&](FModuleOp moduleOp) { runOnModule(moduleOp); }); + SmallVector modules( + circuitOp.getBodyBlock()->getOps()); + auto innerRefMap = + transformReduce(circuitOp.getContext(), modules, InnerRefMap{}, mergeMaps, + [this](FModuleLike mod) { return runOnModuleLike(mod); }); + + // Rewrite any hw::HierPathOps which have namepaths that contain rewritting + // inner refs. + // + // TODO: This unnecessarily computes a new namepath for every hw::HierPathOp + // even if that namepath is not used. It would be better to only build the + // new namepath when a change is needed, e.g., by recording updates to the + // namepath. + for (hw::HierPathOp hierPathOp : circuitOp.getOps()) { + SmallVector newNamepath; + bool modified = false; + for (auto attr : hierPathOp.getNamepath()) { + hw::InnerRefAttr innerRef = dyn_cast(attr); + if (!innerRef) { + newNamepath.push_back(attr); + continue; + } + auto it = innerRefMap.find(innerRef); + if (it == innerRefMap.end()) { + newNamepath.push_back(attr); + continue; + } + + auto &[inst, mod] = it->getSecond(); + newNamepath.push_back( + hw::InnerRefAttr::get(innerRef.getModule(), inst.getSymName())); + newNamepath.push_back(hw::InnerRefAttr::get(mod, innerRef.getName())); + modified = true; + } + if (modified) + hierPathOp.setNamepathAttr( + ArrayAttr::get(circuitOp.getContext(), newNamepath)); + } // Generate the header and footer of each bindings file. The body will be // populated later when binds are exported to Verilog. This produces text // like: // - // `include "groups_A.sv" - // `include "groups_A_B.sv" - // `ifndef groups_A_B_C - // `define groups_A_B_C + // `include "layers_A.sv" + // `include "layers_A_B.sv" + // `ifndef layers_A_B_C + // `define layers_A_B_C // - // `endif // groups_A_B_C + // `endif // layers_A_B_C // - // TODO: Change this comment to "layers_" once we switch to FIRRTL 4.0.0+. // TODO: This would be better handled without the use of verbatim ops. OpBuilder builder(circuitOp); SmallVector> layers; @@ -362,9 +636,8 @@ void LowerLayersPass::runOnOperation() { layers.pop_back(); builder.setInsertionPointToStart(circuitOp.getBodyBlock()); - // Save the "groups_CIRCUIT_GROUP" string as this is reused a bunch. - // TODO: Change this to "layers_" once we switch to FIRRTL 4.0.0+. - SmallString<32> prefix("groups_"); + // Save the "layers_CIRCUIT_GROUP" string as this is reused a bunch. + SmallString<32> prefix("layers_"); prefix.append(circuitName); prefix.append("_"); for (auto [layer, _] : layers) { diff --git a/lib/Dialect/FIRRTL/Transforms/LowerMemory.cpp b/lib/Dialect/FIRRTL/Transforms/LowerMemory.cpp index c572eefee1e4..5a6a722aa92f 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerMemory.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerMemory.cpp @@ -325,7 +325,7 @@ static SmallVector getAllFieldAccesses(Value structValue, assert(isa(op)); auto fieldAccess = cast(op); auto elemIndex = - fieldAccess.getInput().getType().get().getElementIndex(field); + fieldAccess.getInput().getType().base().getElementIndex(field); if (elemIndex && *elemIndex == fieldAccess.getFieldIndex()) accesses.push_back(fieldAccess); } @@ -477,7 +477,8 @@ InstanceOp LowerMemoryPass::emitMemoryInstance(MemOp op, FModuleOp module, op.getLoc(), portTypes, module.getNameAttr(), summary.getFirMemoryName(), op.getNameKind(), portDirections, portNames, /*annotations=*/ArrayRef(), - /*portAnnotations=*/ArrayRef(), /*lowerToBind=*/false, + /*portAnnotations=*/ArrayRef(), + /*layers=*/ArrayRef(), /*lowerToBind=*/false, op.getInnerSymAttr()); // Update all users of the result of read ports diff --git a/lib/Dialect/FIRRTL/Transforms/LowerOpenAggs.cpp b/lib/Dialect/FIRRTL/Transforms/LowerOpenAggs.cpp index eb2512a16951..dc7c1dbc9836 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerOpenAggs.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerOpenAggs.cpp @@ -22,6 +22,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLVisitors.h" #include "circt/Dialect/FIRRTL/FieldRefCache.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Support/Debug.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "mlir/IR/Threading.h" @@ -831,8 +832,7 @@ struct LowerOpenAggsPass : public LowerOpenAggsBase { // This is the main entrypoint for the lowering pass. void LowerOpenAggsPass::runOnOperation() { - LLVM_DEBUG(llvm::dbgs() << "===- Running Lower Open Aggregates Pass " - "-------------------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n"); SmallVector ops(getOperation().getOps()); LLVM_DEBUG(llvm::dbgs() << "Visiting modules:\n"); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerSignatures.cpp b/lib/Dialect/FIRRTL/Transforms/LowerSignatures.cpp index 2d5b5c86f170..4ef95c57dfbe 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerSignatures.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerSignatures.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" + #include "circt/Dialect/FIRRTL/AnnotationDetails.h" #include "circt/Dialect/FIRRTL/FIRRTLAttributes.h" #include "circt/Dialect/FIRRTL/FIRRTLOps.h" @@ -25,6 +26,7 @@ #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOpInterfaces.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Support/Debug.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "mlir/IR/Threading.h" #include "llvm/ADT/APSInt.h" @@ -50,11 +52,12 @@ struct AttrCache { sPortTypes = StringAttr::get(context, "portTypes"); sPortLocations = StringAttr::get(context, "portLocations"); sPortAnnotations = StringAttr::get(context, "portAnnotations"); + sInternalPaths = StringAttr::get(context, "internalPaths"); } AttrCache(const AttrCache &) = default; StringAttr nameAttr, sPortDirections, sPortNames, sPortTypes, sPortLocations, - sPortAnnotations; + sPortAnnotations, sInternalPaths; }; struct FieldMapEntry : public PortInfo { @@ -186,7 +189,7 @@ computeLoweringImpl(FModuleLike mod, PortConversion &newPorts, Convention conv, } return success(); }) - .template Case([&](FVectorType vector) -> LogicalResult { + .Case([&](FVectorType vector) -> LogicalResult { if (conv != Convention::Scalarized && vector.getElementType().isPassive()) { auto lastId = fieldID + vector.getMaxFieldID(); @@ -228,8 +231,8 @@ computeLoweringImpl(FModuleLike mod, PortConversion &newPorts, Convention conv, } return success(); }) - .template Case([&](FEnumType fenum) { return failure(); }) - .template Case([&](auto type) { + .Case([&](FEnumType fenum) { return failure(); }) + .Default([&](FIRRTLType type) { // Properties and other types wind up here. newPorts.push_back( {{StringAttr::get(ctx, name), type, @@ -294,8 +297,10 @@ static LogicalResult lowerModuleSignature(FModuleLike module, Convention conv, // zero-length vectors. for (auto idx = 0U; idx < oldNumArgs; ++idx) { if (!bounceWires[idx]) { - bounceWires[idx] = - theBuilder.create(module.getPortType(idx)).getResult(); + bounceWires[idx] = theBuilder + .create(module.getPortType(idx), + module.getPortNameAttr(idx)) + .getResult(); } body->getArgument(idx).replaceAllUsesWith(bounceWires[idx]); } @@ -327,7 +332,8 @@ static LogicalResult lowerModuleSignature(FModuleLike module, Convention conv, // handled differently below. if (attr.getName() != "portNames" && attr.getName() != "portDirections" && attr.getName() != "portTypes" && attr.getName() != "portAnnotations" && - attr.getName() != "portSyms" && attr.getName() != "portLocations") + attr.getName() != "portSyms" && attr.getName() != "portLocations" && + attr.getName() != "internalPaths") newModuleAttrs.push_back(attr); SmallVector newPortDirections; @@ -336,6 +342,10 @@ static LogicalResult lowerModuleSignature(FModuleLike module, Convention conv, SmallVector newPortSyms; SmallVector newPortLocations; SmallVector newPortAnnotations; + SmallVector newInternalPaths; + + bool hasInternalPaths = false; + auto internalPaths = module->getAttrOfType("internalPaths"); for (auto p : newPorts) { newPortTypes.push_back(TypeAttr::get(p.type)); newPortNames.push_back(p.name); @@ -343,7 +353,14 @@ static LogicalResult lowerModuleSignature(FModuleLike module, Convention conv, newPortSyms.push_back(p.sym); newPortLocations.push_back(p.loc); newPortAnnotations.push_back(p.annotations.getArrayAttr()); + if (internalPaths) { + auto internalPath = internalPaths[p.portID].cast(); + newInternalPaths.push_back(internalPath); + if (internalPath.getPath()) + hasInternalPaths = true; + } } + newModuleAttrs.push_back(NamedAttribute( cache.sPortDirections, direction::packAttribute(module.getContext(), newPortDirections))); @@ -360,6 +377,13 @@ static LogicalResult lowerModuleSignature(FModuleLike module, Convention conv, newModuleAttrs.push_back(NamedAttribute( cache.sPortAnnotations, theBuilder.getArrayAttr(newPortAnnotations))); + assert(newInternalPaths.empty() || + newInternalPaths.size() == newPorts.size()); + if (hasInternalPaths) { + newModuleAttrs.emplace_back(cache.sInternalPaths, + theBuilder.getArrayAttr(newInternalPaths)); + } + // Update the module's attributes. module->setAttrs(newModuleAttrs); module.setPortSymbols(newPortSyms); @@ -368,9 +392,8 @@ static LogicalResult lowerModuleSignature(FModuleLike module, Convention conv, static void lowerModuleBody(FModuleOp mod, const DenseMap &ports) { - ImplicitLocOpBuilder theBuilder(mod.getLoc(), mod.getContext()); mod->walk([&](InstanceOp inst) -> void { - theBuilder.setInsertionPoint(inst); + ImplicitLocOpBuilder theBuilder(inst.getLoc(), inst); const auto &modPorts = ports.at(inst.getModuleNameAttr().getAttr()); // Fix up the Instance @@ -384,10 +407,12 @@ static void lowerModuleBody(FModuleOp mod, auto annos = inst.getAnnotations(); auto newOp = theBuilder.create( instPorts, inst.getModuleName(), inst.getName(), inst.getNameKind(), - annos.getValue(), inst.getLowerToBind(), inst.getInnerSymAttr()); + annos.getValue(), inst.getLayers(), inst.getLowerToBind(), + inst.getInnerSymAttr()); auto oldDict = inst->getDiscardableAttrDictionary(); auto newDict = newOp->getDiscardableAttrDictionary(); + auto oldNames = inst.getPortNamesAttr(); SmallVector newAttrs; for (auto na : oldDict) if (!newDict.contains(na.getName())) @@ -403,8 +428,11 @@ static void lowerModuleBody(FModuleOp mod, continue; } if (!bounce[p.portID]) { - bounce[p.portID] = - theBuilder.create(inst.getResult(p.portID).getType()); + bounce[p.portID] = theBuilder.create( + inst.getResult(p.portID).getType(), + theBuilder.getStringAttr( + inst.getName() + "." + + oldNames[p.portID].cast().getValue())); inst.getResult(p.portID).replaceAllUsesWith( bounce[p.portID].getResult()); } @@ -442,9 +470,7 @@ struct LowerSignaturesPass : public LowerSignaturesBase { // This is the main entrypoint for the lowering pass. void LowerSignaturesPass::runOnOperation() { - LLVM_DEBUG( - llvm::dbgs() << "===- Running Lower Signature Pass " - "------------------------------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n"); // Cached attr AttrCache cache(&getContext()); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerTypes.cpp b/lib/Dialect/FIRRTL/Transforms/LowerTypes.cpp index 2a10b9b60aac..2215f852a827 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerTypes.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerTypes.cpp @@ -28,6 +28,7 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" + #include "circt/Dialect/FIRRTL/AnnotationDetails.h" #include "circt/Dialect/FIRRTL/FIRRTLAttributes.h" #include "circt/Dialect/FIRRTL/FIRRTLOps.h" @@ -40,6 +41,7 @@ #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOpInterfaces.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Support/Debug.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "mlir/IR/Threading.h" #include "llvm/ADT/APSInt.h" @@ -100,31 +102,6 @@ static Type mapLoweredType(Type type, FIRRTLBaseType fieldType) { return mapLoweredType(ftype, fieldType); } -// NOLINTBEGIN(misc-no-recursion) -/// Return true if the type has more than zero bitwidth. -static bool hasZeroBitWidth(FIRRTLType type) { - return FIRRTLTypeSwitch(type) - .Case([&](auto bundle) { - for (size_t i = 0, e = bundle.getNumElements(); i < e; ++i) { - auto elt = bundle.getElement(i); - if (hasZeroBitWidth(elt.type)) - return true; - } - return bundle.getNumElements() == 0; - }) - .Case([&](auto vector) { - if (vector.getNumElements() == 0) - return true; - return hasZeroBitWidth(vector.getElementType()); - }) - .Case([](auto groundType) { - return firrtl::getBitWidth(groundType).value_or(0) == 0; - }) - .Case([](auto ref) { return hasZeroBitWidth(ref.getType()); }) - .Default([](auto) { return false; }); -} -// NOLINTEND(misc-no-recursion) - /// Return true if the type is a 1d vector type or ground type. static bool isOneDimVectorType(FIRRTLType type) { return FIRRTLTypeSwitch(type) @@ -232,7 +209,7 @@ static bool isNotSubAccess(Operation *op) { return true; ConstantOp arg = llvm::dyn_cast_or_null(sao.getIndex().getDefiningOp()); - return arg && sao.getInput().getType().get().getNumElements() != 0; + return arg && sao.getInput().getType().base().getNumElements() != 0; } /// Look through and collect subfields leading to a subaccess. @@ -1430,7 +1407,10 @@ bool TypeLoweringVisitor::visitExpr(RefCastOp op) { auto clone = [&](const FlatBundleFieldEntry &field, ArrayAttr attrs) -> Value { auto input = getSubWhatever(op.getInput(), field.index); - return builder->create(RefType::get(field.type), input); + return builder->create(RefType::get(field.type, + op.getType().getForceable(), + op.getType().getLayer()), + input); }; return lowerProducer(op, clone); } @@ -1486,7 +1466,8 @@ bool TypeLoweringVisitor::visitDecl(InstanceOp op) { resultTypes, op.getModuleNameAttr(), op.getNameAttr(), op.getNameKindAttr(), direction::packAttribute(context, newDirs), builder->getArrayAttr(newNames), op.getAnnotations(), - builder->getArrayAttr(newPortAnno), op.getLowerToBindAttr(), + builder->getArrayAttr(newPortAnno), op.getLayersAttr(), + op.getLowerToBindAttr(), sym ? hw::InnerSymAttr::get(sym) : hw::InnerSymAttr()); // Copy over any attributes which have not already been copied over by @@ -1638,9 +1619,7 @@ struct LowerTypesPass : public LowerFIRRTLTypesBase { // This is the main entrypoint for the lowering pass. void LowerTypesPass::runOnOperation() { - LLVM_DEBUG( - llvm::dbgs() << "===- Running LowerTypes Pass " - "------------------------------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n"); std::vector ops; // Symbol Table auto &symTbl = getAnalysis(); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerXMR.cpp b/lib/Dialect/FIRRTL/Transforms/LowerXMR.cpp index b9cfe5dbe275..a77e365fff18 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerXMR.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerXMR.cpp @@ -262,6 +262,9 @@ class LowerXMRPass : public LowerXMRBase { if (transferFunc(op).failed()) return signalPassFailure(); + // Clear any enabled layers. + module.setLayersAttr(ArrayAttr::get(module.getContext(), {})); + // Since we walk operations pre-order and not along dataflow edges, // ref.sub may not be resolvable when we encounter them (they're not just // unification). This can happen when refs go through an output port or @@ -348,18 +351,15 @@ class LowerXMRPass : public LowerXMRBase { pathInsertPoint = {}; } - /// Generate the ABI ref__ prefix string into `prefix`. + /// Generate the ABI ref_ prefix string into `prefix`. void getRefABIPrefix(FModuleLike mod, SmallVectorImpl &prefix) { auto modName = mod.getModuleName(); - auto circuitName = getOperation().getName(); if (auto ext = dyn_cast(*mod)) { // Use defName for module portion, if set. if (auto defname = ext.getDefname(); defname && !defname->empty()) modName = *defname; - // Assume(/require) all extmodule's are within their own circuit. - circuitName = modName; } - (Twine("ref_") + circuitName + "_" + modName).toVector(prefix); + (Twine("ref_") + modName).toVector(prefix); } /// Get full macro name as StringAttr for the specified ref port. @@ -626,7 +626,7 @@ class LowerXMRPass : public LowerXMRBase { formatString += stringLeaf; // Insert a macro with the format: - // ref___ + // ref__ if (circuitRefPrefix.empty()) getRefABIPrefix(module, circuitRefPrefix); auto macroName = @@ -639,7 +639,7 @@ class LowerXMRPass : public LowerXMRBase { builder.getArrayAttr(ref ? ref : ArrayRef{})); // The macro will be exported to a file with the format: - // ref__.sv + // ref_.sv macroDefOp->setAttr("output_file", hw::OutputFileAttr::getFromFilename( &getContext(), circuitRefPrefix + ".sv")); diff --git a/lib/Dialect/FIRRTL/Transforms/MaterializeDebugInfo.cpp b/lib/Dialect/FIRRTL/Transforms/MaterializeDebugInfo.cpp index 26ab68b39a72..4675666de53f 100644 --- a/lib/Dialect/FIRRTL/Transforms/MaterializeDebugInfo.cpp +++ b/lib/Dialect/FIRRTL/Transforms/MaterializeDebugInfo.cpp @@ -10,6 +10,7 @@ #include "circt/Dialect/Debug/DebugOps.h" #include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/FIRRTLTypes.h" +#include "circt/Support/Naming.h" #include "mlir/IR/Attributes.h" #include "mlir/IR/Builders.h" #include "llvm/ADT/StringExtras.h" diff --git a/lib/Dialect/FIRRTL/Transforms/MergeConnections.cpp b/lib/Dialect/FIRRTL/Transforms/MergeConnections.cpp index 1002af40d66d..e9fd0ee3bd69 100644 --- a/lib/Dialect/FIRRTL/Transforms/MergeConnections.cpp +++ b/lib/Dialect/FIRRTL/Transforms/MergeConnections.cpp @@ -22,8 +22,10 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" + #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Support/Debug.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/Debug.h" @@ -284,9 +286,9 @@ struct MergeConnectionsPass } // namespace void MergeConnectionsPass::runOnOperation() { - LLVM_DEBUG(llvm::dbgs() << "===----- Running MergeConnections " - "--------------------------------------===\n" - << "Module: '" << getOperation().getName() << "'\n";); + LLVM_DEBUG(debugPassHeader(this) + << "\n" + << "Module: '" << getOperation().getName() << "'\n"); MergeConnection mergeConnection(getOperation(), enableAggressiveMerging); bool changed = mergeConnection.run(); diff --git a/lib/Dialect/FIRRTL/Transforms/ModuleInliner.cpp b/lib/Dialect/FIRRTL/Transforms/ModuleInliner.cpp index cd9a5b92054b..899cd8324ca6 100644 --- a/lib/Dialect/FIRRTL/Transforms/ModuleInliner.cpp +++ b/lib/Dialect/FIRRTL/Transforms/ModuleInliner.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" +#include "circt/Dialect/Debug/DebugOps.h" #include "circt/Dialect/FIRRTL/AnnotationDetails.h" #include "circt/Dialect/FIRRTL/CHIRRTLDialect.h" #include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h" @@ -23,6 +24,7 @@ #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/InnerSymbolNamespace.h" +#include "circt/Support/Debug.h" #include "circt/Support/LLVM.h" #include "mlir/IR/IRMapping.h" #include "llvm/ADT/BitVector.h" @@ -510,6 +512,7 @@ class Inliner { struct InliningLevel { InliningLevel(ModuleInliningContext &mic, FModuleOp childModule) : mic(mic), childModule(childModule) {} + /// Top-level inlining context. ModuleInliningContext &mic; /// Map of inner-refs to the new inner sym. @@ -518,8 +521,11 @@ class Inliner { SmallVector newOps; /// Wires and other values introduced for ports. SmallVector wires; - /// Ths module being inlined (this "level"). + /// The module being inlined (this "level"). FModuleOp childModule; + /// The explicit debug scope of the inlined instance. + Value debugScope; + ~InliningLevel() { replaceInnerRefUsers(newOps, relocatedInnerSyms, mic.module.getNameAttr()); @@ -579,6 +585,11 @@ class Inliner { /// Inline any instances in the module which were marked for inlining. void inlineInstances(FModuleOp module); + /// Create a debug scope for an inlined instance at the current insertion + /// point of the `il.mic` builder. + void createDebugScope(InliningLevel &il, InstanceOp instance, + Value parentScope = {}); + /// Identify all module-only NLA's, marking their MutableNLA's accordingly. void identifyNLAsTargetingOnlyModules(); @@ -634,6 +645,10 @@ class Inliner { /// from the InnerRefAttr to the list of HierPathOp names. The InnerRefAttr /// corresponds to the InstanceOp. DenseMap> instOpHierPaths; + + /// The debug scopes created for inlined instances. Scopes that are unused + /// after inlining will be deleted again. + SmallVector debugScopes; }; } // namespace @@ -649,6 +664,17 @@ bool Inliner::doesNLAMatchCurrentPath(hw::HierPathOp nla) { /// these are unique in the namespace. Record renamed inner symbols /// in relocatedInnerSyms map for renaming local users. bool Inliner::rename(StringRef prefix, Operation *op, InliningLevel &il) { + // Debug operations with implicit module scope now need an explicit scope, + // since inlining has destroyed the module whose scope they implicitly used. + auto updateDebugScope = [&](auto op) { + if (!op.getScope()) + op.getScopeMutable().assign(il.debugScope); + }; + if (auto varOp = dyn_cast(op)) + return updateDebugScope(varOp), false; + if (auto scopeOp = dyn_cast(op)) + return updateDebugScope(scopeOp), false; + // Add a prefix to things that has a "name" attribute. We don't prefix // memories since it will affect the name of the generated module. // TODO: We should find a way to prefix the instance of a memory module. @@ -698,6 +724,13 @@ bool Inliner::rename(StringRef prefix, Operation *op, InliningLevel &il) { bool Inliner::renameInstance( StringRef prefix, InliningLevel &il, InstanceOp oldInst, InstanceOp newInst, const DenseMap &symbolRenames) { + // TODO: There is currently no good way to annotate an explicit parent scope + // on instances. Just emit a note in debug runs until this is resolved. + LLVM_DEBUG({ + if (il.debugScope) + llvm::dbgs() << "Discarding parent debug scope for " << oldInst << "\n"; + }); + // Add this instance to the activeHierpaths. This ensures that NLAs that this // instance participates in will be updated correctly. auto parentActivePaths = activeHierpaths; @@ -932,6 +965,7 @@ void Inliner::flattenInto(StringRef prefix, InliningLevel &il, currentPath.emplace_back(moduleName, instInnerSym); InliningLevel childIL(il.mic, childModule); + createDebugScope(childIL, instance, il.debugScope); // Create the wire mapping for results + ports. auto nestedPrefix = (prefix + instance.getName() + "_").str(); @@ -988,6 +1022,7 @@ void Inliner::flattenInstances(FModuleOp module) { mic.b.setInsertionPoint(instance); InliningLevel il(mic, target); + createDebugScope(il, instance); auto nestedPrefix = (instance.getName() + "_").str(); mapPortsToWires(nestedPrefix, il, mapper, localSymbols); @@ -1086,6 +1121,7 @@ void Inliner::inlineInto(StringRef prefix, InliningLevel &il, IRMapping &mapper, currentPath.emplace_back(moduleName, instInnerSym); InliningLevel childIL(il.mic, childModule); + createDebugScope(childIL, instance, il.debugScope); // Create the wire mapping for results + ports. auto nestedPrefix = (prefix + instance.getName() + "_").str(); @@ -1178,6 +1214,7 @@ void Inliner::inlineInstances(FModuleOp module) { auto nestedPrefix = (instance.getName() + "_").str(); InliningLevel childIL(mic, target); + createDebugScope(childIL, instance); mapPortsToWires(nestedPrefix, childIL, mapper, {}); for (unsigned i = 0, e = instance.getNumResults(); i < e; ++i) @@ -1199,6 +1236,15 @@ void Inliner::inlineInstances(FModuleOp module) { } } +void Inliner::createDebugScope(InliningLevel &il, InstanceOp instance, + Value parentScope) { + auto op = il.mic.b.create( + instance.getLoc(), instance.getInstanceNameAttr(), + instance.getModuleNameAttr().getAttr(), parentScope); + debugScopes.push_back(op); + il.debugScope = op; +} + void Inliner::identifyNLAsTargetingOnlyModules() { DenseSet nlaTargetedModules; @@ -1308,6 +1354,13 @@ void Inliner::run() { } } + // Delete debug scopes that ended up being unused. Erase them in reverse order + // since scopes at the back may have uses on scopes at the front. + for (auto scopeOp : llvm::reverse(debugScopes)) + if (scopeOp.use_empty()) + scopeOp.erase(); + debugScopes.clear(); + // Delete all unreferenced modules. Mark any NLAs that originate from dead // modules as also dead. for (auto mod : llvm::make_early_inc_range( @@ -1422,13 +1475,10 @@ void Inliner::run() { namespace { class InlinerPass : public InlinerBase { void runOnOperation() override { - LLVM_DEBUG(llvm::dbgs() - << "===- Running Module Inliner Pass " - "--------------------------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n"); Inliner inliner(getOperation(), getAnalysis()); inliner.run(); - LLVM_DEBUG(llvm::dbgs() << "===--------------------------------------------" - "------------------------------===\n"); + LLVM_DEBUG(debugFooter() << "\n"); } }; } // namespace diff --git a/lib/Dialect/FIRRTL/Transforms/PassDetails.h b/lib/Dialect/FIRRTL/Transforms/PassDetails.h index 8d4d330ec3f6..3826acac5f83 100644 --- a/lib/Dialect/FIRRTL/Transforms/PassDetails.h +++ b/lib/Dialect/FIRRTL/Transforms/PassDetails.h @@ -26,6 +26,10 @@ namespace hw { class HWDialect; } // namespace hw +namespace emit { +class EmitDialect; +} // namespace emit + namespace sv { class SVDialect; } // namespace sv diff --git a/lib/Dialect/FIRRTL/Transforms/PassiveWires.cpp b/lib/Dialect/FIRRTL/Transforms/PassiveWires.cpp index 5d7b73160549..d46ec1db14fd 100644 --- a/lib/Dialect/FIRRTL/Transforms/PassiveWires.cpp +++ b/lib/Dialect/FIRRTL/Transforms/PassiveWires.cpp @@ -15,6 +15,7 @@ #include "PassDetails.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" +#include "circt/Support/Debug.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "llvm/Support/Debug.h" @@ -41,9 +42,7 @@ struct PassiveWiresPass : public PassiveWiresBase { // This is the main entrypoint for the lowering pass. void PassiveWiresPass::runOnOperation() { - LLVM_DEBUG( - llvm::dbgs() << "===- Running Passive Wires Pass " - "---------------------------------------------===\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n";); auto module = getOperation(); // First, expand any connects to resolve flips. diff --git a/lib/Dialect/FIRRTL/Transforms/RegisterOptimizer.cpp b/lib/Dialect/FIRRTL/Transforms/RegisterOptimizer.cpp index 1549632ae7fb..524686eecf5d 100644 --- a/lib/Dialect/FIRRTL/Transforms/RegisterOptimizer.cpp +++ b/lib/Dialect/FIRRTL/Transforms/RegisterOptimizer.cpp @@ -11,7 +11,9 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" + #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Support/Debug.h" #include "mlir/IR/Dominance.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "llvm/Support/Debug.h" @@ -132,9 +134,7 @@ void RegisterOptimizerPass::checkRegReset(mlir::DominanceInfo &dom, void RegisterOptimizerPass::runOnOperation() { auto mod = getOperation(); - LLVM_DEBUG(llvm::dbgs() << "===----- Running RegisterOptimizer " - "--------------------------------------===\n" - << "Module: '" << mod.getName() << "'\n";); + LLVM_DEBUG(debugPassHeader(this) << "\n";); SmallVector toErase; mlir::DominanceInfo dom(mod); diff --git a/lib/Dialect/FIRRTL/Transforms/RemoveUnusedPorts.cpp b/lib/Dialect/FIRRTL/Transforms/RemoveUnusedPorts.cpp index 19ddb8e5853a..fe6f096a7f2c 100644 --- a/lib/Dialect/FIRRTL/Transforms/RemoveUnusedPorts.cpp +++ b/lib/Dialect/FIRRTL/Transforms/RemoveUnusedPorts.cpp @@ -7,10 +7,12 @@ //===----------------------------------------------------------------------===// #include "PassDetails.h" + #include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h" #include "circt/Dialect/FIRRTL/FIRRTLAttributes.h" #include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Support/Debug.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "llvm/ADT/APSInt.h" #include "llvm/ADT/BitVector.h" @@ -39,8 +41,7 @@ struct RemoveUnusedPortsPass void RemoveUnusedPortsPass::runOnOperation() { auto &instanceGraph = getAnalysis(); - LLVM_DEBUG(llvm::dbgs() << "===----- Remove unused ports -----===" - << "\n"); + LLVM_DEBUG(debugPassHeader(this) << "\n"); // Iterate in the reverse order of instance graph iterator, i.e. from leaves // to top. for (auto *node : llvm::post_order(&instanceGraph)) diff --git a/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp b/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp index fc816b56c886..87a4d0d139c9 100644 --- a/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp +++ b/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp @@ -44,15 +44,15 @@ void SFCCompatPass::runOnOperation() { bool madeModifications = false; SmallVector invalidOps; - for (auto &op : llvm::make_early_inc_range(getOperation().getOps())) { + auto result = getOperation()->walk([&](Operation *op) { // Populate invalidOps for later handling. if (auto inv = dyn_cast(op)) { invalidOps.push_back(inv); - continue; + return WalkResult::advance(); } auto reg = dyn_cast(op); if (!reg) - continue; + return WalkResult::advance(); // If the `RegResetOp` has an invalidated initialization, then replace it // with a `RegOp`. @@ -68,14 +68,14 @@ void SFCCompatPass::runOnOperation() { reg.replaceAllUsesWith(newReg); reg.erase(); madeModifications = true; - continue; + return WalkResult::advance(); } // If the `RegResetOp` has an asynchronous reset and the reset value is not // a module-scoped constant when looking through wires and nodes, then // generate an error. This implements the SFC's CheckResets pass. if (!isa(reg.getResetSignal().getType())) - continue; + return WalkResult::advance(); if (walkDrivers( reg.getResetValue(), true, true, true, [&](FieldRef dst, FieldRef src) { @@ -96,9 +96,12 @@ void SFCCompatPass::runOnOperation() { << (rootKnown ? ("\"" + fieldName + "\"") : "here"); return false; })) - continue; + return WalkResult::advance(); + return WalkResult::interrupt(); + }); + + if (result.wasInterrupted()) return signalPassFailure(); - } // Convert all invalid values to zero. for (auto inv : invalidOps) { diff --git a/lib/Dialect/FIRRTL/Transforms/SpecializeOption.cpp b/lib/Dialect/FIRRTL/Transforms/SpecializeOption.cpp index dc2833029e14..ade5355d8445 100644 --- a/lib/Dialect/FIRRTL/Transforms/SpecializeOption.cpp +++ b/lib/Dialect/FIRRTL/Transforms/SpecializeOption.cpp @@ -72,7 +72,7 @@ struct SpecializeOptionPass inst.getNameAttr(), inst.getNameKindAttr(), inst.getPortDirectionsAttr(), inst.getPortNamesAttr(), inst.getAnnotationsAttr(), inst.getPortAnnotationsAttr(), - UnitAttr{}, inst.getInnerSymAttr()); + builder.getArrayAttr({}), UnitAttr{}, inst.getInnerSymAttr()); inst.replaceAllUsesWith(newInst); inst.erase(); diff --git a/lib/Dialect/FIRRTL/Transforms/VBToBV.cpp b/lib/Dialect/FIRRTL/Transforms/VBToBV.cpp index ce83cb811145..612d47231940 100644 --- a/lib/Dialect/FIRRTL/Transforms/VBToBV.cpp +++ b/lib/Dialect/FIRRTL/Transforms/VBToBV.cpp @@ -174,7 +174,8 @@ RefType Visitor::convertType(RefType type) { auto cached = typeMap.lookup(type); if (cached) return type_cast(cached); - auto converted = RefType::get(convertType(type.getType())); + auto converted = RefType::get(convertType(type.getType()), + type.getForceable(), type.getLayer()); typeMap.insert({type, converted}); return converted; } @@ -845,7 +846,8 @@ LogicalResult Visitor::visitExpr(VectorCreateOp op) { } auto value = sinkVecDimIntoOperands( - builder, convertType(oldType.get().getElementType()), convertedOldFields); + builder, convertType(oldType.base().getElementType()), + convertedOldFields); valueMap[op.getResult()] = value; toDelete.push_back(op); return success(); diff --git a/lib/Dialect/FIRRTL/Transforms/Vectorization.cpp b/lib/Dialect/FIRRTL/Transforms/Vectorization.cpp index 6d45a142111c..6a785273a765 100644 --- a/lib/Dialect/FIRRTL/Transforms/Vectorization.cpp +++ b/lib/Dialect/FIRRTL/Transforms/Vectorization.cpp @@ -16,6 +16,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLTypes.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Support/Debug.h" #include "circt/Support/LLVM.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" @@ -79,9 +80,9 @@ struct VectorizationPass : public VectorizationBase { } // namespace void VectorizationPass::runOnOperation() { - LLVM_DEBUG(llvm::dbgs() << "===----- Running Vectorization " - "--------------------------------------===\n" - << "Module: '" << getOperation().getName() << "'\n";); + LLVM_DEBUG(debugPassHeader(this) + << "\n" + << "Module: '" << getOperation().getName() << "'\n";); RewritePatternSet patterns(&getContext()); patterns diff --git a/lib/Dialect/FIRRTL/Transforms/WireDFT.cpp b/lib/Dialect/FIRRTL/Transforms/WireDFT.cpp index fea786e20639..bac2bc1fedaa 100644 --- a/lib/Dialect/FIRRTL/Transforms/WireDFT.cpp +++ b/lib/Dialect/FIRRTL/Transforms/WireDFT.cpp @@ -227,7 +227,7 @@ void WireDFTPass::runOnOperation() { instanceGraph.lookup(dut), [&](InstanceRecord *node) { auto module = node->getTarget()->getModule(); // If this is a clock gate, record the module and return true. - if (module.getModuleName().startswith("EICG_wrapper")) { + if (module.getModuleName().starts_with("EICG_wrapper")) { clockGates.insert(node); return true; } diff --git a/lib/Dialect/FSM/FSMOps.cpp b/lib/Dialect/FSM/FSMOps.cpp index b7262a1cb340..83f9ca65865e 100644 --- a/lib/Dialect/FSM/FSMOps.cpp +++ b/lib/Dialect/FSM/FSMOps.cpp @@ -234,7 +234,7 @@ LogicalResult InstanceOp::verify() { void InstanceOp::getAsmResultNames( function_ref setNameFn) { - setNameFn(getInstance(), getSymName()); + setNameFn(getInstance(), getName()); } //===----------------------------------------------------------------------===// @@ -301,11 +301,9 @@ SmallVector HWInstanceOp::getPortList() { StringRef HWInstanceOp::getModuleName() { return getMachine(); } FlatSymbolRefAttr HWInstanceOp::getModuleNameAttr() { return getMachineAttr(); } -mlir::StringAttr HWInstanceOp::getInstanceNameAttr() { - return getSymNameAttr(); -} +mlir::StringAttr HWInstanceOp::getInstanceNameAttr() { return getNameAttr(); } -llvm::StringRef HWInstanceOp::getInstanceName() { return getSymName(); } +llvm::StringRef HWInstanceOp::getInstanceName() { return getName(); } //===----------------------------------------------------------------------===// // StateOp diff --git a/lib/Dialect/HW/CMakeLists.txt b/lib/Dialect/HW/CMakeLists.txt index 0338e6cd7317..1c9f9fd512f0 100644 --- a/lib/Dialect/HW/CMakeLists.txt +++ b/lib/Dialect/HW/CMakeLists.txt @@ -2,6 +2,7 @@ set(CIRCT_HW_Sources ConversionPatterns.cpp CustomDirectiveImpl.cpp HWAttributes.cpp + HWEnums.cpp HWDialect.cpp HWInstanceGraph.cpp HWInstanceImplementation.cpp diff --git a/lib/Dialect/HW/ConversionPatterns.cpp b/lib/Dialect/HW/ConversionPatterns.cpp index b642362a97de..12f513447652 100644 --- a/lib/Dialect/HW/ConversionPatterns.cpp +++ b/lib/Dialect/HW/ConversionPatterns.cpp @@ -73,7 +73,7 @@ LogicalResult circt::doTypeConversion(Operation *op, ValueRange operands, Operation *newOp = rewriter.create(state); // Move the regions over, converting the signatures as we go. - rewriter.startRootUpdate(newOp); + rewriter.startOpModification(newOp); for (size_t i = 0, e = op->getNumRegions(); i < e; ++i) { Region ®ion = op->getRegion(i); Region *newRegion = &newOp->getRegion(i); @@ -87,7 +87,7 @@ LogicalResult circt::doTypeConversion(Operation *op, ValueRange operands, "type conversion failed"); rewriter.applySignatureConversion(newRegion, result, typeConverter); } - rewriter.finalizeRootUpdate(newOp); + rewriter.finalizeOpModification(newOp); rewriter.replaceOp(op, newOp->getResults()); return success(); diff --git a/lib/Dialect/HW/HWAttributes.cpp b/lib/Dialect/HW/HWAttributes.cpp index 8ffa4a5c9673..43c4a15a4286 100644 --- a/lib/Dialect/HW/HWAttributes.cpp +++ b/lib/Dialect/HW/HWAttributes.cpp @@ -49,7 +49,7 @@ Attribute HWDialect::parseAttribute(DialectAsmParser &p, Type type) const { return attr; // Parse "#hw.param.expr.add" as ParamExprAttr. - if (attrName.startswith(ParamExprAttr::getMnemonic())) { + if (attrName.starts_with(ParamExprAttr::getMnemonic())) { auto string = attrName.drop_front(ParamExprAttr::getMnemonic().size()); if (string.front() == '.') return parseParamExprWithOpcode(string.drop_front(), p, type); @@ -89,7 +89,7 @@ static std::string canonicalizeFilename(const Twine &directory, // separator and return it. // e.g. `directory` + `` -> `directory/`. auto separator = llvm::sys::path::get_separator(); - if (nativeFilename.empty() && !nativeDirectory.endswith(separator)) { + if (nativeFilename.empty() && !nativeDirectory.ends_with(separator)) { nativeDirectory += separator; return std::string(nativeDirectory); } @@ -127,7 +127,7 @@ OutputFileAttr OutputFileAttr::getAsDirectory(MLIRContext *context, } bool OutputFileAttr::isDirectory() { - return getFilename().getValue().endswith(llvm::sys::path::get_separator()); + return getFilename().getValue().ends_with(llvm::sys::path::get_separator()); } /// Option ::= 'excludeFromFileList' | 'includeReplicatedOp' diff --git a/lib/Dialect/HW/HWDialect.cpp b/lib/Dialect/HW/HWDialect.cpp index be773a79aafd..f5964b599fbd 100644 --- a/lib/Dialect/HW/HWDialect.cpp +++ b/lib/Dialect/HW/HWDialect.cpp @@ -111,6 +111,3 @@ Operation *HWDialect::materializeConstant(OpBuilder &builder, Attribute value, return nullptr; } - -// Provide implementations for the enums we use. -#include "circt/Dialect/HW/HWEnums.cpp.inc" diff --git a/lib/Dialect/HW/HWEnums.cpp b/lib/Dialect/HW/HWEnums.cpp new file mode 100644 index 000000000000..bd446bf5c337 --- /dev/null +++ b/lib/Dialect/HW/HWEnums.cpp @@ -0,0 +1,20 @@ +//===- HWEnums.cpp - Implement the HW enums -------------------------------===// +// +// 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 the HW enums. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/HW/HWEnums.h" +#include "mlir/IR/BuiltinAttributes.h" + +using namespace circt; +using namespace hw; + +// Provide implementations for the enums we use. +#include "circt/Dialect/HW/HWEnums.cpp.inc" diff --git a/lib/Dialect/HW/HWInstanceImplementation.cpp b/lib/Dialect/HW/HWInstanceImplementation.cpp index a2c267ac6e51..6c7f0729a82c 100644 --- a/lib/Dialect/HW/HWInstanceImplementation.cpp +++ b/lib/Dialect/HW/HWInstanceImplementation.cpp @@ -13,17 +13,6 @@ using namespace circt; using namespace circt::hw; -Operation * -instance_like_impl::getReferencedModule(const HWSymbolCache *cache, - Operation *instanceOp, - mlir::FlatSymbolRefAttr moduleName) { - if (cache) - if (auto *result = cache->getDefinition(moduleName)) - return result; - - return SymbolTable::lookupNearestSymbolFrom(instanceOp, moduleName); -} - LogicalResult instance_like_impl::verifyReferencedModule( Operation *instanceOp, SymbolTableCollection &symbolTable, mlir::FlatSymbolRefAttr moduleName, Operation *&module) { diff --git a/lib/Dialect/HW/HWModuleOpInterface.cpp b/lib/Dialect/HW/HWModuleOpInterface.cpp index 8628ad0a556a..42730e680940 100644 --- a/lib/Dialect/HW/HWModuleOpInterface.cpp +++ b/lib/Dialect/HW/HWModuleOpInterface.cpp @@ -59,7 +59,7 @@ static LogicalResult convertModuleOpTypes(HWModuleLike modOp, return failure(); auto newType = ModuleType::get(rewriter.getContext(), newPorts); - rewriter.updateRootInPlace(modOp, [&] { modOp.setHWModuleType(newType); }); + rewriter.modifyOpInPlace(modOp, [&] { modOp.setHWModuleType(newType); }); return success(); } diff --git a/lib/Dialect/HW/HWOps.cpp b/lib/Dialect/HW/HWOps.cpp index fa507f8fcc5d..60427b9a6076 100644 --- a/lib/Dialect/HW/HWOps.cpp +++ b/lib/Dialect/HW/HWOps.cpp @@ -20,6 +20,7 @@ #include "circt/Dialect/HW/ModuleImplementation.h" #include "circt/Support/CustomDirectiveImpl.h" #include "circt/Support/Namespace.h" +#include "circt/Support/Naming.h" #include "mlir/IR/Builders.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Interfaces/FunctionImplementation.h" @@ -380,14 +381,10 @@ LogicalResult WireOp::canonicalize(WireOp wire, PatternRewriter &rewriter) { // If the wire has a name or an `sv.namehint` attribute, propagate it as an // `sv.namehint` to the expression. - if (auto *inputOp = wire.getInput().getDefiningOp()) { - auto name = wire.getNameAttr(); - if (!name || name.getValue().empty()) - name = wire->getAttrOfType("sv.namehint"); - if (name) - rewriter.updateRootInPlace( - inputOp, [&] { inputOp->setAttr("sv.namehint", name); }); - } + if (auto *inputOp = wire.getInput().getDefiningOp()) + if (auto name = chooseName(wire, inputOp)) + rewriter.modifyOpInPlace(inputOp, + [&] { inputOp->setAttr("sv.namehint", name); }); rewriter.replaceOp(wire, wire.getInput()); return success(); @@ -542,27 +539,21 @@ StringAttr hw::getVerilogModuleNameAttr(Operation *module) { return module->getAttrOfType(SymbolTable::getSymbolAttrName()); } -// Flag for parsing different module types -enum ExternModKind { PlainMod, ExternMod, GenMod }; - template static void buildModule(OpBuilder &builder, OperationState &result, StringAttr name, const ModulePortInfo &ports, ArrayAttr parameters, ArrayRef attributes, StringAttr comment) { using namespace mlir::function_interface_impl; - LocationAttr unknownLoc = builder.getUnknownLoc(); // Add an attribute for the name. result.addAttribute(SymbolTable::getSymbolAttrName(), name); SmallVector perPortAttrs; - SmallVector portLocs; SmallVector portTypes; for (auto elt : ports) { portTypes.push_back(elt); - portLocs.push_back(elt.loc ? elt.loc : unknownLoc); llvm::SmallVector portAttrs; if (elt.attrs) llvm::copy(elt.attrs, std::back_inserter(portAttrs)); @@ -577,7 +568,6 @@ buildModule(OpBuilder &builder, OperationState &result, StringAttr name, auto type = ModuleType::get(builder.getContext(), portTypes); result.addAttribute(ModuleTy::getModuleTypeAttrName(result.name), TypeAttr::get(type)); - result.addAttribute("port_locs", builder.getArrayAttr(portLocs)); result.addAttribute("per_port_attrs", arrayOrEmpty(builder.getContext(), perPortAttrs)); result.addAttribute("parameters", parameters); @@ -682,11 +672,17 @@ static void modifyModuleArgs( /// the module in the same order as they were listed in the `insert*` array. /// /// The operation must be any of the module-like operations. -void hw::modifyModulePorts( - Operation *op, ArrayRef> insertInputs, - ArrayRef> insertOutputs, - ArrayRef removeInputs, ArrayRef removeOutputs, - Block *body) { +/// +/// This is marked deprecated as it's only used from HandshakeToHW and +/// PortConverter and is likely broken and not currently tested. Users of this +/// are still written dealing with input and output ports separately, which is +/// an old and broken style. +[[deprecated]] static void +modifyModulePorts(Operation *op, + ArrayRef> insertInputs, + ArrayRef> insertOutputs, + ArrayRef removeInputs, + ArrayRef removeOutputs, Block *body = nullptr) { auto moduleOp = cast(op); auto *context = moduleOp.getContext(); @@ -750,6 +746,13 @@ void HWModuleOp::build(OpBuilder &builder, OperationState &result, body->addArgument(type, loc); } + // Add result ports attribute. + auto unknownLocAttr = cast(unknownLoc); + SmallVector resultLocs; + for (auto port : ports.getOutputs()) + resultLocs.push_back(port.loc ? port.loc : unknownLocAttr); + result.addAttribute("result_locs", builder.getArrayAttr(resultLocs)); + if (shouldEnsureTerminator) HWModuleOp::ensureTerminator(*bodyRegion, builder, result.location); } @@ -784,8 +787,8 @@ void HWModuleOp::modifyPorts( ArrayRef> insertInputs, ArrayRef> insertOutputs, ArrayRef eraseInputs, ArrayRef eraseOutputs) { - hw::modifyModulePorts(*this, insertInputs, insertOutputs, eraseInputs, - eraseOutputs); + modifyModulePorts(*this, insertInputs, insertOutputs, eraseInputs, + eraseOutputs); } /// Return the name to use for the Verilog module that we're referencing @@ -813,6 +816,13 @@ void HWModuleExternOp::build(OpBuilder &builder, OperationState &result, buildModule(builder, result, name, ports, parameters, attributes, {}); + // Add the port locations. + LocationAttr unknownLoc = builder.getUnknownLoc(); + SmallVector portLocs; + for (auto elt : ports) + portLocs.push_back(elt.loc ? elt.loc : unknownLoc); + result.addAttribute("port_locs", builder.getArrayAttr(portLocs)); + if (!verilogName.empty()) result.addAttribute("verilogName", builder.getStringAttr(verilogName)); } @@ -829,8 +839,8 @@ void HWModuleExternOp::modifyPorts( ArrayRef> insertInputs, ArrayRef> insertOutputs, ArrayRef eraseInputs, ArrayRef eraseOutputs) { - hw::modifyModulePorts(*this, insertInputs, insertOutputs, eraseInputs, - eraseOutputs); + modifyModulePorts(*this, insertInputs, insertOutputs, eraseInputs, + eraseOutputs); } void HWModuleExternOp::appendOutputs( @@ -843,6 +853,13 @@ void HWModuleGeneratedOp::build(OpBuilder &builder, OperationState &result, ArrayRef attributes) { buildModule(builder, result, name, ports, parameters, attributes, {}); + // Add the port locations. + LocationAttr unknownLoc = builder.getUnknownLoc(); + SmallVector portLocs; + for (auto elt : ports) + portLocs.push_back(elt.loc ? elt.loc : unknownLoc); + result.addAttribute("port_locs", builder.getArrayAttr(portLocs)); + result.addAttribute("generatorKind", genKind); if (!verilogName.empty()) result.addAttribute("verilogName", builder.getStringAttr(verilogName)); @@ -861,8 +878,8 @@ void HWModuleGeneratedOp::modifyPorts( ArrayRef> insertInputs, ArrayRef> insertOutputs, ArrayRef eraseInputs, ArrayRef eraseOutputs) { - hw::modifyModulePorts(*this, insertInputs, insertOutputs, eraseInputs, - eraseOutputs); + modifyModulePorts(*this, insertInputs, insertOutputs, eraseInputs, + eraseOutputs); } void HWModuleGeneratedOp::appendOutputs( @@ -875,40 +892,12 @@ static bool hasAttribute(StringRef name, ArrayRef attrs) { return false; } -static void -addPortAttrsAndLocs(Builder &builder, OperationState &result, - SmallVectorImpl &ports, - StringAttr portAttrsName, StringAttr portLocsName) { - auto unknownLoc = builder.getUnknownLoc(); - auto nonEmptyAttrsFn = [](Attribute attr) { - return attr && !cast(attr).empty(); - }; - auto nonEmptyLocsFn = [unknownLoc](Attribute attr) { - return attr && cast(attr) != unknownLoc; - }; - - // Convert the specified array of dictionary attrs (which may have null - // entries) to an ArrayAttr of dictionaries. - SmallVector attrs; - SmallVector locs; - for (auto &port : ports) { - attrs.push_back(port.attrs ? port.attrs : builder.getDictionaryAttr({})); - locs.push_back(port.sourceLoc ? Location(*port.sourceLoc) : unknownLoc); - } - - // Add the attributes to the ports. - if (llvm::any_of(attrs, nonEmptyAttrsFn)) - result.addAttribute(portAttrsName, builder.getArrayAttr(attrs)); - - if (llvm::any_of(locs, nonEmptyLocsFn)) - result.addAttribute(portLocsName, builder.getArrayAttr(locs)); -} - template -static ParseResult parseHWModuleOp(OpAsmParser &parser, OperationState &result, - ExternModKind modKind = PlainMod) { +static ParseResult parseHWModuleOp(OpAsmParser &parser, + OperationState &result) { using namespace mlir::function_interface_impl; + auto builder = parser.getBuilder(); auto loc = parser.getCurrentLocation(); // Parse the visibility attribute. @@ -922,7 +911,7 @@ static ParseResult parseHWModuleOp(OpAsmParser &parser, OperationState &result, // Parse the generator information. FlatSymbolRefAttr kindAttr; - if (modKind == GenMod) { + if constexpr (std::is_same_v) { if (parser.parseComma() || parser.parseAttribute(kindAttr, "generatorKind", result.attributes)) { return failure(); @@ -950,10 +939,44 @@ static ParseResult parseHWModuleOp(OpAsmParser &parser, OperationState &result, result.addAttribute("parameters", parameters); result.addAttribute(ModuleTy::getModuleTypeAttrName(result.name), modType); - addPortAttrsAndLocs(parser.getBuilder(), result, ports, - ModuleTy::getPerPortAttrsAttrName(result.name), - ModuleTy::getPortLocsAttrName(result.name)); + // Convert the specified array of dictionary attrs (which may have null + // entries) to an ArrayAttr of dictionaries. + SmallVector attrs; + for (auto &port : ports) + attrs.push_back(port.attrs ? port.attrs : builder.getDictionaryAttr({})); + // Add the attributes to the ports. + auto nonEmptyAttrsFn = [](Attribute attr) { + return attr && !cast(attr).empty(); + }; + if (llvm::any_of(attrs, nonEmptyAttrsFn)) + result.addAttribute(ModuleTy::getPerPortAttrsAttrName(result.name), + builder.getArrayAttr(attrs)); + + // Add the port locations. + auto unknownLoc = builder.getUnknownLoc(); + auto nonEmptyLocsFn = [unknownLoc](Attribute attr) { + return attr && cast(attr) != unknownLoc; + }; + SmallVector locs; + StringAttr portLocsAttrName; + if constexpr (std::is_same_v) { + // Plain modules only store the output port locations, as the input port + // locations will be stored in the basic block arguments. + portLocsAttrName = ModuleTy::getResultLocsAttrName(result.name); + for (auto &port : ports) + if (port.direction == ModulePort::Direction::Output) + locs.push_back(port.sourceLoc ? Location(*port.sourceLoc) : unknownLoc); + } else { + // All other modules store all port locations in a single array. + portLocsAttrName = ModuleTy::getPortLocsAttrName(result.name); + for (auto &port : ports) + locs.push_back(port.sourceLoc ? Location(*port.sourceLoc) : unknownLoc); + } + if (llvm::any_of(locs, nonEmptyLocsFn)) + result.addAttribute(portLocsAttrName, builder.getArrayAttr(locs)); + + // Add the entry block arguments. SmallVector entryArgs; for (auto &port : ports) if (port.direction != ModulePort::Direction::Output) @@ -961,7 +984,7 @@ static ParseResult parseHWModuleOp(OpAsmParser &parser, OperationState &result, // Parse the optional function body. auto *body = result.addRegion(); - if (modKind == PlainMod) { + if (std::is_same_v) { if (parser.parseRegion(*body, entryArgs)) return failure(); @@ -976,12 +999,12 @@ ParseResult HWModuleOp::parse(OpAsmParser &parser, OperationState &result) { ParseResult HWModuleExternOp::parse(OpAsmParser &parser, OperationState &result) { - return parseHWModuleOp(parser, result, ExternMod); + return parseHWModuleOp(parser, result); } ParseResult HWModuleGeneratedOp::parse(OpAsmParser &parser, OperationState &result) { - return parseHWModuleOp(parser, result, GenMod); + return parseHWModuleOp(parser, result); } FunctionType getHWModuleOpType(Operation *op) { @@ -1011,12 +1034,15 @@ static void printModuleOp(OpAsmPrinter &p, ModuleTy mod) { // Print the parameter list if present. printOptionalParameterList(p, mod.getOperation(), mod.getParameters()); - module_like_impl::printModuleSignatureNew(p, mod.getOperation()); + module_like_impl::printModuleSignatureNew(p, mod); SmallVector omittedAttrs; if (isa(mod.getOperation())) omittedAttrs.push_back("generatorKind"); - omittedAttrs.push_back(mod.getPortLocsAttrName()); + if constexpr (std::is_same_v) + omittedAttrs.push_back(mod.getResultLocsAttrName()); + else + omittedAttrs.push_back(mod.getPortLocsAttrName()); omittedAttrs.push_back(mod.getModuleTypeAttrName()); omittedAttrs.push_back(mod.getPerPortAttrsAttrName()); omittedAttrs.push_back(mod.getParametersAttrName()); @@ -1109,16 +1135,6 @@ LogicalResult HWModuleOp::verify() { return emitOpError("entry block must have") << numInputs << " arguments to match module signature"; - // Verify that the block arguments match the op's attributes. - for (auto [arg, type, loc] : llvm::zip(getBodyBlock()->getArguments(), - getInputTypes(), getInputLocs())) { - if (arg.getType() != type) - return emitOpError("block argument types should match signature types"); - if (arg.getLoc() != loc.cast()) - return emitOpError( - "block argument locations should match signature locations"); - } - return success(); } @@ -1140,8 +1156,8 @@ HWModuleOp::insertInput(unsigned index, StringAttr name, Type ty) { port.name = nameAttr; port.dir = ModulePort::Direction::Input; port.type = ty; - hw::modifyModulePorts(getOperation(), {std::make_pair(index, port)}, {}, {}, - {}, body); + modifyModulePorts(getOperation(), {std::make_pair(index, port)}, {}, {}, {}, + body); // Add a new argument. return {nameAttr, body->getArgument(index)}; @@ -1162,8 +1178,8 @@ void HWModuleOp::insertOutputs(unsigned index, port.type = value.getType(); indexedNewPorts.emplace_back(index, port); } - hw::modifyModulePorts(getOperation(), {}, indexedNewPorts, {}, {}, - getBodyBlock()); + modifyModulePorts(getOperation(), {}, indexedNewPorts, {}, {}, + getBodyBlock()); // Rewrite the output op. for (auto &[name, value] : outputs) @@ -1200,7 +1216,26 @@ static SmallVector getAllPortLocs(ModTy module) { } SmallVector HWModuleOp::getAllPortLocs() { - return ::getAllPortLocs(*this); + SmallVector portLocs; + auto resultLocs = getResultLocsAttr(); + unsigned inputCount = 0; + auto modType = getModuleType(); + auto unknownLoc = UnknownLoc::get(getContext()); + auto *body = getBodyBlock(); + for (unsigned i = 0, e = getNumPorts(); i < e; ++i) { + if (modType.isOutput(i)) { + auto loc = resultLocs + ? cast( + resultLocs.getValue()[portLocs.size() - inputCount]) + : unknownLoc; + portLocs.push_back(loc); + } else { + auto loc = body ? body->getArgument(inputCount).getLoc() : unknownLoc; + portLocs.push_back(loc); + ++inputCount; + } + } + return portLocs; } SmallVector HWModuleExternOp::getAllPortLocs() { @@ -1211,22 +1246,26 @@ SmallVector HWModuleGeneratedOp::getAllPortLocs() { return ::getAllPortLocs(*this); } -template -static void setAllPortLocs(ArrayRef locs, ModTy module) { - std::vector nLocs(locs.begin(), locs.end()); - module.setPortLocsAttr(ArrayAttr::get(module.getContext(), nLocs)); -} - -void HWModuleOp::setAllPortLocs(ArrayRef locs) { - ::setAllPortLocs(locs, *this); +void HWModuleOp::setAllPortLocsAttrs(ArrayRef locs) { + SmallVector resultLocs; + unsigned inputCount = 0; + auto modType = getModuleType(); + auto *body = getBodyBlock(); + for (unsigned i = 0, e = getNumPorts(); i < e; ++i) { + if (modType.isOutput(i)) + resultLocs.push_back(locs[i]); + else + body->getArgument(inputCount++).setLoc(cast(locs[i])); + } + setResultLocsAttr(ArrayAttr::get(getContext(), resultLocs)); } -void HWModuleExternOp::setAllPortLocs(ArrayRef locs) { - ::setAllPortLocs(locs, *this); +void HWModuleExternOp::setAllPortLocsAttrs(ArrayRef locs) { + setPortLocsAttr(ArrayAttr::get(getContext(), locs)); } -void HWModuleGeneratedOp::setAllPortLocs(ArrayRef locs) { - ::setAllPortLocs(locs, *this); +void HWModuleGeneratedOp::setAllPortLocsAttrs(ArrayRef locs) { + setPortLocsAttr(ArrayAttr::get(getContext(), locs)); } template @@ -1255,25 +1294,25 @@ void HWModuleGeneratedOp::setAllPortNames(ArrayRef names) { ::setAllPortNames(names, *this); } -SmallVector HWModuleOp::getAllPortAttrs() { +ArrayRef HWModuleOp::getAllPortAttrs() { auto attrs = getPerPortAttrs(); if (attrs && !attrs->empty()) - return {attrs->getValue().begin(), attrs->getValue().end()}; - return SmallVector(getNumPorts()); + return attrs->getValue(); + return {}; } -SmallVector HWModuleExternOp::getAllPortAttrs() { +ArrayRef HWModuleExternOp::getAllPortAttrs() { auto attrs = getPerPortAttrs(); if (attrs && !attrs->empty()) - return {attrs->getValue().begin(), attrs->getValue().end()}; - return SmallVector(getNumPorts()); + return attrs->getValue(); + return {}; } -SmallVector HWModuleGeneratedOp::getAllPortAttrs() { +ArrayRef HWModuleGeneratedOp::getAllPortAttrs() { auto attrs = getPerPortAttrs(); if (attrs && !attrs->empty()) - return {attrs->getValue().begin(), attrs->getValue().end()}; - return SmallVector(getNumPorts()); + return attrs->getValue(); + return {}; } void HWModuleOp::setAllPortAttrs(ArrayRef attrs) { @@ -1577,10 +1616,11 @@ LogicalResult InstanceChoiceOp::verify() { ParseResult InstanceChoiceOp::parse(OpAsmParser &parser, OperationState &result) { + StringAttr optionNameAttr; StringAttr instanceNameAttr; InnerSymAttr innerSym; SmallVector moduleNames; - SmallVector targetNames; + SmallVector caseNames; SmallVector inputsOperands; SmallVector inputsTypes, allResultTypes; ArrayAttr argNames, resultNames, parameters; @@ -1598,6 +1638,11 @@ ParseResult InstanceChoiceOp::parse(OpAsmParser &parser, result.addAttribute(InnerSymbolTable::getInnerSymbolAttrName(), innerSym); } + if (parser.parseKeyword("option") || + parser.parseAttribute(optionNameAttr, noneType, "optionName", + result.attributes)) + return failure(); + FlatSymbolRefAttr defaultModuleName; if (parser.parseAttribute(defaultModuleName)) return failure(); @@ -1610,7 +1655,7 @@ ParseResult InstanceChoiceOp::parse(OpAsmParser &parser, parser.parseOptionalKeyword("if") || parser.parseAttribute(targetName)) return failure(); moduleNames.push_back(moduleName); - targetNames.push_back(targetName); + caseNames.push_back(targetName); } llvm::SMLoc parametersLoc, inputsOperandsLoc; @@ -1627,8 +1672,8 @@ ParseResult InstanceChoiceOp::parse(OpAsmParser &parser, result.addAttribute("moduleNames", ArrayAttr::get(parser.getContext(), moduleNames)); - result.addAttribute("targetNames", - ArrayAttr::get(parser.getContext(), targetNames)); + result.addAttribute("caseNames", + ArrayAttr::get(parser.getContext(), caseNames)); result.addAttribute("argNames", argNames); result.addAttribute("resultNames", resultNames); result.addAttribute("parameters", parameters); @@ -1643,18 +1688,18 @@ void InstanceChoiceOp::print(OpAsmPrinter &p) { p << " sym "; attr.print(p); } - p << ' '; + p << " option " << getOptionNameAttr() << ' '; auto moduleNames = getModuleNamesAttr(); - auto targetNames = getTargetNamesAttr(); - assert(moduleNames.size() == targetNames.size() + 1); + auto caseNames = getCaseNamesAttr(); + assert(moduleNames.size() == caseNames.size() + 1); p.printAttributeWithoutType(moduleNames[0]); - for (size_t i = 0, n = targetNames.size(); i < n; ++i) { + for (size_t i = 0, n = caseNames.size(); i < n; ++i) { p << " or "; p.printAttributeWithoutType(moduleNames[i + 1]); p << " if "; - p.printAttributeWithoutType(targetNames[i]); + p.printAttributeWithoutType(caseNames[i]); } printOptionalParameterList(p, *this, getParameters()); @@ -1667,8 +1712,8 @@ void InstanceChoiceOp::print(OpAsmPrinter &p) { (*this)->getAttrs(), /*elidedAttrs=*/{"instanceName", InnerSymbolTable::getInnerSymbolAttrName(), - "moduleNames", "targetNames", "argNames", "resultNames", - "parameters"}); + "moduleNames", "caseNames", "argNames", "resultNames", + "parameters", "optionName"}); } ArrayAttr InstanceChoiceOp::getReferencedModuleNamesAttr() { diff --git a/lib/Dialect/HW/HWReductions.cpp b/lib/Dialect/HW/HWReductions.cpp index 1051cf9c9416..2363b92c2e6d 100644 --- a/lib/Dialect/HW/HWReductions.cpp +++ b/lib/Dialect/HW/HWReductions.cpp @@ -35,10 +35,12 @@ struct ModuleSizeCache { module->walk([&](Operation *op) { size += 1; if (auto instOp = dyn_cast(op)) { - auto *node = instanceGraph.lookup(instOp.getReferencedModuleNameAttr()); - if (auto instModule = - dyn_cast_or_null(*node->getModule())) - size += getModuleSize(instModule, instanceGraph); + for (auto moduleName : instOp.getReferencedModuleNamesAttr()) { + auto *node = instanceGraph.lookup(moduleName.cast()); + if (auto instModule = + dyn_cast_or_null(*node->getModule())) + size += getModuleSize(instModule, instanceGraph); + } } }); moduleSizes.insert({module, size}); diff --git a/lib/Dialect/HW/HWTypes.cpp b/lib/Dialect/HW/HWTypes.cpp index 77b080cc3ee3..2b52e9dd9245 100644 --- a/lib/Dialect/HW/HWTypes.cpp +++ b/lib/Dialect/HW/HWTypes.cpp @@ -169,10 +169,10 @@ static ParseResult parseHWElementType(Type &result, AsmParser &p) { auto typeString = StringRef(curPtr, fullString.size() - (curPtr - fullString.data())); - if (typeString.startswith("array<") || typeString.startswith("inout<") || - typeString.startswith("uarray<") || typeString.startswith("struct<") || - typeString.startswith("typealias<") || typeString.startswith("int<") || - typeString.startswith("enum<")) { + if (typeString.starts_with("array<") || typeString.starts_with("inout<") || + typeString.starts_with("uarray<") || typeString.starts_with("struct<") || + typeString.starts_with("typealias<") || typeString.starts_with("int<") || + typeString.starts_with("enum<")) { llvm::StringRef mnemonic; auto parseResult = generatedTypeParser(p, &mnemonic, result); return parseResult.has_value() ? success() : failure(); @@ -918,22 +918,6 @@ Type ModuleType::getOutputType(size_t idx) { return getPorts()[getPortIdForOutputId(idx)].type; } -SmallVector ModuleType::getInputNamesStr() { - SmallVector retval; - for (auto &p : getPorts()) - if (p.dir != ModulePort::Direction::Output) - retval.push_back(p.name); - return retval; -} - -SmallVector ModuleType::getOutputNamesStr() { - SmallVector retval; - for (auto &p : getPorts()) - if (p.dir == ModulePort::Direction::Output) - retval.push_back(p.name); - return retval; -} - SmallVector ModuleType::getInputNames() { SmallVector retval; for (auto &p : getPorts()) diff --git a/lib/Dialect/HW/ModuleImplementation.cpp b/lib/Dialect/HW/ModuleImplementation.cpp index af97055d659a..cd371623ba47 100644 --- a/lib/Dialect/HW/ModuleImplementation.cpp +++ b/lib/Dialect/HW/ModuleImplementation.cpp @@ -392,18 +392,18 @@ static const char *directionAsString(ModulePort::Direction dir) { return "unknown"; } -void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p, Operation *op) { +void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p, + HWModuleLike op) { - Region &body = op->getRegion(0); + Region &body = op.getModuleBody(); bool isExternal = body.empty(); SmallString<32> resultNameStr; mlir::OpPrintingFlags flags; unsigned curArg = 0; - auto typeAttr = op->getAttrOfType("module_type"); - auto modType = cast(typeAttr.getValue()); - auto portAttrs = op->getAttrOfType("per_port_attrs"); - auto locAttrs = op->getAttrOfType("port_locs"); + auto modType = op.getHWModuleType(); + auto portAttrs = op.getAllPortAttrs(); + auto locAttrs = op.getAllPortLocs(); p << '('; for (auto [i, port] : llvm::enumerate(modType.getPorts())) { @@ -433,17 +433,18 @@ void module_like_impl::printModuleSignatureNew(OpAsmPrinter &p, Operation *op) { } p << " : "; p.printType(port.type); - if (portAttrs && !portAttrs.empty()) + if (!portAttrs.empty()) if (auto attr = dyn_cast(portAttrs[i])) p.printOptionalAttrDict(attr.getValue()); // TODO: `printOptionalLocationSpecifier` will emit aliases for locations, // even if they are not printed. This will have to be fixed upstream. For // now, use what was specified on the command line. - if (flags.shouldPrintDebugInfo() && locAttrs) - if (auto loc = locAttrs[i]) - if (!isa(loc)) - p.printOptionalLocationSpecifier(cast(loc)); + if (flags.shouldPrintDebugInfo()) { + auto loc = locAttrs[i]; + if (!isa(loc)) + p.printOptionalLocationSpecifier(cast(loc)); + } } p << ')'; diff --git a/lib/Dialect/HW/Transforms/FlattenIO.cpp b/lib/Dialect/HW/Transforms/FlattenIO.cpp index 9fee8cb10a9b..5b5a40bf25b3 100644 --- a/lib/Dialect/HW/Transforms/FlattenIO.cpp +++ b/lib/Dialect/HW/Transforms/FlattenIO.cpp @@ -299,13 +299,11 @@ static void updateModulePortNames(ModTy op, hw::ModuleType oldModType, op.setAllPortNames(newNames); } -static llvm::SmallVector +static llvm::SmallVector updateLocAttribute(DenseMap &structMap, - ArrayAttr oldLocs) { - llvm::SmallVector newLocs; - if (!oldLocs) - return newLocs; - for (auto [i, oldLoc] : llvm::enumerate(oldLocs.getAsRange())) { + SmallVectorImpl &oldLocs) { + llvm::SmallVector newLocs; + for (auto [i, oldLoc] : llvm::enumerate(oldLocs)) { // Was this arg/res index a struct? auto it = structMap.find(i); if (it == structMap.end()) { @@ -400,8 +398,8 @@ static LogicalResult flattenOpsOfType(ModuleOp module, bool recursive, }); }); - DenseMap oldArgNames, oldResNames, oldArgLocs, - oldResLocs; + DenseMap oldArgNames, oldResNames; + DenseMap> oldArgLocs, oldResLocs; DenseMap oldModTypes; for (auto op : module.getOps()) { @@ -409,8 +407,8 @@ static LogicalResult flattenOpsOfType(ModuleOp module, bool recursive, oldArgNames[op] = ArrayAttr::get(module.getContext(), op.getInputNames()); oldResNames[op] = ArrayAttr::get(module.getContext(), op.getOutputNames()); - oldArgLocs[op] = op.getInputLocsAttr(); - oldResLocs[op] = op.getOutputLocsAttr(); + oldArgLocs[op] = op.getInputLocs(); + oldResLocs[op] = op.getOutputLocs(); } // Signature conversion and legalization patterns. @@ -426,7 +424,7 @@ static LogicalResult flattenOpsOfType(ModuleOp module, bool recursive, auto newArgLocs = updateLocAttribute(ioInfo.argStructs, oldArgLocs[op]); auto newResLocs = updateLocAttribute(ioInfo.resStructs, oldResLocs[op]); newArgLocs.append(newResLocs.begin(), newResLocs.end()); - op.setPortLocsAttr(ArrayAttr::get(op.getContext(), newArgLocs)); + op.setAllPortLocs(newArgLocs); updateBlockLocations(op, ioInfo.argStructs); } @@ -445,8 +443,8 @@ static LogicalResult flattenOpsOfType(ModuleOp module, bool recursive, ArrayAttr::get(module.getContext(), targetModule.getInputNames()); oldResNames[targetModule] = ArrayAttr::get(module.getContext(), targetModule.getOutputNames()); - oldArgLocs[targetModule] = targetModule.getInputLocsAttr(); - oldResLocs[targetModule] = targetModule.getOutputLocsAttr(); + oldArgLocs[targetModule] = targetModule.getInputLocs(); + oldResLocs[targetModule] = targetModule.getOutputLocs(); } else ioInfo = ioInfoMap[targetModule]; diff --git a/lib/Dialect/HW/Transforms/HWSpecialize.cpp b/lib/Dialect/HW/Transforms/HWSpecialize.cpp index 8845393cebba..c511d87197df 100644 --- a/lib/Dialect/HW/Transforms/HWSpecialize.cpp +++ b/lib/Dialect/HW/Transforms/HWSpecialize.cpp @@ -167,7 +167,7 @@ struct ParametricTypeConversionPattern : public ConversionPattern { llvm::SmallVector convertedOperands; // Update the result types of the operation bool ok = true; - rewriter.updateRootInPlace(op, [&]() { + rewriter.modifyOpInPlace(op, [&]() { // Mutate result types for (auto it : llvm::enumerate(op->getResultTypes())) { FailureOr res = diff --git a/lib/Dialect/Handshake/HandshakeOps.cpp b/lib/Dialect/Handshake/HandshakeOps.cpp index bcb31f8733a1..f65280f7b685 100644 --- a/lib/Dialect/Handshake/HandshakeOps.cpp +++ b/lib/Dialect/Handshake/HandshakeOps.cpp @@ -207,7 +207,7 @@ struct EliminateUnusedForkResultsPattern : mlir::OpRewritePattern { auto operand = op.getOperand(); auto newFork = rewriter.create( op.getLoc(), operand, op.getNumResults() - unusedIndexes.size()); - rewriter.updateRootInPlace(op, [&] { + rewriter.modifyOpInPlace(op, [&] { unsigned i = 0; for (auto oldRes : llvm::enumerate(op.getResults())) if (unusedIndexes.count(oldRes.index()) == 0) @@ -232,7 +232,7 @@ struct EliminateForkToForkPattern : mlir::OpRewritePattern { /// on if op is the single user of the value), but we'll let /// EliminateUnusedForkResultsPattern apply in that case. unsigned totalNumOuts = op.getSize() + parentForkOp.getSize(); - rewriter.updateRootInPlace(parentForkOp, [&] { + rewriter.modifyOpInPlace(parentForkOp, [&] { /// Create a new parent fork op which produces all of the fork outputs and /// replace all of the uses of the old results. auto newParentForkOp = rewriter.create( @@ -364,7 +364,7 @@ struct EliminateCBranchIntoMuxPattern : OpRewritePattern { if (!secondParentCBranch || firstParentCBranch != secondParentCBranch) return failure(); - rewriter.updateRootInPlace(firstParentCBranch, [&] { + rewriter.modifyOpInPlace(firstParentCBranch, [&] { // Replace uses of the mux's output with cbranch's data input rewriter.replaceOp(op, firstParentCBranch.getDataOperand()); }); @@ -738,7 +738,7 @@ LogicalResult EliminateSimpleControlMergesPattern::matchAndRewrite( for (auto &use : llvm::make_early_inc_range(dataResult.getUses())) { auto *user = use.getOwner(); - rewriter.updateRootInPlace( + rewriter.modifyOpInPlace( user, [&]() { user->setOperand(use.getOperandNumber(), merge); }); } diff --git a/lib/Dialect/Handshake/Transforms/LowerExtmemToHW.cpp b/lib/Dialect/Handshake/Transforms/LowerExtmemToHW.cpp index fcd4116d45fa..1046975cc248 100644 --- a/lib/Dialect/Handshake/Transforms/LowerExtmemToHW.cpp +++ b/lib/Dialect/Handshake/Transforms/LowerExtmemToHW.cpp @@ -208,26 +208,26 @@ LogicalResult HandshakeLowerExtmemToHWPass::wrapESI( // Load ports: for (unsigned i = 0; i < memType.loadPorts; ++i) { - auto reqPack = b.create(loc, readPortInfo.type, - (Value)backedges[resIdx]); - b.create( - loc, readPortInfo.port, reqPack.getBundle(), + auto req = b.create( + loc, readPortInfo.type, readPortInfo.port, esi::AppIDAttr::get(ctx, b.getStringAttr("load"), {})); + auto reqUnpack = b.create( + loc, req.getToClient(), ValueRange{backedges[resIdx]}); instanceArgsFromThisMem.push_back( - reqPack.getFromChannels() + reqUnpack.getToChannels() [esi::RandomAccessMemoryDeclOp::RespDirChannelIdx]); ++resIdx; } // Store ports: for (unsigned i = 0; i < memType.storePorts; ++i) { - auto reqPack = b.create(loc, writePortInfo.type, - (Value)backedges[resIdx]); - b.create( - loc, writePortInfo.port, reqPack.getBundle(), + auto req = b.create( + loc, writePortInfo.type, writePortInfo.port, esi::AppIDAttr::get(ctx, b.getStringAttr("store"), {})); + auto reqUnpack = b.create( + loc, req.getToClient(), ValueRange{backedges[resIdx]}); instanceArgsFromThisMem.push_back( - reqPack.getFromChannels() + reqUnpack.getToChannels() [esi::RandomAccessMemoryDeclOp::RespDirChannelIdx]); ++resIdx; } diff --git a/lib/Dialect/Ibis/Transforms/IbisCallPrep.cpp b/lib/Dialect/Ibis/Transforms/IbisCallPrep.cpp index 03c798c18a9b..dd236798ff04 100644 --- a/lib/Dialect/Ibis/Transforms/IbisCallPrep.cpp +++ b/lib/Dialect/Ibis/Transforms/IbisCallPrep.cpp @@ -166,7 +166,7 @@ void MergeCallArgs::rewrite(CallOp call, OpAdaptor adaptor, method.getMethodName().getValue())); // Update the call to use just the new struct. - rewriter.updateRootInPlace(call, [&]() { + rewriter.modifyOpInPlace(call, [&]() { call.getOperandsMutable().clear(); call.getOperandsMutable().append(newArg.getResult()); }); diff --git a/lib/Dialect/LTL/LTLFolds.cpp b/lib/Dialect/LTL/LTLFolds.cpp index 3bfec001fe49..55455463097c 100644 --- a/lib/Dialect/LTL/LTLFolds.cpp +++ b/lib/Dialect/LTL/LTLFolds.cpp @@ -61,7 +61,8 @@ namespace patterns { OpFoldResult DelayOp::fold(FoldAdaptor adaptor) { // delay(s, 0, 0) -> s - if (adaptor.getDelay() == 0 && adaptor.getLength() == 0) + if (adaptor.getDelay() == 0 && adaptor.getLength() == 0 && + !isa(getResult().getType())) return getInput(); return {}; diff --git a/lib/Dialect/MSFT/Transforms/MSFTLowerConstructs.cpp b/lib/Dialect/MSFT/Transforms/MSFTLowerConstructs.cpp index f6818d6b581f..d660c69256e3 100644 --- a/lib/Dialect/MSFT/Transforms/MSFTLowerConstructs.cpp +++ b/lib/Dialect/MSFT/Transforms/MSFTLowerConstructs.cpp @@ -157,35 +157,6 @@ struct SystolicArrayOpLowering : public OpConversionPattern { }; } // anonymous namespace -namespace { -/// Lower MSFT's ChannelOp to a set of registers. -struct ChannelOpLowering : public OpConversionPattern { -public: - ChannelOpLowering(MLIRContext *ctxt, LowerConstructsPass &pass) - : OpConversionPattern(ctxt), pass(pass) {} - - LogicalResult - matchAndRewrite(ChannelOp chan, OpAdaptor adaptor, - ConversionPatternRewriter &rewriter) const final { - Location loc = chan.getLoc(); - Operation *mod = chan->getParentOfType(); - assert(mod && "ChannelOp must be contained by module"); - Namespace &ns = pass.getNamespaceFor(mod); - Value clk = chan.getClk(); - Value v = chan.getInput(); - for (uint64_t stageNum = 0, e = chan.getDefaultStages(); stageNum < e; - ++stageNum) - v = rewriter.create(loc, v, clk, - ns.newName(chan.getSymName())); - rewriter.replaceOp(chan, {v}); - return success(); - } - -protected: - LowerConstructsPass &pass; -}; -} // namespace - void LowerConstructsPass::runOnOperation() { auto top = getOperation(); auto *ctxt = &getContext(); @@ -196,8 +167,6 @@ void LowerConstructsPass::runOnOperation() { RewritePatternSet patterns(ctxt); patterns.insert(ctxt); target.addIllegalOp(); - patterns.insert(ctxt, *this); - target.addIllegalOp(); if (failed(mlir::applyPartialConversion(top, target, std::move(patterns)))) signalPassFailure(); diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 4e6abd44907b..fd5b4049a5da 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -36,6 +36,13 @@ LogicalResult InstanceOp::verifySymbolUses(SymbolTableCollection &symbolTable) { return success(); } +//===----------------------------------------------------------------------===// +// PortOp +//===----------------------------------------------------------------------===// +void PortOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + setNameFn(getResult(), getName()); +} + //===----------------------------------------------------------------------===// // VariableOp //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Moore/Transforms/MooreDeclarations.cpp b/lib/Dialect/Moore/Transforms/MooreDeclarations.cpp index 65735f7ee888..8781ab345de5 100644 --- a/lib/Dialect/Moore/Transforms/MooreDeclarations.cpp +++ b/lib/Dialect/Moore/Transforms/MooreDeclarations.cpp @@ -25,29 +25,47 @@ struct MooreDeclarationsPass }; } // namespace +void Declaration::addValue(Operation *op) { + TypeSwitch(op) + // TODO: The loop, if, case, and other statements. + .Case([&](auto op) { + auto operandIt = op.getOperands(); + auto value = operandIt.empty() ? nullptr : op.getOperand(0); + assignmentChains[op] = value; + }) + .Case([&](auto op) { + auto destOp = op.getOperand(0).getDefiningOp(); + auto srcValue = op.getOperand(1); + assignmentChains[destOp] = srcValue; + }) + .Case([&](auto op) { + SmallVector assignments; + + if (op.getDirection() == Direction::Out) { + auto users = op.getResult().getUsers(); + if (!users.empty()) { + for (auto *user : users) + assignments.push_back(user); + + portChains[op] = assignments.front(); + } + } + }) + .Case([&](auto op) { + for (auto &nestOp : op.getOps()) { + addValue(&nestOp); + } + }); +} + extern Declaration moore::decl; void MooreDeclarationsPass::runOnOperation() { - - getOperation()->walk([&](mlir::func::FuncOp moduleOp) { + getOperation()->walk([&](SVModuleOp moduleOp) { for (auto &op : moduleOp.getOps()) { - - TypeSwitch(&op) - // .Case([&](auto &op) { decl.addValue(op, nullptr); }) - .Case([&](auto &op) { - auto operandIt = op.getOperands(); - auto value = operandIt.empty() ? nullptr : op.getOperand(0); - decl.addValue(op, value); - }) - .Case([&](auto &op) { - auto destOp = op.getOperand(0).getDefiningOp(); - auto srcValue = op.getOperand(1); - decl.addValue(destOp, srcValue); - decl.addIdentifier(op, true); - }); + decl.addValue(&op); }; return WalkResult::advance(); }); - // markAllAnalysesPreserved(); } std::unique_ptr circt::moore::createMooreDeclarationsPass() { diff --git a/lib/Dialect/OM/Evaluator/Evaluator.cpp b/lib/Dialect/OM/Evaluator/Evaluator.cpp index 9794fedc01bf..61573c16c360 100644 --- a/lib/Dialect/OM/Evaluator/Evaluator.cpp +++ b/lib/Dialect/OM/Evaluator/Evaluator.cpp @@ -56,9 +56,7 @@ LogicalResult circt::om::evaluator::EvaluatorValue::finalize() { Type circt::om::evaluator::EvaluatorValue::getType() const { return llvm::TypeSwitch(this) - .Case([](auto *attr) -> Type { - return cast(attr->getAttr()).getType(); - }) + .Case([](auto *attr) -> Type { return attr->getType(); }) .Case([](auto *object) { return object->getObjectType(); }) .Case([](auto *list) { return list->getListType(); }) .Case([](auto *map) { return map->getMapType(); }) @@ -130,6 +128,14 @@ FailureOr circt::om::Evaluator::getOrCreateValue( .Case([&](ConstantOp op) { return evaluateConstant(op, actualParams, loc); }) + .Case([&](IntegerBinaryArithmeticOp op) { + // Create a partially evaluated AttributeValue of + // om::IntegerType in case we need to delay evaluation. + evaluator::EvaluatorValuePtr result = + std::make_shared( + op.getResult().getType(), loc); + return success(result); + }) .Case([&](auto op) { // Create a reference value since the value pointed by object // field op is not created yet. @@ -249,7 +255,7 @@ circt::om::Evaluator::evaluateObjectInstance(StringAttr className, } for (auto field : cls.getOps()) { - StringAttr name = field.getSymNameAttr(); + StringAttr name = field.getNameAttr(); Value value = field.getValue(); FailureOr result = evaluateValue(value, actualParams, field.getLoc()); @@ -339,6 +345,9 @@ circt::om::Evaluator::evaluateValue(Value value, ActualParameters actualParams, .Case([&](ConstantOp op) { return evaluateConstant(op, actualParams, loc); }) + .Case([&](IntegerBinaryArithmeticOp op) { + return evaluateIntegerBinaryArithmetic(op, actualParams, loc); + }) .Case([&](ObjectOp op) { return evaluateObjectInstance(op, actualParams); }) @@ -394,6 +403,75 @@ circt::om::Evaluator::evaluateConstant(ConstantOp op, op.getValue(), loc)); } +// Evaluator dispatch function for integer binary arithmetic. +FailureOr +circt::om::Evaluator::evaluateIntegerBinaryArithmetic( + IntegerBinaryArithmeticOp op, ActualParameters actualParams, Location loc) { + // Get the op's EvaluatorValue handle, in case it hasn't been evaluated yet. + auto handle = getOrCreateValue(op.getResult(), actualParams, loc); + + // If it's fully evaluated, we can return it. + if (handle.value()->isFullyEvaluated()) + return handle; + + // Evaluate operands if necessary, and return the partially evaluated value if + // they aren't ready. + auto lhsResult = evaluateValue(op.getLhs(), actualParams, loc); + if (failed(lhsResult)) + return lhsResult; + if (!lhsResult.value()->isFullyEvaluated()) + return handle; + + auto rhsResult = evaluateValue(op.getRhs(), actualParams, loc); + if (failed(rhsResult)) + return rhsResult; + if (!rhsResult.value()->isFullyEvaluated()) + return handle; + + // Extract the integer attributes. + auto extractAttr = [](evaluator::EvaluatorValue *value) { + return std::move( + llvm::TypeSwitch(value) + .Case([](evaluator::AttributeValue *val) { + return val->getAs(); + }) + .Case([](evaluator::ReferenceValue *val) { + return cast( + val->getStrippedValue()->get()) + ->getAs(); + })); + }; + + om::IntegerAttr lhs = extractAttr(lhsResult.value().get()); + om::IntegerAttr rhs = extractAttr(rhsResult.value().get()); + assert(lhs && rhs && + "expected om::IntegerAttr for IntegerBinaryArithmeticOp operands"); + + // Perform arbitrary precision signed integer binary arithmetic. + FailureOr result = op.evaluateIntegerOperation( + lhs.getValue().getAPSInt(), rhs.getValue().getAPSInt()); + + if (failed(result)) + return op->emitError("failed to evaluate integer operation"); + + // Package the result as a new om::IntegerAttr. + MLIRContext *ctx = op->getContext(); + auto resultAttr = + om::IntegerAttr::get(ctx, mlir::IntegerAttr::get(ctx, result.value())); + + // Finalize the op result value. + auto *handleValue = cast(handle.value().get()); + auto resultStatus = handleValue->setAttr(resultAttr); + if (failed(resultStatus)) + return resultStatus; + + auto finalizeStatus = handleValue->finalize(); + if (failed(finalizeStatus)) + return finalizeStatus; + + return handle; +} + /// Evaluator dispatch function for Object instances. FailureOr circt::om::Evaluator::createParametersFromOperands( @@ -778,3 +856,27 @@ void evaluator::PathValue::setBasepath(const BasePathValue &basepath) { path = PathAttr::get(path.getContext(), newPath); markFullyEvaluated(); } + +//===----------------------------------------------------------------------===// +// AttributeValue +//===----------------------------------------------------------------------===// + +LogicalResult circt::om::evaluator::AttributeValue::setAttr(Attribute attr) { + if (cast(attr).getType() != this->type) + return mlir::emitError(getLoc(), "cannot set AttributeValue of type ") + << this->type << " to Attribute " << attr; + if (isFullyEvaluated()) + return mlir::emitError( + getLoc(), + "cannot set AttributeValue that has already been fully evaluated"); + this->attr = attr; + markFullyEvaluated(); + return success(); +} + +LogicalResult circt::om::evaluator::AttributeValue::finalizeImpl() { + if (!isFullyEvaluated()) + return mlir::emitError( + getLoc(), "cannot finalize AttributeValue that is not fully evaluated"); + return success(); +} diff --git a/lib/Dialect/OM/OMOps.cpp b/lib/Dialect/OM/OMOps.cpp index 71bb9696e118..803617233a56 100644 --- a/lib/Dialect/OM/OMOps.cpp +++ b/lib/Dialect/OM/OMOps.cpp @@ -349,12 +349,10 @@ circt::om::ObjectFieldOp::verifySymbolUses(SymbolTableCollection &symbolTable) { // Verify the field exists on the ClassOp. auto field = fields[i]; ClassFieldLike fieldDef; - classDef.walk([&](SymbolOpInterface symbol) { - if (auto fieldLike = dyn_cast(symbol.getOperation())) { - if (symbol.getNameAttr() == field.getAttr()) { - fieldDef = fieldLike; - return WalkResult::interrupt(); - } + classDef.walk([&](ClassFieldLike fieldLike) { + if (fieldLike.getNameAttr() == field.getAttr()) { + fieldDef = fieldLike; + return WalkResult::interrupt(); } return WalkResult::advance(); }); @@ -537,6 +535,42 @@ PathCreateOp::verifySymbolUses(SymbolTableCollection &symbolTable) { return success(); } +//===----------------------------------------------------------------------===// +// IntegerAddOp +//===----------------------------------------------------------------------===// + +FailureOr +IntegerAddOp::evaluateIntegerOperation(const llvm::APSInt &lhs, + const llvm::APSInt &rhs) { + return success(lhs + rhs); +} + +//===----------------------------------------------------------------------===// +// IntegerMulOp +//===----------------------------------------------------------------------===// + +FailureOr +IntegerMulOp::evaluateIntegerOperation(const llvm::APSInt &lhs, + const llvm::APSInt &rhs) { + return success(lhs * rhs); +} + +//===----------------------------------------------------------------------===// +// IntegerShrOp +//===----------------------------------------------------------------------===// + +FailureOr +IntegerShrOp::evaluateIntegerOperation(const llvm::APSInt &lhs, + const llvm::APSInt &rhs) { + // Check non-negative constraint from operation semantics. + if (!rhs.isNonNegative()) + return emitOpError("shift amount must be non-negative"); + // Check size constraint from implementation detail of using getExtValue. + if (!rhs.isRepresentableByInt64()) + return emitOpError("shift amount must be representable in 64 bits"); + return success(lhs >> rhs.getExtValue()); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/OM/Transforms/FreezePaths.cpp b/lib/Dialect/OM/Transforms/FreezePaths.cpp index 3a1733094c05..f832ef7675d7 100644 --- a/lib/Dialect/OM/Transforms/FreezePaths.cpp +++ b/lib/Dialect/OM/Transforms/FreezePaths.cpp @@ -148,7 +148,11 @@ LogicalResult PathVisitor::processPath(Location loc, hw::HierPathOp hierPathOp, // If this is our inner ref pair: [Foo::bar] // if "bar" is an instance, modules = [Foo::bar], bottomModule = Bar. // if "bar" is a wire, modules = [], bottomModule = Foo, component = bar. - if (auto inst = dyn_cast(op)) { + if (isa(op)) { + // TODO: add support for instance choices. + auto inst = dyn_cast(op); + if (!inst) + return op->emitError("unsupported instance operation"); // We are targeting an instance. modules.emplace_back(currentModule, verilogName); bottomModule = inst.getReferencedModuleNameAttr(); diff --git a/lib/Dialect/Pipeline/PipelineOps.cpp b/lib/Dialect/Pipeline/PipelineOps.cpp index fe5be7c5eecb..2535c90cb1c2 100644 --- a/lib/Dialect/Pipeline/PipelineOps.cpp +++ b/lib/Dialect/Pipeline/PipelineOps.cpp @@ -616,8 +616,7 @@ LogicalResult ScheduledPipelineOp::verify() { } StageKind ScheduledPipelineOp::getStageKind(size_t stageIndex) { - size_t nStages = getNumStages(); - assert(stageIndex < nStages && "invalid stage index"); + assert(stageIndex < getNumStages() && "invalid stage index"); if (!hasStall()) return StageKind::Continuous; diff --git a/lib/Dialect/SV/SVOps.cpp b/lib/Dialect/SV/SVOps.cpp index df46d6483f0c..a0a9c66fd2c1 100644 --- a/lib/Dialect/SV/SVOps.cpp +++ b/lib/Dialect/SV/SVOps.cpp @@ -105,7 +105,7 @@ getVerbatimExprAsmResultNames(Operation *op, auto isOkCharacter = [](char c) { return llvm::isAlnum(c) || c == '_'; }; auto name = op->getAttrOfType("format_string").getValue(); // Ignore a leading ` in macro name. - if (name.startswith("`")) + if (name.starts_with("`")) name = name.drop_front(); name = name.take_while(isOkCharacter); if (!name.empty()) @@ -1018,14 +1018,14 @@ LogicalResult CaseOp::canonicalize(CaseOp op, PatternRewriter &rewriter) { if (op.getCaseStyle() == CaseStmtType::CaseXStmt) { if (noXZ) { - rewriter.updateRootInPlace(op, [&]() { + rewriter.modifyOpInPlace(op, [&]() { op.setCaseStyleAttr( CaseStmtTypeAttr::get(op.getContext(), CaseStmtType::CaseStmt)); }); return success(); } if (noX) { - rewriter.updateRootInPlace(op, [&]() { + rewriter.modifyOpInPlace(op, [&]() { op.setCaseStyleAttr( CaseStmtTypeAttr::get(op.getContext(), CaseStmtType::CaseZStmt)); }); @@ -1034,7 +1034,7 @@ LogicalResult CaseOp::canonicalize(CaseOp op, PatternRewriter &rewriter) { } if (op.getCaseStyle() == CaseStmtType::CaseZStmt && noZ) { - rewriter.updateRootInPlace(op, [&]() { + rewriter.modifyOpInPlace(op, [&]() { op.setCaseStyleAttr( CaseStmtTypeAttr::get(op.getContext(), CaseStmtType::CaseStmt)); }); @@ -1632,7 +1632,7 @@ LogicalResult WireOp::canonicalize(WireOp wire, PatternRewriter &rewriter) { // If the wire has a name attribute, propagate the name to the expression. if (auto *connectedOp = connected.getDefiningOp()) if (!wire.getName().empty()) - rewriter.updateRootInPlace(connectedOp, [&] { + rewriter.modifyOpInPlace(connectedOp, [&] { connectedOp->setAttr("sv.namehint", wire.getNameAttr()); }); diff --git a/lib/Dialect/SV/Transforms/SVExtractTestCode.cpp b/lib/Dialect/SV/Transforms/SVExtractTestCode.cpp index a8f910ba3f32..f6f36ac1f7e7 100644 --- a/lib/Dialect/SV/Transforms/SVExtractTestCode.cpp +++ b/lib/Dialect/SV/Transforms/SVExtractTestCode.cpp @@ -541,10 +541,10 @@ static bool isAssertOp(hw::HWSymbolCache &symCache, Operation *op) { // verifications. See FIRParserAsserts for more details. if (auto error = dyn_cast(op)) { if (auto message = error.getMessage()) - return message->startswith("assert:") || - message->startswith("assert failed (verification library)") || - message->startswith("Assertion failed") || - message->startswith("assertNotX:") || + return message->starts_with("assert:") || + message->starts_with("assert failed (verification library)") || + message->starts_with("Assertion failed") || + message->starts_with("assertNotX:") || message->contains("[verif-library-assert]"); return false; } diff --git a/lib/Dialect/Seq/SeqOps.cpp b/lib/Dialect/Seq/SeqOps.cpp index b224c2256505..a59d7e8213c8 100644 --- a/lib/Dialect/Seq/SeqOps.cpp +++ b/lib/Dialect/Seq/SeqOps.cpp @@ -206,10 +206,10 @@ void HLMemOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { } void HLMemOp::build(OpBuilder &builder, OperationState &result, Value clk, - Value rst, StringRef symName, llvm::ArrayRef shape, + Value rst, StringRef name, llvm::ArrayRef shape, Type elementType) { HLMemType t = HLMemType::get(builder.getContext(), shape, elementType); - HLMemOp::build(builder, result, t, clk, rst, symName); + HLMemOp::build(builder, result, t, clk, rst, name); } //===----------------------------------------------------------------------===// @@ -707,8 +707,8 @@ LogicalResult ClockGateOp::canonicalize(ClockGateOp op, if (auto testEnable = op.getTestEnable()) { if (auto constOp = testEnable.getDefiningOp()) { if (constOp.getValue().isZero()) { - rewriter.updateRootInPlace(op, - [&] { op.getTestEnableMutable().clear(); }); + rewriter.modifyOpInPlace(op, + [&] { op.getTestEnableMutable().clear(); }); return success(); } } @@ -788,7 +788,7 @@ LogicalResult FirMemReadOp::canonicalize(FirMemReadOp op, PatternRewriter &rewriter) { // Remove the enable if it is constant true. if (isConstAllOnes(op.getEnable())) { - rewriter.updateRootInPlace(op, [&] { op.getEnableMutable().erase(0); }); + rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); }); return success(); } return failure(); @@ -806,13 +806,13 @@ LogicalResult FirMemWriteOp::canonicalize(FirMemWriteOp op, // Remove the enable if it is constant true. if (auto enable = op.getEnable(); isConstAllOnes(enable)) { - rewriter.updateRootInPlace(op, [&] { op.getEnableMutable().erase(0); }); + rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); }); anyChanges = true; } // Remove the mask if it is all ones. if (auto mask = op.getMask(); isConstAllOnes(mask)) { - rewriter.updateRootInPlace(op, [&] { op.getMaskMutable().erase(0); }); + rewriter.modifyOpInPlace(op, [&] { op.getMaskMutable().erase(0); }); anyChanges = true; } @@ -838,13 +838,13 @@ LogicalResult FirMemReadWriteOp::canonicalize(FirMemReadWriteOp op, // Remove the enable if it is constant true. if (auto enable = op.getEnable(); isConstAllOnes(enable)) { - rewriter.updateRootInPlace(op, [&] { op.getEnableMutable().erase(0); }); + rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); }); anyChanges = true; } // Remove the mask if it is all ones. if (auto mask = op.getMask(); isConstAllOnes(mask)) { - rewriter.updateRootInPlace(op, [&] { op.getMaskMutable().erase(0); }); + rewriter.modifyOpInPlace(op, [&] { op.getMaskMutable().erase(0); }); anyChanges = true; } @@ -901,6 +901,21 @@ OpFoldResult FromClockOp::fold(FoldAdaptor adaptor) { return {}; } +//===----------------------------------------------------------------------===// +// ClockInverterOp +//===----------------------------------------------------------------------===// + +OpFoldResult ClockInverterOp::fold(FoldAdaptor adaptor) { + if (auto chainedInv = getInput().getDefiningOp()) + return chainedInv.getInput(); + if (auto clockAttr = dyn_cast_or_null(adaptor.getInput())) { + auto clockIn = clockAttr.getValue() == ClockConst::High; + return ClockConstAttr::get(getContext(), + clockIn ? ClockConst::Low : ClockConst::High); + } + return {}; +} + //===----------------------------------------------------------------------===// // FIR memory helper //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Sim/CMakeLists.txt b/lib/Dialect/Sim/CMakeLists.txt new file mode 100644 index 000000000000..27c6eb83b018 --- /dev/null +++ b/lib/Dialect/Sim/CMakeLists.txt @@ -0,0 +1,34 @@ +##===- CMakeLists.txt - build definitions for Sim -------------*- cmake -*-===// +## +## 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 +## +##===----------------------------------------------------------------------===// +## +## +##===----------------------------------------------------------------------===// + +add_circt_dialect_library(CIRCTSim + SimDialect.cpp + SimOps.cpp + + ADDITIONAL_HEADER_DIRS + ${CIRCT_MAIN_INCLUDE_DIR}/circt/Dialect/Sim + + DEPENDS + CIRCTHW + CIRCTSV + MLIRSimIncGen + + LINK_COMPONENTS + Support + + LINK_LIBS PUBLIC + CIRCTHW + CIRCTSeq + CIRCTSV + MLIRIR + MLIRPass + MLIRTransforms +) diff --git a/lib/Dialect/Sim/SimDialect.cpp b/lib/Dialect/Sim/SimDialect.cpp new file mode 100644 index 000000000000..c292425cd4e5 --- /dev/null +++ b/lib/Dialect/Sim/SimDialect.cpp @@ -0,0 +1,34 @@ +//===- SimDialect.cpp - Implement the Sim 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 file implements the Sim dialect. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/Sim/SimOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/DialectImplementation.h" + +using namespace circt; +using namespace sim; + +//===----------------------------------------------------------------------===// +// Dialect specification. +//===----------------------------------------------------------------------===// + +void SimDialect::initialize() { + addOperations< +#define GET_OP_LIST +#include "circt/Dialect/Sim/Sim.cpp.inc" + >(); +} + +#include "circt/Dialect/Sim/SimDialect.cpp.inc" diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp new file mode 100644 index 000000000000..36a8a8353603 --- /dev/null +++ b/lib/Dialect/Sim/SimOps.cpp @@ -0,0 +1,25 @@ +//===- SimOps.cpp - Implement the Sim 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 +// +//===----------------------------------------------------------------------===// +// +// This file implements `sim` dialect ops. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Sim/SimOps.h" + +using namespace mlir; +using namespace circt; +using namespace sim; + +//===----------------------------------------------------------------------===// +// TableGen generated logic. +//===----------------------------------------------------------------------===// + +// Provide the autogenerated implementation guts for the Op classes. +#define GET_OP_CLASSES +#include "circt/Dialect/Sim/Sim.cpp.inc" diff --git a/lib/Dialect/SystemC/SystemCOps.cpp b/lib/Dialect/SystemC/SystemCOps.cpp index 6800ff5bed42..4f2e0c50b95e 100644 --- a/lib/Dialect/SystemC/SystemCOps.cpp +++ b/lib/Dialect/SystemC/SystemCOps.cpp @@ -531,7 +531,8 @@ InstanceDeclOp::verifySymbolUses(SymbolTableCollection &symbolTable) { } SmallVector InstanceDeclOp::getPortList() { - return cast(getReferencedModuleCached(/*cache=*/nullptr)) + return cast(SymbolTable::lookupNearestSymbolFrom( + getOperation(), getReferencedModuleNameAttr())) .getPortList(); } diff --git a/lib/Firtool/CMakeLists.txt b/lib/Firtool/CMakeLists.txt index c87c0fa59a89..5004a6771239 100644 --- a/lib/Firtool/CMakeLists.txt +++ b/lib/Firtool/CMakeLists.txt @@ -9,6 +9,7 @@ add_circt_library(CIRCTFirtool CIRCTHWTransforms CIRCTOMTransforms CIRCTSeqToSV + CIRCTSimToSV CIRCTSeqTransforms CIRCTSVTransforms CIRCTTransforms diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index ad065fee945f..dfc030efadad 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -80,11 +80,14 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, pm.nest().addPass(firrtl::createInferResetsPass()); if (opt.shouldExportChiselInterface()) { - if (opt.getChiselInterfaceOutputDirectory().empty()) { + StringRef outdir = opt.getChiselInterfaceOutputDirectory(); + if (opt.isDefaultOutputFilename() && outdir.empty()) { pm.nest().addPass(createExportChiselInterfacePass()); } else { - pm.nest().addPass(createExportSplitChiselInterfacePass( - opt.getChiselInterfaceOutputDirectory())); + if (outdir.empty()) + outdir = opt.getOutputFilename(); + pm.nest().addPass( + createExportSplitChiselInterfacePass(outdir)); } } @@ -98,7 +101,8 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, if (opt.shouldDedup()) pm.nest().addPass(firrtl::createDedupPass()); - pm.nest().addPass(firrtl::createWireDFTPass()); + if (opt.shouldRunWireDFT()) + pm.nest().addPass(firrtl::createWireDFTPass()); if (opt.shouldConvertVecOfBundle()) { pm.addNestedPass(firrtl::createLowerFIRRTLTypesPass( @@ -265,6 +269,7 @@ LogicalResult firtool::populateHWToSV(mlir::PassManager &pm, opt.shouldEtcDisableModuleInlining())); pm.addPass(seq::createExternalizeClockGatePass(opt.getClockGateOptions())); + pm.addNestedPass(circt::createLowerSimToSVPass()); pm.addPass(circt::createLowerSeqToSVPass( {/*disableRegRandomization=*/!opt.isRandomEnabled( FirtoolOptions::RandomKind::Reg), @@ -317,7 +322,7 @@ populatePrepareForExportVerilog(mlir::PassManager &pm, if (opt.shouldStripFirDebugInfo()) pm.addPass(circt::createStripDebugInfoWithPredPass([](mlir::Location loc) { if (auto fileLoc = loc.dyn_cast()) - return fileLoc.getFilename().getValue().endswith(".fir"); + return fileLoc.getFilename().getValue().ends_with(".fir"); return false; })); @@ -478,6 +483,13 @@ struct FirtoolCmdOptions { llvm::cl::desc("Disable deduplication of structurally identical modules"), llvm::cl::init(false)}; + llvm::cl::opt runWireDFT{ + "run-wire-dft", + llvm::cl::desc("Run the now-deprecated WireDFT transform"), + llvm::cl::init(false), + llvm::cl::Hidden, + }; + llvm::cl::opt companionMode{ "grand-central-companion-mode", llvm::cl::desc("Specifies the handling of Grand Central companions"), @@ -679,7 +691,8 @@ circt::firtool::FirtoolOptions::FirtoolOptions() preserveMode(firrtl::PreserveValues::None), enableDebugInfo(false), buildMode(BuildModeRelease), disableOptimization(false), exportChiselInterface(false), chiselInterfaceOutDirectory(""), - vbToBV(false), noDedup(false), companionMode(firrtl::CompanionMode::Bind), + vbToBV(false), noDedup(false), runWireDFT(false), + companionMode(firrtl::CompanionMode::Bind), disableAggressiveMergeConnections(false), disableHoistingHWPassthrough(true), emitOMIR(true), omirOutFile(""), lowerMemories(false), blackBoxRootPath(""), replSeqMem(false), @@ -709,6 +722,7 @@ circt::firtool::FirtoolOptions::FirtoolOptions() chiselInterfaceOutDirectory = clOptions->chiselInterfaceOutDirectory; vbToBV = clOptions->vbToBV; noDedup = clOptions->noDedup; + runWireDFT = clOptions->runWireDFT; companionMode = clOptions->companionMode; disableAggressiveMergeConnections = clOptions->disableAggressiveMergeConnections; diff --git a/lib/LogicalEquivalence/LogicExporter.cpp b/lib/LogicalEquivalence/LogicExporter.cpp index 0c0689dcc0d1..130b89b34a4d 100644 --- a/lib/LogicalEquivalence/LogicExporter.cpp +++ b/lib/LogicalEquivalence/LogicExporter.cpp @@ -74,8 +74,10 @@ struct Visitor : public hw::StmtVisitor, //===--------------------------------------------------------------------===// LogicalResult visitStmt(hw::InstanceOp op) { - if (auto hwModule = llvm::dyn_cast( - op.getReferencedModuleCached(/*cache=*/nullptr))) { + Operation *moduleOp = + SymbolTable::lookupNearestSymbolFrom(op, op.getModuleNameAttr()); + + if (auto hwModule = llvm::dyn_cast(moduleOp)) { circuit->addInstance(op.getInstanceName(), hwModule, op->getOperands(), op->getResults()); return success(); @@ -85,6 +87,10 @@ struct Visitor : public hw::StmtVisitor, return failure(); } + LogicalResult visitStmt(hw::InstanceChoiceOp op) { + return op.emitError("instance choices are not supported"); + } + LogicalResult visitStmt(hw::OutputOp op) { for (auto operand : op.getOperands()) circuit->addOutput(operand); diff --git a/lib/Support/CMakeLists.txt b/lib/Support/CMakeLists.txt index 27d6fd639a3c..08b9d250ad35 100644 --- a/lib/Support/CMakeLists.txt +++ b/lib/Support/CMakeLists.txt @@ -9,9 +9,11 @@ add_circt_library(CIRCTSupport APInt.cpp BackedgeBuilder.cpp CustomDirectiveImpl.cpp + Debug.cpp FieldRef.cpp JSON.cpp LoweringOptions.cpp + Naming.cpp Passes.cpp Path.cpp PrettyPrinter.cpp diff --git a/lib/Support/Debug.cpp b/lib/Support/Debug.cpp new file mode 100644 index 000000000000..8cffee71f81d --- /dev/null +++ b/lib/Support/Debug.cpp @@ -0,0 +1,49 @@ +//===- Debug.cpp - Debug 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 related to generating run-time debug information. +// +//===----------------------------------------------------------------------===// + +#include "circt/Support/Debug.h" + +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Debug.h" + +llvm::raw_ostream &circt::debugHeader(llvm::StringRef str, int width) { + auto &dbgStream = llvm::dbgs(); + dbgStream << "===- " << str << " "; + width -= 6 + str.size(); + if (width > 3) { + dbgStream << std::string(width - 3, '-'); + width = 3; + } + if (width > 0) + dbgStream << std::string(width, '='); + return dbgStream; +} + +llvm::raw_ostream &circt::debugPassHeader(const mlir::Pass *pass, int width) { + return debugHeader((llvm::Twine("Running ") + pass->getName()).str()); +} + +llvm::raw_ostream &circt::debugFooter(int width) { + auto &dbgStream = llvm::dbgs(); + if (width > 3) { + int startWidth = std::min(width - 3, 3); + dbgStream << std::string(startWidth, '='); + width -= startWidth; + } + if (width > 3) { + dbgStream << std::string(width - 3, '-'); + width = 3; + } + if (width > 0) + dbgStream << std::string(width, '='); + return dbgStream; +} diff --git a/lib/Support/InstanceGraph.cpp b/lib/Support/InstanceGraph.cpp index ed9ff40f0d63..e05c81b11d56 100644 --- a/lib/Support/InstanceGraph.cpp +++ b/lib/Support/InstanceGraph.cpp @@ -118,8 +118,8 @@ InstanceGraphNode *InstanceGraph::lookup(ModuleOpInterface op) { void InstanceGraph::replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst) { - ArrayAttr instRefs = inst.getReferencedModuleNamesAttr(); - assert(instRefs == newInst.getReferencedModuleNamesAttr() && + assert(inst.getReferencedModuleNamesAttr() == + newInst.getReferencedModuleNamesAttr() && "Both instances must be targeting the same modules"); // Replace all edges between the module of the instance and all targets. diff --git a/lib/Support/Naming.cpp b/lib/Support/Naming.cpp new file mode 100644 index 000000000000..e6f31da361bf --- /dev/null +++ b/lib/Support/Naming.cpp @@ -0,0 +1,68 @@ +//===- Naming.cpp - Utilities for handling names ----------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "circt/Support/Naming.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Operation.h" +#include "llvm/ADT/StringRef.h" + +using namespace circt; + +bool circt::isUselessName(StringRef name) { + if (name.empty()) + return true; + // Ignore _.* + return name.starts_with("_T") || name.starts_with("_WIRE"); +} + +// Heuristic to pick the best name. +// Good names are not useless, don't start with an underscore, minimize +// underscores in them, and are short. This function deterministically favors +// the second name on ties. +static bool isNameBetter(StringRef a, StringRef b) { + if (a.empty()) + return false; + if (b.empty()) + return true; + if (isUselessName(a)) + return false; + if (isUselessName(b)) + return true; + if (a.starts_with("_")) + return false; + if (b.starts_with("_")) + return true; + if (b.count('_') < a.count('_')) + return false; + if (b.count('_') > a.count('_')) + return true; + return a.size() <= b.size(); +} + +StringRef circt::chooseName(StringRef a, StringRef b) { + return isNameBetter(a, b) ? a : b; +} + +StringAttr circt::chooseName(StringAttr a, StringAttr b) { + if (!a) + return b; + if (!b) + return a; + return isNameBetter(a.getValue(), b.getValue()) ? a : b; +} + +static StringAttr getNameOrHint(Operation *a) { + StringAttr name = a->getAttrOfType("name"); + if (!name || name.getValue().empty()) + return a->getAttrOfType("sv.namehint"); + return name; +} + +StringAttr circt::chooseName(Operation *a, Operation *b) { + return chooseName(getNameOrHint(a), getNameOrHint(b)); +} diff --git a/lib/Target/DebugInfo/EmitHGLDD.cpp b/lib/Target/DebugInfo/EmitHGLDD.cpp index 7d9e64978019..e71a0cd88687 100644 --- a/lib/Target/DebugInfo/EmitHGLDD.cpp +++ b/lib/Target/DebugInfo/EmitHGLDD.cpp @@ -90,10 +90,10 @@ static FileLineColLoc findBestLocation(Location loc, bool emitted, locs.resize(tail); } for (auto loc : locs) - if (!loc.getFilename().getValue().endswith(".fir")) + if (!loc.getFilename().getValue().ends_with(".fir")) return loc; for (auto loc : locs) - if (loc.getFilename().getValue().endswith(".fir")) + if (loc.getFilename().getValue().ends_with(".fir")) return loc; return {}; } @@ -210,7 +210,8 @@ struct EmittedExpr { operator bool() const { return expr != nullptr && type; } }; -llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const EmittedType &type) { +static llvm::raw_ostream &operator<<(llvm::raw_ostream &os, + const EmittedType &type) { if (!type) return os << ""; os << type.name; @@ -224,7 +225,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const EmittedType &type) { return os; } -llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const EmittedExpr &expr) { +static llvm::raw_ostream &operator<<(llvm::raw_ostream &os, + const EmittedExpr &expr) { if (!expr) return os << ""; return os << expr.expr << " : " << expr.type; @@ -662,7 +664,8 @@ EmittedExpr FileEmitter::emitExpression(Value value) { instName = instOp.getInstanceNameAttr(); if (!instName) return {}; - auto *moduleOp = instOp.getReferencedModuleCached(symbolCache); + auto *moduleOp = + symbolCache->getDefinition(instOp.getReferencedModuleNameAttr()); auto portName = cast(moduleOp) .getPort(instOp.getPortIdForOutputId(result.getResultNumber())) diff --git a/lib/Transforms/InsertMergeBlocks.cpp b/lib/Transforms/InsertMergeBlocks.cpp index 0d7770a9c722..802c3837b3ab 100644 --- a/lib/Transforms/InsertMergeBlocks.cpp +++ b/lib/Transforms/InsertMergeBlocks.cpp @@ -325,13 +325,13 @@ struct FuncOpPattern : public OpConversionPattern { LogicalResult matchAndRewrite(func::FuncOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - rewriter.startRootUpdate(op); + rewriter.startOpModification(op); if (!op.isExternal()) if (failed(insertMergeBlocks(op.getRegion(), rewriter))) return failure(); - rewriter.finalizeRootUpdate(op); + rewriter.finalizeOpModification(op); rewrittenFuncs.insert(op); return success(); diff --git a/lib/Transforms/MapArithToComb.cpp b/lib/Transforms/MapArithToComb.cpp index d3b8ae84a8d6..942fb16deb1b 100644 --- a/lib/Transforms/MapArithToComb.cpp +++ b/lib/Transforms/MapArithToComb.cpp @@ -37,7 +37,7 @@ class MapArithTypeConverter : public mlir::TypeConverter { } }; -template +template class OneToOnePattern : public OpConversionPattern { public: using OpConversionPattern::OpConversionPattern; @@ -46,7 +46,9 @@ class OneToOnePattern : public OpConversionPattern { LogicalResult matchAndRewrite(TFrom op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - rewriter.replaceOpWithNewOp(op, adaptor.getOperands(), op->getAttrs()); + rewriter.replaceOpWithNewOp( + op, adaptor.getOperands(), + cloneAttrs ? op->getAttrs() : ArrayRef<::mlir::NamedAttribute>()); return success(); } }; @@ -169,7 +171,7 @@ struct MapArithToCombPass : public MapArithToCombPassBase { OneToOnePattern, OneToOnePattern, OneToOnePattern, - OneToOnePattern, + OneToOnePattern, OneToOnePattern, ExtSConversionPattern, ExtZConversionPattern, TruncateConversionPattern, CompConversionPattern>( diff --git a/lib/Transforms/MaximizeSSA.cpp b/lib/Transforms/MaximizeSSA.cpp index b1a17c653600..ca7fbd18aff2 100644 --- a/lib/Transforms/MaximizeSSA.cpp +++ b/lib/Transforms/MaximizeSSA.cpp @@ -203,7 +203,7 @@ struct MaxSSAConversion : public ConversionPattern { matchAndRewrite(Operation *op, ArrayRef operands, ConversionPatternRewriter &rewriter) const override { LogicalResult conversionStatus = success(); - rewriter.updateRootInPlace(op, [&] { + rewriter.modifyOpInPlace(op, [&] { for (auto ®ion : op->getRegions()) { SSAMaximizationStrategy strategy; if (failed(maximizeSSA(region, strategy, rewriter))) diff --git a/lib/Transforms/StripDebugInfoWithPred.cpp b/lib/Transforms/StripDebugInfoWithPred.cpp index 2fbc51a2f8a0..29d498bb3e1c 100644 --- a/lib/Transforms/StripDebugInfoWithPred.cpp +++ b/lib/Transforms/StripDebugInfoWithPred.cpp @@ -72,7 +72,7 @@ void StripDebugInfoWithPred::runOnOperation() { if (!pred && !dropSuffix.empty()) { pred = [&](mlir::Location loc) { if (auto fileLoc = loc.dyn_cast()) - return fileLoc.getFilename().getValue().endswith(dropSuffix); + return fileLoc.getFilename().getValue().ends_with(dropSuffix); return false; }; } @@ -87,8 +87,8 @@ void StripDebugInfoWithPred::runOnOperation() { &getContext(), getOperation().getOps(), [&](Operation &toplevelOp) { toplevelOp.walk([&](Operation *op) { updateLocIfChanged(op, getStrippedLoc(op->getLoc())); - updateLocArray(op, "argLocs"); - updateLocArray(op, "resultLocs"); + updateLocArray(op, "arg_locs"); + updateLocArray(op, "result_locs"); updateLocArray(op, "port_locs"); // Strip block arguments debug info. for (Region ®ion : op->getRegions()) diff --git a/llvm b/llvm index 668865789620..0ce01712432d 160000 --- a/llvm +++ b/llvm @@ -1 +1 @@ -Subproject commit 668865789620f390fbad4d7093ed8ca6eb932c31 +Subproject commit 0ce01712432dd34f7e83f78af23fb444f3fd92b6 diff --git a/test/CAPI/firrtl.c b/test/CAPI/firrtl.c index 21820ed80e6f..34925dc7a32c 100644 --- a/test/CAPI/firrtl.c +++ b/test/CAPI/firrtl.c @@ -52,9 +52,70 @@ void testExport(MlirContext ctx) { // CHECK-NEXT: connect out, and(in_1, in_2) @[- 6:5] } +void testValueFoldFlow(MlirContext ctx) { + // clang-format off + const char *testFIR = + "firrtl.circuit \"ValueFoldFlowTest\" {\n" + " firrtl.module @ValueFoldFlowTest(in %in: !firrtl.uint<32>,\n" + " out %out: !firrtl.uint<32>) {\n" + " firrtl.connect %out, %in : !firrtl.uint<32>, !firrtl.uint<32>\n" + " }\n" + "}\n"; + // clang-format on + MlirModule module = + mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(testFIR)); + MlirBlock mlirModule = mlirModuleGetBody(module); + MlirBlock firCircuit = mlirRegionGetFirstBlock( + mlirOperationGetRegion(mlirBlockGetFirstOperation(mlirModule), 0)); + MlirBlock firModule = mlirRegionGetFirstBlock( + mlirOperationGetRegion(mlirBlockGetFirstOperation(firCircuit), 0)); + + MlirValue in = mlirBlockGetArgument(firModule, 0); + MlirValue out = mlirBlockGetArgument(firModule, 1); + + assert(firrtlValueFoldFlow(in, FIRRTL_VALUE_FLOW_SOURCE) == + FIRRTL_VALUE_FLOW_SOURCE); + assert(firrtlValueFoldFlow(out, FIRRTL_VALUE_FLOW_SOURCE) == + FIRRTL_VALUE_FLOW_SINK); +} + +void testImportAnnotations(MlirContext ctx) { + // clang-format off + const char *testFIR = + "firrtl.circuit \"AnnoTest\" {\n" + " firrtl.module @AnnoTest(in %in: !firrtl.uint<32>) {}\n" + "}\n"; + // clang-format on + MlirModule module = + mlirModuleCreateParse(ctx, mlirStringRefCreateFromCString(testFIR)); + MlirBlock mlirModule = mlirModuleGetBody(module); + MlirOperation firCircuit = mlirBlockGetFirstOperation(mlirModule); + + const char *rawAnnotationsJSON = "[{\ + \"class\":\"firrtl.transforms.DontTouchAnnotation\",\ + \"target\":\"~AnnoTest|AnnoTest>in\"\ + }]"; + MlirAttribute rawAnnotationsAttr; + bool succeeded = firrtlImportAnnotationsFromJSONRaw( + ctx, mlirStringRefCreateFromCString(rawAnnotationsJSON), + &rawAnnotationsAttr); + assert(succeeded); + mlirOperationSetAttributeByName( + firCircuit, mlirStringRefCreateFromCString("rawAnnotations"), + rawAnnotationsAttr); + + mlirOperationPrint(mlirModuleGetOperation(module), exportCallback, NULL); + + // clang-format off + // CHECK: firrtl.circuit "AnnoTest" attributes {rawAnnotations = [{class = "firrtl.transforms.DontTouchAnnotation", target = "~AnnoTest|AnnoTest>in"}]} { + // clang-format on +} + int main(void) { MlirContext ctx = mlirContextCreate(); mlirDialectHandleLoadDialect(mlirGetDialectHandle__firrtl__(), ctx); testExport(ctx); + testValueFoldFlow(ctx); + testImportAnnotations(ctx); return 0; } diff --git a/test/CAPI/firtool.c b/test/CAPI/firtool.c index a7c67c0dc01c..7bd47cb5105a 100644 --- a/test/CAPI/firtool.c +++ b/test/CAPI/firtool.c @@ -79,7 +79,7 @@ void testExportVerilog(MlirContext ctx) { // CHECK-NEXT: output [31:0] out // -:4:45 // CHECK-NEXT: ); // CHECK-EMPTY: - // CHECK-NEXT: assign out = in_1 & in_2; // -:2:3, :6:10 + // CHECK-NEXT: assign out = in_1 & in_2; // -:2:3, :5:10, :6:10 // CHECK-NEXT: endmodule exportVerilog(ctx, true); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fc4720bc1336..9cc78d42f54a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -44,6 +44,10 @@ if(CIRCT_LLHD_SIM_ENABLED) list(APPEND CIRCT_TEST_DEPENDS circt-llhd-signals-runtime-wrappers) endif() +if(CIRCT_SLANG_FRONTEND_ENABLED) + list(APPEND CIRCT_TEST_DEPENDS circt-verilog) +endif() + add_lit_testsuite(check-circt "Running the CIRCT regression tests" ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CIRCT_TEST_DEPENDS} diff --git a/test/Conversion/ConvertToArcs/convert-to-arcs-names.mlir b/test/Conversion/ConvertToArcs/convert-to-arcs-names.mlir index 8f12174c5dc5..43d54ad510c1 100644 --- a/test/Conversion/ConvertToArcs/convert-to-arcs-names.mlir +++ b/test/Conversion/ConvertToArcs/convert-to-arcs-names.mlir @@ -3,7 +3,7 @@ // CHECK-LABEL: hw.module @Trivial( hw.module @Trivial(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out o0: i4) { - // CHECK: arc.state {{@.+}}(%i0) clock %clock lat 1 + // CHECK: arc.state {{@.+}}(%i0) clock %clock latency 1 // CHECK-TAP-OFF-NOT: names = ["foo"] // CHECK-TAP-ON: names = ["foo"] %foo = seq.compreg %i0, %clock : i4 diff --git a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir index 33b54d355dd0..21fac9ecc0b5 100644 --- a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir +++ b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir @@ -26,7 +26,7 @@ hw.module @Passthrough(in %a: i4, out z: i4) { // CHECK-LABEL: hw.module @CombOnly hw.module @CombOnly(in %i0: i4, in %i1: i4, out z: i4) { - // CHECK-NEXT: [[TMP:%.+]] = arc.state @CombOnly_arc(%i0, %i1) lat 0 + // CHECK-NEXT: [[TMP:%.+]] = arc.call @CombOnly_arc(%i0, %i1) // CHECK-NEXT: hw.output [[TMP]] %0 = comb.add %i0, %i1 : i4 %1 = comb.xor %0, %i0 : i4 @@ -45,7 +45,7 @@ hw.module @CombOnly(in %i0: i4, in %i1: i4, out z: i4) { // CHECK-LABEL: hw.module @SplitAtConstants hw.module @SplitAtConstants(out z: i4) { // CHECK-NEXT: %c1_i4 = hw.constant 1 - // CHECK-NEXT: [[TMP:%.+]] = arc.state @SplitAtConstants_arc(%c1_i4) lat 0 + // CHECK-NEXT: [[TMP:%.+]] = arc.call @SplitAtConstants_arc(%c1_i4) // CHECK-NEXT: hw.output [[TMP]] %c1_i4 = hw.constant 1 : i4 %0 = comb.add %c1_i4, %c1_i4 : i4 @@ -71,9 +71,9 @@ hw.module @SplitAtConstants(out z: i4) { // CHECK-LABEL: hw.module @Pipeline hw.module @Pipeline(in %clock: !seq.clock, in %i0: i4, in %i1: i4, out z: i4) { - // CHECK-NEXT: [[S0:%.+]] = arc.state @Pipeline_arc(%i0, %i1) clock %clock lat 1 - // CHECK-NEXT: [[S1:%.+]] = arc.state @Pipeline_arc_0([[S0]], %i0) clock %clock lat 1 - // CHECK-NEXT: [[S2:%.+]] = arc.state @Pipeline_arc_1([[S1]], %i1) lat 0 + // CHECK-NEXT: [[S0:%.+]] = arc.state @Pipeline_arc(%i0, %i1) clock %clock latency 1 + // CHECK-NEXT: [[S1:%.+]] = arc.state @Pipeline_arc_0([[S0]], %i0) clock %clock latency 1 + // CHECK-NEXT: [[S2:%.+]] = arc.call @Pipeline_arc_1([[S1]], %i1) // CHECK-NEXT: hw.output [[S2]] %0 = comb.add %i0, %i1 : i4 %1 = seq.compreg %0, %clock : i4 @@ -96,8 +96,8 @@ hw.module @Pipeline(in %clock: !seq.clock, in %i0: i4, in %i1: i4, out z: i4) { // CHECK-LABEL: hw.module @Reshuffling hw.module @Reshuffling(in %clockA: !seq.clock, in %clockB: !seq.clock, out z0: i4, out z1: i4, out z2: i4, out z3: i4) { // CHECK-NEXT: hw.instance "x" @Reshuffling2() - // CHECK-NEXT: arc.state @Reshuffling_arc(%x.z0, %x.z1) clock %clockA lat 1 - // CHECK-NEXT: arc.state @Reshuffling_arc_0(%x.z2, %x.z3) clock %clockB lat 1 + // CHECK-NEXT: arc.state @Reshuffling_arc(%x.z0, %x.z1) clock %clockA latency 1 + // CHECK-NEXT: arc.state @Reshuffling_arc_0(%x.z2, %x.z3) clock %clockB latency 1 // CHECK-NEXT: hw.output %x.z0, %x.z1, %x.z2, %x.z3 = hw.instance "x" @Reshuffling2() -> (z0: i4, z1: i4, z2: i4, z3: i4) %4 = seq.compreg %x.z0, %clockA : i4 @@ -128,10 +128,10 @@ hw.module.extern private @Reshuffling2(out z0: i4, out z1: i4, out z2: i4, out z // CHECK-LABEL: hw.module @FactorOutCommonOps hw.module @FactorOutCommonOps(in %clock: !seq.clock, in %i0: i4, in %i1: i4, out o0: i4, out o1: i4) { - // CHECK-DAG: [[T0:%.+]] = arc.state @FactorOutCommonOps_arc_1(%i0, %i1) lat 0 + // CHECK-DAG: [[T0:%.+]] = arc.call @FactorOutCommonOps_arc_1(%i0, %i1) %0 = comb.add %i0, %i1 : i4 - // CHECK-DAG: [[T1:%.+]] = arc.state @FactorOutCommonOps_arc([[T0]], %i0) clock %clock lat 1 - // CHECK-DAG: [[T2:%.+]] = arc.state @FactorOutCommonOps_arc_0([[T0]], %i1) clock %clock lat 1 + // CHECK-DAG: [[T1:%.+]] = arc.state @FactorOutCommonOps_arc([[T0]], %i0) clock %clock latency 1 + // CHECK-DAG: [[T2:%.+]] = arc.state @FactorOutCommonOps_arc_0([[T0]], %i1) clock %clock latency 1 %1 = comb.xor %0, %i0 : i4 %2 = comb.mul %0, %i1 : i4 %3 = seq.compreg %1, %clock : i4 @@ -154,9 +154,9 @@ hw.module @FactorOutCommonOps(in %clock: !seq.clock, in %i0: i4, in %i1: i4, out // CHECK-LABEL: hw.module @SplitAtInstance( hw.module @SplitAtInstance(in %a: i4, out z: i4) { - // CHECK-DAG: [[T0:%.+]] = arc.state @SplitAtInstance_arc(%a) lat 0 + // CHECK-DAG: [[T0:%.+]] = arc.call @SplitAtInstance_arc(%a) // CHECK-DAG: [[T1:%.+]] = hw.instance "x" @SplitAtInstance2(a: [[T0]]: i4) - // CHECK-DAG: [[T2:%.+]] = arc.state @SplitAtInstance_arc_0([[T1]]) lat 0 + // CHECK-DAG: [[T2:%.+]] = arc.call @SplitAtInstance_arc_0([[T1]]) %0 = comb.mul %a, %a : i4 %1 = hw.instance "x" @SplitAtInstance2(a: %0: i4) -> (z: i4) %2 = comb.shl %1, %1 : i4 @@ -171,7 +171,7 @@ hw.module.extern private @SplitAtInstance2(in %a: i4, out z: i4) // CHECK-LABEL: hw.module @AbsorbNames hw.module @AbsorbNames(in %clock: !seq.clock) { // CHECK-NEXT: %x.z0, %x.z1 = hw.instance "x" @AbsorbNames2() - // CHECK-NEXT: arc.state @AbsorbNames_arc(%x.z0, %x.z1) clock %clock lat 1 + // CHECK-NEXT: arc.state @AbsorbNames_arc(%x.z0, %x.z1) clock %clock latency 1 // CHECK-SAME: {names = ["myRegA", "myRegB"]} // CHECK-NEXT: hw.output %x.z0, %x.z1 = hw.instance "x" @AbsorbNames2() -> (z0: i4, z1: i4) @@ -188,7 +188,7 @@ hw.module.extern @AbsorbNames2(out z0: i4, out z1: i4) // CHECK-LABEL: hw.module @Trivial( hw.module @Trivial(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out out: i4) { - // CHECK: [[RES0:%.+]] = arc.state @[[TRIVIAL_ARC]](%i0) clock %clock reset %reset lat 1 {names = ["foo"] + // CHECK: [[RES0:%.+]] = arc.state @[[TRIVIAL_ARC]](%i0) clock %clock reset %reset latency 1 {names = ["foo"] // CHECK-NEXT: hw.output [[RES0:%.+]] %0 = hw.constant 0 : i4 %foo = seq.compreg %i0, %clock reset %reset, %0 : i4 @@ -206,8 +206,8 @@ hw.module @Trivial(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out out: i4 // CHECK-LABEL: hw.module @NonTrivial( hw.module @NonTrivial(in %clock: !seq.clock, in %i0: i4, in %reset1: i1, in %reset2: i1, out out1: i4, out out2: i4) { - // CHECK: [[RES2:%.+]] = arc.state @[[NONTRIVIAL_ARC_0]](%i0) clock %clock reset %reset1 lat 1 {names = ["foo"] - // CHECK-NEXT: [[RES3:%.+]] = arc.state @[[NONTRIVIAL_ARC_1]](%i0) clock %clock reset %reset2 lat 1 {names = ["bar"] + // CHECK: [[RES2:%.+]] = arc.state @[[NONTRIVIAL_ARC_0]](%i0) clock %clock reset %reset1 latency 1 {names = ["foo"] + // CHECK-NEXT: [[RES3:%.+]] = arc.state @[[NONTRIVIAL_ARC_1]](%i0) clock %clock reset %reset2 latency 1 {names = ["bar"] // CHECK-NEXT: hw.output [[RES2]], [[RES3]] %0 = hw.constant 0 : i4 %foo = seq.compreg %i0, %clock reset %reset1, %0 : i4 @@ -228,6 +228,6 @@ hw.module @ObserveWires(in %in1: i32, in %in2: i32, out out: i32) { // CHECK: [[V1:%.+]] = comb.xor [[V0]], {{.*}} : // CHECK: arc.output [[V0]], [[V1]] : // CHECK: hw.module @ObserveWires -// CHECK-DAG: [[RES:%.+]]:2 = arc.state @[[ARC_NAME]]({{.*}}) lat 0 : +// CHECK-DAG: [[RES:%.+]]:2 = arc.call @[[ARC_NAME]]({{.*}}) : // CHECK-DAG: arc.tap [[RES]]#0 {name = "z"} : i32 // CHECK: hw.output %0#1 : i32 diff --git a/test/Conversion/ExportVerilog/emit.mlir b/test/Conversion/ExportVerilog/emit.mlir new file mode 100644 index 000000000000..6694d7866ce2 --- /dev/null +++ b/test/Conversion/ExportVerilog/emit.mlir @@ -0,0 +1,15 @@ +// RUN: firtool --split-verilog --emit-omir -o=%t%{fs-sep} %s +// RUN: cat %t%{fs-sep}some-filelist.f | FileCheck %s --check-prefix=FILELIST +// RUN: cat %t%{fs-sep}some-file.sv | FileCheck %s --check-prefix=FILE + +// FILE: SimpleVerbatim +// FILE-NEXT: WithNewlines +// FILE-NEXT: A +// FILE-NEXT: B +emit.file "some-file.sv" sym @SomeFile.sv { + emit.verbatim "SimpleVerbatim" + emit.verbatim "WithNewlines\nA\nB\n" +} + +// FILELIST: some-file.sv +emit.file_list "some-filelist.f", [@SomeFile.sv] sym @SomeFileList diff --git a/test/Conversion/ExportVerilog/hw-lower-instance-choices.mlir b/test/Conversion/ExportVerilog/hw-lower-instance-choices.mlir new file mode 100644 index 000000000000..596d54e0e5b6 --- /dev/null +++ b/test/Conversion/ExportVerilog/hw-lower-instance-choices.mlir @@ -0,0 +1,38 @@ +// RUN: circt-opt %s -hw-lower-instance-choices --split-input-file -verify-diagnostics | FileCheck %s + +// CHECK: sv.macro.decl @__circt_choice_top_inst1 +// CHECK: sv.macro.decl @__circt_choice_top_inst2 + +hw.module private @TargetA(in %a: i32, out b: i32) { + hw.output %a : i32 +} + +hw.module private @TargetB(in %a: i32, out b: i32) { + hw.output %a : i32 +} + +hw.module private @TargetDefault(in %a: i32, out b: i32) { + hw.output %a : i32 +} + +hw.module public @top(in %a: i32, out b: i32, out d: i32) { + // CHECK: sv.ifdef "__circt_choice_top_inst1" { + // CHECK-NEXT: } else { + // CHECK-NEXT{LITERAL}: sv.macro.def @__circt_choice_top_inst1 "{{0}}"([@TargetDefault]) + // CHECK-NEXT: } + // CHECK-NEXT: hw.instance_choice "inst1" + // CHECK-SAME: {hw.choiceTarget = @__circt_choice_top_inst1} + %b = hw.instance_choice "inst1" sym @inst1 option "Perf" @TargetDefault or @TargetA if "A" or @TargetB if "B"(a: %a: i32) -> (b: i32) + + // CHECK: sv.ifdef "__circt_choice_top_inst2" { + // CHECK-NEXT: } else { + // CHECK-NEXT{LITERAL}: sv.macro.def @__circt_choice_top_inst2 "{{0}}"([@TargetB]) + // CHECK-NEXT: } + // CHECK-NEXT: hw.instance_choice "inst2" + // CHECK-SAME: {hw.choiceTarget = @__circt_choice_top_inst2} + %c = hw.instance_choice "inst2" sym @inst2 option "Perf" @TargetB or @TargetA if "A" or @TargetDefault if "B"(a: %a: i32) -> (b: i32) + + %d = comb.add %c, %a : i32 + + hw.output %b, %d : i32, i32 +} diff --git a/test/Conversion/ExportVerilog/hw-typedecls.mlir b/test/Conversion/ExportVerilog/hw-typedecls.mlir index 27f78d987312..dbceac14ea95 100644 --- a/test/Conversion/ExportVerilog/hw-typedecls.mlir +++ b/test/Conversion/ExportVerilog/hw-typedecls.mlir @@ -34,16 +34,6 @@ hw.type_scope @_other_scope { } // CHECK: `endif // _TYPESCOPE__other_scope -// CHECK: `ifndef __PYCDE_TYPES__ -// CHECK: typedef struct packed {logic a; } exTypedef; -// CHECK: `endif -sv.ifdef "__PYCDE_TYPES__" { -} else { - hw.type_scope @pycde { - hw.typedecl @exTypedef : !hw.struct - } -} - // CHECK-LABEL: module testTypeAlias hw.module @testTypeAlias( // CHECK: input foo arg0, diff --git a/test/Conversion/ExportVerilog/instance-choice.mlir b/test/Conversion/ExportVerilog/instance-choice.mlir new file mode 100644 index 000000000000..76f487e54bf2 --- /dev/null +++ b/test/Conversion/ExportVerilog/instance-choice.mlir @@ -0,0 +1,40 @@ +// RUN: circt-opt %s -export-split-verilog='dir-name=%t.dir' +// RUN: cat %t.dir%{fs-sep}top.sv | FileCheck %s + +hw.module private @TargetA(in %a: i32, out b: i32) { + hw.output %a : i32 +} + +hw.module private @TargetB(in %a: i32, out b: i32) { + hw.output %a : i32 +} + +hw.module private @TargetDefault(in %a: i32, out b: i32) { + hw.output %a : i32 +} + +// CHECK-LABEL: module top +hw.module public @top(in %a: i32, out b: i32, out d: i32) { + // CHECK: `ifndef __circt_choice_top_inst1 + // CHECK-NEXT: `define __circt_choice_top_inst1 TargetDefault + // CHECK-NEXT: `endif + // CHECK-NEXT: `__circt_choice_top_inst1 inst1 ( + // CHECK-NEXT: .a (a), + // CHECK-NEXT: .b (b) + // CHECK-NEXT: ); + %b = hw.instance_choice "inst1" sym @inst1 option "Perf" @TargetDefault or @TargetA if "A" or @TargetB if "B"(a: %a: i32) -> (b: i32) + + // CHECK: `ifndef __circt_choice_top_inst2 + // CHECK-NEXT: `define __circt_choice_top_inst2 TargetDefault + // CHECK-NEXT: `endif + // CHECK-NEXT: `__circt_choice_top_inst2 inst2 ( + // CHECK-NEXT: .a (a), + // CHECK-NEXT: .b (_inst2_b) + // CHECK-NEXT: ); + + %c = hw.instance_choice "inst2" sym @inst2 option "Perf" @TargetDefault or @TargetA if "A" or @TargetB if "B"(a: %a: i32) -> (b: i32) + + %d = comb.add %c, %a : i32 + + hw.output %b, %d : i32, i32 +} diff --git a/test/Conversion/ExportVerilog/prepare-for-emission.mlir b/test/Conversion/ExportVerilog/prepare-for-emission.mlir index 46fa41f73d72..6f024439254b 100644 --- a/test/Conversion/ExportVerilog/prepare-for-emission.mlir +++ b/test/Conversion/ExportVerilog/prepare-for-emission.mlir @@ -292,3 +292,35 @@ module attributes {circt.loweringOptions = "disallowLocalVariables"} { } hw.hierpath @xmr [@Foo::@a] } + + +// ----- + +// CHECK-LABEL: @constantInitRegWithBackEdge +hw.module @constantInitRegWithBackEdge() { + // CHECK: %reg = sv.reg init %false : !hw.inout + // CHECK-NEXT: %false = hw.constant false + // CHECK-NEXT: %[[VAL_0:.*]] = sv.read_inout %reg : !hw.inout + // CHECK-NEXT: %[[VAL_1:.*]] = comb.or %false, %[[VAL_0]] : i1 + %false = hw.constant false + %0 = comb.or %false, %1 : i1 + %reg = sv.reg init %false : !hw.inout + %1 = sv.read_inout %reg : !hw.inout +} + +// ----- + +// CHECK-LABEL: @temporaryWireForReg +hw.module @temporaryWireForReg() { + // CHECK: %[[WIRE:.*]] = sv.wire : !hw.inout + // CHECK-NEXT: %[[VAL_0:.*]] = sv.read_inout %[[WIRE]] : !hw.inout + // CHECK-NEXT: %b = sv.reg init %[[VAL_0]] : !hw.inout + // CHECK-NEXT: %[[VAL_1:.*]] = sv.read_inout %b : !hw.inout + // CHECK-NEXT: %a = sv.reg init %[[VAL_1]] : !hw.inout + // CHECK-NEXT: %[[VAL_2:.*]] = sv.read_inout %a : !hw.inout + // CHECK-NEXT: sv.assign %[[WIRE]], %[[VAL_2]] : i1 + %0 = sv.read_inout %a : !hw.inout + %1 = sv.read_inout %b : !hw.inout + %b = sv.reg init %0 : !hw.inout + %a = sv.reg init %1 : !hw.inout +} diff --git a/test/Conversion/ExportVerilog/sv-dialect.mlir b/test/Conversion/ExportVerilog/sv-dialect.mlir index 7f6dee70bedb..862de84aad7f 100644 --- a/test/Conversion/ExportVerilog/sv-dialect.mlir +++ b/test/Conversion/ExportVerilog/sv-dialect.mlir @@ -1296,7 +1296,7 @@ hw.module.extern @MyExtModule(in %in: i8) hw.module.extern @ExtModule(in %in: i8, out out: i8) // CHECK-LABEL: module InlineBind -// CHEC: output wire_0 +// CHECK: output [7:0] wire_0 hw.module @InlineBind(in %a_in: i8, out wire: i8){ // CHECK: wire [7:0] _ext1_out; // CHECK-NEXT: wire [7:0] _GEN; diff --git a/test/Conversion/FIRRTLToHW/intrinsics.mlir b/test/Conversion/FIRRTLToHW/intrinsics.mlir index bad065757be1..d64f41e71a1a 100644 --- a/test/Conversion/FIRRTLToHW/intrinsics.mlir +++ b/test/Conversion/FIRRTLToHW/intrinsics.mlir @@ -3,9 +3,6 @@ firrtl.circuit "Intrinsics" { // CHECK-LABEL: hw.module @Intrinsics firrtl.module @Intrinsics(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>) { - // CHECK-NEXT: %c0_i32 = hw.constant 0 : i32 - // CHECK-NEXT: %z_i5 = sv.constantZ : i5 - // CHECK-NEXT: %false = hw.constant false // CHECK-NEXT: [[CLK:%.+]] = seq.from_clock %clk // CHECK-NEXT: %x_i1 = sv.constantX : i1 // CHECK-NEXT: [[T0:%.+]] = comb.icmp bin ceq %a, %x_i1 @@ -17,32 +14,10 @@ firrtl.circuit "Intrinsics" { %x0 = firrtl.node interesting_name %0 : !firrtl.uint<1> %x1 = firrtl.node interesting_name %1 : !firrtl.uint<1> - // CHECK-NEXT: [[FOO_STR:%.*]] = sv.constantStr "foo" - // CHECK-NEXT: [[FOO_DECL:%.*]] = sv.reg : !hw.inout - // CHECK-NEXT: sv.initial { - // CHECK-NEXT: [[TMP:%.*]] = sv.system "test$plusargs"([[FOO_STR]]) - // CHECK-NEXT: sv.bpassign [[FOO_DECL]], [[TMP]] - // CHECK-NEXT: } - // CHECK-NEXT: [[FOO:%.*]] = sv.read_inout [[FOO_DECL]] - // CHECK-NEXT: [[BAR_VALUE_DECL:%.*]] = sv.reg : !hw.inout - // CHECK-NEXT: [[BAR_FOUND_DECL:%.*]] = sv.reg : !hw.inout - // CHECK-NEXT: sv.ifdef "SYNTHESIS" { - // CHECK-NEXT: sv.assign [[BAR_VALUE_DECL]], %z_i5 - // CHECK-SAME: #sv.attribute<"This dummy assignment exists to avoid undriven lint warnings - // CHECK-SAME: emitAsComment - // CHECK-NEXT: sv.assign [[BAR_FOUND_DECL]], %false - // CHECK-NEXT: } else { - // CHECK-NEXT: sv.initial { - // CHECK-NEXT: [[BAR_STR:%.*]] = sv.constantStr "bar" - // CHECK-NEXT: [[TMP:%.*]] = sv.system "value$plusargs"([[BAR_STR]], [[BAR_VALUE_DECL]]) - // CHECK-NEXT: [[TMP2:%.*]] = comb.icmp bin ne [[TMP]], %c0_i32 - // CHECK-NEXT: sv.bpassign [[BAR_FOUND_DECL]], [[TMP2]] - // CHECK-NEXT: } - // CHECK-NEXT: } - // CHECK-NEXT: [[BAR_FOUND:%.*]] = sv.read_inout [[BAR_FOUND_DECL]] - // CHECK-NEXT: [[BAR_VALUE:%.*]] = sv.read_inout [[BAR_VALUE_DECL]] - // CHECK-NEXT: %x2 = hw.wire [[FOO]] - // CHECK-NEXT: %x3 = hw.wire [[BAR_FOUND]] + // CHECK-NEXT: [[FOO_TEST:%.+]] = sim.plusargs.test "foo" + // CHECK-NEXT: [[BAR_TEST:%.+]], [[BAR_VALUE:%.+]] = sim.plusargs.value "bar" : i5 + // CHECK-NEXT: %x2 = hw.wire [[FOO_TEST]] + // CHECK-NEXT: %x3 = hw.wire [[BAR_TEST]] // CHECK-NEXT: %x4 = hw.wire [[BAR_VALUE]] %2 = firrtl.int.plusargs.test "foo" %3, %4 = firrtl.int.plusargs.value "bar" : !firrtl.uint<5> @@ -186,4 +161,14 @@ firrtl.circuit "Intrinsics" { // CHECK: hw.wire %in firrtl.int.fpga_probe %clock, %in : !firrtl.uint<8> } + + // CHECK-LABEL: hw.module @ClockInverter + firrtl.module @ClockInverter( + in %clock_in: !firrtl.clock, + out %clock_out: !firrtl.clock + ) { + // CHECK: seq.clock_inv %clock_in + %0 = firrtl.int.clock_inv %clock_in + firrtl.strictconnect %clock_out, %0 : !firrtl.clock + } } diff --git a/test/Conversion/FIRRTLToHW/lower-to-hw.mlir b/test/Conversion/FIRRTLToHW/lower-to-hw.mlir index 5ec14b4c08a0..c31b672e536d 100644 --- a/test/Conversion/FIRRTLToHW/lower-to-hw.mlir +++ b/test/Conversion/FIRRTLToHW/lower-to-hw.mlir @@ -145,7 +145,7 @@ firrtl.circuit "Simple" attributes {annotations = [{class = // CHECK-NEXT: = comb.extract [[CONCAT1]] from 3 : (i8) -> i5 %11 = firrtl.shr %6, 3 : (!firrtl.uint<8>) -> !firrtl.uint<5> - %12 = firrtl.shr %6, 8 : (!firrtl.uint<8>) -> !firrtl.uint<1> + %12 = firrtl.shr %6, 8 : (!firrtl.uint<8>) -> !firrtl.uint<0> // CHECK-NEXT: = comb.extract %in3 from 7 : (i8) -> i1 %13 = firrtl.shr %in3, 8 : (!firrtl.sint<8>) -> !firrtl.sint<1> @@ -361,28 +361,14 @@ firrtl.circuit "Simple" attributes {annotations = [{class = // CHECK-LABEL: hw.module private @Stop firrtl.module private @Stop(in %clock1: !firrtl.clock, in %clock2: !firrtl.clock, in %reset: !firrtl.uint<1>) { - // CHECK: [[CLOCK2:%.+]] = seq.from_clock %clock2 - // CHECK: [[CLOCK1:%.+]] = seq.from_clock %clock1 - - // CHECK-NEXT: sv.ifdef "SYNTHESIS" { - // CHECK-NEXT: } else { - // CHECK-NEXT: sv.always posedge [[CLOCK1]] { - // CHECK-NEXT: %STOP_COND_ = sv.macro.ref @STOP_COND_ - // CHECK-NEXT: [[COND:%.+]] = comb.and bin %STOP_COND_, %reset : i1 - // CHECK-NEXT: sv.if [[COND]] { - // CHECK-NEXT: sv.fatal - // CHECK-NEXT: } - // CHECK-NEXT: } + // CHECK-NEXT: [[STOP_COND_1:%.+]] = sv.macro.ref @STOP_COND_ + // CHECK-NEXT: [[COND:%.+]] = comb.and bin [[STOP_COND_1]], %reset : i1 + // CHECK-NEXT: sim.fatal %clock1, [[COND]] firrtl.stop %clock1, %reset, 42 : !firrtl.clock, !firrtl.uint<1> - // CHECK-NEXT: sv.always posedge [[CLOCK2]] { - // CHECK-NEXT: %STOP_COND_ = sv.macro.ref @STOP_COND_ - // CHECK-NEXT: [[COND:%.+]] = comb.and bin %STOP_COND_, %reset : i1 - // CHECK-NEXT: sv.if [[COND]] { - // CHECK-NEXT: sv.finish - // CHECK-NEXT: } - // CHECK-NEXT: } - // CHECK-NEXT: } + // CHECK-NEXT: [[STOP_COND_2:%.+]] = sv.macro.ref @STOP_COND_ + // CHECK-NEXT: [[COND:%.+]] = comb.and bin [[STOP_COND_2:%.+]], %reset : i1 + // CHECK-NEXT: sim.finish %clock2, [[COND]] firrtl.stop %clock2, %reset, 0 : !firrtl.clock, !firrtl.uint<1> } diff --git a/test/Conversion/FIRRTLToHW/zero-width.mlir b/test/Conversion/FIRRTLToHW/zero-width.mlir index 080f041efd40..355be5eaaac2 100644 --- a/test/Conversion/FIRRTLToHW/zero-width.mlir +++ b/test/Conversion/FIRRTLToHW/zero-width.mlir @@ -79,4 +79,14 @@ firrtl.circuit "Arithmetic" { // CHECK-NEXT: hw.output } + // Check that a zero-width value shifted right produces a zero. + // See: https://github.com/llvm/circt/issues/6652 + // CHECK-LABEL: hw.module @ShrZW + firrtl.module @ShrZW(in %x: !firrtl.uint<0>, out %out: !firrtl.uint<1>) attributes {convention = #firrtl} { + %0 = firrtl.shr %x, 5 : (!firrtl.uint<0>) -> !firrtl.uint<0> + firrtl.connect %out, %0 : !firrtl.uint<1>, !firrtl.uint<0> + // CHECK: %[[false:.+]] = hw.constant false + // CHECK-NEXT: hw.output %false + } + } diff --git a/test/Conversion/HWToBTOR2/alias.mlir b/test/Conversion/HWToBTOR2/alias.mlir new file mode 100644 index 000000000000..b6765cf26386 --- /dev/null +++ b/test/Conversion/HWToBTOR2/alias.mlir @@ -0,0 +1,48 @@ +// RUN: circt-opt %s --convert-hw-to-btor2 -o tmp.mlir | FileCheck %s + +module { + //CHECK: [[NID0:[0-9]+]] sort bitvec 1 + //CHECK: [[NID1:[0-9]+]] input [[NID0]] reset + //CHECK: [[NID2:[0-9]+]] input [[NID0]] in + hw.module @Alias(in %clock : !seq.clock, in %reset : i1, in %in : i1, out out : i32) { + //CHECK: [[NID3:[0-9]+]] sort bitvec 32 + //CHECK: [[NID4:[0-9]+]] state [[NID3]] count + + //CHECK: [[NID5:[0-9]+]] constd [[NID0]] 0 + %false = hw.constant false + //CHECK: [[NID6:[0-9]+]] constd [[NID3]] 0 + %c0_i32 = hw.constant 0 : i32 + //CHECK: [[NID7:[0-9]+]] constd [[NID0]] -1 + %true = hw.constant true + //CHECK: [[NID8:[0-9]+]] sort bitvec 2 + //CHECK: [[NID9:[0-9]+]] constd [[NID8]] -2 + %c-2_i2 = hw.constant -2 : i2 + %count = seq.compreg %6, %clock reset %reset, %c0_i32 : i32 + %b = hw.wire %2 : i32 + %d = hw.wire %b : i32 + %c = hw.wire %5 : i32 + //CHECK: [[NID10:[0-9]+]] sort bitvec 33 + //CHECK: [[NID11:[0-9]+]] concat [[NID10]] [[NID5]] [[NID4]] + %0 = comb.concat %false, %count : i1, i32 + //CHECK: [[NID12:[0-9]+]] constd [[NID10]] 1 + %c1_i33 = hw.constant 1 : i33 + //CHECK: [[NID13:[0-9]+]] sub [[NID10]] [[NID11]] [[NID12]] + %1 = comb.sub bin %0, %c1_i33 : i33 + //CHECK: [[NID14:[0-9]+]] slice [[NID3]] [[NID13]] 31 0 + %2 = comb.extract %1 from 0 : (i33) -> i32 + //CHECK: [[NID15:[0-9]+]] concat [[NID10]] [[NID5]] [[NID14]] + %3 = comb.concat %false, %d : i1, i32 + //CHECK: [[NID16:[0-9]+]] constd [[NID10]] 2 + %c2_i33 = hw.constant 2 : i33 + //CHECK: [[NID17:[0-9]+]] add [[NID10]] [[NID15]] [[NID16]] + %4 = comb.add bin %3, %c2_i33 : i33 + //CHECK: [[NID18:[0-9]+]] slice [[NID3]] [[NID17]] 31 0 + %5 = comb.extract %4 from 0 : (i33) -> i32 + //CHECK: [[NID19:[0-9]+]] ite [[NID3]] [[NID2]] [[NID14]] [[NID18]] + //CHECK: [[NID20:[0-9]+]] ite [[NID3]] [[NID1]] [[NID6]] [[NID19]] + %6 = comb.mux bin %in, %b, %c : i32 + //CHECK: [[NID21:[0-9]+]] next [[NID3]] [[NID4]] [[NID20]] + hw.output %count : i32 + } + +} diff --git a/test/Conversion/HWToBTOR2/compreg.mlir b/test/Conversion/HWToBTOR2/compreg.mlir new file mode 100644 index 000000000000..a64181561cd4 --- /dev/null +++ b/test/Conversion/HWToBTOR2/compreg.mlir @@ -0,0 +1,85 @@ +// RUN: circt-opt %s --convert-hw-to-btor2 -o tmp.mlir | FileCheck %s + +module { + //CHECK: [[NID0:[0-9]+]] sort bitvec 1 + //CHECK: [[NID1:[0-9]+]] input [[NID0]] reset + //CHECK: [[NID2:[0-9]+]] input [[NID0]] en + hw.module @Counter(in %clock : !seq.clock, in %reset : i1, in %en : i1) { + %0 = seq.from_clock %clock + // Registers are all emitted before any other operation + //CHECK: [[NID6:[0-9]+]] sort bitvec 32 + //CHECK: [[NID12:[0-9]+]] state [[NID6]] count + + //CHECK: [[NID3:[0-9]+]] sort bitvec 28 + //CHECK: [[NID4:[0-9]+]] constd [[NID3]] 0 + %c0_i28 = hw.constant 0 : i28 + + //CHECK: [[NID5:[0-9]+]] constd [[NID0]] 0 + %false = hw.constant false + + //CHECK: [[NID7:[0-9]+]] constd [[NID6]] 22 + %c22_i32 = hw.constant 22 : i32 + + //CHECK: [[NID8:[0-9]+]] constd [[NID0]] -1 + %true = hw.constant true + + //CHECK: [[NID9:[0-9]+]] sort bitvec 4 + //CHECK: [[NID10:[0-9]+]] constd [[NID9]] -6 + %c-6_i4 = hw.constant -6 : i4 + + //CHECK: [[NID11:[0-9]+]] constd [[NID6]] 0 + %c0_i32 = hw.constant 0 : i32 + + %count = seq.compreg %9, %clock reset %reset, %c0_i32 : i32 + + //CHECK: [[NID13:[0-9]+]] eq [[NID0]] [[NID12]] [[NID7]] + %1 = comb.icmp bin eq %count, %c22_i32 : i32 + + //CHECK: [[NID14:[0-9]+]] and [[NID0]] [[NID13]] [[NID2]] + %2 = comb.and bin %1, %en : i1 + + //CHECK: [[NID15:[0-9]+]] ite [[NID6]] [[NID14]] [[NID11]] [[NID12]] + %3 = comb.mux bin %2, %c0_i32, %count : i32 + + //CHECK: [[NID16:[0-9]+]] neq [[NID0]] [[NID12]] [[NID7]] + %4 = comb.icmp bin ne %count, %c22_i32 : i32 + + //CHECK: [[NID17:[0-9]+]] and [[NID0]] [[NID16]] [[NID2]] + %5 = comb.and bin %4, %en : i1 + + //CHECK: [[NID18:[0-9]+]] sort bitvec 33 + //CHECK: [[NID19:[0-9]+]] concat [[NID18]] [[NID5]] [[NID12]] + %6 = comb.concat %false, %count : i1, i32 + + //CHECK: [[NID20:[0-9]+]] constd [[NID18]] 1 + %c1_i33 = hw.constant 1 : i33 + + //CHECK: [[NID21:[0-9]+]] add [[NID18]] [[NID19]] [[NID20]] + %7 = comb.add bin %6, %c1_i33 : i33 + + //CHECK: [[NID22:[0-9]+]] slice [[NID6]] [[NID21]] 31 0 + %8 = comb.extract %7 from 0 : (i33) -> i32 + + //CHECK: [[NID23:[0-9]+]] ite [[NID6]] [[NID17]] [[NID22]] [[NID15]] + %9 = comb.mux bin %5, %8, %3 : i32 + + //CHECK: [[NID24:[0-9]+]] constd [[NID6]] 10 + %c10_i32 = hw.constant 10 : i32 + + //CHECK: [[NID25:[0-9]+]] neq [[NID0]] [[NID12]] [[NID24]] + %10 = comb.icmp bin ne %count, %c10_i32 : i32 + sv.always posedge %0 { + sv.if %en { + + //CHECK: [[NID26:[0-9]+]] implies [[NID0]] [[NID2]] [[NID25]] + //CHECK: [[NID27:[0-9]+]] not [[NID0]] [[NID26]] + //CHECK: [[NID28:[0-9]+]] bad [[NID27]] + sv.assert %10, immediate message "Counter reached 10!" + } + } + hw.output + + //CHECK: [[NID30:[0-9]+]] ite [[NID6]] [[NID1]] [[NID11]] [[NID23]] + //CHECK: [[NID31:[0-9]+]] next [[NID6]] [[NID12]] [[NID30]] + } +} diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index 63e3cd7230dd..7e7993a6ee76 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -1,5 +1,41 @@ // RUN: circt-translate --import-verilog %s | FileCheck %s +// REQUIRES: slang +// Internal issue in Slang v3 about jump depending on uninitialised value. +// UNSUPPORTED: valgrind + +// CHECK-LABEL: moore.module @Empty { +// CHECK: } +module Empty; + ; // empty member +endmodule + +// CHECK-LABEL: moore.module @NestedA { +// CHECK: moore.instance "NestedB" @NestedB +// CHECK: } +// CHECK-LABEL: moore.module @NestedB { +// CHECK: moore.instance "NestedC" @NestedC +// CHECK: } +// CHECK-LABEL: moore.module @NestedC { +// CHECK: } +module NestedA; + module NestedB; + module NestedC; + endmodule + endmodule +endmodule + +// CHECK-LABEL: moore.module @Child { +// CHECK: } +module Child; +endmodule + +// CHECK-LABEL: moore.module @Parent +// CHECK: moore.instance "child" @Child +// CHECK: } +module Parent; + Child child(); +endmodule // CHECK-LABEL: moore.module @Variables module Variables(); @@ -33,6 +69,7 @@ module Expressions(); // CHECK: %b = moore.variable : !moore.int // CHECK: %c = moore.variable : !moore.int int a, b, c; + int unsigned u; bit [1:0][3:0] v; integer d, e, f; bit x; @@ -44,7 +81,7 @@ module Expressions(); // Unary operators - // CHECK: moore.mir.bpassign %c, %a : !moore.int + // CHECK: moore.bpassign %c, %a : !moore.int c = a; // CHECK: moore.neg %a : !moore.int c = -a; @@ -81,23 +118,23 @@ module Expressions(); // CHECK: [[TMP1:%.+]] = moore.constant 1 : !moore.int // CHECK: [[TMP2:%.+]] = moore.add %a, [[TMP1]] : !moore.int - // CHECK: moore.mir.bpassign %a, [[TMP2]] - // CHECK: moore.mir.bpassign %c, %a + // CHECK: moore.bpassign %a, [[TMP2]] + // CHECK: moore.bpassign %c, %a c = a++; // CHECK: [[TMP1:%.+]] = moore.constant 1 : !moore.int // CHECK: [[TMP2:%.+]] = moore.sub %a, [[TMP1]] : !moore.int - // CHECK: moore.mir.bpassign %a, [[TMP2]] - // CHECK: moore.mir.bpassign %c, %a + // CHECK: moore.bpassign %a, [[TMP2]] + // CHECK: moore.bpassign %c, %a c = a--; // CHECK: [[TMP1:%.+]] = moore.constant 1 : !moore.int // CHECK: [[TMP2:%.+]] = moore.add %a, [[TMP1]] : !moore.int - // CHECK: moore.mir.bpassign %a, [[TMP2]] - // CHECK: moore.mir.bpassign %c, [[TMP2]] + // CHECK: moore.bpassign %a, [[TMP2]] + // CHECK: moore.bpassign %c, [[TMP2]] c = ++a; // CHECK: [[TMP1:%.+]] = moore.constant 1 : !moore.int // CHECK: [[TMP2:%.+]] = moore.sub %a, [[TMP1]] : !moore.int - // CHECK: moore.mir.bpassign %a, [[TMP2]] - // CHECK: moore.mir.bpassign %c, [[TMP2]] + // CHECK: moore.bpassign %a, [[TMP2]] + // CHECK: moore.bpassign %c, [[TMP2]] c = --a; // Binary operators @@ -163,23 +200,66 @@ module Expressions(); // CHECK: moore.lt %a, %b : !moore.int -> !moore.bit c = a < b; - // CHECK: moore.mir.logic and %a, %b : !moore.int, !moore.int + // CHECK: [[A:%.+]] = moore.bool_cast %a : !moore.int -> !moore.bit + // CHECK: [[B:%.+]] = moore.bool_cast %b : !moore.int -> !moore.bit + // CHECK: moore.and [[A]], [[B]] : !moore.bit c = a && b; - // CHECK: moore.mir.logic equiv %a, %b : !moore.int, !moore.int - c = a <-> b; - // CHECK: moore.mir.logic impl %a, %b : !moore.int, !moore.int - c = a -> b; - // CHECK: moore.mir.logic or %a, %b : !moore.int, !moore.int + // CHECK: [[A:%.+]] = moore.bool_cast %a : !moore.int -> !moore.bit + // CHECK: [[B:%.+]] = moore.bool_cast %b : !moore.int -> !moore.bit + // CHECK: moore.or [[A]], [[B]] : !moore.bit c = a || b; + // CHECK: [[A:%.+]] = moore.bool_cast %a : !moore.int -> !moore.bit + // CHECK: [[B:%.+]] = moore.bool_cast %b : !moore.int -> !moore.bit + // CHECK: [[NOT_A:%.+]] = moore.not [[A]] : !moore.bit + // CHECK: moore.or [[NOT_A]], [[B]] : !moore.bit + c = a -> b; + // CHECK: [[A:%.+]] = moore.bool_cast %a : !moore.int -> !moore.bit + // CHECK: [[B:%.+]] = moore.bool_cast %b : !moore.int -> !moore.bit + // CHECK: [[NOT_A:%.+]] = moore.not [[A]] : !moore.bit + // CHECK: [[NOT_B:%.+]] = moore.not [[B]] : !moore.bit + // CHECK: [[BOTH:%.+]] = moore.and [[A]], [[B]] : !moore.bit + // CHECK: [[NOT_BOTH:%.+]] = moore.and [[NOT_A]], [[NOT_B]] : !moore.bit + // CHECK: moore.or [[BOTH]], [[NOT_BOTH]] : !moore.bit + c = a <-> b; - // CHECK: moore.mir.shl %a, %b : !moore.int, !moore.int + // CHECK: moore.shl %a, %b : !moore.int, !moore.int c = a << b; - // CHECK: moore.mir.shr %a, %b : !moore.int, !moore.int + // CHECK: moore.shr %a, %b : !moore.int, !moore.int c = a >> b; - // CHECK: moore.mir.shl arithmetic %a, %b : !moore.int, !moore.int + // CHECK: moore.shl %a, %b : !moore.int, !moore.int c = a <<< b; - // CHECK: moore.mir.shr arithmetic %a, %b : !moore.int, !moore.int + // CHECK: moore.ashr %a, %b : !moore.int, !moore.int c = a >>> b; + // CHECK: moore.shr %u, %b : !moore.int, !moore.int + c = u >>> b; + + // Assign operators + + // CHECK: moore.add %a, %b : !moore.int + a += b; + // CHECK: moore.sub %a, %b : !moore.int + a -= b; + // CHECK: moore.mul %a, %b : !moore.int + a *= b; + // CHECK: moore.div %f, %d : !moore.integer + f /= d; + // CHECK: moore.mod %f, %d : !moore.integer + f %= d; + // CHECK: moore.and %a, %b : !moore.int + a &= b; + // CHECK: moore.or %a, %b : !moore.int + a |= b; + // CHECK: moore.xor %a, %b : !moore.int + a ^= b; + // CHECK: moore.shl %a, %b : !moore.int, !moore.int + a <<= b; + // CHECK: moore.shl %a, %b : !moore.int, !moore.int + a <<<= b; + // CHECK: moore.shr %a, %b : !moore.int, !moore.int + a >>= b; + // CHECK: moore.ashr %a, %b : !moore.int, !moore.int + a >>>= b; + end endmodule @@ -221,11 +301,11 @@ module Assignments(); int a, b; initial begin - // CHECK: moore.mir.bpassign %a, %b : !moore.int + // CHECK: moore.bpassign %a, %b : !moore.int a = b; - // CHECK: moore.mir.passign %a, %b : !moore.int + // CHECK: moore.passign %a, %b : !moore.int a <= b; - // CHECK: moore.mir.pcassign %a, %b : !moore.int + // CHECK: moore.pcassign %a, %b : !moore.int assign a = b; end endmodule @@ -238,8 +318,8 @@ module Statements(); int a, b; initial begin - // CHECK: [[ZERO:%.+]] = moore.constant 0 : !moore.int - // CHECK: [[COND:%.+]] = moore.mir.ne %a, [[ZERO]] : !moore.int, !moore.int + // CHECK: [[TMP:%.+]] = moore.bool_cast %a : !moore.int -> !moore.bit + // CHECK: [[COND:%.+]] = moore.conversion [[TMP]] : !moore.bit -> i1 // CHECK: scf.if [[COND]] if (a) ; diff --git a/test/Conversion/ImportVerilog/errors.sv b/test/Conversion/ImportVerilog/errors.sv index 1d402c8b19ab..d56891b98aaa 100644 --- a/test/Conversion/ImportVerilog/errors.sv +++ b/test/Conversion/ImportVerilog/errors.sv @@ -1,9 +1,15 @@ // RUN: circt-translate --import-verilog --verify-diagnostics --split-input-file %s +// REQUIRES: slang + +// Internal issue in Slang v3 about jump depending on uninitialised value. +// UNSUPPORTED: valgrind // expected-error @below {{expected ';'}} module Foo 4; endmodule +// ----- + // expected-note @below {{expanded from macro 'FOO'}} `define FOO input // expected-note @below {{expanded from macro 'BAR'}} @@ -15,6 +21,24 @@ endmodule // ----- module Foo; - // expected-warning @below {{unsupported construct ignored}} + mailbox a; + string b; + // expected-error @below {{value of type 'string' cannot be assigned to type 'mailbox'}} + initial a = b; +endmodule + +// ----- + +module Foo; + // expected-error @below {{unsupported construct}} genvar a; endmodule + +// ----- + +// expected-error @below {{unsupported top-level construct}} +package Foo; +endpackage + +module Bar; +endmodule diff --git a/test/Conversion/ImportVerilog/hierarchy.sv b/test/Conversion/ImportVerilog/hierarchy.sv index 6514245535b8..e91b24b688a0 100644 --- a/test/Conversion/ImportVerilog/hierarchy.sv +++ b/test/Conversion/ImportVerilog/hierarchy.sv @@ -1,4 +1,5 @@ // RUN: circt-translate --import-verilog %s | FileCheck %s +// REQUIRES: slang // CHECK-LABEL: moore.module @Top1 module Top1; @@ -23,10 +24,6 @@ module Child; Parametrized #(9001) p2(); endmodule -// CHECK-NOT: Bar -package Bar; -endpackage - // CHECK-LABEL: moore.module @Parametrized // CHECK-NEXT: %x = moore.variable : !moore.packed> // CHECK-LABEL: moore.module @Parametrized_1 diff --git a/test/Conversion/ImportVerilog/loop.sv b/test/Conversion/ImportVerilog/loop.sv new file mode 100644 index 000000000000..9c01c3fcb739 --- /dev/null +++ b/test/Conversion/ImportVerilog/loop.sv @@ -0,0 +1,132 @@ +// RUN: circt-translate --import-verilog %s | FileCheck %s + + +// CHECK-LABEL: moore.module @while_tb +module while_tb (); + static int i = 0; + initial begin + // CHECK: scf.while : () -> () { + // CHECK: [[TMP1:%.+]] = moore.constant 2 : !moore.int + // CHECK: [[TMP2:%.+]] = moore.ne %i, [[TMP1]] : !moore.int -> !moore.bit + // CHECK: [[TMP3:%.+]] = moore.bool_cast [[TMP2]] : !moore.bit -> !moore.bit + // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.bit -> i1 + // CHECK: scf.condition([[TMP4]]) + // CHECK: } do { + // CHECK: [[TMP1:%.+]] = moore.constant 1 : !moore.int + // CHECK: [[TMP2:%.+]] = moore.add %i, [[TMP1]] : !moore.int + // CHECK: moore.bpassign %i, [[TMP2]] : !moore.int + // CHECK: scf.yield + // CHECK: } + while(i != 2)begin + i++; + end + end +endmodule + +// CHECK-LABEL: moore.module @dowhile_tb +module dowhile_tb (); + static int i = 0; + initial begin + // CHECK: scf.while : () -> () { + // CHECK: [[TMP1:%.+]] = moore.constant 1 : !moore.int + // CHECK: [[TMP2:%.+]] = moore.add %i, [[TMP1]] : !moore.int + // CHECK: moore.bpassign %i, [[TMP2]] : !moore.int + // CHECK: [[TMP3:%.+]] = moore.constant 2 : !moore.int + // CHECK: [[TMP4:%.+]] = moore.ne %i, [[TMP3]] : !moore.int -> !moore.bit + // CHECK: [[TMP5:%.+]] = moore.bool_cast [[TMP4]] : !moore.bit -> !moore.bit + // CHECK: [[TMP6:%.+]] = moore.conversion [[TMP5]] : !moore.bit -> i1 + // CHECK: scf.condition([[TMP6]]) + // CHECK: } do { + // CHECK: scf.yield + // CHECK: } + do begin + i++; + end while(i != 2); + end +endmodule + +// CHECK-LABEL: moore.module @for_tb +module for_tb (); + // CHECK: [[TMP0:%.+]] = moore.constant 0 : !moore.int + // CHECK: %i = moore.variable [[TMP0]] : !moore.int + // CHECK: scf.while : () -> () { + // CHECK: [[TMP1:%.+]] = moore.constant 2 : !moore.int + // CHECK: [[TMP2:%.+]] = moore.lt %i, [[TMP1]] : !moore.int -> !moore.bit + // CHECK: [[TMP3:%.+]] = moore.bool_cast [[TMP2]] : !moore.bit -> !moore.bit + // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.bit -> i1 + // CHECK: scf.condition([[TMP4]]) + // CHECK: } do { + // CHECK: [[TMP1:%.+]] = moore.constant 1 : !moore.int + // CHECK: [[TMP2:%.+]] = moore.add %i, [[TMP1]] : !moore.int + // CHECK: moore.bpassign %i, [[TMP2]] : !moore.int + // CHECK: scf.yield + // CHECK: } + initial begin + for(int i=0;i<2;++i)begin + end + end +endmodule + +// CHECK-LABEL: moore.module @repeat_tb { +// CHECK: [[TMP1:%.*]] = moore.variable {{%.+}} : !moore.int +// CHECK: moore.procedure initial { +// CHECK: scf.while ([[TMP0:%.*]] = [[TMP1]]) : (!moore.int) -> !moore.int { +// CHECK: [[TMP2:%.+]] = moore.bool_cast [[TMP1]] : !moore.int -> !moore.bit +// CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.bit -> i1 +// CHECK: scf.condition([[TMP3]]) [[TMP0]] : !moore.int +// CHECK: } do { +// CHECK: ^bb0([[TMP4:%.+]]: !moore.int): +// CHECK: [[TMP2:%.+]] = moore.constant 1 : !moore.int +// CHECK: [[TMP3:%.+]] = moore.sub [[TMP4]], [[TMP2]] : !moore.int +// CHECK: scf.yield [[TMP3]] : !moore.int +module repeat_tb (); + int a = 10; + initial begin + repeat(a)begin + end + end +endmodule + +// CHECK-LABEL: moore.module @TestForeach +// CHECK: [[t0:%.+]] = moore.constant 1 : !moore.int +// CHECK: [[t2:%.+]] = moore.constant 2 : !moore.int +// CHECK: [[t3:%.+]] = moore.constant 0 : !moore.int +// CHECK: [[t4:%.+]] = scf.while ([[arg0:%.+]] = [[t3]]) : (!moore.int) -> !moore.int { +// CHECK: [[t7:%.+]] = moore.lt [[t3]], [[t2]] : !moore.int -> !moore.bit +// CHECK: [[t8:%.+]] = moore.conversion [[t7]] : !moore.bit -> i1 +// CHECK: scf.condition([[t8]]) [[arg0]] : !moore.int +// CHECK: } do { +// CHECK: ^bb0([[arg0]]: !moore.int): +// CHECK: [[t7:%.+]] = moore.constant 0 : !moore.int +// CHECK: [[t8:%.+]] = moore.constant 3 : !moore.int +// CHECK: [[t9:%.+]] = moore.constant 0 : !moore.int +// CHECK: [[t10:%.+]] = scf.while ([[arg1:%.+]] = [[t9]]) : (!moore.int) -> !moore.int { +// CHECK: [[t12:%.+]] = moore.lt [[t9]], [[t8]] : !moore.int -> !moore.bit +// CHECK: [[t13:%.+]] = moore.conversion [[t12]] : !moore.bit -> i1 +// CHECK: scf.condition([[t13]]) [[arg1]] : !moore.int +// CHECK: } do { +// CHECK: ^bb0([[arg1]]: !moore.int): +// CHECK: [[t12:%.+]] = moore.constant 1 : !moore.int +// CHECK: [[t13:%.+]] = moore.add %a, [[t12]] : !moore.int +// CHECK: moore.bpassign %a, [[t13]] : !moore.int +// CHECK: [[t14:%.+]] = moore.add [[arg1]], [[t0]] : !moore.int +// CHECK: scf.yield [[t14]] : !moore.int +// CHECK: } +// CHECK: [[t11:%.+]] = moore.add [[arg0]], [[t0]] : !moore.int +// CHECK: scf.yield [[t11]] : !moore.int +// CHECK: } +// CHECK: [[t5:%.+]] = moore.constant 1 : !moore.int +// CHECK: [[t6:%.+]] = moore.add %a, [[t5]] : !moore.int +// CHECK: moore.bpassign %a, [[t6]] : !moore.int +// CHECK:} + +module TestForeach; +bit array[3][4][4][4]; +int a; +initial begin + foreach (array[i, ,m,]) begin + a++; + end + a++; +end +endmodule diff --git a/test/Conversion/ImportVerilog/procedures.sv b/test/Conversion/ImportVerilog/procedures.sv index 823f183b2094..c348d47b2bd5 100644 --- a/test/Conversion/ImportVerilog/procedures.sv +++ b/test/Conversion/ImportVerilog/procedures.sv @@ -1,4 +1,5 @@ // RUN: circt-translate --import-verilog %s | FileCheck %s +// REQUIRES: slang // CHECK-LABEL: moore.module @Procedures module Procedures(); diff --git a/test/Conversion/ImportVerilog/test.sv b/test/Conversion/ImportVerilog/test.sv index 0f01feca468f..4d5539c4e6cc 100644 --- a/test/Conversion/ImportVerilog/test.sv +++ b/test/Conversion/ImportVerilog/test.sv @@ -1,9 +1,8 @@ // RUN: circt-translate --import-verilog %s | FileCheck %s +// REQUIRES: slang // CHECK-LABEL: moore.module @Port -// CHECK-NEXT: moore.port In "a" -// CHECK-NEXT: %a = moore.net "wire" : !moore.packed> -// CHECK-NEXT: moore.port Out "b" -// CHECK-NEXT: %b = moore.net "wire" : !moore.packed> +// CHECK-NEXT: %a = moore.port In : !moore.packed> +// CHECK-NEXT: %b = moore.port Out : !moore.packed> module Port(input [3:0] a, output [3:0] b); endmodule diff --git a/test/Conversion/ImportVerilog/types.sv b/test/Conversion/ImportVerilog/types.sv index 1c4bc698897f..1ea4a4910f77 100644 --- a/test/Conversion/ImportVerilog/types.sv +++ b/test/Conversion/ImportVerilog/types.sv @@ -1,4 +1,5 @@ // RUN: circt-translate --import-verilog %s | FileCheck %s +// REQUIRES: slang // CHECK-LABEL: moore.module @Enums module Enums; diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 0ae3727f3d4c..904c8d0833f1 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -1,91 +1,112 @@ // RUN: circt-opt %s --convert-moore-to-core --verify-diagnostics | FileCheck %s -// CHECK-LABEL: func @FuncArgsAndReturns -// CHECK-SAME: (%arg0: i8, %arg1: i32, %arg2: i1) -> i8 -func.func @FuncArgsAndReturns(%arg0: !moore.byte, %arg1: !moore.int, %arg2: !moore.bit) -> !moore.byte { - // CHECK-NEXT: return %arg0 : i8 - return %arg0 : !moore.byte -} +// CHECK-LABEL: hw.module @MoorePortsAndAssignments(in +// CHECK-SAME: %[[IN1:.*]] : i8, in +// CHECK-SAME: %[[IN2:.*]] : i32, in +// CHECK-SAME: %[[IN3:.*]] : i1, out res : i1) { +moore.module @MoorePortsAndAssignments { + %arg0 = moore.port In : !moore.byte + %arg1 = moore.port In : !moore.int + %arg2 = moore.port In : !moore.bit + %res = moore.port Out : !moore.logic -// CHECK-LABEL: func @ControlFlow -// CHECK-SAME: (%arg0: i32, %arg1: i1) -func.func @ControlFlow(%arg0: !moore.int, %arg1: i1) { - // CHECK-NEXT: cf.br ^bb1(%arg0 : i32) - // CHECK-NEXT: ^bb1(%0: i32): - // CHECK-NEXT: cf.cond_br %arg1, ^bb1(%0 : i32), ^bb2(%arg0 : i32) - // CHECK-NEXT: ^bb2(%1: i32): - // CHECK-NEXT: return - cf.br ^bb1(%arg0: !moore.int) -^bb1(%0: !moore.int): - cf.cond_br %arg1, ^bb1(%0 : !moore.int), ^bb2(%arg0 : !moore.int) -^bb2(%1: !moore.int): - return + // CHECK: %[[EXT1:.*]] = comb.extract %[[IN1]] from 1 : (i8) -> i7 + // CHECK: %[[CONST1:.*]] = hw.constant 0 : i7 + // CHECK: %[[ICMP:.*]] = comb.icmp eq %[[EXT1]], %[[CONST1]] : i7 + // CHECK: %[[EXT2:.*]] = comb.extract %[[IN1]] from 0 : (i8) -> i1 + // CHECK: %[[CONST2:.*]] = hw.constant true + // CHECK: %[[MUX:.*]] = comb.mux %[[ICMP]], %[[EXT2]], %[[CONST2]] : i1 + // CHECK: %[[OUT:.*]] = hw.bitcast %[[MUX]] : (i1) -> i1 + %0 = moore.conversion %arg0 : !moore.byte -> !moore.logic + moore.mir.cassign %res, %0 : !moore.logic + // CHECK: hw.output %[[OUT]] : i1 } -// CHECK-LABEL: func @Calls -// CHECK-SAME: (%arg0: i8, %arg1: i32, %arg2: i1) -> i8 -func.func @Calls(%arg0: !moore.byte, %arg1: !moore.int, %arg2: !moore.bit) -> !moore.byte { - // CHECK-NEXT: %true = - // CHECK-NEXT: call @ControlFlow(%arg1, %true) : (i32, i1) -> () - // CHECK-NEXT: [[TMP:%.+]] = call @FuncArgsAndReturns(%arg0, %arg1, %arg2) : (i8, i32, i1) -> i8 - // CHECK-NEXT: return [[TMP]] : i8 - %true = hw.constant true - call @ControlFlow(%arg1, %true) : (!moore.int, i1) -> () - %0 = call @FuncArgsAndReturns(%arg0, %arg1, %arg2) : (!moore.byte, !moore.int, !moore.bit) -> !moore.byte - return %0 : !moore.byte +// CHECK-LABEL: hw.module @Instances(in +// CHECK-SAME: %[[VAL_0:.*]] : i8, in +// CHECK-SAME: %[[VAL_1:.*]] : i32, in +// CHECK-SAME: %[[VAL_2:.*]] : i1, out out : i1) +moore.module @Instances { + %arg0 = moore.port In : !moore.byte + %arg1 = moore.port In : !moore.int + %arg2 = moore.port In : !moore.bit + %out = moore.port Out : !moore.logic + + // CHECK: %[[VAL_3:.*]] = hw.instance "MoorePortsAndAssignments_1" @MoorePortsAndAssignments(arg0: %[[VAL_0]]: i8, arg1: %[[VAL_1]]: i32, arg2: %[[VAL_2]]: i1) -> (res: i1) + moore.instance "MoorePortsAndAssignments_1" @MoorePortsAndAssignments(%arg0, %arg1, %arg2) -> (%out) : (!moore.byte, !moore.int, !moore.bit) -> !moore.logic + + // CHECK: hw.output %[[VAL_3]] : i1 } -// CHECK-LABEL: func @UnrealizedConversionCast -func.func @UnrealizedConversionCast(%arg0: !moore.byte) -> !moore.shortint { - // CHECK-NEXT: [[TMP:%.+]] = comb.concat %arg0, %arg0 : i8, i8 - // CHECK-NEXT: return [[TMP]] : i16 - %0 = builtin.unrealized_conversion_cast %arg0 : !moore.byte to i8 - %1 = comb.concat %0, %0 : i8, i8 - %2 = builtin.unrealized_conversion_cast %1 : i16 to !moore.shortint - return %2 : !moore.shortint +// CHECK-LABEL: hw.module @ConversionCast(in +// CHECK-SAME: %[[IN:.*]] : i1, out res : i8) { +moore.module @ConversionCast { + %arg0 = moore.port In : !moore.bit + %res = moore.port Out : !moore.byte + + // CHECK: %[[CAT1:.*]] = comb.concat %[[IN]], %[[IN]] : i1, i1 + // CHECK: %[[CONST:.*]] = hw.constant 0 : i6 + // CHECK: %[[CAT2:.*]] = comb.concat %[[CONST]], %[[CAT1]] : i6, i2 + %1 = moore.mir.concat %arg0, %arg0 : (!moore.bit, !moore.bit) -> !moore.packed> + + // CHECK: %[[OUT:.*]] = hw.bitcast %[[CAT2]] : (i8) -> i8 + %2 = moore.conversion %1 : !moore.packed> -> !moore.byte + moore.mir.cassign %res, %2 : !moore.byte + // CHECK: hw.output %[[OUT]] : i8 } -// CHECK-LABEL: func @Expressions -func.func @Expressions(%arg0: !moore.bit, %arg1: !moore.logic, %arg2: !moore.packed>, %arg3: !moore.packed, 4:0>>) { +// CHECK-LABEL: hw.module @Expressions(in +moore.module @Expressions { + %arg0 = moore.port In : !moore.bit + %arg1 = moore.port In : !moore.logic + %arg2 = moore.port In : !moore.packed> + %arg3 = moore.port In : !moore.packed, 4:0>> + %arg4 = moore.port In : !moore.bit + // CHECK-NEXT: %0 = comb.concat %arg0, %arg0 : i1, i1 // CHECK-NEXT: %1 = comb.concat %arg1, %arg1 : i1, i1 - %0 = moore.mir.concat %arg0, %arg0 : (!moore.bit, !moore.bit) -> !moore.packed> - %1 = moore.mir.concat %arg1, %arg1 : (!moore.logic, !moore.logic) -> !moore.packed> - // CHECK-NEXT: %[[V0:.+]] = hw.constant 0 : i5 - // CHECK-NEXT: %[[V1:.+]] = comb.concat %[[V0]], %arg0 : i5, i1 - // CHECK-NEXT: comb.shl %arg2, %[[V1]] : i6 - // CHECK-NEXT: %[[V2:.+]] = comb.extract %arg2 from 5 : (i6) -> i1 - // CHECK-NEXT: %[[V3:.+]] = hw.constant false - // CHECK-NEXT: %[[V4:.+]] = comb.icmp eq %[[V2]], %[[V3]] : i1 - // CHECK-NEXT: %[[V5:.+]] = comb.extract %arg2 from 0 : (i6) -> i5 - // CHECK-NEXT: %[[V6:.+]] = hw.constant -1 : i5 - // CHECK-NEXT: %[[V7:.+]] = comb.mux %[[V4]], %[[V5]], %[[V6]] : i5 - // CHECK-NEXT: comb.shl %arg3, %[[V7]] : i5 - %2 = moore.mir.shl %arg2, %arg0 : !moore.packed>, !moore.bit - %3 = moore.mir.shl arithmetic %arg3, %arg2 : !moore.packed, 4:0>>, !moore.packed> - // CHECK-NEXT: %[[V8:.+]] = hw.constant 0 : i5 - // CHECK-NEXT: %[[V9:.+]] = comb.concat %[[V8]], %arg0 : i5, i1 - // CHECK-NEXT: comb.shru %arg2, %[[V9]] : i6 - // CHECK-NEXT: comb.shru %arg2, %arg2 : i6 - // CHECK-NEXT: %[[V10:.+]] = comb.extract %arg2 from 5 : (i6) -> i1 - // CHECK-NEXT: %[[V11:.+]] = hw.constant false - // CHECK-NEXT: %[[V12:.+]] = comb.icmp eq %[[V10]], %[[V11]] : i1 - // CHECK-NEXT: %[[V13:.+]] = comb.extract %arg2 from 0 : (i6) -> i5 - // CHECK-NEXT: %[[V14:.+]] = hw.constant -1 : i5 - // CHECK-NEXT: %[[V15:.+]] = comb.mux %[[V12]], %[[V13]], %[[V14]] : i5 - // CHECK-NEXT: comb.shrs %arg3, %[[V15]] : i5 - %4 = moore.mir.shr %arg2, %arg0 : !moore.packed>, !moore.bit - %5 = moore.mir.shr arithmetic %arg2, %arg2 : !moore.packed>, !moore.packed> - %6 = moore.mir.shr arithmetic %arg3, %arg2 : !moore.packed, 4:0>>, !moore.packed> - - // CHECK: comb.add %arg0, %arg0 : i1 - // CHECK: comb.sub %arg0, %arg0 : i1 - // CHECK: comb.mul %arg0, %arg0 : i1 - // CHECK: comb.divu %arg0, %arg0 : i1 - // CHECK: comb.modu %arg0, %arg0 : i1 - // CHECK: comb.and %arg0, %arg0 : i1 - // CHECK: comb.or %arg0, %arg0 : i1 - // CHECK: comb.xor %arg0, %arg0 : i1 + %0 = moore.concat %arg0, %arg0 : (!moore.bit, !moore.bit) -> !moore.packed> + %1 = moore.concat %arg1, %arg1 : (!moore.logic, !moore.logic) -> !moore.packed> + + // CHECK-NEXT: [[V0:%.+]] = hw.constant 0 : i5 + // CHECK-NEXT: [[V1:%.+]] = comb.concat [[V0]], %arg0 : i5, i1 + // CHECK-NEXT: comb.shl %arg2, [[V1]] : i6 + moore.shl %arg2, %arg0 : !moore.packed>, !moore.bit + + // CHECK-NEXT: [[V2:%.+]] = comb.extract %arg2 from 5 : (i6) -> i1 + // CHECK-NEXT: [[V3:%.+]] = hw.constant false + // CHECK-NEXT: [[V4:%.+]] = comb.icmp eq [[V2]], [[V3]] : i1 + // CHECK-NEXT: [[V5:%.+]] = comb.extract %arg2 from 0 : (i6) -> i5 + // CHECK-NEXT: [[V6:%.+]] = hw.constant -1 : i5 + // CHECK-NEXT: [[V7:%.+]] = comb.mux [[V4]], [[V5]], [[V6]] : i5 + // CHECK-NEXT: comb.shl %arg3, [[V7]] : i5 + moore.shl %arg3, %arg2 : !moore.packed, 4:0>>, !moore.packed> + + // CHECK-NEXT: [[V8:%.+]] = hw.constant 0 : i5 + // CHECK-NEXT: [[V9:%.+]] = comb.concat [[V8]], %arg0 : i5, i1 + // CHECK-NEXT: comb.shru %arg2, [[V9]] : i6 + moore.shr %arg2, %arg0 : !moore.packed>, !moore.bit + + // CHECK-NEXT: comb.shrs %arg2, %arg2 : i6 + moore.ashr %arg2, %arg2 : !moore.packed>, !moore.packed> + + // CHECK-NEXT: [[V10:%.+]] = comb.extract %arg2 from 5 : (i6) -> i1 + // CHECK-NEXT: [[V11:%.+]] = hw.constant false + // CHECK-NEXT: [[V12:%.+]] = comb.icmp eq [[V10]], [[V11]] : i1 + // CHECK-NEXT: [[V13:%.+]] = comb.extract %arg2 from 0 : (i6) -> i5 + // CHECK-NEXT: [[V14:%.+]] = hw.constant -1 : i5 + // CHECK-NEXT: [[V15:%.+]] = comb.mux [[V12]], [[V13]], [[V14]] : i5 + // CHECK-NEXT: comb.shrs %arg3, [[V15]] : i5 + moore.ashr %arg3, %arg2 : !moore.packed, 4:0>>, !moore.packed> + + // CHECK-NEXT: comb.add %arg0, %arg0 : i1 + // CHECK-NEXT: comb.sub %arg0, %arg0 : i1 + // CHECK-NEXT: comb.mul %arg0, %arg0 : i1 + // CHECK-NEXT: comb.divu %arg0, %arg0 : i1 + // CHECK-NEXT: comb.modu %arg0, %arg0 : i1 + // CHECK-NEXT: comb.and %arg0, %arg0 : i1 + // CHECK-NEXT: comb.or %arg0, %arg0 : i1 + // CHECK-NEXT: comb.xor %arg0, %arg0 : i1 %7 = moore.add %arg0, %arg0 : !moore.bit %8 = moore.sub %arg0, %arg0 : !moore.bit %9 = moore.mul %arg0, %arg0 : !moore.bit @@ -94,38 +115,90 @@ func.func @Expressions(%arg0: !moore.bit, %arg1: !moore.logic, %arg2: !moore.pac %13 = moore.and %arg0, %arg0 : !moore.bit %14 = moore.or %arg0, %arg0 : !moore.bit %15 = moore.xor %arg0, %arg0 : !moore.bit - - // CHECK: comb.icmp ult %arg0, %arg0 : i1 - // CHECK: comb.icmp ule %arg0, %arg0 : i1 - // CHECK: comb.icmp ugt %arg0, %arg0 : i1 - // CHECK: comb.icmp uge %arg0, %arg0 : i1 - // CHECK: comb.icmp eq %arg0, %arg0 : i1 - // CHECK: comb.icmp ne %arg0, %arg0 : i1 - // CHECK: comb.icmp ceq %arg0, %arg0 : i1 - // CHECK: comb.icmp cne %arg0, %arg0 : i1 - // CHECK: comb.icmp weq %arg0, %arg0 : i1 - // CHECK: comb.icmp wne %arg0, %arg0 : i1 + + // CHECK-NEXT: comb.icmp ult %arg0, %arg0 : i1 + // CHECK-NEXT: comb.icmp ule %arg0, %arg0 : i1 + // CHECK-NEXT: comb.icmp ugt %arg0, %arg0 : i1 + // CHECK-NEXT: comb.icmp uge %arg0, %arg0 : i1 %16 = moore.lt %arg0, %arg0 : !moore.bit -> !moore.bit %17 = moore.le %arg0, %arg0 : !moore.bit -> !moore.bit %18 = moore.gt %arg0, %arg0 : !moore.bit -> !moore.bit %19 = moore.ge %arg0, %arg0 : !moore.bit -> !moore.bit - %20 = moore.eq %arg0, %arg0 : !moore.bit -> !moore.bit - %21 = moore.ne %arg0, %arg0 : !moore.bit -> !moore.bit - %22 = moore.case_eq %arg0, %arg0 : !moore.bit - %23 = moore.case_ne %arg0, %arg0 : !moore.bit - %24 = moore.wildcard_eq %arg0, %arg0 : !moore.bit -> !moore.bit - %25 = moore.wildcard_ne %arg0, %arg0 : !moore.bit -> !moore.bit - - // CHECK: comb.extract %arg2 from 2 : (i6) -> i2 - // CHECK: comb.extract %arg2 from 2 : (i6) -> i1 - %26 = moore.mir.extract %arg2 from 2 : (!moore.packed>) -> !moore.packed> - %27 = moore.mir.extract %arg2 from 2 : (!moore.packed>) -> !moore.bit - - // CHECK: hw.constant 12 : i32 - // CHECK: hw.constant 3 : i6 - %28 = moore.constant 12 : !moore.int - %29 = moore.constant 3 : !moore.packed> - - // CHECK-NEXT: return - return + + // CHECK-NEXT: comb.icmp slt %arg4, %arg4 : i1 + // CHECK-NEXT: comb.icmp sle %arg4, %arg4 : i1 + // CHECK-NEXT: comb.icmp sgt %arg4, %arg4 : i1 + // CHECK-NEXT: comb.icmp sge %arg4, %arg4 : i1 + %20 = moore.lt %arg4, %arg4 : !moore.bit -> !moore.bit + %21 = moore.le %arg4, %arg4 : !moore.bit -> !moore.bit + %22 = moore.gt %arg4, %arg4 : !moore.bit -> !moore.bit + %23 = moore.ge %arg4, %arg4 : !moore.bit -> !moore.bit + + // CHECK-NEXT: comb.icmp eq %arg0, %arg0 : i1 + // CHECK-NEXT: comb.icmp ne %arg0, %arg0 : i1 + // CHECK-NEXT: comb.icmp ceq %arg0, %arg0 : i1 + // CHECK-NEXT: comb.icmp cne %arg0, %arg0 : i1 + // CHECK-NEXT: comb.icmp weq %arg0, %arg0 : i1 + // CHECK-NEXT: comb.icmp wne %arg0, %arg0 : i1 + %24 = moore.eq %arg0, %arg0 : !moore.bit -> !moore.bit + %25 = moore.ne %arg0, %arg0 : !moore.bit -> !moore.bit + %26 = moore.case_eq %arg0, %arg0 : !moore.bit + %27 = moore.case_ne %arg0, %arg0 : !moore.bit + %28 = moore.wildcard_eq %arg0, %arg0 : !moore.bit -> !moore.bit + %29 = moore.wildcard_ne %arg0, %arg0 : !moore.bit -> !moore.bit + + // CHECK-NEXT: comb.extract %arg2 from 2 : (i6) -> i2 + // CHECK-NEXT: comb.extract %arg2 from 2 : (i6) -> i1 + %30 = moore.mir.extract %arg2 from 2 : (!moore.packed>) -> !moore.packed> + %31 = moore.mir.extract %arg2 from 2 : (!moore.packed>) -> !moore.bit + + // CHECK-NEXT: %c12_i32 = hw.constant 12 : i32 + // CHECK-NEXT: %c3_i6 = hw.constant 3 : i6 + %32 = moore.constant 12 : !moore.int + %33 = moore.constant 3 : !moore.packed> + + // CHECK-NEXT: comb.parity %arg0 : i1 + // CHECK-NEXT: comb.parity %arg0 : i1 + // CHECK-NEXT: comb.parity %arg0 : i1 + %34 = moore.reduce_and %arg0 : !moore.bit -> !moore.bit + %35 = moore.reduce_or %arg0 : !moore.bit -> !moore.bit + %36 = moore.reduce_xor %arg0 : !moore.bit -> !moore.bit + + // CHECK-NEXT: %c-1_i6 = hw.constant -1 : i6 + // CHECK-NEXT: comb.xor %arg2, %c-1_i6 : i6 + %37 = moore.not %arg2 : !moore.packed> + + // CHECK-NEXT: comb.parity %arg2 : i6 + %38 = moore.bool_cast %arg2 : !moore.packed> -> !moore.bit + + // CHECK-NEXT: hw.output +} + +// CHECK-LABEL: hw.module @Declaration +moore.module @Declaration { + + // CHECK-NEXT: %a = hw.wire %c12_i32 : i32 + %a = moore.variable : !moore.int + moore.procedure always_comb { + + // CHECK-NEXT: %c5_i32 = hw.constant 5 : i32 + %0 = moore.constant 5 : !moore.int + moore.bpassign %a, %0 : !moore.int + + // CHECK-NEXT: %c12_i32 = hw.constant 12 : i32 + %1 = moore.constant 12 : !moore.int + moore.bpassign %a, %1 : !moore.int + } + + // CHECK-NEXT: %b = hw.wire [[V0:%.+]] : i1 + %b = moore.net "wire" : !moore.logic + + // CHECK-NEXT: %true = hw.constant true + %0 = moore.constant true : !moore.packed> + + // CHECK-NEXT: [[V0:%.+]] = hw.bitcast %true : (i1) -> i1 + %1 = moore.conversion %0 : !moore.packed> -> !moore.logic + moore.cassign %b, %1 : !moore.logic + + // CHECK-NEXT : hw.output } diff --git a/test/Conversion/SCFToCalyx/convert_memory.mlir b/test/Conversion/SCFToCalyx/convert_memory.mlir index 3e35180fa35a..dd7c9b6d95bc 100644 --- a/test/Conversion/SCFToCalyx/convert_memory.mlir +++ b/test/Conversion/SCFToCalyx/convert_memory.mlir @@ -95,8 +95,8 @@ module { // CHECK-SAME: %[[VAL_4:.*]]: i32, // CHECK-SAME: %[[VAL_5:.*]]: i1 {done}) { // CHECK: %[[VAL_6:.*]] = hw.constant true -// CHECK: %[[VAL_7:.*]] = hw.constant 1 : i32 // CHECK: %[[VAL_8:.*]] = hw.constant 0 : i32 +// CHECK: %[[VAL_7:.*]] = hw.constant 1 : i32 // CHECK: %[[VAL_9:.*]], %[[VAL_10:.*]] = calyx.std_slice @std_slice_0 : i32, i6 // CHECK: %[[VAL_11:.*]], %[[VAL_12:.*]], %[[VAL_13:.*]] = calyx.std_add @std_add_1 : i32, i32, i32 // CHECK: %[[VAL_14:.*]], %[[VAL_15:.*]], %[[VAL_16:.*]] = calyx.std_add @std_add_0 : i32, i32, i32 @@ -152,8 +152,8 @@ module { // CHECK-SAME: %[[VAL_4:.*]]: i32, // CHECK-SAME: %[[VAL_5:.*]]: i1 {done}) { // CHECK: %[[VAL_6:.*]] = hw.constant true -// CHECK: %[[VAL_7:.*]] = hw.constant 1 : i32 // CHECK: %[[VAL_8:.*]] = hw.constant 0 : i32 +// CHECK: %[[VAL_7:.*]] = hw.constant 1 : i32 // CHECK: %[[VAL_9:.*]], %[[VAL_10:.*]] = calyx.std_slice @std_slice_0 : i32, i6 // CHECK: %[[VAL_11:.*]], %[[VAL_12:.*]], %[[VAL_13:.*]] = calyx.std_add @std_add_2 : i32, i32, i32 // CHECK: %[[VAL_14:.*]], %[[VAL_15:.*]], %[[VAL_16:.*]] = calyx.std_add @std_add_1 : i32, i32, i32 diff --git a/test/Conversion/SimToSV/plusargs.mlir b/test/Conversion/SimToSV/plusargs.mlir new file mode 100644 index 000000000000..0391aa2d6b12 --- /dev/null +++ b/test/Conversion/SimToSV/plusargs.mlir @@ -0,0 +1,42 @@ +// RUN: circt-opt --lower-sim-to-sv %s | FileCheck %s + +// CHECK-LABEL: hw.module @plusargs_test +hw.module @plusargs_test(out test: i1) { + // CHECK-NEXT: [[FOO_STR:%.*]] = sv.constantStr "foo" + // CHECK-NEXT: [[FOO_DECL:%.*]] = sv.reg : !hw.inout + // CHECK-NEXT: sv.initial { + // CHECK-NEXT: [[TMP:%.*]] = sv.system "test$plusargs"([[FOO_STR]]) + // CHECK-NEXT: sv.bpassign [[FOO_DECL]], [[TMP]] + // CHECK-NEXT: } + // CHECK-NEXT: [[FOO:%.*]] = sv.read_inout [[FOO_DECL]] + // CHECK-NEXT: hw.output [[FOO]] : i1 + %0 = sim.plusargs.test "foo" + hw.output %0 : i1 +} + +// CHECK-LABEL: hw.module @plusargs_value +hw.module @plusargs_value(out test: i1, out value: i5) { + // CHECK-NEXT: [[BAR_VALUE_DECL:%.*]] = sv.reg : !hw.inout + // CHECK-NEXT: [[BAR_FOUND_DECL:%.*]] = sv.reg : !hw.inout + // CHECK-NEXT: sv.ifdef "SYNTHESIS" { + // CHECK-NEXT: %false = hw.constant false + // CHECK-NEXT: %z_i5 = sv.constantZ : i5 + // CHECK-NEXT: sv.assign [[BAR_VALUE_DECL]], %z_i5 + // CHECK-SAME: #sv.attribute<"This dummy assignment exists to avoid undriven lint warnings + // CHECK-SAME: emitAsComment + // CHECK-NEXT: sv.assign [[BAR_FOUND_DECL]], %false + // CHECK-NEXT: } else { + // CHECK-NEXT: sv.initial { + // CHECK-NEXT: %c0_i32 = hw.constant 0 : i32 + // CHECK-NEXT: [[BAR_STR:%.*]] = sv.constantStr "bar" + // CHECK-NEXT: [[TMP:%.*]] = sv.system "value$plusargs"([[BAR_STR]], [[BAR_VALUE_DECL]]) + // CHECK-NEXT: [[TMP2:%.*]] = comb.icmp bin ne [[TMP]], %c0_i32 + // CHECK-NEXT: sv.bpassign [[BAR_FOUND_DECL]], [[TMP2]] + // CHECK-NEXT: } + // CHECK-NEXT: } + // CHECK-NEXT: [[BAR_FOUND:%.*]] = sv.read_inout [[BAR_FOUND_DECL]] + // CHECK-NEXT: [[BAR_VALUE:%.*]] = sv.read_inout [[BAR_VALUE_DECL]] + // CHECK-NEXT: hw.output [[BAR_FOUND]], [[BAR_VALUE]] : i1, i5 + %0, %1 = sim.plusargs.value "bar" : i5 + hw.output %0, %1 : i1, i5 +} diff --git a/test/Conversion/SimToSV/stop.mlir b/test/Conversion/SimToSV/stop.mlir new file mode 100644 index 000000000000..4afc57563ccd --- /dev/null +++ b/test/Conversion/SimToSV/stop.mlir @@ -0,0 +1,29 @@ +// RUN: circt-opt --lower-sim-to-sv %s | FileCheck %s + +// CHECK-LABEL: hw.module @finish +hw.module @finish(in %clock : !seq.clock, in %cond : i1) { + // CHECK: [[CLK_SV:%.+]] = seq.from_clock %clock + // CHECK-NEXT: sv.ifdef "SYNTHESIS" { + // CHECK-NEXT: } else { + // CHECK-NEXT: sv.always posedge [[CLK_SV]] { + // CHECK-NEXT: sv.if %cond { + // CHECK-NEXT: sv.finish 1 + // CHECK-NEXT: } + // CHECK-NEXT: } + // CHECK-NEXT: } + sim.finish %clock, %cond +} + +// CHECK-LABEL: hw.module @fatal +hw.module @fatal(in %clock : !seq.clock, in %cond : i1) { + // CHECK: [[CLK_SV:%.+]] = seq.from_clock %clock + // CHECK-NEXT: sv.ifdef "SYNTHESIS" { + // CHECK-NEXT: } else { + // CHECK-NEXT: sv.always posedge [[CLK_SV]] { + // CHECK-NEXT: sv.if %cond { + // CHECK-NEXT: sv.fatal 1 + // CHECK-NEXT: } + // CHECK-NEXT: } + // CHECK-NEXT: } + sim.fatal %clock, %cond +} diff --git a/test/Dialect/Arc/print-state-info-errors.mlir b/test/Dialect/Arc/Export/info-gathering-errors.mlir similarity index 94% rename from test/Dialect/Arc/print-state-info-errors.mlir rename to test/Dialect/Arc/Export/info-gathering-errors.mlir index 8b2d306bb260..62024022b681 100644 --- a/test/Dialect/Arc/print-state-info-errors.mlir +++ b/test/Dialect/Arc/Export/info-gathering-errors.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt %s --arc-print-state-info --verify-diagnostics --split-input-file +// RUN: circt-translate %s --export-arc-model-info --split-input-file --verify-diagnostics arc.model "Foo" { ^bb0(%arg0: !arc.storage<42>): diff --git a/test/Dialect/Arc/print-state-info.mlir b/test/Dialect/Arc/Export/serialize-state-info.mlir similarity index 94% rename from test/Dialect/Arc/print-state-info.mlir rename to test/Dialect/Arc/Export/serialize-state-info.mlir index 9d9bc8a5b097..f578f76e94a9 100644 --- a/test/Dialect/Arc/print-state-info.mlir +++ b/test/Dialect/Arc/Export/serialize-state-info.mlir @@ -1,5 +1,4 @@ -// RUN: circt-opt %s --arc-print-state-info=state-file=%t -// RUN: cat %t | FileCheck %s +// RUN: circt-translate %s --export-arc-model-info | FileCheck %s // CHECK-LABEL: "name": "Foo" // CHECK-DAG: "numStateBytes": 5724 diff --git a/test/Dialect/Arc/Reduction/state-elimination.mlir b/test/Dialect/Arc/Reduction/state-elimination.mlir index a98017ff3e50..95d8cd7073f9 100644 --- a/test/Dialect/Arc/Reduction/state-elimination.mlir +++ b/test/Dialect/Arc/Reduction/state-elimination.mlir @@ -5,7 +5,7 @@ // CHECK-LABEL: hw.module @Foo hw.module @Foo(in %clk: !seq.clock, in %en: i1, in %rst: i1, in %arg0: i32, out out: i32) { // CHECK-NEXT: [[V0:%.+]] = arc.call @DummyArc(%arg0) : (i32) -> i32 - %0 = arc.state @DummyArc(%arg0) clock %clk enable %en reset %rst lat 1 {name="reg1"} : (i32) -> (i32) + %0 = arc.state @DummyArc(%arg0) clock %clk enable %en reset %rst latency 1 {name="reg1"} : (i32) -> (i32) // CHECK-NEXT: hw.output [[V0]] hw.output %0 : i32 } diff --git a/test/Dialect/Arc/arc-canonicalizer.mlir b/test/Dialect/Arc/arc-canonicalizer.mlir index 17e0cca50b1e..aed4ad5cb89a 100644 --- a/test/Dialect/Arc/arc-canonicalizer.mlir +++ b/test/Dialect/Arc/arc-canonicalizer.mlir @@ -5,17 +5,14 @@ //===----------------------------------------------------------------------===// // CHECK-LABEL: hw.module @passthoughChecks -hw.module @passthoughChecks(in %clock: !seq.clock, in %in0: i1, in %in1: i1, out out0: i1, out out1: i1, out out2: i1, out out3: i1, out out4: i1, out out5: i1, out out6: i1, out out7: i1, out out8: i1, out out9: i1) { +hw.module @passthoughChecks(in %clock: !seq.clock, in %in0: i1, in %in1: i1, out out0: i1, out out1: i1, out out2: i1, out out3: i1, out out4: i1, out out5: i1) { %0:2 = arc.call @passthrough(%in0, %in1) : (i1, i1) -> (i1, i1) %1:2 = arc.call @noPassthrough(%in0, %in1) : (i1, i1) -> (i1, i1) - %2:2 = arc.state @passthrough(%in0, %in1) lat 0 : (i1, i1) -> (i1, i1) - %3:2 = arc.state @noPassthrough(%in0, %in1) lat 0 : (i1, i1) -> (i1, i1) - %4:2 = arc.state @passthrough(%in0, %in1) clock %clock lat 1 : (i1, i1) -> (i1, i1) - hw.output %0#0, %0#1, %1#0, %1#1, %2#0, %2#1, %3#0, %3#1, %4#0, %4#1 : i1, i1, i1, i1, i1, i1, i1, i1, i1, i1 + %2:2 = arc.state @passthrough(%in0, %in1) clock %clock latency 1 : (i1, i1) -> (i1, i1) + hw.output %0#0, %0#1, %1#0, %1#1, %2#0, %2#1 : i1, i1, i1, i1, i1, i1 // CHECK-NEXT: [[V0:%.+]]:2 = arc.call @noPassthrough(%in0, %in1) : - // CHECK-NEXT: [[V1:%.+]]:2 = arc.state @noPassthrough(%in0, %in1) lat 0 : - // CHECK-NEXT: [[V2:%.+]]:2 = arc.state @passthrough(%in0, %in1) clock %clock lat 1 : - // CHECK-NEXT: hw.output %in0, %in1, [[V0]]#0, [[V0]]#1, %in0, %in1, [[V1]]#0, [[V1]]#1, [[V2]]#0, [[V2]]#1 : + // CHECK-NEXT: [[V2:%.+]]:2 = arc.state @passthrough(%in0, %in1) clock %clock latency 1 : + // CHECK-NEXT: hw.output %in0, %in1, [[V0]]#0, [[V0]]#1, [[V2]]#0, [[V2]]#1 : } arc.define @passthrough(%arg0: i1, %arg1: i1) -> (i1, i1) { arc.output %arg0, %arg1 : i1, i1 @@ -41,12 +38,13 @@ arc.define @memArcTrue(%arg0: i1, %arg1: i32) -> (i1, i32, i1) { hw.module @memoryWritePortCanonicalizations(in %clk: !seq.clock, in %addr: i1, in %data: i32) { // CHECK-NEXT: [[MEM:%.+]] = arc.memory <2 x i32, i1> %mem = arc.memory <2 x i32, i1> - arc.memory_write_port %mem, @memArcFalse(%addr, %data) clock %clk enable lat 1 : <2 x i32, i1>, i1, i32 - // CHECK-NEXT: arc.memory_write_port [[MEM]], @memArcTrue_0(%addr, %data) clock %clk lat 1 : - arc.memory_write_port %mem, @memArcTrue(%addr, %data) clock %clk enable lat 1 : <2 x i32, i1>, i1, i32 - // CHECK-NEXT: arc.memory_write_port [[MEM]], @memArcTrue_0(%addr, %data) clock %clk lat 1 : - arc.memory_write_port %mem, @memArcTrue(%addr, %data) clock %clk enable lat 1 : <2 x i32, i1>, i1, i32 - %0:3 = arc.state @memArcTrue(%addr, %data) lat 0 : (i1, i32) -> (i1, i32, i1) + arc.memory_write_port %mem, @memArcFalse(%addr, %data) clock %clk enable latency 1 : <2 x i32, i1>, i1, i32 + // CHECK-NEXT: arc.memory_write_port [[MEM]], @memArcTrue_0(%addr, %data) clock %clk latency 1 : + arc.memory_write_port %mem, @memArcTrue(%addr, %data) clock %clk enable latency 1 : <2 x i32, i1>, i1, i32 + // CHECK-NEXT: arc.memory_write_port [[MEM]], @memArcTrue_0(%addr, %data) clock %clk latency 1 : + arc.memory_write_port %mem, @memArcTrue(%addr, %data) clock %clk enable latency 1 : <2 x i32, i1>, i1, i32 + // COM: trivially dead operation, requires listener callback to keep symbol cache up-to-date + %0:3 = arc.call @memArcTrue(%addr, %data) : (i1, i32) -> (i1, i32, i1) // CHECK-NEXT: hw.output hw.output } @@ -151,10 +149,10 @@ arc.define @OneOfThreeUsed(%arg0: i1, %arg1: i1, %arg2: i1) -> i1 { // CHECK: @test1 hw.module @test1 (in %arg0: i1, in %arg1: i1, in %arg2: i1, in %clock: !seq.clock, out out0: i1, out out1: i1) { - // CHECK-NEXT: arc.state @OneOfThreeUsed(%arg1) clock %clock lat 1 : (i1) -> i1 - %0 = arc.state @OneOfThreeUsed(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> i1 + // CHECK-NEXT: arc.state @OneOfThreeUsed(%arg1) clock %clock latency 1 : (i1) -> i1 + %0 = arc.state @OneOfThreeUsed(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> i1 // CHECK-NEXT: arc.state @NestedCall(%arg1) - %1 = arc.state @NestedCall(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> i1 + %1 = arc.state @NestedCall(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> i1 hw.output %0, %1 : i1, i1 } @@ -166,8 +164,8 @@ arc.define @NoArgsToRemove() -> i1 { // CHECK: @test2 hw.module @test2 (out out: i1) { - // CHECK-NEXT: arc.state @NoArgsToRemove() lat 0 : () -> i1 - %0 = arc.state @NoArgsToRemove() lat 0 : () -> i1 + // CHECK-NEXT: arc.call @NoArgsToRemove() : () -> i1 + %0 = arc.call @NoArgsToRemove() : () -> i1 hw.output %0 : i1 } @@ -197,14 +195,14 @@ arc.define @Foo(%arg0: i4) -> i4 { // CHECK: hw.module @SinkSameConstants hw.module @SinkSameConstants(in %x: i4, out out0: i4, out out1: i4, out out2: i4) { // CHECK-NOT: hw.constant - // CHECK-NEXT: %0 = arc.state @SinkSameConstantsArc(%x) - // CHECK-NEXT: %1 = arc.state @SinkSameConstantsArc(%x) + // CHECK-NEXT: %0 = arc.call @SinkSameConstantsArc(%x) + // CHECK-NEXT: %1 = arc.call @SinkSameConstantsArc(%x) // CHECK-NEXT: arc.call // CHECK-NEXT: hw.output %k1 = hw.constant 2 : i4 %k2 = hw.constant 2 : i4 - %0 = arc.state @SinkSameConstantsArc(%x, %k1) lat 0 : (i4, i4) -> i4 - %1 = arc.state @SinkSameConstantsArc(%x, %k2) lat 0 : (i4, i4) -> i4 + %0 = arc.call @SinkSameConstantsArc(%x, %k1) : (i4, i4) -> i4 + %1 = arc.call @SinkSameConstantsArc(%x, %k2) : (i4, i4) -> i4 %2 = arc.call @Foo(%x) : (i4) -> i4 hw.output %0, %1, %2 : i4, i4, i4 } @@ -224,13 +222,13 @@ arc.define @DontSinkDifferentConstantsArc(%arg0: i4, %arg1: i4) -> i4 { hw.module @DontSinkDifferentConstants(in %x: i4, out out0: i4, out out1: i4) { // CHECK-NEXT: %c2_i4 = hw.constant 2 : i4 // CHECK-NEXT: %c3_i4 = hw.constant 3 : i4 - // CHECK-NEXT: %0 = arc.state @DontSinkDifferentConstantsArc(%x, %c2_i4) - // CHECK-NEXT: %1 = arc.state @DontSinkDifferentConstantsArc(%x, %c3_i4) + // CHECK-NEXT: %0 = arc.call @DontSinkDifferentConstantsArc(%x, %c2_i4) + // CHECK-NEXT: %1 = arc.call @DontSinkDifferentConstantsArc(%x, %c3_i4) // CHECK-NEXT: hw.output %c2_i4 = hw.constant 2 : i4 %c3_i4 = hw.constant 3 : i4 - %0 = arc.state @DontSinkDifferentConstantsArc(%x, %c2_i4) lat 0 : (i4, i4) -> i4 - %1 = arc.state @DontSinkDifferentConstantsArc(%x, %c3_i4) lat 0 : (i4, i4) -> i4 + %0 = arc.call @DontSinkDifferentConstantsArc(%x, %c2_i4) : (i4, i4) -> i4 + %1 = arc.call @DontSinkDifferentConstantsArc(%x, %c3_i4) : (i4, i4) -> i4 hw.output %0, %1 : i4, i4 } // CHECK-NEXT: } @@ -257,14 +255,14 @@ arc.define @Bar(%arg0: i4) -> i4 { // CHECK: hw.module @DontSinkDifferentConstants1 hw.module @DontSinkDifferentConstants1(in %x: i4, out out0: i4, out out1: i4, out out2: i4) { // CHECK-NEXT: %c2_i4 = hw.constant 2 : i4 - // CHECK-NEXT: %0 = arc.state @DontSinkDifferentConstantsArc1(%x, %c2_i4) - // CHECK-NEXT: %1 = arc.state @DontSinkDifferentConstantsArc1(%x, %c2_i4) + // CHECK-NEXT: %0 = arc.call @DontSinkDifferentConstantsArc1(%x, %c2_i4) + // CHECK-NEXT: %1 = arc.call @DontSinkDifferentConstantsArc1(%x, %c2_i4) // CHECK-NEXT: arc.call // CHECK-NEXT: hw.output %k1 = hw.constant 2 : i4 %k2 = hw.constant 2 : i4 - %0 = arc.state @DontSinkDifferentConstantsArc1(%x, %k1) lat 0 : (i4, i4) -> i4 - %1 = arc.state @DontSinkDifferentConstantsArc1(%x, %k2) lat 0 : (i4, i4) -> i4 + %0 = arc.call @DontSinkDifferentConstantsArc1(%x, %k1) : (i4, i4) -> i4 + %1 = arc.call @DontSinkDifferentConstantsArc1(%x, %k2) : (i4, i4) -> i4 %2 = arc.call @Bar(%x) : (i4) -> i4 hw.output %0, %1, %2 : i4, i4, i4 } diff --git a/test/Dialect/Arc/basic-errors.mlir b/test/Dialect/Arc/basic-errors.mlir index 84fea66eb47a..9f65068ca91e 100644 --- a/test/Dialect/Arc/basic-errors.mlir +++ b/test/Dialect/Arc/basic-errors.mlir @@ -3,7 +3,7 @@ // expected-error @+1 {{body contains non-pure operation}} arc.define @Foo(%arg0: !seq.clock) { // expected-note @+1 {{first non-pure operation here:}} - arc.state @Bar() clock %arg0 lat 1 : () -> () + arc.state @Bar() clock %arg0 latency 1 : () -> () arc.output } arc.define @Bar() { @@ -13,8 +13,8 @@ arc.define @Bar() { // ----- hw.module @Foo(in %clock: !seq.clock) { - // expected-error @+1 {{'arc.state' op with non-zero latency outside a clock domain requires a clock}} - arc.state @Bar() lat 1 : () -> () + // expected-error @+1 {{'arc.state' op outside a clock domain requires a clock}} + arc.state @Bar() latency 1 : () -> () } arc.define @Bar() { arc.output @@ -23,28 +23,8 @@ arc.define @Bar() { // ----- hw.module @Foo(in %clock: !seq.clock) { - // expected-error @+1 {{'arc.state' op with zero latency cannot have a clock}} - arc.state @Bar() clock %clock lat 0 : () -> () -} -arc.define @Bar() { - arc.output -} - -// ----- - -hw.module @Foo(in %clock: !seq.clock, in %enable: i1) { - // expected-error @+1 {{'arc.state' op with zero latency cannot have an enable}} - arc.state @Bar() enable %enable lat 0 : () -> () -} -arc.define @Bar() { - arc.output -} - -// ----- - -hw.module @Foo(in %clock: !seq.clock, in %reset: i1) { - // expected-error @+1 {{'arc.state' op with zero latency cannot have a reset}} - arc.state @Bar() reset %reset lat 0 : () -> () + // expected-error @+1 {{'arc.state' op latency must be a positive integer}} + arc.state @Bar() clock %clock latency 0 : () -> () } arc.define @Bar() { arc.output @@ -56,7 +36,7 @@ arc.define @Bar() { arc.define @SupportRecursiveMemoryEffects(%arg0: i1, %arg1: !seq.clock) { // expected-note @+1 {{first non-pure operation here:}} scf.if %arg0 { - arc.state @Bar() clock %arg1 lat 1 : () -> () + arc.state @Bar() clock %arg1 latency 1 : () -> () } arc.output } @@ -272,7 +252,7 @@ hw.module @stateOpInsideClockDomain(in %clk: !seq.clock) { arc.clock_domain (%clk) clock %clk : (!seq.clock) -> () { ^bb0(%arg0: !seq.clock): // expected-error @+1 {{inside a clock domain cannot have a clock}} - arc.state @dummyArc() clock %arg0 lat 1 : () -> () + arc.state @dummyArc() clock %arg0 latency 1 : () -> () arc.output } hw.output @@ -289,7 +269,7 @@ hw.module @memoryWritePortOpInsideClockDomain(in %clk: !seq.clock) { %mem = arc.memory <4 x i32, i32> %c0_i32 = hw.constant 0 : i32 // expected-error @+1 {{inside a clock domain cannot have a clock}} - arc.memory_write_port %mem, @identity(%c0_i32, %c0_i32, %arg0) clock %arg0 enable lat 1: !arc.memory<4 x i32, i32>, i32, i32, !seq.clock + arc.memory_write_port %mem, @identity(%c0_i32, %c0_i32, %arg0) clock %arg0 enable latency 1: !arc.memory<4 x i32, i32>, i32, i32, !seq.clock arc.output } } @@ -303,7 +283,7 @@ hw.module @memoryWritePortOpOutsideClockDomain(in %clock: !seq.clock, in %en: i1 %mem = arc.memory <4 x i32, i32> %c0_i32 = hw.constant 0 : i32 // expected-error @+1 {{outside a clock domain requires a clock}} - arc.memory_write_port %mem, @identity(%c0_i32, %c0_i32, %en) lat 1 : !arc.memory<4 x i32, i32>, i32, i32, i1 + arc.memory_write_port %mem, @identity(%c0_i32, %c0_i32, %en) latency 1 : !arc.memory<4 x i32, i32>, i32, i32, i1 } arc.define @identity(%addr: i32, %data: i32, %enable: i1) -> (i32, i32, i1) { arc.output %addr, %data, %enable : i32, i32, i1 @@ -315,7 +295,7 @@ hw.module @memoryWritePortOpLatZero(in %clock: !seq.clock, in %en: i1) { %mem = arc.memory <4 x i32, i32> %c0_i32 = hw.constant 0 : i32 // expected-error @+1 {{latency must be at least 1}} - arc.memory_write_port %mem, @identity(%c0_i32, %c0_i32, %en) lat 0 : !arc.memory<4 x i32, i32>, i32, i32, i1 + arc.memory_write_port %mem, @identity(%c0_i32, %c0_i32, %en) latency 0 : !arc.memory<4 x i32, i32>, i32, i32, i1 } arc.define @identity(%addr: i32, %data: i32, %enable: i1) -> (i32, i32, i1) { arc.output %addr, %data, %enable : i32, i32, i1 diff --git a/test/Dialect/Arc/basic.mlir b/test/Dialect/Arc/basic.mlir index 869ca407d2fe..ee126971c796 100644 --- a/test/Dialect/Arc/basic.mlir +++ b/test/Dialect/Arc/basic.mlir @@ -14,11 +14,11 @@ arc.define @Bar(%arg0: i42) -> i42 { // CHECK-LABEL: hw.module @Module hw.module @Module(in %clock: !seq.clock, in %enable: i1, in %a: i42, in %b: i9) { - // CHECK: arc.state @Foo(%a, %b) clock %clock lat 1 : (i42, i9) -> (i42, i9) - arc.state @Foo(%a, %b) clock %clock lat 1 : (i42, i9) -> (i42, i9) + // CHECK: arc.state @Foo(%a, %b) clock %clock latency 1 : (i42, i9) -> (i42, i9) + arc.state @Foo(%a, %b) clock %clock latency 1 : (i42, i9) -> (i42, i9) - // CHECK: arc.state @Foo(%a, %b) clock %clock enable %enable lat 1 : (i42, i9) -> (i42, i9) - arc.state @Foo(%a, %b) clock %clock enable %enable lat 1 : (i42, i9) -> (i42, i9) + // CHECK: arc.state @Foo(%a, %b) clock %clock enable %enable latency 1 : (i42, i9) -> (i42, i9) + arc.state @Foo(%a, %b) clock %clock enable %enable latency 1 : (i42, i9) -> (i42, i9) } // CHECK-LABEL: arc.define @SupportRecurisveMemoryEffects @@ -115,14 +115,14 @@ hw.module @memoryOps(in %clk: !seq.clock, in %en: i1, in %mask: i32, in %arg: i1 // CHECK-NEXT: %{{.+}} = arc.memory_read_port [[MEM]][%c0_i32] : <4 x i32, i32> %0 = arc.memory_read_port %mem[%c0_i32] : <4 x i32, i32> - // CHECK-NEXT: arc.memory_write_port [[MEM]], @identity1(%c0_i32, %c0_i32, %en) clock %clk enable lat 1 : <4 x i32, i32>, i32, i32, i1 - arc.memory_write_port %mem, @identity1(%c0_i32, %c0_i32, %en) clock %clk enable lat 1 : <4 x i32, i32>, i32, i32, i1 - // CHECK-NEXT: arc.memory_write_port [[MEM]], @identity2(%c0_i32, %c0_i32, %en, %mask) clock %clk enable mask lat 2 : <4 x i32, i32>, i32, i32, i1, i32 - arc.memory_write_port %mem, @identity2(%c0_i32, %c0_i32, %en, %mask) clock %clk enable mask lat 2 : <4 x i32, i32>, i32, i32, i1, i32 - // CHECK-NEXT: arc.memory_write_port [[MEM]], @identity3(%c0_i32, %c0_i32, %mask) clock %clk mask lat 3 : <4 x i32, i32>, i32, i32, i32 - arc.memory_write_port %mem, @identity3(%c0_i32, %c0_i32, %mask) clock %clk mask lat 3 : <4 x i32, i32>, i32, i32, i32 - // CHECK-NEXT: arc.memory_write_port [[MEM]], @identity(%c0_i32, %c0_i32) clock %clk lat 4 : <4 x i32, i32>, i32, i32 - arc.memory_write_port %mem, @identity(%c0_i32, %c0_i32) clock %clk lat 4 : <4 x i32, i32>, i32, i32 + // CHECK-NEXT: arc.memory_write_port [[MEM]], @identity1(%c0_i32, %c0_i32, %en) clock %clk enable latency 1 : <4 x i32, i32>, i32, i32, i1 + arc.memory_write_port %mem, @identity1(%c0_i32, %c0_i32, %en) clock %clk enable latency 1 : <4 x i32, i32>, i32, i32, i1 + // CHECK-NEXT: arc.memory_write_port [[MEM]], @identity2(%c0_i32, %c0_i32, %en, %mask) clock %clk enable mask latency 2 : <4 x i32, i32>, i32, i32, i1, i32 + arc.memory_write_port %mem, @identity2(%c0_i32, %c0_i32, %en, %mask) clock %clk enable mask latency 2 : <4 x i32, i32>, i32, i32, i1, i32 + // CHECK-NEXT: arc.memory_write_port [[MEM]], @identity3(%c0_i32, %c0_i32, %mask) clock %clk mask latency 3 : <4 x i32, i32>, i32, i32, i32 + arc.memory_write_port %mem, @identity3(%c0_i32, %c0_i32, %mask) clock %clk mask latency 3 : <4 x i32, i32>, i32, i32, i32 + // CHECK-NEXT: arc.memory_write_port [[MEM]], @identity(%c0_i32, %c0_i32) clock %clk latency 4 : <4 x i32, i32>, i32, i32 + arc.memory_write_port %mem, @identity(%c0_i32, %c0_i32) clock %clk latency 4 : <4 x i32, i32>, i32, i32 // CHECK-NEXT: arc.clock_domain arc.clock_domain (%arg) clock %clk : (i1) -> () { @@ -132,10 +132,10 @@ hw.module @memoryOps(in %clk: !seq.clock, in %en: i1, in %mask: i32, in %arg: i1 %mem2 = arc.memory <4 x i32, i32> // CHECK-NEXT: %{{.+}} = arc.memory_read_port [[MEM2]][%c1_i32] : <4 x i32, i32> %2 = arc.memory_read_port %mem2[%c1_i32] : <4 x i32, i32> - // CHECK-NEXT: arc.memory_write_port [[MEM2]], @identity1(%c1_i32, %c1_i32, %arg0) enable lat 1 : <4 x i32, i32>, i32, i32, i1 - arc.memory_write_port %mem2, @identity1(%c1_i32, %c1_i32, %arg0) enable lat 1 : <4 x i32, i32>, i32, i32, i1 - // CHECK-NEXT: arc.memory_write_port [[MEM2]], @identity(%c1_i32, %c1_i32) lat 1 : <4 x i32, i32>, i32, i32 - arc.memory_write_port %mem2, @identity(%c1_i32, %c1_i32) lat 1 : <4 x i32, i32>, i32, i32 + // CHECK-NEXT: arc.memory_write_port [[MEM2]], @identity1(%c1_i32, %c1_i32, %arg0) enable latency 1 : <4 x i32, i32>, i32, i32, i1 + arc.memory_write_port %mem2, @identity1(%c1_i32, %c1_i32, %arg0) enable latency 1 : <4 x i32, i32>, i32, i32, i1 + // CHECK-NEXT: arc.memory_write_port [[MEM2]], @identity(%c1_i32, %c1_i32) latency 1 : <4 x i32, i32>, i32, i32 + arc.memory_write_port %mem2, @identity(%c1_i32, %c1_i32) latency 1 : <4 x i32, i32>, i32, i32 } // CHECK: %{{.+}} = arc.memory_read [[MEM]][%c0_i32] : <4 x i32, i32> @@ -166,7 +166,7 @@ hw.module @vectorize_in_clock_domain(in %in0: i2, in %in1: i2, in %in2: i1, in % ^bb0(%arg0: i2, %arg1: i2, %arg2: i1, %arg3: i1): %1:2 = arc.vectorize (%arg0, %arg1), (%arg2, %arg3) : (i2, i2, i1, i1) -> (i1, i1) { ^bb0(%arg4: i2, %arg5: i1): - %2 = arc.state @vectorizable(%arg4, %arg5) lat 1 : (i2, i1) -> i1 + %2 = arc.state @vectorizable(%arg4, %arg5) latency 1 : (i2, i1) -> i1 arc.vectorize.return %2 : i1 } arc.output %1#0, %1#1 : i1, i1 @@ -183,7 +183,7 @@ arc.define @vectorizable(%arg0: i2, %arg1: i1) -> i1 { // CHECK: arc.clock_domain // CHECK: %{{.+}}:2 = arc.vectorize ({{.*}}, {{.*}}), ({{.*}}, {{.*}}) : (i2, i2, i1, i1) -> (i1, i1) { // CHECK: ^bb0([[A:%.+]]: i2, [[B:%.+]]: i1): -// CHECK: [[V1:%.+]] = arc.state @vectorizable([[A]], [[B]]) lat 1 : (i2, i1) -> i1 +// CHECK: [[V1:%.+]] = arc.state @vectorizable([[A]], [[B]]) latency 1 : (i2, i1) -> i1 // CHECK: arc.vectorize.return [[V1]] : i1 // CHECK: } diff --git a/test/Dialect/Arc/canonicalizers.mlir b/test/Dialect/Arc/canonicalizers.mlir index 0f4ec3594682..25be49d4166d 100644 --- a/test/Dialect/Arc/canonicalizers.mlir +++ b/test/Dialect/Arc/canonicalizers.mlir @@ -8,37 +8,36 @@ hw.module @stateOpCanonicalizer(in %clk: !seq.clock, in %in: i32, in %en: i1, in %true = hw.constant true %false = hw.constant false - arc.state @Foo(%in) clock %clk lat 1 : (i32) -> () - %0 = arc.state @Bar(%in) lat 0 : (i32) -> (i32) - // CHECK-NEXT: {{%.+}} = arc.state @Bar(%in) clock %clk lat 1 {name = "stateName"} : (i32) -> i32 - %1 = arc.state @Bar(%in) clock %clk lat 1 {name = "stateName"} : (i32) -> i32 - // CHECK-NEXT: {{%.+}} = arc.state @Bar(%in) clock %clk lat 1 {names = ["stateName"]} : (i32) -> i32 - %2 = arc.state @Bar(%in) clock %clk lat 1 {names = ["stateName"]} : (i32) -> i32 + arc.state @Foo(%in) clock %clk latency 1 : (i32) -> () + // CHECK-NEXT: {{%.+}} = arc.state @Bar(%in) clock %clk latency 1 {name = "stateName"} : (i32) -> i32 + %1 = arc.state @Bar(%in) clock %clk latency 1 {name = "stateName"} : (i32) -> i32 + // CHECK-NEXT: {{%.+}} = arc.state @Bar(%in) clock %clk latency 1 {names = ["stateName"]} : (i32) -> i32 + %2 = arc.state @Bar(%in) clock %clk latency 1 {names = ["stateName"]} : (i32) -> i32 - %3 = arc.state @Passthrough(%in) clock %clk enable %false reset %false lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V4:%.+]] = arc.state @Passthrough(%in) clock %clk lat 1 : (i32) -> i32 - %4 = arc.state @Passthrough(%in) clock %clk enable %true reset %false lat 1 : (i32) -> i32 - %5 = arc.state @Passthrough(%in) clock %clk enable %false reset %true lat 1 : (i32) -> i32 - %6 = arc.state @Passthrough(%in) clock %clk enable %true reset %true lat 1 : (i32) -> i32 + %3 = arc.state @Passthrough(%in) clock %clk enable %false reset %false latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V4:%.+]] = arc.state @Passthrough(%in) clock %clk latency 1 : (i32) -> i32 + %4 = arc.state @Passthrough(%in) clock %clk enable %true reset %false latency 1 : (i32) -> i32 + %5 = arc.state @Passthrough(%in) clock %clk enable %false reset %true latency 1 : (i32) -> i32 + %6 = arc.state @Passthrough(%in) clock %clk enable %true reset %true latency 1 : (i32) -> i32 - %7 = arc.state @Passthrough(%in) clock %clk enable %false reset %rst lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V8:%.+]] = arc.state @Passthrough(%in) clock %clk reset %rst lat 1 : (i32) -> i32 - %8 = arc.state @Passthrough(%in) clock %clk enable %true reset %rst lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V9:%.+]] = arc.state @Passthrough(%in) clock %clk enable %en lat 1 : (i32) -> i32 - %9 = arc.state @Passthrough(%in) clock %clk enable %en reset %false lat 1 : (i32) -> i32 - %10 = arc.state @Passthrough(%in) clock %clk enable %en reset %true lat 1 : (i32) -> i32 + %7 = arc.state @Passthrough(%in) clock %clk enable %false reset %rst latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V8:%.+]] = arc.state @Passthrough(%in) clock %clk reset %rst latency 1 : (i32) -> i32 + %8 = arc.state @Passthrough(%in) clock %clk enable %true reset %rst latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V9:%.+]] = arc.state @Passthrough(%in) clock %clk enable %en latency 1 : (i32) -> i32 + %9 = arc.state @Passthrough(%in) clock %clk enable %en reset %false latency 1 : (i32) -> i32 + %10 = arc.state @Passthrough(%in) clock %clk enable %en reset %true latency 1 : (i32) -> i32 - %11:2 = arc.state @Passthrough2(%in, %in) clock %clk enable %false lat 1 : (i32, i32) -> (i32, i32) - // CHECK-NEXT: [[V12:%.+]]:2 = arc.state @Passthrough2(%in, %in) clock %clk lat 1 : (i32, i32) -> (i32, i32) - %12:2 = arc.state @Passthrough2(%in, %in) clock %clk enable %true lat 1 : (i32, i32) -> (i32, i32) - // CHECK-NEXT: [[V13:%.+]]:2 = arc.state @Passthrough2(%in, %in) clock %clk lat 1 : (i32, i32) -> (i32, i32) - %13:2 = arc.state @Passthrough2(%in, %in) clock %clk reset %false lat 1 : (i32, i32) -> (i32, i32) - %14:2 = arc.state @Passthrough2(%in, %in) clock %clk reset %true lat 1 : (i32, i32) -> (i32, i32) + %11:2 = arc.state @Passthrough2(%in, %in) clock %clk enable %false latency 1 : (i32, i32) -> (i32, i32) + // CHECK-NEXT: [[V12:%.+]]:2 = arc.state @Passthrough2(%in, %in) clock %clk latency 1 : (i32, i32) -> (i32, i32) + %12:2 = arc.state @Passthrough2(%in, %in) clock %clk enable %true latency 1 : (i32, i32) -> (i32, i32) + // CHECK-NEXT: [[V13:%.+]]:2 = arc.state @Passthrough2(%in, %in) clock %clk latency 1 : (i32, i32) -> (i32, i32) + %13:2 = arc.state @Passthrough2(%in, %in) clock %clk reset %false latency 1 : (i32, i32) -> (i32, i32) + %14:2 = arc.state @Passthrough2(%in, %in) clock %clk reset %true latency 1 : (i32, i32) -> (i32, i32) - // CHECK-NEXT: %{{.+}} = arc.state @Passthrough(%in) clock %clk enable %false reset %true lat 1 {name = "stateName"} : (i32) -> i32 - %15 = arc.state @Passthrough(%in) clock %clk enable %false reset %true lat 1 {name = "stateName"} : (i32) -> i32 - // CHECK-NEXT: %{{.+}} = arc.state @Passthrough(%in) clock %clk enable %false reset %true lat 1 {names = ["stateName"]} : (i32) -> i32 - %16 = arc.state @Passthrough(%in) clock %clk enable %false reset %true lat 1 {names = ["stateName"]} : (i32) -> i32 + // CHECK-NEXT: %{{.+}} = arc.state @Passthrough(%in) clock %clk enable %false reset %true latency 1 {name = "stateName"} : (i32) -> i32 + %15 = arc.state @Passthrough(%in) clock %clk enable %false reset %true latency 1 {name = "stateName"} : (i32) -> i32 + // CHECK-NEXT: %{{.+}} = arc.state @Passthrough(%in) clock %clk enable %false reset %true latency 1 {names = ["stateName"]} : (i32) -> i32 + %16 = arc.state @Passthrough(%in) clock %clk enable %false reset %true latency 1 {names = ["stateName"]} : (i32) -> i32 // CHECK-NEXT: hw.output %c0_i32, [[V4]], %c0_i32, %c0_i32, %c0_i32, [[V8]], [[V9]], %c0_i32, %c0_i32, %c0_i32, [[V12]]#0, [[V12]]#1, [[V13]]#0, [[V13]]#1, %c0_i32, %c0_i32 : i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32 hw.output %3, %4, %5, %6, %7, %8, %9, %10, %11#0, %11#1, %12#0, %12#1, %13#0, %13#1, %14#0, %14#1 : i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32 @@ -84,13 +83,13 @@ hw.module @clockDomainCanonicalizer(in %clk: !seq.clock, in %data: i32, out out0 // COM: check that memories only used in one clock domain are pulled in and // COM: constants are cloned when used in multiple clock domains. // CHECK: arc.clock_domain () - // CHECK-NEXT: [[C0:%.+]] = hw.constant 0 // CHECK-NEXT: [[T:%.+]] = hw.constant true + // CHECK-NEXT: [[C0:%.+]] = hw.constant 0 // CHECK-NEXT: [[MEM:%.+]] = arc.memory - // CHECK-NEXT: arc.memory_write_port [[MEM]], @memWrite([[C0]], [[C0]], [[T]]) enable lat 1 : + // CHECK-NEXT: arc.memory_write_port [[MEM]], @memWrite([[C0]], [[C0]], [[T]]) enable latency 1 : %0 = arc.clock_domain (%c0_i32, %mem, %true) clock %clk : (i32, !arc.memory<4 x i32, i32>, i1) -> i32 { ^bb0(%arg0: i32, %arg1: !arc.memory<4 x i32, i32>, %arg2: i1): - arc.memory_write_port %arg1, @memWrite(%arg0, %arg0, %arg2) enable lat 1 : !arc.memory<4 x i32, i32>, i32, i32, i1 + arc.memory_write_port %arg1, @memWrite(%arg0, %arg0, %arg2) enable latency 1 : !arc.memory<4 x i32, i32>, i32, i32, i1 arc.output %arg0 : i32 } // COM: check that unused inputs are removed, and constants are cloned into it @@ -99,7 +98,7 @@ hw.module @clockDomainCanonicalizer(in %clk: !seq.clock, in %data: i32, out out0 // CHECK-NEXT: arc.state %1 = arc.clock_domain (%true, %data) clock %clk : (i1, i32) -> i1 { ^bb0(%arg0: i1, %arg1: i32): - %1 = arc.state @identityi1(%arg0) lat 1 : (i1) -> i1 + %1 = arc.state @identityi1(%arg0) latency 1 : (i1) -> i1 arc.output %1 : i1 } // COM: check that duplicate inputs are merged @@ -107,7 +106,7 @@ hw.module @clockDomainCanonicalizer(in %clk: !seq.clock, in %data: i32, out out0 %2 = arc.clock_domain (%data, %data, %data) clock %clk : (i32, i32, i32) -> i32 { ^bb0(%arg0: i32, %arg1: i32, %arg2: i32): %3 = comb.add %arg0, %arg1, %arg2 : i32 - %4 = arc.state @Passthrough(%3) lat 1 : (i32) -> i32 + %4 = arc.state @Passthrough(%3) latency 1 : (i32) -> i32 arc.output %4 : i32 } // COM: check that unused outputs are removed @@ -121,8 +120,8 @@ hw.module @clockDomainCanonicalizer(in %clk: !seq.clock, in %data: i32, out out0 ^bb0(%arg0: i32, %arg1: i32): // TODO: add op such that it is not folded away because it's just passthrough %3 = hw.constant 0 : i32 - %4 = arc.state @Passthrough(%arg1) lat 1 : (i32) -> i32 - %5 = arc.state @Passthrough(%arg0) lat 1 : (i32) -> i32 + %4 = arc.state @Passthrough(%arg1) latency 1 : (i32) -> i32 + %5 = arc.state @Passthrough(%arg0) latency 1 : (i32) -> i32 arc.output %arg0, %3, %4, %5 : i32, i32, i32, i32 } diff --git a/test/Dialect/Arc/dedup.mlir b/test/Dialect/Arc/dedup.mlir index 6331fdb117e8..16c15b2aa14b 100644 --- a/test/Dialect/Arc/dedup.mlir +++ b/test/Dialect/Arc/dedup.mlir @@ -14,10 +14,10 @@ arc.define @SimpleB(%arg0: i4, %arg1: i4) -> i4 { // CHECK-LABEL: hw.module @Simple hw.module @Simple(in %x: i4, in %y: i4, in %clock: !seq.clock) { - // CHECK-NEXT: arc.state @SimpleA(%x, %y) - // CHECK-NEXT: arc.state @SimpleA(%y, %x) - %0 = arc.state @SimpleA(%x, %y) lat 0 : (i4, i4) -> i4 - %1 = arc.state @SimpleB(%y, %x) lat 0 : (i4, i4) -> i4 + // CHECK-NEXT: arc.call @SimpleA(%x, %y) + // CHECK-NEXT: arc.call @SimpleA(%y, %x) + %0 = arc.call @SimpleA(%x, %y) : (i4, i4) -> i4 + %1 = arc.call @SimpleB(%y, %x) : (i4, i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -38,10 +38,10 @@ arc.define @MismatchB(%arg0: i4, %arg1: i4) -> i4 { // CHECK-LABEL: hw.module @Mismatch hw.module @Mismatch(in %x: i4, in %y: i4) { - // CHECK-NEXT: arc.state @MismatchA(%x, %y) - // CHECK-NEXT: arc.state @MismatchB(%y, %x) - %0 = arc.state @MismatchA(%x, %y) lat 0 : (i4, i4) -> i4 - %1 = arc.state @MismatchB(%y, %x) lat 0 : (i4, i4) -> i4 + // CHECK-NEXT: arc.call @MismatchA(%x, %y) + // CHECK-NEXT: arc.call @MismatchB(%y, %x) + %0 = arc.call @MismatchA(%x, %y) : (i4, i4) -> i4 + %1 = arc.call @MismatchB(%y, %x) : (i4, i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -71,10 +71,10 @@ arc.define @OutlineConstB(%arg0: i4) -> i4 { hw.module @OutlineConst(in %x: i4, in %y: i4) { // CHECK-DAG: %c3_i4 = hw.constant 3 : i4 // CHECK-DAG: %c7_i4 = hw.constant 7 : i4 - // CHECK-DAG: arc.state @OutlineConstA(%x, %c3_i4) - // CHECK-DAG: arc.state @OutlineConstA(%y, %c7_i4) - %0 = arc.state @OutlineConstA(%x) lat 0 : (i4) -> i4 - %1 = arc.state @OutlineConstB(%y) lat 0 : (i4) -> i4 + // CHECK-DAG: arc.call @OutlineConstA(%x, %c3_i4) + // CHECK-DAG: arc.call @OutlineConstA(%y, %c7_i4) + %0 = arc.call @OutlineConstA(%x) : (i4) -> i4 + %1 = arc.call @OutlineConstB(%y) : (i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -120,14 +120,14 @@ hw.module @OutlineNonUniformConsts(in %x: i4) { // CHECK-DAG: %c7_i4 = hw.constant 7 : i4 // CHECK-DAG: %c5_i4 = hw.constant 5 : i4 // CHECK-DAG: %c4_i4 = hw.constant 4 : i4 - // CHECK-DAG: arc.state @OutlineNonUniformConstsA(%x, %c3_i4) - // CHECK-DAG: arc.state @OutlineNonUniformConstsA(%c7_i4, %x) - // CHECK-DAG: arc.state @OutlineNonUniformConstsA(%c5_i4, %c4_i4) - // CHECK-DAG: arc.state @OutlineNonUniformConstsA(%x, %x) - %0 = arc.state @OutlineNonUniformConstsA(%x) lat 0 : (i4) -> i4 - %1 = arc.state @OutlineNonUniformConstsB(%x) lat 0 : (i4) -> i4 - %2 = arc.state @OutlineNonUniformConstsC() lat 0 : () -> i4 - %3 = arc.state @OutlineNonUniformConstsD(%x, %x) lat 0 : (i4, i4) -> i4 + // CHECK-DAG: arc.call @OutlineNonUniformConstsA(%x, %c3_i4) + // CHECK-DAG: arc.call @OutlineNonUniformConstsA(%c7_i4, %x) + // CHECK-DAG: arc.call @OutlineNonUniformConstsA(%c5_i4, %c4_i4) + // CHECK-DAG: arc.call @OutlineNonUniformConstsA(%x, %x) + %0 = arc.call @OutlineNonUniformConstsA(%x) : (i4) -> i4 + %1 = arc.call @OutlineNonUniformConstsB(%x) : (i4) -> i4 + %2 = arc.call @OutlineNonUniformConstsC() : () -> i4 + %3 = arc.call @OutlineNonUniformConstsD(%x, %x) : (i4, i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -159,12 +159,12 @@ arc.define @SplitArgumentsC(%arg0: i4, %arg1: i4) -> i4 { // CHECK-LABEL: hw.module @SplitArguments hw.module @SplitArguments(in %x: i4, in %y: i4) { - // CHECK-DAG: arc.state @SplitArgumentsA(%x, %x) - // CHECK-DAG: arc.state @SplitArgumentsA(%x, %y) - // CHECK-DAG: arc.state @SplitArgumentsA(%y, %x) - %0 = arc.state @SplitArgumentsA(%x) lat 0 : (i4) -> i4 - %1 = arc.state @SplitArgumentsB(%x, %y) lat 0 : (i4, i4) -> i4 - %2 = arc.state @SplitArgumentsC(%x, %y) lat 0 : (i4, i4) -> i4 + // CHECK-DAG: arc.call @SplitArgumentsA(%x, %x) + // CHECK-DAG: arc.call @SplitArgumentsA(%x, %y) + // CHECK-DAG: arc.call @SplitArgumentsA(%y, %x) + %0 = arc.call @SplitArgumentsA(%x) : (i4) -> i4 + %1 = arc.call @SplitArgumentsB(%x, %y) : (i4, i4) -> i4 + %2 = arc.call @SplitArgumentsC(%x, %y) : (i4, i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -198,12 +198,12 @@ arc.define @WeirdSplitArgumentsC(%arg0: i4, %arg1: i4) -> i4 { // CHECK-LABEL: hw.module @WeirdSplitArguments hw.module @WeirdSplitArguments(in %x: i4, in %y: i4) { - // CHECK-DAG: arc.state @WeirdSplitArgumentsA(%x, %x, %x, %y) - // CHECK-DAG: arc.state @WeirdSplitArgumentsA(%x, %x, %y, %y) - // CHECK-DAG: arc.state @WeirdSplitArgumentsA(%x, %y, %x, %y) - %0 = arc.state @WeirdSplitArgumentsA(%x, %y) lat 0 : (i4, i4) -> i4 - %1 = arc.state @WeirdSplitArgumentsB(%x, %y) lat 0 : (i4, i4) -> i4 - %2 = arc.state @WeirdSplitArgumentsC(%x, %y) lat 0 : (i4, i4) -> i4 + // CHECK-DAG: arc.call @WeirdSplitArgumentsA(%x, %x, %x, %y) + // CHECK-DAG: arc.call @WeirdSplitArgumentsA(%x, %x, %y, %y) + // CHECK-DAG: arc.call @WeirdSplitArgumentsA(%x, %y, %x, %y) + %0 = arc.call @WeirdSplitArgumentsA(%x, %y) : (i4, i4) -> i4 + %1 = arc.call @WeirdSplitArgumentsB(%x, %y) : (i4, i4) -> i4 + %2 = arc.call @WeirdSplitArgumentsC(%x, %y) : (i4, i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -224,10 +224,10 @@ arc.define @VariadicDiffsDontDedupB(%arg0: i4, %arg1: i4) -> i4 { // CHECK-LABEL: hw.module @VariadicDiffsDontDedup hw.module @VariadicDiffsDontDedup(in %x: i4, in %y: i4, in %z: i4) { - // CHECK-DAG: arc.state @VariadicDiffsDontDedupA(%x, %y, %z) - // CHECK-DAG: arc.state @VariadicDiffsDontDedupB(%x, %y) - %0 = arc.state @VariadicDiffsDontDedupA(%x, %y, %z) lat 0 : (i4, i4, i4) -> i4 - %1 = arc.state @VariadicDiffsDontDedupB(%x, %y) lat 0 : (i4, i4) -> i4 + // CHECK-DAG: arc.call @VariadicDiffsDontDedupA(%x, %y, %z) + // CHECK-DAG: arc.call @VariadicDiffsDontDedupB(%x, %y) + %0 = arc.call @VariadicDiffsDontDedupA(%x, %y, %z) : (i4, i4, i4) -> i4 + %1 = arc.call @VariadicDiffsDontDedupB(%x, %y) : (i4, i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -258,10 +258,10 @@ arc.define @DedupWithRegionsB(%arg0: i1, %arg1: i4) -> i4 { // CHECK-LABEL: hw.module @DedupWithRegions hw.module @DedupWithRegions(in %x: i4, in %y: i1) { - // CHECK-NEXT: arc.state @DedupWithRegionsA(%x, %y) - // CHECK-NEXT: arc.state @DedupWithRegionsA(%x, %y) - %0 = arc.state @DedupWithRegionsA(%x, %y) lat 0 : (i4, i1) -> i4 - %1 = arc.state @DedupWithRegionsB(%y, %x) lat 0 : (i1, i4) -> i4 + // CHECK-NEXT: arc.call @DedupWithRegionsA(%x, %y) + // CHECK-NEXT: arc.call @DedupWithRegionsA(%x, %y) + %0 = arc.call @DedupWithRegionsA(%x, %y) : (i4, i1) -> i4 + %1 = arc.call @DedupWithRegionsB(%y, %x) : (i1, i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -282,10 +282,10 @@ arc.define @DiffAttrsBlockDedupB(%arg0: i4) -> i4 { // CHECK-LABEL: hw.module @DiffAttrsBlockDedup hw.module @DiffAttrsBlockDedup(in %x: i4) { - // CHECK-NEXT: arc.state @DiffAttrsBlockDedupA(%x) - // CHECK-NEXT: arc.state @DiffAttrsBlockDedupB(%x) - %0 = arc.state @DiffAttrsBlockDedupA(%x) lat 0 : (i4) -> i4 - %1 = arc.state @DiffAttrsBlockDedupB(%x) lat 0 : (i4) -> i4 + // CHECK-NEXT: arc.call @DiffAttrsBlockDedupA(%x) + // CHECK-NEXT: arc.call @DiffAttrsBlockDedupB(%x) + %0 = arc.call @DiffAttrsBlockDedupA(%x) : (i4) -> i4 + %1 = arc.call @DiffAttrsBlockDedupB(%x) : (i4) -> i4 // CHECK-NEXT: hw.output } // CHECK-NEXT: } @@ -308,42 +308,10 @@ arc.define @DiffTypesBlockDedupB(%arg0: i4) -> i1 { // CHECK-LABEL: hw.module @DiffTypesBlockDedup hw.module @DiffTypesBlockDedup(in %x: i4) { - // CHECK-NEXT: arc.state @DiffTypesBlockDedupA(%x) - // CHECK-NEXT: arc.state @DiffTypesBlockDedupB(%x) - %0 = arc.state @DiffTypesBlockDedupA(%x) lat 0 : (i4) -> i1 - %1 = arc.state @DiffTypesBlockDedupB(%x) lat 0 : (i4) -> i1 - // CHECK-NEXT: hw.output -} -// CHECK-NEXT: } - -//===----------------------------------------------------------------------===// - -// CHECK-LABEL: arc.define @StateAndCallA -arc.define @StateAndCallA(%arg0: i4, %arg1: i4) -> i4 { - %0 = arc.call @NestedArc(%arg0, %arg1) {NestedArc} : (i4, i4) -> i4 - arc.output %0 : i4 -} - -// CHECK-NOT: arc.define @StateAndCallB -arc.define @StateAndCallB(%arg0: i4, %arg1: i4) -> i4 { - %0 = arc.call @NestedArc(%arg0, %arg1) {NestedArc} : (i4, i4) -> i4 - arc.output %0 : i4 -} - -// CHECK-LABEL: arc.define @NestedArc -arc.define @NestedArc(%arg0: i4, %arg1: i4) -> i4 { - %0 = comb.and %arg0, %arg1 {NestedArc} : i4 - arc.output %0 : i4 -} - -// CHECK-LABEL: hw.module @StateAndCall -hw.module @StateAndCall(in %x: i4, in %y: i4) { - // CHECK-NEXT: arc.state @StateAndCallA(%x, %y) - // CHECK-NEXT: arc.call @StateAndCallA(%y, %x) - // CHECK-NEXT: arc.call @StateAndCallA(%y, %x) - %0 = arc.state @StateAndCallB(%x, %y) lat 0 : (i4, i4) -> i4 - %1 = arc.call @StateAndCallA(%y, %x) : (i4, i4) -> i4 - %2 = arc.call @StateAndCallB(%y, %x) : (i4, i4) -> i4 + // CHECK-NEXT: arc.call @DiffTypesBlockDedupA(%x) + // CHECK-NEXT: arc.call @DiffTypesBlockDedupB(%x) + %0 = arc.call @DiffTypesBlockDedupA(%x) : (i4) -> i1 + %1 = arc.call @DiffTypesBlockDedupB(%x) : (i4) -> i1 // CHECK-NEXT: hw.output } // CHECK-NEXT: } diff --git a/test/Dialect/Arc/infer-memories.mlir b/test/Dialect/Arc/infer-memories.mlir index 150975adb1b8..d47f8fac1097 100644 --- a/test/Dialect/Arc/infer-memories.mlir +++ b/test/Dialect/Arc/infer-memories.mlir @@ -7,7 +7,7 @@ hw.generator.schema @FIRRTLMem, "FIRRTL_Memory", ["depth", "numReadPorts", "numW hw.module @TestWOMemory(in %clock: !seq.clock, in %addr: i10, in %enable: i1, in %data: i8) { // CHECK-NOT: hw.instance // CHECK-NEXT: [[FOO:%.+]] = arc.memory <1024 x i8, i10> {name = "foo"} - // CHECK-NEXT: arc.memory_write_port [[FOO]], @mem_write{{.*}}(%addr, %data, %enable) clock %clock enable lat 1 : <1024 x i8, i10>, i10, i8, i1 + // CHECK-NEXT: arc.memory_write_port [[FOO]], @mem_write{{.*}}(%addr, %data, %enable) clock %clock enable latency 1 : <1024 x i8, i10>, i10, i8, i1 // CHECK-NEXT: hw.output hw.instance "foo" @WOMemory(W0_addr: %addr: i10, W0_en: %enable: i1, W0_clk: %clock: !seq.clock, W0_data: %data: i8) -> () } @@ -25,7 +25,7 @@ hw.module @TestWOMemoryWithMask(in %clock: !seq.clock, in %addr: i10, in %enable // CHECK-NEXT: [[MASK_BIT1:%.+]] = comb.extract %mask from 1 : (i2) -> i1 // CHECK-NEXT: [[MASK_BYTE1:%.+]] = comb.replicate [[MASK_BIT1]] : (i1) -> i8 // CHECK-NEXT: [[MASK:%.+]] = comb.concat [[MASK_BYTE1]], [[MASK_BYTE0]] - // CHECK-NEXT: arc.memory_write_port [[FOO]], @mem_write{{.*}}(%addr, %data, %enable, [[MASK]]) clock %clock enable mask lat 1 : <1024 x i16, i10>, i10, i16, i1, i16 + // CHECK-NEXT: arc.memory_write_port [[FOO]], @mem_write{{.*}}(%addr, %data, %enable, [[MASK]]) clock %clock enable mask latency 1 : <1024 x i16, i10>, i10, i16, i1, i16 // CHECK-NEXT: hw.output hw.instance "foo" @WOMemoryWithMask(W0_addr: %addr: i10, W0_en: %enable: i1, W0_clk: %clock: !seq.clock, W0_data: %data: i16, W0_mask: %mask: i2) -> () } @@ -83,7 +83,7 @@ hw.module @TestRWMemory(in %clock: !seq.clock, in %addr: i10, in %enable: i1, in // CHECK-NEXT: [[ZERO:%.+]] = hw.constant 0 : i8 // CHECK-NEXT: [[MUX:%.+]] = comb.mux [[RENABLE]], [[RDATA]], [[ZERO]] : i8 // CHECK-NEXT: [[WENABLE:%.+]] = comb.and %enable, %wmode - // CHECK-NEXT: arc.memory_write_port [[FOO]], @mem_write{{.*}}(%addr, %wdata, [[WENABLE]]) clock %clock enable lat 1 : <1024 x i8, i10>, i10, i8, i1 + // CHECK-NEXT: arc.memory_write_port [[FOO]], @mem_write{{.*}}(%addr, %wdata, [[WENABLE]]) clock %clock enable latency 1 : <1024 x i8, i10>, i10, i8, i1 // CHECK-NEXT: hw.output [[MUX]] %0 = hw.instance "foo" @RWMemory(RW0_addr: %addr: i10, RW0_en: %enable: i1, RW0_clk: %clock: !seq.clock, RW0_wmode: %wmode: i1, RW0_wdata: %wdata: i8) -> (RW0_rdata: i8) hw.output %0 : i8 diff --git a/test/Dialect/Arc/infer-state-properties.mlir b/test/Dialect/Arc/infer-state-properties.mlir index 58eb9957f076..9ed39c913a35 100644 --- a/test/Dialect/Arc/infer-state-properties.mlir +++ b/test/Dialect/Arc/infer-state-properties.mlir @@ -173,76 +173,76 @@ arc.define @onlyOneReset(%arg0: i1, %arg1: i1, %arg2: i1) -> (i1, i1) { // CHECK-LABEL: hw.module @testModule hw.module @testModule (in %arg0: i1, in %arg1: i1, in %arg2: i1, in %arg3: i1, in %clock: !seq.clock) { // COM: Test: AND based reset pattern detected - // CHECK: arc.state @ANDBasedReset(%arg0, %arg1, %arg2) clock %clock reset %arg0 lat 1 : (i1, i1, i1) -> i1 - %0 = arc.state @ANDBasedReset(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> i1 + // CHECK: arc.state @ANDBasedReset(%arg0, %arg1, %arg2) clock %clock reset %arg0 latency 1 : (i1, i1, i1) -> i1 + %0 = arc.state @ANDBasedReset(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> i1 // COM: Test: MUX based reset pattern detected - // CHECK: arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock reset %arg0 lat 1 : (i1, i1, i1) -> i1 - %1 = arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> i1 + // CHECK: arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock reset %arg0 latency 1 : (i1, i1, i1) -> i1 + %1 = arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> i1 // COM: Test: MUX based enable pattern detected - // CHECK: arc.state @enable(%true{{(_[0-9]+)?}}, %arg1, %arg2, %false{{(_[0-9]+)?}}) clock %clock enable %arg0 lat 1 : (i1, i1, i1, i1) -> i1 - %2 = arc.state @enable(%arg0, %arg1, %arg2, %2) clock %clock lat 1 : (i1, i1, i1, i1) -> i1 + // CHECK: arc.state @enable(%true{{(_[0-9]+)?}}, %arg1, %arg2, %false{{(_[0-9]+)?}}) clock %clock enable %arg0 latency 1 : (i1, i1, i1, i1) -> i1 + %2 = arc.state @enable(%arg0, %arg1, %arg2, %2) clock %clock latency 1 : (i1, i1, i1, i1) -> i1 // COM: Test: MUX based disable pattern detected // CHECK: [[DISABLE:%.+]] = comb.xor %arg0, %true{{(_[0-9]+)?}} - // CHECK: arc.state @disable(%false{{(_[0-9]+)?}}, %arg1, %arg2, %false{{(_[0-9]+)?}}) clock %clock enable [[DISABLE]] lat 1 : (i1, i1, i1, i1) -> i1 - %3 = arc.state @disable(%arg0, %arg1, %arg2, %3) clock %clock lat 1 : (i1, i1, i1, i1) -> i1 + // CHECK: arc.state @disable(%false{{(_[0-9]+)?}}, %arg1, %arg2, %false{{(_[0-9]+)?}}) clock %clock enable [[DISABLE]] latency 1 : (i1, i1, i1, i1) -> i1 + %3 = arc.state @disable(%arg0, %arg1, %arg2, %3) clock %clock latency 1 : (i1, i1, i1, i1) -> i1 // COM: Test: both reset and enable are detected in one go - // CHECK: arc.state @resetAndEnable(%true{{(_[0-9]+)?}}, %arg1, %arg2, %false{{(_[0-9]+)?}}, %arg3) clock %clock enable %arg0 reset %arg3 lat 1 : (i1, i1, i1, i1, i1) -> i1 - %4 = arc.state @resetAndEnable(%arg0, %arg1, %arg2, %4, %arg3) clock %clock lat 1 : (i1, i1, i1, i1, i1) -> i1 + // CHECK: arc.state @resetAndEnable(%true{{(_[0-9]+)?}}, %arg1, %arg2, %false{{(_[0-9]+)?}}, %arg3) clock %clock enable %arg0 reset %arg3 latency 1 : (i1, i1, i1, i1, i1) -> i1 + %4 = arc.state @resetAndEnable(%arg0, %arg1, %arg2, %4, %arg3) clock %clock latency 1 : (i1, i1, i1, i1, i1) -> i1 // COM: Test: mixed enables and disables do not work - // CHECK-NEXT: [[EN_DIS:%.+]]:2 = arc.state @mixedEnableAndDisable(%arg0, %arg1, %arg2, [[EN_DIS]]#0, [[EN_DIS]]#1) clock %clock lat 1 : (i1, i1, i1, i1, i1) -> (i1, i1) - %5, %6 = arc.state @mixedEnableAndDisable(%arg0, %arg1, %arg2, %5, %6) clock %clock lat 1 : (i1, i1, i1, i1, i1) -> (i1, i1) + // CHECK-NEXT: [[EN_DIS:%.+]]:2 = arc.state @mixedEnableAndDisable(%arg0, %arg1, %arg2, [[EN_DIS]]#0, [[EN_DIS]]#1) clock %clock latency 1 : (i1, i1, i1, i1, i1) -> (i1, i1) + %5, %6 = arc.state @mixedEnableAndDisable(%arg0, %arg1, %arg2, %5, %6) clock %clock latency 1 : (i1, i1, i1, i1, i1) -> (i1, i1) // COM: Test: mixed MUX and AND resets for different output work - // CHECK-NEXT: arc.state @mixedAndMuxReset(%arg0, %arg1, %arg2) clock %clock reset %arg0 lat 1 : (i1, i1, i1) -> (i1, i1) - %7, %8 = arc.state @mixedAndMuxReset(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> (i1, i1) + // CHECK-NEXT: arc.state @mixedAndMuxReset(%arg0, %arg1, %arg2) clock %clock reset %arg0 latency 1 : (i1, i1, i1) -> (i1, i1) + %7, %8 = arc.state @mixedAndMuxReset(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> (i1, i1) // COM: Test: mixed MUX and AND resets do not work when the reset conditions are different - // CHECK-NEXT: arc.state @mixedAndMuxResetDifferentConditions(%arg0, %arg1, %arg2, %arg3) clock %clock lat 1 : (i1, i1, i1, i1) -> (i1, i1) - %9, %10 = arc.state @mixedAndMuxResetDifferentConditions(%arg0, %arg1, %arg2, %arg3) clock %clock lat 1 : (i1, i1, i1, i1) -> (i1, i1) + // CHECK-NEXT: arc.state @mixedAndMuxResetDifferentConditions(%arg0, %arg1, %arg2, %arg3) clock %clock latency 1 : (i1, i1, i1, i1) -> (i1, i1) + %9, %10 = arc.state @mixedAndMuxResetDifferentConditions(%arg0, %arg1, %arg2, %arg3) clock %clock latency 1 : (i1, i1, i1, i1) -> (i1, i1) // COM: Test: Reset can be pulled out even if there is already a reset operand // CHECK-NEXT: [[NEW_RST:%.+]] = comb.or %arg1, %arg0 : i1 - // CHECK-NEXT: arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock reset [[NEW_RST]] lat 1 : (i1, i1, i1) -> i1 - %11 = arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock reset %arg1 lat 1 : (i1, i1, i1) -> i1 + // CHECK-NEXT: arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock reset [[NEW_RST]] latency 1 : (i1, i1, i1) -> i1 + %11 = arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock reset %arg1 latency 1 : (i1, i1, i1) -> i1 // COM: Test: Enable can be pulled out even if there is already an enable operand // CHECK: [[NEW_EN:%.+]] = comb.and %arg1, %arg0 : i1 - // CHECK: arc.state @enable(%true{{(_[0-9]+)?}}, %arg1, %arg2, %false{{(_[0-9]+)?}}) clock %clock enable [[NEW_EN]] lat 1 : (i1, i1, i1, i1) -> i1 - %12 = arc.state @enable(%arg0, %arg1, %arg2, %12) clock %clock enable %arg1 lat 1 : (i1, i1, i1, i1) -> i1 + // CHECK: arc.state @enable(%true{{(_[0-9]+)?}}, %arg1, %arg2, %false{{(_[0-9]+)?}}) clock %clock enable [[NEW_EN]] latency 1 : (i1, i1, i1, i1) -> i1 + %12 = arc.state @enable(%arg0, %arg1, %arg2, %12) clock %clock enable %arg1 latency 1 : (i1, i1, i1, i1) -> i1 // COM: Test: Reset can be pulled out even if there is already an enable operand // CHECK: [[RST_COND:%.+]] = comb.and %arg1, %arg0 : i1 - // CHECK: arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock enable %arg1 reset [[RST_COND]] lat 1 : (i1, i1, i1) -> i1 - %13 = arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock enable %arg1 lat 1 : (i1, i1, i1) -> i1 + // CHECK: arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock enable %arg1 reset [[RST_COND]] latency 1 : (i1, i1, i1) -> i1 + %13 = arc.state @MUXBasedReset(%arg0, %arg1, %arg2) clock %clock enable %arg1 latency 1 : (i1, i1, i1) -> i1 // COM: Test: mixed high and low resets cannot be pulled out - // CHECK-NEXT: arc.state @mixedAndMuxResetLowAndHigh(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> (i1, i1) - %14, %15 = arc.state @mixedAndMuxResetLowAndHigh(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> (i1, i1) + // CHECK-NEXT: arc.state @mixedAndMuxResetLowAndHigh(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> (i1, i1) + %14, %15 = arc.state @mixedAndMuxResetLowAndHigh(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> (i1, i1) // COM: Test: outputs with different enable conditions cannot be combined - // CHECK-NEXT: [[EN0:%.+]]:2 = arc.state @differentEnables(%arg0, %arg1, %arg2, [[EN0]]#0, [[EN0]]#1) clock %clock lat 1 : (i1, i1, i1, i1, i1) -> (i1, i1) - %16, %17 = arc.state @differentEnables(%arg0, %arg1, %arg2, %16, %17) clock %clock lat 1 : (i1, i1, i1, i1, i1) -> (i1, i1) + // CHECK-NEXT: [[EN0:%.+]]:2 = arc.state @differentEnables(%arg0, %arg1, %arg2, [[EN0]]#0, [[EN0]]#1) clock %clock latency 1 : (i1, i1, i1, i1, i1) -> (i1, i1) + %16, %17 = arc.state @differentEnables(%arg0, %arg1, %arg2, %16, %17) clock %clock latency 1 : (i1, i1, i1, i1, i1) -> (i1, i1) // COM: Test: enable where not all outputs have them (some have none rather than mismatching) cannot be combined - // CHECK-NEXT: [[EN1:%.+]]:2 = arc.state @onlyOneEnable(%arg0, %arg1, %arg2, [[EN1]]#0) clock %clock lat 1 : (i1, i1, i1, i1) -> (i1, i1) - %18, %19 = arc.state @onlyOneEnable(%arg0, %arg1, %arg2, %18) clock %clock lat 1 : (i1, i1, i1, i1) -> (i1, i1) + // CHECK-NEXT: [[EN1:%.+]]:2 = arc.state @onlyOneEnable(%arg0, %arg1, %arg2, [[EN1]]#0) clock %clock latency 1 : (i1, i1, i1, i1) -> (i1, i1) + %18, %19 = arc.state @onlyOneEnable(%arg0, %arg1, %arg2, %18) clock %clock latency 1 : (i1, i1, i1, i1) -> (i1, i1) // COM: Test: reset where not all outputs have them (some have none rather than mismatching) cannot be combined - // CHECK-NEXT: arc.state @onlyOneReset(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> (i1, i1) - %20, %21 = arc.state @onlyOneReset(%arg0, %arg1, %arg2) clock %clock lat 1 : (i1, i1, i1) -> (i1, i1) + // CHECK-NEXT: arc.state @onlyOneReset(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> (i1, i1) + %20, %21 = arc.state @onlyOneReset(%arg0, %arg1, %arg2) clock %clock latency 1 : (i1, i1, i1) -> (i1, i1) // COM: the next case can in theory be supported, but requires quite a bit more complexity in the pass - // CHECK-NEXT: [[EN2:%.+]] = arc.state @enableConditionHasOtherUse(%arg0, %arg1, [[EN2]]) clock %clock lat 1 : (i1, i1, i1) -> i1 - %22 = arc.state @enableConditionHasOtherUse(%arg0, %arg1, %22) clock %clock lat 1 : (i1, i1, i1) -> i1 + // CHECK-NEXT: [[EN2:%.+]] = arc.state @enableConditionHasOtherUse(%arg0, %arg1, [[EN2]]) clock %clock latency 1 : (i1, i1, i1) -> i1 + %22 = arc.state @enableConditionHasOtherUse(%arg0, %arg1, %22) clock %clock latency 1 : (i1, i1, i1) -> i1 // COM: Test: When the feedback loop has another use inside the arc, we can not simply replace it with constant 0 - // CHECK: [[EN3:%.+]] = arc.state @enableFeedbackHasOtherUse(%true{{(_[0-9]+)?}}, %arg1, [[EN3]]) clock %clock enable %arg0 lat 1 : (i1, i1, i1) -> i1 - %23 = arc.state @enableFeedbackHasOtherUse(%arg0, %arg1, %23) clock %clock lat 1 : (i1, i1, i1) -> i1 + // CHECK: [[EN3:%.+]] = arc.state @enableFeedbackHasOtherUse(%true{{(_[0-9]+)?}}, %arg1, [[EN3]]) clock %clock enable %arg0 latency 1 : (i1, i1, i1) -> i1 + %23 = arc.state @enableFeedbackHasOtherUse(%arg0, %arg1, %23) clock %clock latency 1 : (i1, i1, i1) -> i1 } // TODO: test that patterns handle the case where the output is used for another thing as well properly diff --git a/test/Dialect/Arc/inline-arcs.mlir b/test/Dialect/Arc/inline-arcs.mlir index 589b95994e36..541d00db9bdf 100644 --- a/test/Dialect/Arc/inline-arcs.mlir +++ b/test/Dialect/Arc/inline-arcs.mlir @@ -6,10 +6,10 @@ // CHECK-LABEL: func.func @Simple func.func @Simple(%arg0: i4, %arg1: !seq.clock) -> (i4, i4) { // CHECK-NEXT: %0 = comb.and %arg0, %arg0 - // CHECK-NEXT: %1 = arc.state @SimpleB(%arg0) clock %arg1 lat 1 + // CHECK-NEXT: %1 = arc.state @SimpleB(%arg0) clock %arg1 latency 1 // CHECK-NEXT: return %0, %1 - %0 = arc.state @SimpleA(%arg0) lat 0 : (i4) -> i4 - %1 = arc.state @SimpleB(%arg0) clock %arg1 lat 1 : (i4) -> i4 + %0 = arc.call @SimpleA(%arg0) : (i4) -> i4 + %1 = arc.state @SimpleB(%arg0) clock %arg1 latency 1 : (i4) -> i4 return %0, %1 : i4, i4 } // CHECK-NEXT: } @@ -26,7 +26,7 @@ arc.define @SimpleB(%arg0: i4) -> i4 { hw.module @nestedRegionTest(in %arg0: i4, in %arg1: i4, out out0: i4) { - %0 = arc.state @sub3(%arg0, %arg1) lat 0 : (i4, i4) -> i4 + %0 = arc.call @sub3(%arg0, %arg1) : (i4, i4) -> i4 hw.output %0 : i4 } @@ -54,8 +54,8 @@ arc.define @sub3(%arg0: i4, %arg1: i4) -> i4 { // CHECK-NEXT: hw.output [[IFRES]] : i4 hw.module @opsInNestedRegionsAreAlsoCounted(in %arg0: i4, in %arg1: i4, out out0: i4, out out1: i4) { - %0 = arc.state @sub4(%arg0, %arg1) lat 0 : (i4, i4) -> i4 - %1 = arc.state @sub4(%arg0, %arg1) lat 0 : (i4, i4) -> i4 + %0 = arc.call @sub4(%arg0, %arg1) : (i4, i4) -> i4 + %1 = arc.call @sub4(%arg0, %arg1) : (i4, i4) -> i4 hw.output %0, %1 : i4, i4 } @@ -74,12 +74,12 @@ arc.define @sub4(%arg0: i4, %arg1: i4) -> i4 { } // CHECK-LABEL: hw.module @opsInNestedRegionsAreAlsoCounted -// CHECK-NEXT: [[STATERES1:%.+]] = arc.state @sub4(%arg0, %arg1) lat 0 : (i4, i4) -> i4 -// CHECK-NEXT: [[STATERES2:%.+]] = arc.state @sub4(%arg0, %arg1) lat 0 : (i4, i4) -> i4 +// CHECK-NEXT: [[STATERES1:%.+]] = arc.call @sub4(%arg0, %arg1) : (i4, i4) -> i4 +// CHECK-NEXT: [[STATERES2:%.+]] = arc.call @sub4(%arg0, %arg1) : (i4, i4) -> i4 // CHECK-NEXT: hw.output [[STATERES1]], [[STATERES2]] : i4, i4 hw.module @nestedBlockArgumentsTest(in %arg0: index, in %arg1: i4, out out0: i4) { - %0 = arc.state @sub5(%arg0, %arg1) lat 0 : (index, i4) -> i4 + %0 = arc.call @sub5(%arg0, %arg1) : (index, i4) -> i4 hw.output %0 : i4 } @@ -102,8 +102,8 @@ arc.define @sub5(%arg0: index, %arg1: i4) -> i4 { // CHECK-LABEL: hw.module @TopLevel hw.module @TopLevel(in %clk: !seq.clock, in %arg0: i32, in %arg1: i32, out out0: i32, out out1: i32, out out2: i32, out out3: i32) { - %0:2 = arc.state @inlineIntoArc(%arg0, %arg1) clock %clk lat 1 : (i32, i32) -> (i32, i32) - %1:2 = arc.state @inlineIntoArc2(%arg0, %arg1) clock %clk lat 1 : (i32, i32) -> (i32, i32) + %0:2 = arc.state @inlineIntoArc(%arg0, %arg1) clock %clk latency 1 : (i32, i32) -> (i32, i32) + %1:2 = arc.state @inlineIntoArc2(%arg0, %arg1) clock %clk latency 1 : (i32, i32) -> (i32, i32) hw.output %0#0, %0#1, %1#0, %1#1 : i32, i32, i32, i32 } @@ -169,7 +169,7 @@ arc.define @ToBeRemoved3(%arg0: i32) -> i32 { // CHECK-LABEL: hw.module @onlyIntoArcs hw.module @onlyIntoArcs(in %arg0: i4, in %arg1: i4, in %arg2: !seq.clock, out out0: i4) { - %0 = arc.state @sub1(%arg0, %arg1) lat 0 : (i4, i4) -> i4 + %0 = arc.call @sub1(%arg0, %arg1) : (i4, i4) -> i4 hw.output %0 : i4 } // CHECK-LABEL: arc.define @sub1 diff --git a/test/Dialect/Arc/isolate-clocks.mlir b/test/Dialect/Arc/isolate-clocks.mlir index 30ccd963b3df..7ec7867c6c94 100644 --- a/test/Dialect/Arc/isolate-clocks.mlir +++ b/test/Dialect/Arc/isolate-clocks.mlir @@ -2,37 +2,37 @@ // CHECK-LABEL: hw.module @basics hw.module @basics(in %clk0: !seq.clock, in %clk1: !seq.clock, in %clk2: !seq.clock, in %c0: i1, in %c1: i1, in %in: i32, out out0: i32, out out1: i32) { - // COM: check the basic things: clocked ops are grouped properly, lat 0 states + // COM: check the basic things: clocked ops are grouped properly // COM: are not considered clocked, clock domain materialization %0 = comb.and %c0, %c1 : i1 - %1 = arc.state @DummyArc(%in) clock %clk0 enable %0 reset %c1 lat 1 : (i32) -> i32 + %1 = arc.state @DummyArc(%in) clock %clk0 enable %0 reset %c1 latency 1 : (i32) -> i32 %mem = arc.memory <2 x i32, i1> - arc.memory_write_port %mem, @identity(%c0, %2) clock %clk0 lat 1 : <2 x i32, i1>, i1, i32 + arc.memory_write_port %mem, @identity(%c0, %2) clock %clk0 latency 1 : <2 x i32, i1>, i1, i32 %2 = arc.memory_read_port %mem[%c0] : <2 x i32, i1> - arc.memory_write_port %mem, @identity(%c1, %1) clock %clk0 lat 1 : <2 x i32, i1>, i1, i32 - %3 = arc.state @DummyArc(%4) clock %clk1 enable %0 reset %c1 lat 1 : (i32) -> i32 - %4 = arc.state @DummyArc(%2) lat 0 : (i32) -> i32 - %5 = arc.state @DummyArc(%4) clock %clk2 lat 1 : (i32) -> i32 + arc.memory_write_port %mem, @identity(%c1, %1) clock %clk0 latency 1 : <2 x i32, i1>, i1, i32 + %3 = arc.state @DummyArc(%4) clock %clk1 enable %0 reset %c1 latency 1 : (i32) -> i32 + %4 = arc.call @DummyArc(%2) : (i32) -> i32 + %5 = arc.state @DummyArc(%4) clock %clk2 latency 1 : (i32) -> i32 hw.output %3, %5 : i32, i32 // CHECK-NEXT: [[V0:%.+]] = comb.and %c0, %c1 : i1 // CHECK-NEXT: [[MEM:%.+]] = arc.memory <2 x i32, i1> // CHECK-NEXT: [[V6:%.+]] = arc.memory_read_port [[MEM]][%c0] : <2 x i32, i1> - // CHECK-NEXT: [[V1:%.+]] = arc.state @DummyArc([[V6]]) lat 0 : (i32) -> i32 + // CHECK-NEXT: [[V1:%.+]] = arc.call @DummyArc([[V6]]) : (i32) -> i32 // CHECK-NEXT: arc.clock_domain ([[MEM]], %c1, %c0, [[V6]], [[V0]], %in) clock %clk0 : (!arc.memory<2 x i32, i1>, i1, i1, i32, i1, i32) -> () { // CHECK-NEXT: ^bb0(%arg0: !arc.memory<2 x i32, i1>, %arg1: i1, %arg2: i1, %arg3: i32, %arg4: i1, %arg5: i32): - // CHECK-NEXT: arc.memory_write_port %arg0, @identity(%arg1, [[V7:%.+]]) lat 1 : - // CHECK-NEXT: arc.memory_write_port %arg0, @identity(%arg2, %arg3) lat 1 : - // CHECK-NEXT: [[V7]] = arc.state @DummyArc(%arg5) enable %arg4 reset %arg1 lat 1 : (i32) -> i32 + // CHECK-NEXT: arc.memory_write_port %arg0, @identity(%arg1, [[V7:%.+]]) latency 1 : + // CHECK-NEXT: arc.memory_write_port %arg0, @identity(%arg2, %arg3) latency 1 : + // CHECK-NEXT: [[V7]] = arc.state @DummyArc(%arg5) enable %arg4 reset %arg1 latency 1 : (i32) -> i32 // CHECK-NEXT: } // CHECK-NEXT: [[V3:%.+]] = arc.clock_domain ([[V0]], %c1, [[V1]]) clock %clk1 : (i1, i1, i32) -> i32 { // CHECK-NEXT: ^bb0(%arg0: i1, %arg1: i1, %arg2: i32): - // CHECK-NEXT: [[V5:%.+]] = arc.state @DummyArc(%arg2) enable %arg0 reset %arg1 lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V5:%.+]] = arc.state @DummyArc(%arg2) enable %arg0 reset %arg1 latency 1 : (i32) -> i32 // CHECK-NEXT: arc.output [[V5]] : i32 // CHECK-NEXT: } // CHECK-NEXT: [[V4:%.+]] = arc.clock_domain ([[V1]]) clock %clk2 : (i32) -> i32 { // CHECK-NEXT: ^bb0(%arg0: i32): - // CHECK-NEXT: [[V5:%.+]] = arc.state @DummyArc(%arg0) lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V5:%.+]] = arc.state @DummyArc(%arg0) latency 1 : (i32) -> i32 // CHECK-NEXT: arc.output [[V5]] : i32 // CHECK-NEXT: } // CHECK-NEXT: hw.output [[V3]], [[V4]] : i32, i32 @@ -49,40 +49,40 @@ hw.module @preexistingClockDomain(in %clk0: !seq.clock, in %clk1: !seq.clock, in // COM: the pass also works when there are already clock domains present, but // COM: it creates new clock domain ops for all clocks of clocked operations // COM: outside of the existing domains instead of merging them. - %0 = arc.state @DummyArc(%in) clock %clk0 lat 1 : (i32) -> i32 + %0 = arc.state @DummyArc(%in) clock %clk0 latency 1 : (i32) -> i32 %1 = arc.clock_domain (%0) clock %clk0 : (i32) -> i32 { ^bb0(%arg0: i32): - %2 = arc.state @DummyArc(%arg0) lat 1 : (i32) -> i32 + %2 = arc.state @DummyArc(%arg0) latency 1 : (i32) -> i32 arc.output %2 : i32 } - %2 = arc.state @DummyArc(%in) clock %clk0 lat 1 : (i32) -> i32 + %2 = arc.state @DummyArc(%in) clock %clk0 latency 1 : (i32) -> i32 %3 = arc.clock_domain (%2) clock %clk1 : (i32) -> i32 { ^bb0(%arg2: i32): - %4 = arc.state @DummyArc(%arg2) lat 1 : (i32) -> i32 + %4 = arc.state @DummyArc(%arg2) latency 1 : (i32) -> i32 arc.output %4 : i32 } - %4 = arc.state @DummyArc(%1) clock %clk0 lat 1 : (i32) -> i32 - %5 = arc.state @DummyArc(%3) clock %clk0 lat 1 : (i32) -> i32 - %6 = arc.state @DummyArc2(%1, %3) clock %clk0 lat 1 : (i32, i32) -> i32 + %4 = arc.state @DummyArc(%1) clock %clk0 latency 1 : (i32) -> i32 + %5 = arc.state @DummyArc(%3) clock %clk0 latency 1 : (i32) -> i32 + %6 = arc.state @DummyArc2(%1, %3) clock %clk0 latency 1 : (i32, i32) -> i32 hw.output %4, %6 : i32, i32 // CHECK-NEXT: [[V0:%.+]] = arc.clock_domain ([[V2:%.+]]#3) clock %clk0 : (i32) -> i32 { // CHECK-NEXT: ^bb0(%arg0: i32): - // CHECK-NEXT: [[V3:%.+]] = arc.state @DummyArc(%arg0) lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V3:%.+]] = arc.state @DummyArc(%arg0) latency 1 : (i32) -> i32 // CHECK-NEXT: arc.output [[V3]] : i32 // CHECK-NEXT: } // CHECK-NEXT: [[V1:%.+]] = arc.clock_domain ([[V2]]#2) clock %clk1 : (i32) -> i32 { // CHECK-NEXT: ^bb0(%arg0: i32): - // CHECK-NEXT: [[V3:%.+]] = arc.state @DummyArc(%arg0) lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V3:%.+]] = arc.state @DummyArc(%arg0) latency 1 : (i32) -> i32 // CHECK-NEXT: arc.output [[V3]] : i32 // CHECK-NEXT: } // CHECK-NEXT: [[V2]]:4 = arc.clock_domain ([[V0]], [[V1]], %in) clock %clk0 : (i32, i32, i32) -> (i32, i32, i32, i32) { // CHECK-NEXT: ^bb0(%arg0: i32, %arg1: i32, %arg2: i32): - // CHECK-NEXT: [[V3:%.+]] = arc.state @DummyArc2(%arg0, %arg1) lat 1 : (i32, i32) -> i32 - // CHECK-NEXT: [[V4:%.+]] = arc.state @DummyArc(%arg1) lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V5:%.+]] = arc.state @DummyArc(%arg0) lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V6:%.+]] = arc.state @DummyArc(%arg2) lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V7:%.+]] = arc.state @DummyArc(%arg2) lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V3:%.+]] = arc.state @DummyArc2(%arg0, %arg1) latency 1 : (i32, i32) -> i32 + // CHECK-NEXT: [[V4:%.+]] = arc.state @DummyArc(%arg1) latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V5:%.+]] = arc.state @DummyArc(%arg0) latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V6:%.+]] = arc.state @DummyArc(%arg2) latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V7:%.+]] = arc.state @DummyArc(%arg2) latency 1 : (i32) -> i32 // CHECK-NEXT: arc.output [[V3]], [[V5]], [[V6]], [[V7]] : i32, i32, i32, i32 // CHECK-NEXT: } // CHECK-NEXT: hw.output [[V2]]#1, [[V2]]#0 : i32, i32 diff --git a/test/Dialect/Arc/latency-retiming.mlir b/test/Dialect/Arc/latency-retiming.mlir index 89a90bc75312..88ad35ade1b2 100644 --- a/test/Dialect/Arc/latency-retiming.mlir +++ b/test/Dialect/Arc/latency-retiming.mlir @@ -3,86 +3,87 @@ // CHECK-LABEL: hw.module @Foo hw.module @Foo(in %clk: !seq.clock, in %clk2: !seq.clock, in %en: i1, in %rst: i1, in %arg0: i32, in %arg1: i32, out out0 : i32, out out1 : i32, out out2: i32, out out3: i32, out out4: i32, out out5: i32, out out6: i32, out out7: i32, out out8: i32, out out9: i32, out out10: i32, out out11: i32) { // COM: simple shift-register of depth 3 is merged to one arc.state - %0 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - %1 = arc.state @Bar(%0) clock %clk lat 1 : (i32) -> i32 - %2 = arc.state @Bar(%1) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V0:%.+]] = arc.state @Bar(%arg0) clock %clk lat 3 : + %0 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + %1 = arc.state @Bar(%0) clock %clk latency 1 : (i32) -> i32 + %2 = arc.state @Bar(%1) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V0:%.+]] = arc.state @Bar(%arg0) clock %clk latency 3 : // COM: looks like a shift register from the outside (and actually is), but // COM: the arcs are not exactly passthroughs, so don't erase them // COM: TODO: a canonicalization pattern could reorder the outputs such that // COM: this also folds to just one state, but inlining will get rid of // COM: them anyways. - // CHECK-NEXT: [[V1:%.+]]:2 = arc.state @Baz(%arg0, %arg1) lat 0 : - %3:2 = arc.state @Baz(%arg0, %arg1) clock %clk lat 1 : (i32, i32) -> (i32, i32) - // CHECK-NEXT: [[V2:%.+]]:2 = arc.state @Baz([[V1]]#0, [[V1]]#1) lat 0 : - %4:2 = arc.state @Baz(%3#0, %3#1) clock %clk lat 2 : (i32, i32) -> (i32, i32) - // CHECK-NEXT: [[V3:%.+]]:2 = arc.state @Baz([[V2]]#0, [[V2]]#1) clock %clk lat 6 : - %5:2 = arc.state @Baz(%4#0, %4#1) clock %clk lat 3 : (i32, i32) -> (i32, i32) + // CHECK-NEXT: [[V1:%.+]]:2 = arc.call @Baz(%arg0, %arg1) : + %3:2 = arc.state @Baz(%arg0, %arg1) clock %clk latency 1 : (i32, i32) -> (i32, i32) + // CHECK-NEXT: [[V2:%.+]]:2 = arc.call @Baz([[V1]]#0, [[V1]]#1) : + %4:2 = arc.state @Baz(%3#0, %3#1) clock %clk latency 2 : (i32, i32) -> (i32, i32) + // CHECK-NEXT: [[V3:%.+]]:2 = arc.state @Baz([[V2]]#0, [[V2]]#1) clock %clk latency 6 : + %5:2 = arc.state @Baz(%4#0, %4#1) clock %clk latency 3 : (i32, i32) -> (i32, i32) - // COM: a fan-in tree, op that gets the latencies starts with lat 0 - %6 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - %7 = arc.state @Bar(%arg1) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V4:%.+]]:2 = arc.state @Baz(%arg0, %arg1) lat 0 : - %8:2 = arc.state @Baz(%6, %7) clock %clk lat 1 : (i32, i32) -> (i32, i32) - %9 = arc.state @Bar(%arg1) clock %clk lat 2 : (i32) -> i32 - // CHECK-NEXT: [[V5:%.+]]:2 = arc.state @Baz([[V4]]#0, %arg1) clock %clk lat 2 : - %10:2 = arc.state @Baz(%8#0, %9) lat 0 : (i32, i32) -> (i32, i32) + // COM: a fan-in tree, op that gets the latencies is initially a call and + // becomes a state op + %6 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + %7 = arc.state @Bar(%arg1) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V4:%.+]]:2 = arc.call @Baz(%arg0, %arg1) : + %8:2 = arc.state @Baz(%6, %7) clock %clk latency 1 : (i32, i32) -> (i32, i32) + %9 = arc.state @Bar(%arg1) clock %clk latency 2 : (i32) -> i32 + // CHECK-NEXT: [[V5:%.+]]:2 = arc.state @Baz([[V4]]#0, %arg1) clock %clk latency 2 : + %10:2 = arc.call @Baz(%8#0, %9) : (i32, i32) -> (i32, i32) // COM: fan-out tree - // CHECK-NEXT: [[V6:%.+]] = arc.state @Bar(%arg0) clock %clk lat 1 : - %11 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: arc.state @Bar([[V6]]) clock %clk lat 1 : - %12 = arc.state @Bar(%11) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: arc.state @Bar([[V6]]) clock %clk lat 1 : - %13 = arc.state @Bar(%11) clock %clk lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V6:%.+]] = arc.state @Bar(%arg0) clock %clk latency 1 : + %11 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar([[V6]]) clock %clk latency 1 : + %12 = arc.state @Bar(%11) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar([[V6]]) clock %clk latency 1 : + %13 = arc.state @Bar(%11) clock %clk latency 1 : (i32) -> i32 // COM: states with names attached are not touched - %14 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V7:%.+]] = arc.state @Bar(%arg0) clock %clk lat 2 {name = "reg"} : - %15 = arc.state @Bar(%14) clock %clk lat 1 {name = "reg"} : (i32) -> i32 - // CHECK-NEXT: arc.state @Bar([[V7]]) clock %clk lat 1 : - %16 = arc.state @Bar(%15) clock %clk lat 1 : (i32) -> i32 + %14 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V7:%.+]] = arc.state @Bar(%arg0) clock %clk latency 2 {name = "reg"} : + %15 = arc.state @Bar(%14) clock %clk latency 1 {name = "reg"} : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar([[V7]]) clock %clk latency 1 : + %16 = arc.state @Bar(%15) clock %clk latency 1 : (i32) -> i32 // COM: states with names attached are not touched - %17 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V8:%.+]] = arc.state @Bar(%arg0) clock %clk lat 2 {names = ["reg"]} : - %18 = arc.state @Bar(%17) clock %clk lat 1 {names = ["reg"]} : (i32) -> i32 - // CHECK-NEXT: arc.state @Bar([[V8]]) clock %clk lat 1 : - %19 = arc.state @Bar(%18) clock %clk lat 1 : (i32) -> i32 + %17 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V8:%.+]] = arc.state @Bar(%arg0) clock %clk latency 2 {names = ["reg"]} : + %18 = arc.state @Bar(%17) clock %clk latency 1 {names = ["reg"]} : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar([[V8]]) clock %clk latency 1 : + %19 = arc.state @Bar(%18) clock %clk latency 1 : (i32) -> i32 // COM: states with enables are not touched - // CHECK-NEXT: [[V9:%.+]] = arc.state @Bar(%arg0) clock %clk lat 1 : - %20 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V10:%.+]] = arc.state @Bar([[V9]]) clock %clk enable %en lat 1 : - %21 = arc.state @Bar(%20) clock %clk enable %en lat 1 : (i32) -> i32 - // CHECK-NEXT: arc.state @Bar([[V10]]) clock %clk lat 1 : - %22 = arc.state @Bar(%21) clock %clk lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V9:%.+]] = arc.state @Bar(%arg0) clock %clk latency 1 : + %20 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V10:%.+]] = arc.state @Bar([[V9]]) clock %clk enable %en latency 1 : + %21 = arc.state @Bar(%20) clock %clk enable %en latency 1 : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar([[V10]]) clock %clk latency 1 : + %22 = arc.state @Bar(%21) clock %clk latency 1 : (i32) -> i32 // COM: states with resets are not touched - // CHECK-NEXT: [[V11:%.+]] = arc.state @Bar(%arg0) clock %clk lat 1 : - %23 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: [[V12:%.+]] = arc.state @Bar([[V11]]) clock %clk reset %rst lat 1 : - %24 = arc.state @Bar(%23) clock %clk reset %rst lat 1 : (i32) -> i32 - // CHECK-NEXT: arc.state @Bar([[V12]]) clock %clk lat 1 : - %25 = arc.state @Bar(%24) clock %clk lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V11:%.+]] = arc.state @Bar(%arg0) clock %clk latency 1 : + %23 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: [[V12:%.+]] = arc.state @Bar([[V11]]) clock %clk reset %rst latency 1 : + %24 = arc.state @Bar(%23) clock %clk reset %rst latency 1 : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar([[V12]]) clock %clk latency 1 : + %25 = arc.state @Bar(%24) clock %clk latency 1 : (i32) -> i32 // COM: using own result value - // CHECK-NEXT: [[V13:%.+]] = arc.state @Bar([[V13]]) clock %clk lat 1 : - %26 = arc.state @Bar(%26) clock %clk lat 1 : (i32) -> i32 + // CHECK-NEXT: [[V13:%.+]] = arc.state @Bar([[V13]]) clock %clk latency 1 : + %26 = arc.state @Bar(%26) clock %clk latency 1 : (i32) -> i32 // COM: different clocks - // CHECK-NEXT: arc.state @Bar(%arg0) clock %clk lat 1 : - %27 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: arc.state @Bar({{.*}}) clock %clk2 lat 1 : - %28 = arc.state @Bar(%27) clock %clk2 lat 1 : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar(%arg0) clock %clk latency 1 : + %27 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar({{.*}}) clock %clk2 latency 1 : + %28 = arc.state @Bar(%27) clock %clk2 latency 1 : (i32) -> i32 // COM: can only partially take over latencies - %29 = arc.state @Bar(%arg0) clock %clk lat 1 : (i32) -> i32 - // CHECK-NEXT: arc.state @Bar(%arg1) clock %clk lat 1 : - %30 = arc.state @Bar(%arg1) clock %clk lat 2 : (i32) -> i32 - // CHECK-NEXT: arc.state @Baz(%arg0, {{.+}}) clock %clk lat 2 : - %31:2 = arc.state @Baz(%29, %30) clock %clk lat 1 : (i32, i32) -> (i32, i32) + %29 = arc.state @Bar(%arg0) clock %clk latency 1 : (i32) -> i32 + // CHECK-NEXT: arc.state @Bar(%arg1) clock %clk latency 1 : + %30 = arc.state @Bar(%arg1) clock %clk latency 2 : (i32) -> i32 + // CHECK-NEXT: arc.state @Baz(%arg0, {{.+}}) clock %clk latency 2 : + %31:2 = arc.state @Baz(%29, %30) clock %clk latency 1 : (i32, i32) -> (i32, i32) // CHECK-NEXT: hw.output [[V0]], [[V3]]#0, [[V3]]#1, [[V5]]#0 hw.output %2, %5#0, %5#1, %10#0, %12, %13, %16, %19, %22, %25, %28, %31 : i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32 diff --git a/test/Dialect/Arc/lower-arcs-to-funcs.mlir b/test/Dialect/Arc/lower-arcs-to-funcs.mlir index 1282733a2911..dd22227821e7 100644 --- a/test/Dialect/Arc/lower-arcs-to-funcs.mlir +++ b/test/Dialect/Arc/lower-arcs-to-funcs.mlir @@ -15,9 +15,9 @@ arc.define @callTest(%arg0: i32) -> (i32) { // CHECK-NEXT: return %0 : i32 } -// CHECK-LABEL: hw.module @stateTest -hw.module @stateTest(in %arg0: i32, out out0: i32) { - %0 = arc.state @sub1(%arg0) lat 0 : (i32) -> i32 +// CHECK-LABEL: hw.module @callInModuleTest +hw.module @callInModuleTest(in %arg0: i32, out out0: i32) { + %0 = arc.call @sub1(%arg0) : (i32) -> i32 // CHECK-NEXT: %0 = func.call @sub1(%arg0) : (i32) -> i32 hw.output %0 : i32 // CHECK-NEXT: hw.output %0 : i32 diff --git a/test/Dialect/Arc/lower-state.mlir b/test/Dialect/Arc/lower-state.mlir index 927326887196..f95261d6d3f8 100644 --- a/test/Dialect/Arc/lower-state.mlir +++ b/test/Dialect/Arc/lower-state.mlir @@ -30,8 +30,8 @@ hw.module @InputsAndOutputs(in %a: i42, in %b: i17, out c: i42, out d: i17) { // CHECK-LABEL: arc.model "State" { hw.module @State(in %clk: !seq.clock, in %en: i1, in %en2: i1) { %gclk = seq.clock_gate %clk, %en, %en2 - %3 = arc.state @DummyArc(%6) clock %clk lat 1 : (i42) -> i42 - %4 = arc.state @DummyArc(%5) clock %gclk lat 1 : (i42) -> i42 + %3 = arc.state @DummyArc(%6) clock %clk latency 1 : (i42) -> i42 + %4 = arc.state @DummyArc(%5) clock %gclk latency 1 : (i42) -> i42 %5 = comb.add %3, %3 : i42 %6 = comb.add %4, %4 : i42 // CHECK-NEXT: (%arg0: !arc.storage): @@ -51,22 +51,22 @@ hw.module @State(in %clk: !seq.clock, in %en: i1, in %en2: i1) { // CHECK-NEXT: arc.clock_tree [[TMP4]] { // CHECK-NEXT: [[TMP0:%.+]] = arc.state_read [[S1]] : // CHECK-NEXT: [[TMP1:%.+]] = comb.add [[TMP0]], [[TMP0]] - // CHECK-NEXT: [[TMP2:%.+]] = arc.state @DummyArc([[TMP1]]) lat 0 : (i42) -> i42 + // CHECK-NEXT: [[TMP2:%.+]] = arc.call @DummyArc([[TMP1]]) : (i42) -> i42 // CHECK-NEXT: arc.state_write [[S0]] = [[TMP2]] : // CHECK-NEXT: [[EN:%.+]] = arc.state_read [[INEN]] : // CHECK-NEXT: [[EN2:%.+]] = arc.state_read [[INEN2]] : // CHECK-NEXT: [[TMP3:%.+]] = comb.or [[EN]], [[EN2]] : i1 // CHECK-NEXT: [[TMP0:%.+]] = arc.state_read [[S0]] : // CHECK-NEXT: [[TMP1:%.+]] = comb.add [[TMP0]], [[TMP0]] - // CHECK-NEXT: [[TMP2:%.+]] = arc.state @DummyArc([[TMP1]]) lat 0 : (i42) -> i42 + // CHECK-NEXT: [[TMP2:%.+]] = arc.call @DummyArc([[TMP1]]) : (i42) -> i42 // CHECK-NEXT: arc.state_write [[S1]] = [[TMP2]] if [[TMP3]] : // CHECK-NEXT: } } // CHECK-LABEL: arc.model "State2" { hw.module @State2(in %clk: !seq.clock) { - %3 = arc.state @DummyArc(%3) clock %clk lat 1 : (i42) -> i42 - %4 = arc.state @DummyArc(%4) clock %clk lat 1 : (i42) -> i42 + %3 = arc.state @DummyArc(%3) clock %clk latency 1 : (i42) -> i42 + %4 = arc.state @DummyArc(%4) clock %clk latency 1 : (i42) -> i42 // CHECK-NEXT: (%arg0: !arc.storage): // CHECK-NEXT: [[INCLK:%.+]] = arc.root_input "clk", %arg0 : (!arc.storage) -> !arc.state // CHECK-NEXT: [[CLK_OLD:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state @@ -81,10 +81,10 @@ hw.module @State2(in %clk: !seq.clock) { // CHECK-NEXT: arc.clock_tree [[TMP4]] { // CHECK-NEXT: [[TMP0:%.+]] = arc.state_read [[S0:%.+]] : - // CHECK-NEXT: [[TMP1:%.+]] = arc.state @DummyArc([[TMP0]]) lat 0 : (i42) -> i42 + // CHECK-NEXT: [[TMP1:%.+]] = arc.call @DummyArc([[TMP0]]) : (i42) -> i42 // CHECK-NEXT: arc.state_write [[S0]] = [[TMP1]] : // CHECK-NEXT: [[TMP2:%.+]] = arc.state_read [[S1:%.+]] : - // CHECK-NEXT: [[TMP3:%.+]] = arc.state @DummyArc([[TMP2]]) lat 0 : (i42) -> i42 + // CHECK-NEXT: [[TMP3:%.+]] = arc.call @DummyArc([[TMP2]]) : (i42) -> i42 // CHECK-NEXT: arc.state_write [[S1]] = [[TMP3]] : // CHECK-NEXT: } } @@ -98,7 +98,7 @@ hw.module @NonMaskedMemoryWrite(in %clk0: !seq.clock) { %c0_i2 = hw.constant 0 : i2 %c9001_i42 = hw.constant 9001 : i42 %mem = arc.memory <4 x i42, i2> - arc.memory_write_port %mem, @identity(%c0_i2, %c9001_i42) clock %clk0 lat 1 : <4 x i42, i2>, i2, i42 + arc.memory_write_port %mem, @identity(%c0_i2, %c9001_i42) clock %clk0 latency 1 : <4 x i42, i2>, i2, i42 // CHECK-NEXT: (%arg0: !arc.storage): // CHECK-NEXT: [[INCLK:%.+]] = arc.root_input "clk0", %arg0 : (!arc.storage) -> !arc.state @@ -147,7 +147,7 @@ hw.module @maskedMemoryWrite(in %clk: !seq.clock) { %c9001_i42 = hw.constant 9001 : i42 %c1010_i42 = hw.constant 1010 : i42 %mem = arc.memory <4 x i42, i2> - arc.memory_write_port %mem, @identity2(%c0_i2, %c9001_i42, %true, %c1010_i42) clock %clk enable mask lat 1 : <4 x i42, i2>, i2, i42, i1, i42 + arc.memory_write_port %mem, @identity2(%c0_i2, %c9001_i42, %true, %c1010_i42) clock %clk enable mask latency 1 : <4 x i42, i2>, i2, i42, i1, i42 } arc.define @identity2(%arg0: i2, %arg1: i42, %arg2: i1, %arg3: i42) -> (i2, i42, i1, i42) { arc.output %arg0, %arg1, %arg2, %arg3 : i2, i42, i1, i42 @@ -207,7 +207,7 @@ hw.module @MaterializeOpsWithRegions(in %clk0: !seq.clock, in %clk1: !seq.clock, // CHECK-NEXT: %c42_i42 = hw.constant 42 // CHECK-NEXT: scf.yield %c42_i42 // CHECK-NEXT: } - // CHECK-NEXT: arc.state @DummyArc([[TMP]]) lat 0 + // CHECK-NEXT: arc.call @DummyArc([[TMP]]) // CHECK-NEXT: arc.state_write // CHECK-NEXT: } @@ -222,12 +222,12 @@ hw.module @MaterializeOpsWithRegions(in %clk0: !seq.clock, in %clk1: !seq.clock, // CHECK-NEXT: %c42_i42 = hw.constant 42 // CHECK-NEXT: scf.yield %c42_i42 // CHECK-NEXT: } - // CHECK-NEXT: arc.state @DummyArc([[TMP]]) lat 0 + // CHECK-NEXT: arc.call @DummyArc([[TMP]]) // CHECK-NEXT: arc.state_write // CHECK-NEXT: } - %1 = arc.state @DummyArc(%0) clock %clk0 lat 1 : (i42) -> i42 - %2 = arc.state @DummyArc(%0) clock %clk1 lat 1 : (i42) -> i42 + %1 = arc.state @DummyArc(%0) clock %clk0 latency 1 : (i42) -> i42 + %2 = arc.state @DummyArc(%0) clock %clk1 latency 1 : (i42) -> i42 hw.output %0 : i42 } @@ -240,9 +240,9 @@ arc.define @DummyArc2(%arg0: i42) -> (i42, i42) { } hw.module @stateReset(in %clk: !seq.clock, in %arg0: i42, in %rst: i1, out out0: i42, out out1: i42) { - %0 = arc.state @i1Identity(%rst) lat 0 : (i1) -> (i1) - %1 = arc.state @i1Identity(%rst) lat 0 : (i1) -> (i1) - %2, %3 = arc.state @DummyArc2(%arg0) clock %clk enable %0 reset %1 lat 1 : (i42) -> (i42, i42) + %0 = arc.call @i1Identity(%rst) : (i1) -> (i1) + %1 = arc.call @i1Identity(%rst) : (i1) -> (i1) + %2, %3 = arc.state @DummyArc2(%arg0) clock %clk enable %0 reset %1 latency 1 : (i42) -> (i42, i42) hw.output %2, %3 : i42, i42 } // CHECK-LABEL: arc.model "stateReset" @@ -250,22 +250,22 @@ hw.module @stateReset(in %clk: !seq.clock, in %arg0: i42, in %rst: i1, out out0: // CHECK: [[ALLOC2:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state // CHECK: arc.clock_tree %{{.*}} { // CHECK: [[IN_RST:%.+]] = arc.state_read %in_rst : -// CHECK: [[EN:%.+]] = arc.state @i1Identity([[IN_RST]]) lat 0 : (i1) -> i1 -// CHECK: [[RST:%.+]] = arc.state @i1Identity([[IN_RST]]) lat 0 : (i1) -> i1 +// CHECK: [[EN:%.+]] = arc.call @i1Identity([[IN_RST]]) : (i1) -> i1 +// CHECK: [[RST:%.+]] = arc.call @i1Identity([[IN_RST]]) : (i1) -> i1 // CHECK: scf.if [[RST]] { // CHECK: arc.state_write [[ALLOC1]] = %c0_i42{{.*}} : // CHECK: arc.state_write [[ALLOC2]] = %c0_i42{{.*}} : // CHECK: } else { // CHECK: [[ARG:%.+]] = arc.state_read %in_arg0 : -// CHECK: [[STATE:%.+]]:2 = arc.state @DummyArc2([[ARG]]) lat 0 : (i42) -> (i42, i42) +// CHECK: [[STATE:%.+]]:2 = arc.call @DummyArc2([[ARG]]) : (i42) -> (i42, i42) // CHECK: arc.state_write [[ALLOC1]] = [[STATE]]#0 if [[EN]] : // CHECK: arc.state_write [[ALLOC2]] = [[STATE]]#1 if [[EN]] : // CHECK: } // CHECK: } hw.module @SeparateResets(in %clock: !seq.clock, in %i0: i42, in %rst1: i1, in %rst2: i1, out out1: i42, out out2: i42) { - %0 = arc.state @DummyArc(%i0) clock %clock reset %rst1 lat 1 {names = ["foo"]} : (i42) -> i42 - %1 = arc.state @DummyArc(%i0) clock %clock reset %rst2 lat 1 {names = ["bar"]} : (i42) -> i42 + %0 = arc.state @DummyArc(%i0) clock %clock reset %rst1 latency 1 {names = ["foo"]} : (i42) -> i42 + %1 = arc.state @DummyArc(%i0) clock %clock reset %rst2 latency 1 {names = ["bar"]} : (i42) -> i42 hw.output %0, %1 : i42, i42 } @@ -279,7 +279,7 @@ hw.module @SeparateResets(in %clock: !seq.clock, in %i0: i42, in %rst1: i1, in % // CHECK: arc.state_write [[FOO_ALLOC]] = %c0_i42{{.*}} : // CHECK: } else { // CHECK: [[IN_I0:%.+]] = arc.state_read %in_i0 : -// CHECK: [[STATE:%.+]] = arc.state @DummyArc([[IN_I0]]) lat 0 : (i42) -> i42 +// CHECK: [[STATE:%.+]] = arc.call @DummyArc([[IN_I0]]) : (i42) -> i42 // CHECK: arc.state_write [[FOO_ALLOC]] = [[STATE]] : // CHECK: } // CHECK: [[IN_RST2:%.+]] = arc.state_read %in_rst2 : @@ -288,16 +288,16 @@ hw.module @SeparateResets(in %clock: !seq.clock, in %i0: i42, in %rst1: i1, in % // CHECK: arc.state_write [[BAR_ALLOC]] = %c0_i42{{.*}} : // CHECK: } else { // CHECK: [[IN_I0_2:%.+]] = arc.state_read %in_i0 : -// CHECK: [[STATE_2:%.+]] = arc.state @DummyArc([[IN_I0_2]]) lat 0 : (i42) -> i42 +// CHECK: [[STATE_2:%.+]] = arc.call @DummyArc([[IN_I0_2]]) : (i42) -> i42 // CHECK: arc.state_write [[BAR_ALLOC]] = [[STATE_2]] : // CHECK: } // Regression check on worklist producing false positive comb loop errors. // CHECK-LABEL: @CombLoopRegression hw.module @CombLoopRegression(in %clk: !seq.clock) { - %0 = arc.state @CombLoopRegressionArc1(%3, %3) clock %clk lat 1 : (i1, i1) -> i1 - %1, %2 = arc.state @CombLoopRegressionArc2(%0) lat 0 : (i1) -> (i1, i1) - %3 = arc.state @CombLoopRegressionArc1(%1, %2) lat 0 : (i1, i1) -> i1 + %0 = arc.state @CombLoopRegressionArc1(%3, %3) clock %clk latency 1 : (i1, i1) -> i1 + %1, %2 = arc.call @CombLoopRegressionArc2(%0) : (i1) -> (i1, i1) + %3 = arc.call @CombLoopRegressionArc1(%1, %2) : (i1, i1) -> i1 } arc.define @CombLoopRegressionArc1(%arg0: i1, %arg1: i1) -> i1 { arc.output %arg0 : i1 @@ -311,9 +311,9 @@ arc.define @CombLoopRegressionArc2(%arg0: i1) -> (i1, i1) { hw.module private @MemoryPortRegression(in %clock: !seq.clock, in %reset: i1, in %in: i3, out x: i3) { %0 = arc.memory <2 x i3, i1> {name = "ram_ext"} %1 = arc.memory_read_port %0[%3] : <2 x i3, i1> - arc.memory_write_port %0, @identity3(%3, %in) clock %clock lat 1 : <2 x i3, i1>, i1, i3 - %3 = arc.state @Queue_arc_0(%reset) clock %clock lat 1 : (i1) -> i1 - %4 = arc.state @Queue_arc_1(%1) lat 0 : (i3) -> i3 + arc.memory_write_port %0, @identity3(%3, %in) clock %clock latency 1 : <2 x i3, i1>, i1, i3 + %3 = arc.state @Queue_arc_0(%reset) clock %clock latency 1 : (i1) -> i1 + %4 = arc.call @Queue_arc_1(%1) : (i3) -> i3 hw.output %4 : i3 } arc.define @identity3(%arg0: i1, %arg1: i3) -> (i1, i3) { @@ -328,7 +328,7 @@ arc.define @Queue_arc_1(%arg0: i3) -> i3 { // CHECK-LABEL: arc.model "BlackBox" hw.module @BlackBox(in %clk: !seq.clock) { - %0 = arc.state @DummyArc(%2) clock %clk lat 1 : (i42) -> i42 + %0 = arc.state @DummyArc(%2) clock %clk latency 1 : (i42) -> i42 %1 = comb.and %0, %0 : i42 %ext.c, %ext.d = hw.instance "ext" @BlackBoxExt(a: %0: i42, b: %1: i42) -> (c: i42, d: i42) %2 = comb.or %ext.c, %ext.d : i42 @@ -342,7 +342,7 @@ hw.module @BlackBox(in %clk: !seq.clock) { // CHECK-DAG: [[TMP1:%.+]] = arc.state_read [[EXT_C]] // CHECK-DAG: [[TMP2:%.+]] = arc.state_read [[EXT_D]] // CHECK-DAG: [[TMP3:%.+]] = comb.or [[TMP1]], [[TMP2]] - // CHECK-DAG: [[TMP4:%.+]] = arc.state @DummyArc([[TMP3]]) + // CHECK-DAG: [[TMP4:%.+]] = arc.call @DummyArc([[TMP3]]) // CHECK-DAG: arc.state_write [[STATE]] = [[TMP4]] // Passthrough diff --git a/test/Dialect/Arc/split-loops-errors.mlir b/test/Dialect/Arc/split-loops-errors.mlir index 446a12ed9d7c..97908f3ef59b 100644 --- a/test/Dialect/Arc/split-loops-errors.mlir +++ b/test/Dialect/Arc/split-loops-errors.mlir @@ -3,7 +3,7 @@ hw.module @UnbreakableLoop(in %clock : !seq.clock, in %a : i4, out x : i4) { // expected-error @below {{loop splitting did not eliminate all loops; loop detected}} // expected-note @below {{through operand 1 here:}} - %0, %1 = arc.state @UnbreakableLoopArc(%a, %0) lat 0 : (i4, i4) -> (i4, i4) + %0, %1 = arc.call @UnbreakableLoopArc(%a, %0) : (i4, i4) -> (i4, i4) hw.output %1 : i4 } diff --git a/test/Dialect/Arc/split-loops.mlir b/test/Dialect/Arc/split-loops.mlir index 230211e3505d..903d82eb9905 100644 --- a/test/Dialect/Arc/split-loops.mlir +++ b/test/Dialect/Arc/split-loops.mlir @@ -2,11 +2,11 @@ // CHECK-LABEL: hw.module @Simple( hw.module @Simple(in %clock: !seq.clock, in %a: i4, in %b: i4, out x: i4, out y: i4) { - // CHECK-NEXT: %0 = arc.state @SimpleArc_split_0(%a, %b) - // CHECK-NEXT: %1 = arc.state @SimpleArc_split_1(%0, %a) - // CHECK-NEXT: %2 = arc.state @SimpleArc_split_2(%0, %b) + // CHECK-NEXT: %0 = arc.call @SimpleArc_split_0(%a, %b) + // CHECK-NEXT: %1 = arc.call @SimpleArc_split_1(%0, %a) + // CHECK-NEXT: %2 = arc.call @SimpleArc_split_2(%0, %b) // CHECK-NEXT: hw.output %1, %2 - %0:2 = arc.state @SimpleArc(%a, %b) lat 0 : (i4, i4) -> (i4, i4) + %0:2 = arc.call @SimpleArc(%a, %b) : (i4, i4) -> (i4, i4) hw.output %0#0, %0#1 : i4, i4 } // CHECK-NEXT: } @@ -35,11 +35,11 @@ arc.define @SimpleArc(%arg0: i4, %arg1: i4) -> (i4, i4) { // CHECK-LABEL: hw.module @Unchanged( hw.module @Unchanged(in %a: i4, out x: i4, out y0: i4, out y1: i4) { - // CHECK-NEXT: %0 = arc.state @UnchangedArc1(%a) - // CHECK-NEXT: %1:2 = arc.state @UnchangedArc2(%a) + // CHECK-NEXT: %0 = arc.call @UnchangedArc1(%a) + // CHECK-NEXT: %1:2 = arc.call @UnchangedArc2(%a) // CHECK-NEXT: hw.output %0, %1#0, %1#1 - %0 = arc.state @UnchangedArc1(%a) lat 0 : (i4) -> i4 - %1:2 = arc.state @UnchangedArc2(%a) lat 0 : (i4) -> (i4, i4) + %0 = arc.call @UnchangedArc1(%a) : (i4) -> i4 + %1:2 = arc.call @UnchangedArc2(%a) : (i4) -> (i4, i4) hw.output %0, %1#0, %1#1 : i4, i4, i4 } // CHECK-NEXT: } @@ -65,10 +65,10 @@ arc.define @UnchangedArc2(%arg0: i4) -> (i4, i4) { // CHECK-LABEL: hw.module @Passthrough( hw.module @Passthrough(in %a: i4, in %b: i4, out x0: i4, out x1: i4, out y0: i4, out y1: i4) { - // CHECK-NEXT: %0 = arc.state @PassthroughArc2(%a) + // CHECK-NEXT: %0 = arc.call @PassthroughArc2(%a) // CHECK-NEXT: hw.output %a, %b, %0, %b - %0:2 = arc.state @PassthroughArc1(%a, %b) lat 0 : (i4, i4) -> (i4, i4) - %1:2 = arc.state @PassthroughArc2(%a, %b) lat 0 : (i4, i4) -> (i4, i4) + %0:2 = arc.call @PassthroughArc1(%a, %b) : (i4, i4) -> (i4, i4) + %1:2 = arc.call @PassthroughArc2(%a, %b) : (i4, i4) -> (i4, i4) hw.output %0#0, %0#1, %1#0, %1#1 : i4, i4, i4, i4 } // CHECK-NEXT: } @@ -91,11 +91,11 @@ arc.define @PassthroughArc2(%arg0: i4, %arg1: i4) -> (i4, i4) { // CHECK-LABEL: hw.module @NestedRegions( hw.module @NestedRegions(in %a: i4, in %b: i4, in %c: i4, out x: i4, out y: i4) { - // CHECK-NEXT: %0:3 = arc.state @NestedRegionsArc_split_0(%a, %b, %c) - // CHECK-NEXT: %1 = arc.state @NestedRegionsArc_split_1(%0#0, %0#1) - // CHECK-NEXT: %2 = arc.state @NestedRegionsArc_split_2(%0#2) + // CHECK-NEXT: %0:3 = arc.call @NestedRegionsArc_split_0(%a, %b, %c) + // CHECK-NEXT: %1 = arc.call @NestedRegionsArc_split_1(%0#0, %0#1) + // CHECK-NEXT: %2 = arc.call @NestedRegionsArc_split_2(%0#2) // CHECK-NEXT: hw.output %1, %2 - %0, %1 = arc.state @NestedRegionsArc(%a, %b, %c) lat 0 : (i4, i4, i4) -> (i4, i4) + %0, %1 = arc.call @NestedRegionsArc(%a, %b, %c) : (i4, i4, i4) -> (i4, i4) hw.output %0, %1 : i4, i4 } // CHECK-NEXT: } @@ -134,13 +134,13 @@ arc.define @NestedRegionsArc(%arg0: i4, %arg1: i4, %arg2: i4) -> (i4, i4) { // CHECK-LABEL: hw.module @BreakFalseLoops( hw.module @BreakFalseLoops(in %a: i4, out x: i4, out y: i4) { - // CHECK-NEXT: %0 = arc.state @BreakFalseLoopsArc_split_0(%a) - // CHECK-NEXT: %1 = arc.state @BreakFalseLoopsArc_split_1(%0) - // CHECK-NEXT: %2 = arc.state @BreakFalseLoopsArc_split_0(%3) - // CHECK-NEXT: %3 = arc.state @BreakFalseLoopsArc_split_1(%a) + // CHECK-NEXT: %0 = arc.call @BreakFalseLoopsArc_split_0(%a) + // CHECK-NEXT: %1 = arc.call @BreakFalseLoopsArc_split_1(%0) + // CHECK-NEXT: %2 = arc.call @BreakFalseLoopsArc_split_0(%3) + // CHECK-NEXT: %3 = arc.call @BreakFalseLoopsArc_split_1(%a) // CHECK-NEXT: hw.output %1, %2 - %0, %1 = arc.state @BreakFalseLoopsArc(%a, %0) lat 0 : (i4, i4) -> (i4, i4) - %2, %3 = arc.state @BreakFalseLoopsArc(%3, %a) lat 0 : (i4, i4) -> (i4, i4) + %0, %1 = arc.call @BreakFalseLoopsArc(%a, %0) : (i4, i4) -> (i4, i4) + %2, %3 = arc.call @BreakFalseLoopsArc(%3, %a) : (i4, i4) -> (i4, i4) hw.output %1, %2 : i4, i4 } // CHECK-NEXT: } @@ -165,10 +165,10 @@ arc.define @BreakFalseLoopsArc(%arg0: i4, %arg1: i4) -> (i4, i4) { // CHECK-LABEL: @SplitDependencyModule hw.module @SplitDependencyModule(in %a: i1, out x: i1, out y: i1) { - // CHECK-NEXT: %0 = arc.state @SplitDependency_split_1(%a, %a) lat 0 : (i1, i1) -> i1 - // CHECK-NEXT: %1 = arc.state @SplitDependency_split_0(%a, %a, %0) lat 0 : (i1, i1, i1) -> i1 + // CHECK-NEXT: %0 = arc.call @SplitDependency_split_1(%a, %a) : (i1, i1) -> i1 + // CHECK-NEXT: %1 = arc.call @SplitDependency_split_0(%a, %a, %0) : (i1, i1, i1) -> i1 // CHECK-NEXT: hw.output %0, %1 : i1, i1 - %0, %1 = arc.state @SplitDependency(%a, %a, %a) lat 0 : (i1, i1, i1) -> (i1, i1) + %0, %1 = arc.call @SplitDependency(%a, %a, %a) : (i1, i1, i1) -> (i1, i1) hw.output %0, %1 : i1, i1 } // CHECK-NEXT: } diff --git a/test/Dialect/Comb/basic.mlir b/test/Dialect/Comb/basic.mlir new file mode 100644 index 000000000000..a688190d5d5f --- /dev/null +++ b/test/Dialect/Comb/basic.mlir @@ -0,0 +1,12 @@ +// RUN: circt-opt %s | FileCheck %s + +hw.type_scope @__hw_typedecls { + hw.typedecl @foo : i1 +} + +// https://github.com/llvm/circt/issues/5772 +// CHECK-LABEL: @Issue5772 +hw.module @Issue5772(in %arg0: !hw.typealias<@__hw_typedecls::@foo,i1>) { + // CHECK: comb.concat %arg0 : !hw.typealias<@__hw_typedecls::@foo, i1> + %0 = comb.concat %arg0 : !hw.typealias<@__hw_typedecls::@foo,i1> +} diff --git a/test/Dialect/Comb/canonicalization.mlir b/test/Dialect/Comb/canonicalization.mlir index dee7073a3441..cdf7c9977045 100644 --- a/test/Dialect/Comb/canonicalization.mlir +++ b/test/Dialect/Comb/canonicalization.mlir @@ -22,8 +22,8 @@ hw.module @muxConstantInputs(in %cond: i1, out o: i2) { // CHECK-LABEL: @muxConstantInputs2 hw.module @muxConstantInputs2(in %cond: i1, out o: i2) { -// CHECK-NEXT: %true = hw.constant true // CHECK-NEXT: %false = hw.constant false +// CHECK-NEXT: %true = hw.constant true // CHECK-NEXT: %0 = comb.xor %cond, %true : i1 // CHECK-NEXT: %1 = comb.concat %0, %false : i1, i1 %c0 = hw.constant 0 : i2 @@ -1554,10 +1554,10 @@ hw.module @OrMuxSameTrueValueAndZero(in %tag_0: i1, in %tag_1: i1, in %tag_2: i1 }) : () -> () // CHECK-LABEL: hw.module @combineOppositeBinCmpIntoConstant -// CHECK: %[[ALL_ZEROS:.+]] = hw.constant 0 : i4 -// CHECK: %[[ALL_ONES:.+]] = hw.constant -1 : i4 -// CHECK: %[[FALSE:.+]] = hw.constant false // CHECK: %[[TRUE:.+]] = hw.constant true +// CHECK: %[[FALSE:.+]] = hw.constant false +// CHECK: %[[ALL_ONES:.+]] = hw.constant -1 : i4 +// CHECK: %[[ALL_ZEROS:.+]] = hw.constant 0 : i4 // CHECK-NEXT: hw.output %[[TRUE]], %[[FALSE]], %[[TRUE]], %[[FALSE]], %[[TRUE]], %[[FALSE]], %[[TRUE]], %[[FALSE]], %[[TRUE]], %[[FALSE]], %[[ALL_ONES]], %[[ALL_ZEROS]] hw.module @combineOppositeBinCmpIntoConstant(in %tag_0: i4, in %tag_1: i4, out o0: i1, out o1: i1, out o2: i1, out o3: i1, out o4: i1, out o5: i1, out o6: i1, out o7: i1, out o8: i1, out o9: i1, out o10: i4, out o11: i4) { %op_ne = comb.icmp bin ne %tag_0, %tag_1 : i4 diff --git a/test/Dialect/ESI/appid_hier.mlir b/test/Dialect/ESI/appid_hier.mlir index 6149078c6b74..d99cc3576aa8 100644 --- a/test/Dialect/ESI/appid_hier.mlir +++ b/test/Dialect/ESI/appid_hier.mlir @@ -13,13 +13,13 @@ hw.module @Top() { } hw.module @LoopbackInOutAdd7() { - esi.manifest.req #esi.appid<"loopback_inout"[0]>, <@HostComms::@req_resp>, toServer, !esi.bundle<[!esi.channel to "resp", !esi.channel from "req"]> + esi.manifest.req #esi.appid<"loopback_inout"[0]>, <@HostComms::@req_resp>, !esi.bundle<[!esi.channel to "resp", !esi.channel from "req"]> } hw.module @Bar () {} // CHECK-LABEL: esi.manifest.hier_root @main { -// CHECK-NEXT: esi.manifest.req #esi.appid<"loopback_inout"[0]>, <@HostComms::@req_resp>, toServer, !esi.bundle<[!esi.channel to "resp", !esi.channel from "req"]> +// CHECK-NEXT: esi.manifest.req #esi.appid<"loopback_inout"[0]>, <@HostComms::@req_resp>, !esi.bundle<[!esi.channel to "resp", !esi.channel from "req"]> // CHECK-NEXT: esi.manifest.hier_node #esi.appid<"bar"[0]> mod @Bar { // CHECK-NEXT: } // CHECK-NEXT: esi.manifest.service_impl #esi.appid<"cosim"[0]> by "cosim" with {} { diff --git a/test/Dialect/ESI/bundles.mlir b/test/Dialect/ESI/bundles.mlir index 70b6ae8636d2..aae55049a19b 100644 --- a/test/Dialect/ESI/bundles.mlir +++ b/test/Dialect/ESI/bundles.mlir @@ -59,3 +59,9 @@ hw.module @BundleTest(in %s1_in : !esi.channel, out b_send : !esi.bundle<[! %bundle, %resp = esi.bundle.pack %s1_in : !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]> hw.output %bundle, %resp: !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]>, !esi.channel } + +hw.module @TestNullFold() { + %c0_i0 = hw.constant 0 : i0 + %false = hw.constant false + %chanOutput, %ready = esi.wrap.vr %c0_i0, %false: i0 +} diff --git a/test/Dialect/ESI/errors.mlir b/test/Dialect/ESI/errors.mlir index ea4f88a7f3de..d11c9a78776c 100644 --- a/test/Dialect/ESI/errors.mlir +++ b/test/Dialect/ESI/errors.mlir @@ -47,45 +47,23 @@ hw.module @test(in %m : !sv.modport<@IData::@Noexist>) { // ----- esi.service.decl @HostComms { - esi.service.to_client @Send : !esi.bundle<[!esi.channel to "send"]> -} - -hw.module @Loopback (in %clk: i1, in %dataIn: !esi.bundle<[!esi.channel to "send"]>) { - // expected-error @+1 {{Service port is not a to-server port}} - esi.service.req.to_server %dataIn -> <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !esi.bundle<[!esi.channel to "send"]> -} - -// ----- - -esi.service.decl @HostComms { - esi.service.to_server @Send : !esi.bundle<[!esi.channel to "send"]> + esi.service.port @Send : !esi.bundle<[!esi.channel from "send"]> } hw.module @Loopback (in %clk: i1) { - // expected-error @+1 {{Service port is not a to-client port}} - esi.service.req.to_client <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !esi.bundle<[!esi.channel to "send"]> -} - -// ----- - -esi.service.decl @HostComms { - esi.service.to_server @Send : !esi.bundle<[!esi.channel to "send"]> -} - -hw.module @Loopback (in %clk: i1, in %dataIn: !esi.bundle<[!esi.channel to "send"]>) { // expected-error @+1 {{Request channel type does not match service port bundle channel type}} - esi.service.req.to_server %dataIn -> <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !esi.bundle<[!esi.channel to "send"]> + %dataIn = esi.service.req <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !esi.bundle<[!esi.channel from "send"]> } // ----- esi.service.decl @HostComms { - esi.service.to_server @Send : !esi.bundle<[!esi.channel to "send"]> + esi.service.port @Send : !esi.bundle<[!esi.channel from "send"]> } -hw.module @Loopback (in %clk: i1, in %dataIn: !esi.bundle<[!esi.channel to "send", !esi.channel to "foo"]>) { +hw.module @Loopback (in %clk: i1) { // expected-error @+1 {{Request port bundle channel count does not match service port bundle channel count}} - esi.service.req.to_server %dataIn -> <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !esi.bundle<[!esi.channel to "send", !esi.channel to "foo"]> + %dataIn = esi.service.req<@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !esi.bundle<[!esi.channel to "send", !esi.channel to "foo"]> } // ----- @@ -94,22 +72,22 @@ esi.service.decl @HostComms { } hw.module @Loopback (in %clk: i1) { - // expected-error @+1 {{'esi.service.req.to_client' op Could not locate port "Recv"}} - %dataIn = esi.service.req.to_client <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) : !esi.bundle<[!esi.channel from "foo"]> + // expected-error @+1 {{'esi.service.req' op Could not locate port "Recv"}} + %dataIn = esi.service.req <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) : !esi.bundle<[!esi.channel from "foo"]> } // ----- hw.module @Loopback (in %clk: i1) { - // expected-error @+1 {{'esi.service.req.to_client' op Could not find service declaration @HostComms}} - %dataIn = esi.service.req.to_client <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) : !esi.bundle<[!esi.channel from "foo"]> + // expected-error @+1 {{'esi.service.req' op Could not find service declaration @HostComms}} + %dataIn = esi.service.req <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) : !esi.bundle<[!esi.channel from "foo"]> } // ----- -!reqResp = !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]> +!reqResp = !esi.bundle<[!esi.channel from "req", !esi.channel to "resp"]> esi.service.decl @HostComms { - esi.service.to_server @ReqResp : !reqResp + esi.service.port @ReqResp : !reqResp } hw.module @Top(in %clk: i1, in %rst: i1) { diff --git a/test/Dialect/ESI/manifest.mlir b/test/Dialect/ESI/manifest.mlir index 7c106239e112..a0bd78d5cdbf 100644 --- a/test/Dialect/ESI/manifest.mlir +++ b/test/Dialect/ESI/manifest.mlir @@ -9,27 +9,27 @@ hw.type_scope @__hw_typedecls { } !alias = !hw.typealias<@__hw_typedecls::@foo, i1> -!sendI8 = !esi.bundle<[!esi.channel to "send"]> +!sendI8 = !esi.bundle<[!esi.channel from "send"]> !recvI8 = !esi.bundle<[!esi.channel to "recv"]> -!sendI0 = !esi.bundle<[!esi.channel to "send"]> +!sendI0 = !esi.bundle<[!esi.channel from "send"]> esi.service.decl @HostComms { - esi.service.to_server @Send : !sendI8 - esi.service.to_client @Recv : !recvI8 - esi.service.to_server @SendI0 : !sendI0 + esi.service.port @Send : !sendI8 + esi.service.port @Recv : !recvI8 + esi.service.port @SendI0 : !sendI0 } 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 + %dataInBundle = esi.service.req <@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.bundle.unpack %dataOut from %dataOutBundle : !sendI8 + %dataOutBundle = esi.service.req <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !sendI8 %c0_0 = hw.constant 0 : i0 %c0_1 = hw.constant 0 : i1 %sendi0_channel, %ready = esi.wrap.vr %c0_0, %c0_1 : i0 - %sendi0_bundle = esi.bundle.pack %sendi0_channel : !sendI0 - esi.service.req.to_server %sendi0_bundle -> <@HostComms::@SendI0> (#esi.appid<"loopback_fromhw_i0">) : !sendI0 + esi.bundle.unpack %sendi0_channel from %sendi0_bundle : !sendI0 + %sendi0_bundle = esi.service.req <@HostComms::@SendI0> (#esi.appid<"loopback_fromhw_i0">) : !sendI0 } esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} @@ -41,7 +41,7 @@ esi.service.std.func @funcs // CONN-NEXT: %arg = esi.bundle.unpack %arg from %func1 : !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> !func1Signature = !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> hw.module @CallableFunc1() { - %call = esi.service.req.to_client <@funcs::@call> (#esi.appid<"func1">) : !func1Signature + %call = esi.service.req <@funcs::@call> (#esi.appid<"func1">) : !func1Signature %arg = esi.bundle.unpack %arg from %call : !func1Signature } @@ -63,23 +63,34 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // HIER-NEXT: esi.manifest.impl_conn [#esi.appid<"loopback_inst"[1]>, #esi.appid<"loopback_fromhw_i0">] req <@HostComms::@SendI0>(!esi.bundle<[!esi.channel from "send"]>) with {channel_assignments = {send = "loopback_inst[1].loopback_fromhw_i0.send"}} // HIER-NEXT: } // HIER-NEXT: esi.manifest.hier_node #esi.appid<"loopback_inst"[0]> mod @Loopback { -// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_tohw">, <@HostComms::@Recv>, toClient, !esi.bundle<[!esi.channel to "recv"]> -// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw">, <@HostComms::@Send>, toServer, !esi.bundle<[!esi.channel to "send"]> -// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw_i0">, <@HostComms::@SendI0>, toServer, !esi.bundle<[!esi.channel to "send"]> +// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_tohw">, <@HostComms::@Recv>, !esi.bundle<[!esi.channel to "recv"]> +// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw">, <@HostComms::@Send>, !esi.bundle<[!esi.channel from "send"]> +// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw_i0">, <@HostComms::@SendI0>, !esi.bundle<[!esi.channel from "send"]> // HIER-NEXT: } // HIER-NEXT: esi.manifest.hier_node #esi.appid<"loopback_inst"[1]> mod @Loopback { -// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_tohw">, <@HostComms::@Recv>, toClient, !esi.bundle<[!esi.channel to "recv"]> -// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw">, <@HostComms::@Send>, toServer, !esi.bundle<[!esi.channel to "send"]> -// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw_i0">, <@HostComms::@SendI0>, toServer, !esi.bundle<[!esi.channel to "send"]> +// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_tohw">, <@HostComms::@Recv>, !esi.bundle<[!esi.channel to "recv"]> +// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw">, <@HostComms::@Send>, !esi.bundle<[!esi.channel from "send"]> +// HIER-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw_i0">, <@HostComms::@SendI0>, !esi.bundle<[!esi.channel from "send"]> // HIER-NEXT: } -// HIER-NEXT: esi.manifest.req #esi.appid<"func1">, <@funcs::@call> std "esi.service.std.func", toClient, !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> +// HIER-NEXT: esi.manifest.req #esi.appid<"func1">, <@funcs::@call> std "esi.service.std.func", !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> // HIER-NEXT: } +// HW-LABEL: hw.module @__ESI_Manifest_ROM(in %clk : !seq.clock, in %address : i30, out data : i32) { +// HW: [[R0:%.+]] = hw.aggregate_constant +// HW: [[R1:%.+]] = sv.reg : !hw.inout> +// HW: sv.assign [[R1]], [[R0]] : !hw.uarray<{{.*}}xi32> +// HW: [[R2:%.+]] = comb.extract %address from 0 : (i30) -> i9 +// HW: [[R3:%.+]] = seq.compreg [[R2]], %clk : i9 +// HW: [[R4:%.+]] = sv.array_index_inout [[R1]][[[R3]]] : !hw.inout>, i9 +// HW: [[R5:%.+]] = sv.read_inout [[R4]] : !hw.inout +// HW: [[R6:%.+]] = seq.compreg [[R5]], %clk : i32 +// HW: hw.output [[R6]] : i32 + // HW-LABEL: hw.module @top // HW: hw.instance "__manifest" @__ESIManifest() -> () -// HW-LABEL: hw.module.extern @Cosim_Manifest(in %compressed_manifest : !hw.uarray<#hw.param.decl.ref<"COMPRESSED_MANIFEST_SIZE">xi8>) attributes {verilogName = "Cosim_Manifest"} +// HW-LABEL: hw.module.extern @Cosim_Manifest(in %compressed_manifest : !hw.array<#hw.param.decl.ref<"COMPRESSED_MANIFEST_SIZE">xi8>) attributes {verilogName = "Cosim_Manifest"} // HW-LABEL: hw.module @__ESIManifest() -// HW: hw.instance "__manifest" @Cosim_Manifest(compressed_manifest: %{{.+}}: !hw.uarray<{{.+}}xi8>) -> () +// HW: hw.instance "__manifest" @Cosim_Manifest(compressed_manifest: %{{.+}}: !hw.array<{{.+}}xi8>) -> () // CHECK: { // CHECK-LABEL: "api_version": 1, @@ -220,7 +231,6 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "func1" // CHECK-NEXT: }, -// CHECK-NEXT: "direction": "toClient", // CHECK-NEXT: "bundleType": { // CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>" // CHECK-NEXT: }, @@ -244,7 +254,6 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_tohw" // CHECK-NEXT: }, -// CHECK-NEXT: "direction": "toClient", // CHECK-NEXT: "bundleType": { // CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: }, @@ -258,9 +267,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw" // CHECK-NEXT: }, -// CHECK-NEXT: "direction": "toServer", // CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>" +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: }, // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Send", @@ -272,9 +280,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw_i0" // CHECK-NEXT: }, -// CHECK-NEXT: "direction": "toServer", // CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>" +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: }, // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "SendI0", @@ -296,7 +303,6 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_tohw" // CHECK-NEXT: }, -// CHECK-NEXT: "direction": "toClient", // CHECK-NEXT: "bundleType": { // CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: }, @@ -310,9 +316,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw" // CHECK-NEXT: }, -// CHECK-NEXT: "direction": "toServer", // CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>" +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: }, // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Send", @@ -324,9 +329,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw_i0" // CHECK-NEXT: }, -// CHECK-NEXT: "direction": "toServer", // CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>" +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: }, // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "SendI0", @@ -345,33 +349,30 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "ports": [ // CHECK-NEXT: { // CHECK-NEXT: "name": "Send", -// CHECK-NEXT: "direction": "toServer", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>" +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: } // CHECK-NEXT: }, // CHECK-NEXT: { -// CHECK-NEXT: "name": "SendI0", -// CHECK-NEXT: "direction": "toServer", +// CHECK-NEXT: "name": "Recv", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>" +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: } // CHECK-NEXT: }, // CHECK-NEXT: { -// CHECK-NEXT: "name": "Recv", -// CHECK-NEXT: "direction": "toClient", +// CHECK-NEXT: "name": "SendI0", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ] // CHECK-NEXT: }, // CHECK-NEXT: { // CHECK-NEXT: "symbol": "funcs", +// CHECK-NEXT: "type_name": "esi.service.std.func", // CHECK-NEXT: "ports": [ // CHECK-NEXT: { // CHECK-NEXT: "name": "call", -// CHECK-NEXT: "direction": "toClient", // CHECK-NEXT: "type": { // CHECK-NEXT: "type": { // CHECK-NEXT: "channels": [ @@ -442,7 +443,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: { // CHECK-NEXT: "channels": [ // CHECK-NEXT: { -// CHECK-NEXT: "direction": "to", +// CHECK-NEXT: "direction": "from", // CHECK-NEXT: "name": "send", // CHECK-NEXT: "type": { // CHECK-NEXT: "circt_name": "!esi.channel", @@ -459,14 +460,14 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>", +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, // CHECK-NEXT: { // CHECK-NEXT: "channels": [ // CHECK-NEXT: { -// CHECK-NEXT: "direction": "to", +// CHECK-NEXT: "direction": "from", // CHECK-NEXT: "name": "send", // CHECK-NEXT: "type": { // CHECK-NEXT: "circt_name": "!esi.channel", @@ -483,7 +484,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"send\"]>", +// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, diff --git a/test/Dialect/ESI/services.mlir b/test/Dialect/ESI/services.mlir index 0a801b095742..2cd543e56421 100644 --- a/test/Dialect/ESI/services.mlir +++ b/test/Dialect/ESI/services.mlir @@ -1,14 +1,14 @@ // RUN: circt-opt --esi-connect-services --canonicalize %s | circt-opt | FileCheck %s --check-prefix=CONN // RUN: circt-opt --esi-connect-services --lower-esi-bundles %s -!sendI8 = !esi.bundle<[!esi.channel to "send"]> +!sendI8 = !esi.bundle<[!esi.channel from "send"]> !recvI8 = !esi.bundle<[!esi.channel to "recv"]> !reqResp = !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]> esi.service.decl @HostComms { - esi.service.to_server @Send : !sendI8 - esi.service.to_client @Recv : !recvI8 - esi.service.to_client @ReqResp : !reqResp + esi.service.port @Send : !sendI8 + esi.service.port @Recv : !recvI8 + esi.service.port @ReqResp : !reqResp } @@ -25,15 +25,15 @@ hw.module @Top (in %clk: !seq.clock, in %rst: i1) { // CONN-LABEL: hw.module @Loopback(in %clk : !seq.clock, in %loopback_tohw : !esi.bundle<[!esi.channel to "recv"]>, in %loopback_fromhw : !esi.bundle<[!esi.channel from "send"]>) { -// CONN-NEXT: esi.manifest.req #esi.appid<"loopback_tohw">, <@HostComms::@Recv>, toClient, !esi.bundle<[!esi.channel to "recv"]> +// CONN-NEXT: esi.manifest.req #esi.appid<"loopback_tohw">, <@HostComms::@Recv>, !esi.bundle<[!esi.channel to "recv"]> +// CONN-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw">, <@HostComms::@Send>, !esi.bundle<[!esi.channel from "send"]> // CONN-NEXT: %recv = esi.bundle.unpack from %loopback_tohw : !esi.bundle<[!esi.channel to "recv"]> // CONN-NEXT: esi.bundle.unpack %recv from %loopback_fromhw : !esi.bundle<[!esi.channel from "send"]> -// CONN-NEXT: esi.manifest.req #esi.appid<"loopback_fromhw">, <@HostComms::@Send>, toServer, !esi.bundle<[!esi.channel to "send"]> hw.module @Loopback (in %clk: !seq.clock) { - %dataInBundle = esi.service.req.to_client <@HostComms::@Recv> (#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 + %dataInBundle = esi.service.req <@HostComms::@Recv> (#esi.appid<"loopback_tohw">) : !recvI8 + %dataOutBundle = esi.service.req <@HostComms::@Send> (#esi.appid<"loopback_fromhw">) : !sendI8 + %dataIn = esi.bundle.unpack from %dataInBundle : !recvI8 + esi.bundle.unpack %dataIn from %dataOutBundle : !sendI8 } // CONN-LABEL: hw.module @Top2(in %clk : !seq.clock, out chksum : i8) { @@ -68,7 +68,7 @@ hw.module @Rec(in %clk: !seq.clock) { // CONN: %rawOutput, %valid = esi.unwrap.vr %recv, %true : i8 // CONN: hw.output %rawOutput : i8 hw.module @Consumer(in %clk: !seq.clock, out rawData: i8) { - %dataInBundle = esi.service.req.to_client <@HostComms::@Recv> (#esi.appid<"consumingFromChan">) : !recvI8 + %dataInBundle = esi.service.req <@HostComms::@Recv> (#esi.appid<"consumingFromChan">) : !recvI8 %rdy = hw.constant 1 : i1 %dataIn = esi.bundle.unpack from %dataInBundle : !recvI8 %rawData, %valid = esi.unwrap.vr %dataIn, %rdy: i8 @@ -84,8 +84,8 @@ hw.module @Producer(in %clk: !seq.clock) { %data = hw.constant 0 : i8 %valid = hw.constant 1 : i1 %dataIn, %rdy = esi.wrap.vr %data, %valid : i8 - %dataInBundle = esi.bundle.pack %dataIn : !sendI8 - esi.service.req.to_server %dataInBundle -> <@HostComms::@Send> (#esi.appid<"producedMsgChan">) : !sendI8 + %dataInBundle = esi.service.req <@HostComms::@Send> (#esi.appid<"producedMsgChan">) : !sendI8 + esi.bundle.unpack %dataIn from %dataInBundle : !sendI8 } // CONN-LABEL: hw.module @InOutLoopback(in %clk : !seq.clock, in %loopback_inout : !esi.bundle<[!esi.channel to "req", !esi.channel from "resp"]>) { @@ -94,7 +94,7 @@ hw.module @Producer(in %clk: !seq.clock) { // CONN: %0 = comb.extract %rawOutput from 0 : (i16) -> i8 // CONN: %chanOutput, %ready = esi.wrap.vr %0, %valid : i8 hw.module @InOutLoopback (in %clk: !seq.clock) { - %dataInBundle = esi.service.req.to_client <@HostComms::@ReqResp> (#esi.appid<"loopback_inout">) : !reqResp + %dataInBundle = esi.service.req <@HostComms::@ReqResp> (#esi.appid<"loopback_inout">) : !reqResp %dataIn = esi.bundle.unpack %dataTrunc from %dataInBundle : !reqResp %unwrap, %valid = esi.unwrap.vr %dataIn, %rdy: i16 %trunc = comb.extract %unwrap from 0 : (i16) -> (i8) @@ -144,16 +144,16 @@ esi.pure_module @LoopbackCosimPure { esi.mem.ram @MemA i64 x 20 !write = !hw.struct -!writeBundle = !esi.bundle<[!esi.channel to "req", !esi.channel from "ack"]> -!readBundle = !esi.bundle<[!esi.channel to "address", !esi.channel from "data"]> +!writeBundle = !esi.bundle<[!esi.channel from "req", !esi.channel to "ack"]> +!readBundle = !esi.bundle<[!esi.channel from "address", !esi.channel to "data"]> hw.module @MemoryAccess1(in %clk : !seq.clock, in %rst : i1, in %write : !esi.channel, in %readAddress : !esi.channel, out readData : !esi.channel, out writeDone : !esi.channel) { esi.service.instance #esi.appid<"mem"> svc @MemA impl as "sv_mem" (%clk, %rst) : (!seq.clock, i1) -> () - %writeBundle, %done = esi.bundle.pack %write : !writeBundle - esi.service.req.to_server %writeBundle -> <@MemA::@write> (#esi.appid<"write">) : !writeBundle + %writeBundle = esi.service.req <@MemA::@write> (#esi.appid<"write">) : !writeBundle + %done = esi.bundle.unpack %write from %writeBundle : !writeBundle - %readBundle, %readData = esi.bundle.pack %readAddress : !readBundle - esi.service.req.to_server %readBundle -> <@MemA::@read> (#esi.appid<"read">) : !readBundle + %readBundle = esi.service.req <@MemA::@read> (#esi.appid<"read">) : !readBundle + %readData = esi.bundle.unpack %readAddress from %readBundle : !readBundle hw.output %readData, %done : !esi.channel, !esi.channel } @@ -164,14 +164,14 @@ hw.module @MemoryAccess1(in %clk : !seq.clock, in %rst : i1, in %write : !esi.ch hw.module @MemoryAccess2Read(in %clk: !seq.clock, in %rst: i1, in %write: !esi.channel, in %readAddress: !esi.channel, in %readAddress2: !esi.channel, out readData: !esi.channel, out readData2: !esi.channel, out writeDone: !esi.channel) { esi.service.instance #esi.appid<"mem"> svc @MemA impl as "sv_mem" (%clk, %rst) : (!seq.clock, i1) -> () - %writeBundle, %done = esi.bundle.pack %write : !writeBundle - esi.service.req.to_server %writeBundle -> <@MemA::@write> (#esi.appid<"write">) : !writeBundle + %writeBundle = esi.service.req <@MemA::@write> (#esi.appid<"write">) : !writeBundle + %done = esi.bundle.unpack %write from %writeBundle : !writeBundle - %readBundle, %readData = esi.bundle.pack %readAddress : !readBundle - esi.service.req.to_server %readBundle -> <@MemA::@read> (#esi.appid<"read"[0]>) : !readBundle + %readBundle = esi.service.req <@MemA::@read> (#esi.appid<"read"[0]>) : !readBundle + %readData = esi.bundle.unpack %readAddress from %readBundle : !readBundle - %readBundle2, %readData2 = esi.bundle.pack %readAddress2 : !readBundle - esi.service.req.to_server %readBundle2 -> <@MemA::@read> (#esi.appid<"read"[1]>) : !readBundle + %readBundle2 = esi.service.req <@MemA::@read> (#esi.appid<"read"[1]>) : !readBundle + %readData2 = esi.bundle.unpack %readAddress2 from %readBundle2 : !readBundle hw.output %readData, %readData2, %done : !esi.channel, !esi.channel, !esi.channel } @@ -183,11 +183,11 @@ hw.module.extern @extern() esi.service.std.func @funcs // CONN-LABEL: hw.module @CallableFunc1(in %func1 : !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]>) { -// CONN-NEXT: esi.manifest.req #esi.appid<"func1">, <@funcs::@call> std "esi.service.std.func", toClient, !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> +// CONN-NEXT: esi.manifest.req #esi.appid<"func1">, <@funcs::@call> std "esi.service.std.func", !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> // CONN-NEXT: %arg = esi.bundle.unpack %arg from %func1 : !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> !func1Signature = !esi.bundle<[!esi.channel to "arg", !esi.channel from "result"]> hw.module @CallableFunc1() { - %call = esi.service.req.to_client <@funcs::@call> (#esi.appid<"func1">) : !func1Signature + %call = esi.service.req <@funcs::@call> (#esi.appid<"func1">) : !func1Signature %arg = esi.bundle.unpack %arg from %call : !func1Signature } @@ -203,3 +203,14 @@ hw.module @CallableAccel1(in %clk: !seq.clock, in %rst: i1) { hw.instance "func1" @CallableFunc1() -> () esi.service.instance #esi.appid<"funcComms"> svc @funcs impl as "cosim" (%clk, %rst) : (!seq.clock, i1) -> () } + +esi.service.std.mmio @mmio +!mmioReq = !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]> + +// CONN-LABEL: hw.module @MMIOManifest(in %clk : !seq.clock, in %rst : i1, in %manifest : !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]>) { +// CONN-NEXT: esi.manifest.req #esi.appid<"manifest">, <@mmio::@read> std "esi.service.std.mmio", !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]> +// CONN-NEXT: %offset = esi.bundle.unpack %offset from %manifest : !esi.bundle<[!esi.channel to "offset", !esi.channel from "data"]> +hw.module @MMIOManifest(in %clk: !seq.clock, in %rst: i1) { + %req = esi.service.req <@mmio::@read> (#esi.appid<"manifest">) : !mmioReq + %loopback = esi.bundle.unpack %loopback from %req : !mmioReq +} diff --git a/test/Dialect/Emit/emit-errors.mlir b/test/Dialect/Emit/emit-errors.mlir new file mode 100644 index 000000000000..b28ce870de90 --- /dev/null +++ b/test/Dialect/Emit/emit-errors.mlir @@ -0,0 +1,11 @@ +// RUN: circt-opt %s -verify-diagnostics --split-input-file + +sv.macro.decl @SomeMacro + +// expected-error @below {{referenced operation is not a file: @SomeMacro}} +emit.file_list "filelist.f", [@SomeMacro] + +// ----- + +// expected-error @below {{invalid symbol reference: @InvalidRef}} +emit.file_list "filelist.f", [@InvalidRef] diff --git a/test/Dialect/Emit/round-trip.mlir b/test/Dialect/Emit/round-trip.mlir new file mode 100644 index 000000000000..ae83d5caefa7 --- /dev/null +++ b/test/Dialect/Emit/round-trip.mlir @@ -0,0 +1,13 @@ +// RUN: circt-opt %s | circt-opt | FileCheck %s + +sv.macro.decl @SomeMacro + +// CHECK: emit.file "some-file.sv" sym @SomeFile.sv +emit.file "some-file.sv" sym @SomeFile.sv { + // CHECK: emit.verbatim "SimpleVerbatim" + emit.verbatim "SimpleVerbatim" + +} + +// CHECK: emit.file_list "filelist.f", [@SomeFile.sv] sym @SomeFileList +emit.file_list "filelist.f", [@SomeFile.sv] sym @SomeFileList diff --git a/test/Dialect/FIRRTL/SFCTests/GrandCentralInterfaces/Companion.fir b/test/Dialect/FIRRTL/SFCTests/GrandCentralInterfaces/Companion.fir index e7f0d7d35076..f4cee3212abf 100644 --- a/test/Dialect/FIRRTL/SFCTests/GrandCentralInterfaces/Companion.fir +++ b/test/Dialect/FIRRTL/SFCTests/GrandCentralInterfaces/Companion.fir @@ -69,7 +69,7 @@ circuit Foo : %[[ ; DROP-NOT: module Companion ; DROP: module Foo - ; DROP NOT: Companion + ; DROP-NOT: Companion ; DROP: endmodule ; DROP: FILE "gct.yaml" ; DROP: [] diff --git a/test/Dialect/FIRRTL/SFCTests/data-taps-flip-errors.fir b/test/Dialect/FIRRTL/SFCTests/data-taps-flip-errors.fir index e75d479cca2c..e5cc34c7a8da 100644 --- a/test/Dialect/FIRRTL/SFCTests/data-taps-flip-errors.fir +++ b/test/Dialect/FIRRTL/SFCTests/data-taps-flip-errors.fir @@ -27,6 +27,7 @@ circuit Top : %[[ ; expected-error @below {{Wiring Problem sink type "'!firrtl.bundle, b flip: uint<2>>'" must be passive (no flips) when using references}} wire sink : {a : UInt<2>, flip b: UInt<2>} + sink is invalid tap <= sink diff --git a/test/Dialect/FIRRTL/SFCTests/directories.fir b/test/Dialect/FIRRTL/SFCTests/directories.fir index d92afec0e509..39c44f838820 100644 --- a/test/Dialect/FIRRTL/SFCTests/directories.fir +++ b/test/Dialect/FIRRTL/SFCTests/directories.fir @@ -123,7 +123,7 @@ circuit TestHarness: ; CHECK: endmodule ; SITEST_DUT: FILE "testbench.sitest.json" -; SITEST-DUT-NOT: FILE +; SITEST_DUT-NOT: FILE ; SITEST_DUT: "Foo_BlackBox" ; SITEST_DUT: FILE "design.sitest.json" @@ -132,7 +132,7 @@ circuit TestHarness: ; SITEST_DUT-DAG: "Baz_BlackBox" ; SITEST_NODUT: FILE "testbench.sitest.json" -; SITEST-NODUT-NOT: FILE +; SITEST_NODUT-NOT: FILE ; MLIR_OUT: om.class @SitestBlackBoxModulesSchema(%moduleName: !om.sym_ref) { ; MLIR_OUT: om.class.field @moduleName, %moduleName : !om.sym_ref diff --git a/test/Dialect/FIRRTL/SFCTests/width-spec-errors.fir b/test/Dialect/FIRRTL/SFCTests/width-spec-errors.fir index 32f61d7b58b4..ff5ec6a25242 100644 --- a/test/Dialect/FIRRTL/SFCTests/width-spec-errors.fir +++ b/test/Dialect/FIRRTL/SFCTests/width-spec-errors.fir @@ -96,3 +96,4 @@ circuit Foo : module Bar : ; expected-error @+1 {{uninferred width: wire "a.c.e" is unconstrained}} wire a: { b : UInt<1>, c : { d : UInt<1>, e : UInt } } + a is invalid diff --git a/test/Dialect/FIRRTL/blackbox-reader.mlir b/test/Dialect/FIRRTL/blackbox-reader.mlir index 717577103b99..15ccb541c012 100644 --- a/test/Dialect/FIRRTL/blackbox-reader.mlir +++ b/test/Dialect/FIRRTL/blackbox-reader.mlir @@ -36,20 +36,36 @@ firrtl.circuit "Foo" attributes {annotations = [ firrtl.instance foo3 @ExtFoo3() firrtl.instance dut @DUTBlackboxes() } - // CHECK: sv.verbatim "// world" {output_file = #hw.output_file<"..{{/|\\\\}}testbench{{/|\\\\}}hello.v">} - // CHECK: sv.verbatim "// world" {output_file = #hw.output_file<"cover{{/|\\\\}}hello2.v">} - // CHECK: sv.verbatim "// world" {output_file = #hw.output_file<"..{{/|\\\\}}testbench{{/|\\\\}}hello3.v">} - // CHECK: sv.verbatim "/* Bar */\0A" {output_file = #hw.output_file<"bar{{/|\\\\}}Bar.v">} - // CHECK: sv.verbatim "/* Baz */{{(\\0D)?}}\0A" {output_file = #hw.output_file<"baz{{/|\\\\}}Baz.sv">} - // CHECK: sv.verbatim "/* Qux */\0A" {output_file = #hw.output_file<"qux{{/|\\\\}}NotQux.jpeg">} - // CHECK: sv.verbatim "..{{/|\\\\}}testbench{{/|\\\\}}hello.v\0A - // CHECK-SAME: ..{{/|\\\\}}testbench{{/|\\\\}}hello3.v\0A - // CHECK-SAME: bar{{/|\\\\}}Bar.v\0A - // CHECK-SAME: baz{{/|\\\\}}Baz.sv\0A - // CHECK-SAME: cover{{/|\\\\}}hello2.v\0A - // CHECK-SAME: qux{{/|\\\\}}NotQux.jpeg" - // CHECK-SAME: output_file = #hw.output_file<"firrtl_black_box_resource_files.f", excludeFromFileList> + + // CHECK: emit.file "..{{/|\\\\}}testbench{{/|\\\\}}hello.v" sym @blackbox_hello.v { + // CHECK-NEXT: emit.verbatim "// world" + // CHECK-NEXT: } + // CHECK: emit.file "cover{{/|\\\\}}hello2.v" sym @blackbox_hello2.v { + // CHECK-NEXT: emit.verbatim "// world" + // CHECK-NEXT: } + // CHECK: emit.file "..{{/|\\\\}}testbench{{/|\\\\}}hello3.v" sym @blackbox_hello3.v { + // CHECK-NEXT: emit.verbatim "// world" + // CHECK-NEXT: } + // CHECK: emit.file "bar{{/|\\\\}}Bar.v" sym @blackbox_Bar.v { + // CHECK-NEXT: emit.verbatim "/* Bar */\0A" + // CHECK-NEXT: } + // CHECK: emit.file "baz{{/|\\\\}}Baz.sv" sym @blackbox_Baz.sv { + // CHECK-NEXT: emit.verbatim "/* Baz */\0A" + // CHECK-NEXT: } + // CHECK: emit.file "qux{{/|\\\\}}NotQux.jpeg" sym @blackbox_Qux.sv { + // CHECK-NEXT: emit.verbatim "/* Qux */\0A" + // CHECK-NEXT: } + + // CHECK: emit.file_list "firrtl_black_box_resource_files.f", [ + // CHECK-SAME: @blackbox_hello.v + // CHECK-SAME: @blackbox_hello3.v + // CHECK-SAME: @blackbox_Bar.v + // CHECK-SAME: @blackbox_Baz.sv + // CHECK-SAME: @blackbox_hello2.v + // CHECK-SAME: @blackbox_Qux.sv + // CHECK-SAME: ] sym @blackbox_filelist } + //--- NoDUT.mlir // Check that a TestBenchDirAnnotation has no effect without the presence of a // MarkDUTAnnotation. @@ -72,7 +88,10 @@ firrtl.circuit "NoDUT" attributes {annotations = [ firrtl.module @NoDUT() { firrtl.instance noDUTBlackBox @NoDUTBlackBox() } - // CHECK: sv.verbatim "module NoDUTBlackBox() - // CHECK-SAME: #hw.output_file<".{{/|\\\\}}NoDUTBlackBox.sv"> - // CHECK: sv.verbatim "NoDUTBlackBox.sv" + // CHECK: emit.file ".{{/|\\\\}}NoDUTBlackBox.sv" sym @blackbox_NoDUTBlackBox.sv { + // CHECK-NEXT: emit.verbatim "module NoDUTBlackBox();\0Aendmodule\0A" + // CHECK-NEXT: } + // CHECK: emit.file_list "firrtl_black_box_resource_files.f", [ + // CHECK-SAME: @blackbox_NoDUTBlackBox.sv + // CHECK-SAME: ] sym @blackbox_filelist } diff --git a/test/Dialect/FIRRTL/canonicalization.mlir b/test/Dialect/FIRRTL/canonicalization.mlir index 1e14b0571746..98c3c44a4967 100644 --- a/test/Dialect/FIRRTL/canonicalization.mlir +++ b/test/Dialect/FIRRTL/canonicalization.mlir @@ -599,23 +599,31 @@ firrtl.module @Shl(in %in1u: !firrtl.uint<1>, // CHECK-LABEL: firrtl.module @Shr firrtl.module @Shr(in %in1u: !firrtl.uint<1>, in %in4u: !firrtl.uint<4>, + in %inu: !firrtl.uint, in %in1s: !firrtl.sint<1>, in %in4s: !firrtl.sint<4>, + in %ins: !firrtl.sint, in %in0u: !firrtl.uint<0>, + in %in0s: !firrtl.sint<0>, + out %out0u: !firrtl.uint<0>, out %out1s: !firrtl.sint<1>, out %out1u: !firrtl.uint<1>, - out %outu: !firrtl.uint<4>) { + out %out4u: !firrtl.uint<4>, + out %out4s: !firrtl.sint<4>, + out %outu: !firrtl.uint, + out %outs: !firrtl.sint + ) { // CHECK: firrtl.strictconnect %out1u, %in1u %0 = firrtl.shr %in1u, 0 : (!firrtl.uint<1>) -> !firrtl.uint<1> firrtl.connect %out1u, %0 : !firrtl.uint<1>, !firrtl.uint<1> // CHECK: firrtl.strictconnect %out1u, %c0_ui1 - %1 = firrtl.shr %in4u, 4 : (!firrtl.uint<4>) -> !firrtl.uint<1> - firrtl.connect %out1u, %1 : !firrtl.uint<1>, !firrtl.uint<1> + %1 = firrtl.shr %in4u, 4 : (!firrtl.uint<4>) -> !firrtl.uint<0> + firrtl.connect %out1u, %1 : !firrtl.uint<1>, !firrtl.uint<0> // CHECK: firrtl.strictconnect %out1u, %c0_ui1 - %2 = firrtl.shr %in4u, 5 : (!firrtl.uint<4>) -> !firrtl.uint<1> - firrtl.connect %out1u, %2 : !firrtl.uint<1>, !firrtl.uint<1> + %2 = firrtl.shr %in4u, 5 : (!firrtl.uint<4>) -> !firrtl.uint<0> + firrtl.connect %out1u, %2 : !firrtl.uint<1>, !firrtl.uint<0> // CHECK: [[BITS:%.+]] = firrtl.bits %in4s 3 to 3 // CHECK-NEXT: [[CAST:%.+]] = firrtl.asSInt [[BITS]] @@ -655,6 +663,40 @@ firrtl.module @Shr(in %in1u: !firrtl.uint<1>, %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> %9 = firrtl.dshr %in0u, %c1_ui1 : (!firrtl.uint<0>, !firrtl.uint<1>) -> !firrtl.uint<0> firrtl.connect %out1u, %9 : !firrtl.uint<1>, !firrtl.uint<0> + + // Issue #6608: https://github.com/llvm/circt/issues/6608 + // CHECK: firrtl.strictconnect %out0u, %c0_ui0 + %10 = firrtl.shr %in0u, 0 : (!firrtl.uint<0>) -> !firrtl.uint<0> + firrtl.strictconnect %out0u, %10 : !firrtl.uint<0> + + // Issue #6608: https://github.com/llvm/circt/issues/6608 + // CHECK: firrtl.strictconnect %out1s, %c0_si1 + %11 = firrtl.shr %in0s, 0 : (!firrtl.sint<0>) -> !firrtl.sint<1> + firrtl.strictconnect %out1s, %11 : !firrtl.sint<1> + + // Issue #6608: https://github.com/llvm/circt/issues/6608 + // CHECK: firrtl.strictconnect %out4u, %in4u + %12 = firrtl.shr %in4u, 0 : (!firrtl.uint<4>) -> !firrtl.uint<4> + firrtl.strictconnect %out4u, %12 : !firrtl.uint<4> + + // Issue #6608: https://github.com/llvm/circt/issues/6608 + // CHECK: firrtl.strictconnect %out4s, %in4s + %13 = firrtl.shr %in4s, 0 : (!firrtl.sint<4>) -> !firrtl.sint<4> + firrtl.strictconnect %out4s, %13 : !firrtl.sint<4> + + // Issue #6608: https://github.com/llvm/circt/issues/6608 + // Will change to drop op once FIRRTL spec changes sizeof(shr(uint)) + // CHECK: %[[UINT:.+]] = firrtl.shr %inu + // CHECK: firrtl.connect %outu, %[[UINT]] + %14 = firrtl.shr %inu, 0 : (!firrtl.uint) -> !firrtl.uint + firrtl.connect %outu, %14 : !firrtl.uint, !firrtl.uint + + // Issue #6608: https://github.com/llvm/circt/issues/6608 + // CHECK: %[[SINT:.+]] = firrtl.shr %ins + // CHECK: firrtl.connect %outs, %[[SINT]] + %15 = firrtl.shr %ins, 0 : (!firrtl.sint) -> !firrtl.sint + firrtl.connect %outs, %15 : !firrtl.sint, !firrtl.sint + } // CHECK-LABEL: firrtl.module @Tail @@ -2553,12 +2595,12 @@ firrtl.module @DontMergeVector(out %o:!firrtl.vector, 1>, in %i:!firrtl. // CHECK-NEXT: firrtl.strictconnect %0, %i } -// TODO: Move to an apporpriate place +// TODO: Move to an appropriate place // Issue #2197 // CHECK-LABEL: @Issue2197 firrtl.module @Issue2197(in %clock: !firrtl.clock, out %x: !firrtl.uint<2>) { -// // _HECK: [[ZERO:%.+]] = firrtl.constant 0 : !firrtl.uint<2> -// // _HECK-NEXT: firrtl.strictconnect %x, [[ZERO]] : !firrtl.uint<2> +// // COM: CHECK: [[ZERO:%.+]] = firrtl.constant 0 : !firrtl.uint<2> +// // COM: CHECK-NEXT: firrtl.strictconnect %x, [[ZERO]] : !firrtl.uint<2> // %invalid_ui1 = firrtl.invalidvalue : !firrtl.uint<1> // %_reg = firrtl.reg droppable_name %clock : !firrtl.clock, !firrtl.uint<2> // %0 = firrtl.pad %invalid_ui1, 2 : (!firrtl.uint<1>) -> !firrtl.uint<2> @@ -2614,8 +2656,8 @@ firrtl.module @Issue2251(out %o: !firrtl.sint<15>) { // %invalid_si1 = firrtl.invalidvalue : !firrtl.sint<1> // %0 = firrtl.pad %invalid_si1, 15 : (!firrtl.sint<1>) -> !firrtl.sint<15> // firrtl.connect %o, %0 : !firrtl.sint<15>, !firrtl.sint<15> -// // _HECK: %[[zero:.+]] = firrtl.constant 0 : !firrtl.sint<15> -// // _HECK-NEXT: firrtl.strictconnect %o, %[[zero]] +// // COM: CHECK: %[[zero:.+]] = firrtl.constant 0 : !firrtl.sint<15> +// // COM: CHECK-NEXT: firrtl.strictconnect %o, %[[zero]] } // Issue mentioned in #2289 @@ -3163,9 +3205,9 @@ firrtl.module @Issue5650(in %io_y: !firrtl.uint<1>, out %io_x: !firrtl.uint<1>) // CHECK-LABEL: @HasBeenReset firrtl.module @HasBeenReset(in %clock: !firrtl.clock, in %reset1: !firrtl.uint<1>, in %reset2: !firrtl.asyncreset, in %reset3: !firrtl.reset) { - // CHECK-NEXT: %c0_ui1 = firrtl.constant 0 // CHECK-NEXT: %c0_clock = firrtl.specialconstant 0 // CHECK-NEXT: %c1_clock = firrtl.specialconstant 1 + // CHECK-NEXT: %c0_ui1 = firrtl.constant 0 %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> %c0_asyncreset = firrtl.specialconstant 0 : !firrtl.asyncreset @@ -3319,4 +3361,13 @@ firrtl.module @Whens(in %clock: !firrtl.clock, in %a: !firrtl.uint<1>, in %reset } } +firrtl.module @Probes(in %clock: !firrtl.clock) { + // CHECK-NOT: firrtl.int.fpga_probe %clock, %zero_width : !firrtl.uint<0> + %zero_width = firrtl.wire : !firrtl.uint<0> + firrtl.int.fpga_probe %clock, %zero_width : !firrtl.uint<0> + // CHECK-NOT: firrtl.int.fpga_probe %clock, %empty_bundle : !firrtl.bundle> + %empty_bundle = firrtl.wire : !firrtl.bundle> + firrtl.int.fpga_probe %clock, %empty_bundle : !firrtl.bundle> +} + } diff --git a/test/Dialect/FIRRTL/check-comb-cycles.mlir b/test/Dialect/FIRRTL/check-comb-cycles.mlir index e66af4b88b4c..a5daf0676d01 100644 --- a/test/Dialect/FIRRTL/check-comb-cycles.mlir +++ b/test/Dialect/FIRRTL/check-comb-cycles.mlir @@ -1,10 +1,12 @@ -// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-check-comb-loops))' --split-input-file --verify-diagnostics %s | FileCheck %s +// RUN: circt-opt -allow-unregistered-dialect --pass-pipeline='builtin.module(firrtl.circuit(firrtl-check-comb-loops))' --split-input-file --verify-diagnostics %s | FileCheck %s // Loop-free circuit // CHECK: firrtl.circuit "hasnoloops" firrtl.circuit "hasnoloops" { firrtl.module @thru(in %in1: !firrtl.uint<1>, in %in2: !firrtl.uint<1>, out %out1: !firrtl.uint<1>, out %out2: !firrtl.uint<1>) { - firrtl.connect %out1, %in1 : !firrtl.uint<1>, !firrtl.uint<1> + %a = firrtl.wire : !firrtl.uint<1> + firrtl.connect %out1, %a : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %a, %in1 : !firrtl.uint<1>, !firrtl.uint<1> firrtl.connect %out2, %in2 : !firrtl.uint<1>, !firrtl.uint<1> } firrtl.module @hasnoloops(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>, out %b: !firrtl.uint<1>) { @@ -54,8 +56,8 @@ firrtl.circuit "hasloops" { firrtl.module @hasloops(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, out %d: !firrtl.uint<1>) { %y = firrtl.wire : !firrtl.uint<1> firrtl.connect %c, %b : !firrtl.uint<1>, !firrtl.uint<1> - %0 = firrtl.and %c, %y : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1> - %z = firrtl.node %0 : !firrtl.uint<1> + %t = firrtl.and %c, %y : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1> + %z = firrtl.node %t : !firrtl.uint<1> firrtl.connect %y, %z : !firrtl.uint<1>, !firrtl.uint<1> firrtl.connect %d, %z : !firrtl.uint<1>, !firrtl.uint<1> } @@ -66,7 +68,7 @@ firrtl.circuit "hasloops" { // Combinational loop through a combinational memory read port // CHECK-NOT: firrtl.circuit "hasloops" firrtl.circuit "hasloops" { - // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: hasloops.{y <- z <- m.r.data <- m.r.addr <- y}}} + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: hasloops.{m.r.addr <- y <- z <- m.r.data <- m.r.addr}}} firrtl.module @hasloops(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, out %d: !firrtl.uint<1>) { %y = firrtl.wire : !firrtl.uint<1> %z = firrtl.wire : !firrtl.uint<1> @@ -95,7 +97,7 @@ firrtl.circuit "hasloops" { firrtl.module @thru(in %in: !firrtl.uint<1>, out %out: !firrtl.uint<1>) { firrtl.connect %out, %in : !firrtl.uint<1>, !firrtl.uint<1> } - // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: hasloops.{y <- z <- inner.out <- inner.in <- y}}} + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: hasloops.{inner.in <- y <- z <- inner.out <- inner.in}}} firrtl.module @hasloops(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, out %d: !firrtl.uint<1>) { %y = firrtl.wire : !firrtl.uint<1> %z = firrtl.wire : !firrtl.uint<1> @@ -113,7 +115,7 @@ firrtl.circuit "hasloops" { // Multiple simple loops in one SCC // CHECK-NOT: firrtl.circuit "hasloops" firrtl.circuit "hasloops" { - // expected-error @below {{hasloops.{c <- b <- ... <- a <- ... <- c}}} + // expected-error @below {{hasloops.{b <- ... <- d <- ... <- e <- b}}} firrtl.module @hasloops(in %i: !firrtl.uint<1>, out %o: !firrtl.uint<1>) { %a = firrtl.wire : !firrtl.uint<1> %b = firrtl.wire : !firrtl.uint<1> @@ -136,7 +138,7 @@ firrtl.circuit "hasloops" { // ----- firrtl.circuit "strictConnectAndConnect" { - // expected-error @below {{strictConnectAndConnect.{b <- a <- b}}} + // expected-error @below {{strictConnectAndConnect.{a <- b <- a}}} firrtl.module @strictConnectAndConnect(out %a: !firrtl.uint<11>, out %b: !firrtl.uint<11>) { %w = firrtl.wire : !firrtl.uint<11> firrtl.strictconnect %b, %w : !firrtl.uint<11> @@ -147,11 +149,34 @@ firrtl.circuit "strictConnectAndConnect" { // ----- +firrtl.circuit "outputPortCycle" { + // expected-error @below {{outputPortCycle.{reg[0].a <- w.a <- reg[0].a}}} + firrtl.module @outputPortCycle(out %reg: !firrtl.vector>, 2>) { + %0 = firrtl.subindex %reg[0] : !firrtl.vector>, 2> + %1 = firrtl.subindex %reg[0] : !firrtl.vector>, 2> + %w = firrtl.wire : !firrtl.bundle> + firrtl.connect %w, %0 : !firrtl.bundle>, !firrtl.bundle> + firrtl.connect %1, %w : !firrtl.bundle>, !firrtl.bundle> + } +} + +// ----- + +firrtl.circuit "outputRead" { + firrtl.module @outputRead(out %reg: !firrtl.vector>, 2>) { + %0 = firrtl.subindex %reg[0] : !firrtl.vector>, 2> + %1 = firrtl.subindex %reg[1] : !firrtl.vector>, 2> + firrtl.connect %1, %0 : !firrtl.bundle>, !firrtl.bundle> + } +} + +// ----- + firrtl.circuit "vectorRegInit" { firrtl.module @vectorRegInit(in %clk: !firrtl.clock) { - %reg = firrtl.reg %clk : !firrtl.clock, !firrtl.vector, 2> - %0 = firrtl.subindex %reg[0] : !firrtl.vector, 2> - firrtl.connect %0, %0 : !firrtl.uint<8>, !firrtl.uint<8> + %reg = firrtl.reg %clk : !firrtl.clock, !firrtl.vector>, 2> + %0 = firrtl.subindex %reg[0] : !firrtl.vector>, 2> + firrtl.connect %0, %0 : !firrtl.bundle>, !firrtl.bundle> } } @@ -182,7 +207,7 @@ firrtl.circuit "PortReadWrite" { firrtl.circuit "Foo" { firrtl.module private @Bar(in %a: !firrtl.uint<1>) {} - // expected-error @below {{Foo.{bar.a <- a <- bar.a}}} + // expected-error @below {{Foo.{a <- bar.a <- a}}} firrtl.module @Foo(out %a: !firrtl.uint<1>) { %bar_a = firrtl.instance bar interesting_name @Bar(in a: !firrtl.uint<1>) firrtl.strictconnect %bar_a, %a : !firrtl.uint<1> @@ -192,6 +217,20 @@ firrtl.circuit "Foo" { // ----- +firrtl.circuit "outputPortCycle" { + firrtl.module private @Bar(in %a: !firrtl.bundle, b: uint<4>>) {} + // expected-error @below {{outputPortCycle.{bar.a.a <- port[0].a <- bar.a.a}}} + firrtl.module @outputPortCycle(out %port: !firrtl.vector, b: uint<4>>, 2>) { + %0 = firrtl.subindex %port[0] : !firrtl.vector, b: uint<4>>, 2> + %1 = firrtl.subindex %port[0] : !firrtl.vector, b: uint<4>>, 2> + %w = firrtl.instance bar interesting_name @Bar(in a: !firrtl.bundle, b: uint<4>>) + firrtl.connect %w, %0 : !firrtl.bundle, b: uint<4>>, !firrtl.bundle, b: uint<4>> + firrtl.connect %1, %w : !firrtl.bundle, b: uint<4>>, !firrtl.bundle, b: uint<4>> + } +} + +// ----- + // Node combinational loop through vector subindex // CHECK-NOT: firrtl.circuit "hasloops" firrtl.circuit "hasloops" { @@ -212,7 +251,7 @@ firrtl.circuit "hasloops" { // Node combinational loop through vector subindex // CHECK-NOT: firrtl.circuit "hasloops" firrtl.circuit "hasloops" { - // expected-error @below {{hasloops.{bar_a[0] <- b[0] <- bar_b[0] <- bar_a[0]}}} + // expected-error @below {{hasloops.{b[0] <- bar_b[0] <- bar_a[0] <- b[0]}}} firrtl.module @hasloops(out %b: !firrtl.vector, 2>) { %bar_a = firrtl.wire : !firrtl.vector, 2> %bar_b = firrtl.wire : !firrtl.vector, 2> @@ -233,7 +272,7 @@ firrtl.circuit "hasloops" { // Combinational loop through instance ports // CHECK-NOT: firrtl.circuit "hasloops" firrtl.circuit "hasLoops" { - // expected-error @below {{hasLoops.{bar.a[0] <- b[0] <- bar.b[0] <- bar.a[0]}}} + // expected-error @below {{hasLoops.{b[0] <- bar.b[0] <- bar.a[0] <- b[0]}}} firrtl.module @hasLoops(out %b: !firrtl.vector, 2>) { %bar_a, %bar_b = firrtl.instance bar @Bar(in a: !firrtl.vector, 2>, out b: !firrtl.vector, 2>) %0 = firrtl.subindex %b[0] : !firrtl.vector, 2> @@ -243,7 +282,7 @@ firrtl.circuit "hasLoops" { %5 = firrtl.subindex %b[0] : !firrtl.vector, 2> firrtl.strictconnect %5, %4 : !firrtl.uint<1> } - + firrtl.module private @Bar(in %a: !firrtl.vector, 2>, out %b: !firrtl.vector, 2>) { %0 = firrtl.subindex %a[0] : !firrtl.vector, 2> %1 = firrtl.subindex %b[0] : !firrtl.vector, 2> @@ -257,7 +296,7 @@ firrtl.circuit "hasLoops" { // ----- firrtl.circuit "bundleWire" { - // expected-error @below {{bundleWire.{w.foo.bar.baz <- out2 <- x <- w.foo.bar.baz}}} + // expected-error @below {{bundleWire.{out2 <- x <- w.foo.bar.baz <- out2}}} firrtl.module @bundleWire(in %arg: !firrtl.bundle>, qux: sint<64>>>, out %out1: !firrtl.uint<1>, out %out2: !firrtl.sint<64>) { @@ -281,6 +320,27 @@ firrtl.circuit "bundleWire" { // ----- +// Combinational loop through instance ports +// CHECK-NOT: firrtl.circuit "hasloops" +firrtl.circuit "hasLoops" { + // expected-error @below {{hasLoops.{b[0] <- bar.b[0] <- bar.a[0] <- b[0]}}} + firrtl.module @hasLoops(out %b: !firrtl.vector, 2>) { + %bar_a, %bar_b = firrtl.instance bar @Bar(in a: !firrtl.vector, 2>, out b: !firrtl.vector, 2>) + %0 = firrtl.subindex %b[0] : !firrtl.vector, 2> + %1 = firrtl.subindex %bar_a[0] : !firrtl.vector, 2> + firrtl.strictconnect %1, %0 : !firrtl.uint<1> + %4 = firrtl.subindex %bar_b[0] : !firrtl.vector, 2> + %5 = firrtl.subindex %b[0] : !firrtl.vector, 2> + firrtl.strictconnect %5, %4 : !firrtl.uint<1> + } + + firrtl.module private @Bar(in %a: !firrtl.vector, 2>, out %b: !firrtl.vector, 2>) { + firrtl.strictconnect %b, %a : !firrtl.vector, 2> + } +} + +// ----- + firrtl.circuit "registerLoop" { // CHECK: firrtl.module @registerLoop(in %clk: !firrtl.clock) firrtl.module @registerLoop(in %clk: !firrtl.clock) { @@ -316,7 +376,7 @@ firrtl.circuit "hasloops" { // Combinational loop through a combinational memory read port // CHECK-NOT: firrtl.circuit "hasloops" firrtl.circuit "hasloops" { - // expected-error @below {{hasloops.{y <- z <- m.r.data <- m.r.en <- y}}} + // expected-error @below {{hasloops.{m.r.data <- m.r.en <- y <- z <- m.r.data}}} firrtl.module @hasloops(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, out %d: !firrtl.uint<1>) { %y = firrtl.wire : !firrtl.uint<1> %z = firrtl.wire : !firrtl.uint<1> @@ -351,7 +411,7 @@ firrtl.circuit "hasloops" { firrtl.connect %inner_in, %in : !firrtl.uint<1>, !firrtl.uint<1> firrtl.connect %out, %inner_out : !firrtl.uint<1>, !firrtl.uint<1> } - // expected-error @below {{hasloops.{y <- z <- inner2.out <- inner2.in <- y}}} + // expected-error @below {{hasloops.{inner2.in <- y <- z <- inner2.out <- inner2.in}}} firrtl.module @hasloops(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, out %d: !firrtl.uint<1>) { %y = firrtl.wire : !firrtl.uint<1> %z = firrtl.wire : !firrtl.uint<1> @@ -364,7 +424,6 @@ firrtl.circuit "hasloops" { } } - // ----- // CHECK: firrtl.circuit "hasloops" @@ -467,7 +526,7 @@ firrtl.circuit "revisitOps" { %1 = firrtl.mux(%in1, %in1, %in2) : (!firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1> firrtl.connect %out, %1 : !firrtl.uint<1>, !firrtl.uint<1> } - // expected-error @below {{revisitOps.{inner2.out <- inner2.in2 <- x <- inner2.out}}} + // expected-error @below {{revisitOps.{inner2.in2 <- x <- inner2.out <- inner2.in2}}} firrtl.module @revisitOps() { %in1, %in2, %out = firrtl.instance inner2 @thru(in in1: !firrtl.uint<1>,in in2: !firrtl.uint<1>, out out: !firrtl.uint<1>) %x = firrtl.wire : !firrtl.uint<1> @@ -489,7 +548,7 @@ firrtl.circuit "revisitOps" { %1 = firrtl.mux(%w, %in1_0, %in2_1) : (!firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1> firrtl.connect %out_1, %1 : !firrtl.uint<1>, !firrtl.uint<1> } - // expected-error @below {{revisitOps.{inner2.out[1] <- inner2.in2[1] <- x <- inner2.out[1]}}} + // expected-error @below {{revisitOps.{inner2.in2[1] <- x <- inner2.out[1] <- inner2.in2[1]}}} firrtl.module @revisitOps() { %in1, %in2, %out = firrtl.instance inner2 @thru(in in1: !firrtl.vector,2>, in in2: !firrtl.vector,3>, out out: !firrtl.vector,2>) %in1_0 = firrtl.subindex %in1[0] : !firrtl.vector,2> @@ -516,7 +575,7 @@ firrtl.circuit "revisitOps" { %2 = firrtl.mux(%w, %in0_0, %1) : (!firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1> firrtl.connect %out_1, %2 : !firrtl.uint<1>, !firrtl.uint<1> } - // expected-error @below {{revisitOps.{inner2.out[1] <- inner2.in2[1] <- x <- inner2.out[1]}}} + // expected-error @below {{revisitOps.{inner2.in2[1] <- x <- inner2.out[1] <- inner2.in2[1]}}} firrtl.module @revisitOps() { %in0, %in1, %in2, %out = firrtl.instance inner2 @thru(in in0: !firrtl.vector,2>, in in1: !firrtl.vector,2>, in in2: !firrtl.vector,3>, out out: !firrtl.vector,2>) %in1_0 = firrtl.subindex %in1[0] : !firrtl.vector,2> @@ -577,7 +636,7 @@ firrtl.circuit "CycleStartsUnnammed" { // ----- firrtl.circuit "CycleThroughForceable" { - // expected-error @below {{sample path: CycleThroughForceable.{w <- n <- w}}} + // expected-error @below {{sample path: CycleThroughForceable.{n <- w <- n}}} firrtl.module @CycleThroughForceable() { %w, %w_ref = firrtl.wire forceable : !firrtl.uint<1>, !firrtl.rwprobe> %n, %n_ref = firrtl.node %w forceable : !firrtl.uint<1> @@ -587,6 +646,39 @@ firrtl.circuit "CycleThroughForceable" { // ----- +firrtl.circuit "CycleThroughForceableRef" { + // expected-error @below {{sample path: CycleThroughForceableRef.{n <- n <- w <- ... <- n}}} + firrtl.module @CycleThroughForceableRef() { + %w, %w_ref = firrtl.wire forceable : !firrtl.uint<1>, !firrtl.rwprobe> + %n, %n_ref = firrtl.node %w forceable : !firrtl.uint<1> + %read = firrtl.ref.resolve %n_ref : !firrtl.rwprobe> + firrtl.strictconnect %w, %read : !firrtl.uint<1> + } +} + +// ----- + +firrtl.circuit "Force" { + // expected-error @below {{sample path: Force.{w <- w}}} + firrtl.module @Force(in %clock: !firrtl.clock, in %x: !firrtl.uint<4>) { + %c = firrtl.constant 0 : !firrtl.uint<1> + %w, %w_ref = firrtl.wire forceable : !firrtl.uint<4>, !firrtl.rwprobe> + firrtl.ref.force %clock, %c, %w_ref, %w : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<4> + } +} + +// ----- + +firrtl.circuit "RefDefineAndCastWidths" { + firrtl.module @RefDefineAndCastWidths(in %x: !firrtl.uint<2>, out %p : !firrtl.probe) { + %w, %ref = firrtl.wire forceable : !firrtl.uint<2>, !firrtl.rwprobe> + %cast = firrtl.ref.cast %ref : (!firrtl.rwprobe>) -> !firrtl.probe + firrtl.ref.define %p, %cast : !firrtl.probe + } +} + +// ----- + firrtl.circuit "Properties" { firrtl.module @Child(in %in: !firrtl.string, out %out: !firrtl.string) { firrtl.propassign %out, %in : !firrtl.string @@ -599,8 +691,202 @@ firrtl.circuit "Properties" { } // ----- -// Incorrect visit of instance op results was resulting in missed cycles. +firrtl.circuit "hasnoloops" { + firrtl.module @thru(in %clk: !firrtl.clock, in %in1: !firrtl.uint<1>, in %in2: !firrtl.uint<1>, out %out1: !firrtl.uint<1>, out %out2: !firrtl.uint<1>) { + %a, %w_ref = firrtl.wire forceable : !firrtl.uint<1>, !firrtl.rwprobe> + firrtl.connect %out1, %a : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.ref.force %clk, %a, %w_ref, %in1 : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %out2, %in2 : !firrtl.uint<1>, !firrtl.uint<1> + } + firrtl.module @hasnoloops(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>, out %b: !firrtl.uint<1>) { + %x = firrtl.wire : !firrtl.uint<1> + %clock, %inner_in1, %inner_in2, %inner_out1, %inner_out2 = firrtl.instance inner @thru(in clk: !firrtl.clock, in in1: !firrtl.uint<1>, in in2: !firrtl.uint<1>, out out1: !firrtl.uint<1>, out out2: !firrtl.uint<1>) + firrtl.connect %inner_in1, %a : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %x, %inner_out1 : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %inner_in2, %x : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %b, %inner_out2 : !firrtl.uint<1>, !firrtl.uint<1> + } +} + +// ----- + +firrtl.circuit "forceLoop" { + firrtl.module @thru1(in %clk: !firrtl.clock, in %in: !firrtl.uint<1>, out %out: !firrtl.rwprobe>) { + %wire, %w_ref = firrtl.wire forceable : !firrtl.uint<1>, !firrtl.rwprobe> + firrtl.ref.define %out, %w_ref : !firrtl.rwprobe> + } + firrtl.module @thru2(in %clk: !firrtl.clock, in %in: !firrtl.uint<1>, out %out: !firrtl.rwprobe>) { + %inner1_clk, %inner1_in, %inner1_out = firrtl.instance inner1 @thru1(in clk: !firrtl.clock, in in: !firrtl.uint<1>, out out: !firrtl.rwprobe>) + firrtl.connect %inner1_clk, %clk : !firrtl.clock, !firrtl.clock + firrtl.connect %inner1_in, %in : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.ref.define %out, %inner1_out : !firrtl.rwprobe> + } + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: forceLoop.{inner2.in <- y <- z <- ... <- inner2.out <- inner2.in}}} + firrtl.module @forceLoop(in %clk: !firrtl.clock, in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, out %d: !firrtl.uint<1>) { + %y = firrtl.wire : !firrtl.uint<1> + %z = firrtl.wire : !firrtl.uint<1> + firrtl.connect %c, %b : !firrtl.uint<1>, !firrtl.uint<1> + %inner2_clk, %inner2_in, %w_ref = firrtl.instance inner2 @thru2(in clk: !firrtl.clock, in in: !firrtl.uint<1>, out out: !firrtl.rwprobe>) + firrtl.connect %inner2_clk, %clk : !firrtl.clock, !firrtl.clock + firrtl.connect %inner2_in, %y : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.ref.force %clk, %c, %w_ref, %inner2_in : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> + %inner2_out = firrtl.ref.resolve %w_ref : !firrtl.rwprobe> + firrtl.connect %z, %inner2_out : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %y, %z : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %d, %z : !firrtl.uint<1>, !firrtl.uint<1> + } +} + +// ----- + +// Cycle through RWProbe ports. +firrtl.circuit "RefSink" { + + firrtl.module @RefSource(out %a_ref: !firrtl.probe>, + out %a_rwref: !firrtl.rwprobe>) { + %a, %_a_rwref = firrtl.wire forceable : !firrtl.uint<1>, + !firrtl.rwprobe> + %b, %_b_rwref = firrtl.wire forceable : !firrtl.uint<1>, + !firrtl.rwprobe> + %a_ref_send = firrtl.ref.send %b : !firrtl.uint<1> + firrtl.ref.define %a_ref, %a_ref_send : !firrtl.probe> + firrtl.ref.define %a_rwref, %_a_rwref : !firrtl.rwprobe> + firrtl.strictconnect %b, %a : !firrtl.uint<1> + } + +// expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: RefSink.{b <- ... <- refSource.a_ref <- refSource.a_rwref <- b}}} + firrtl.module @RefSink( + in %clock: !firrtl.clock, + in %enable: !firrtl.uint<1> + ) { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + %refSource_a_ref, %refSource_a_rwref = + firrtl.instance refSource @RefSource( + out a_ref: !firrtl.probe>, + out a_rwref: !firrtl.rwprobe> + ) + %a_ref_resolve = + firrtl.ref.resolve %refSource_a_ref : !firrtl.probe> + %b = firrtl.node %a_ref_resolve : !firrtl.uint<1> + firrtl.ref.force_initial %c1_ui1, %refSource_a_rwref, %b : + !firrtl.uint<1>, !firrtl.uint<1> + } +} + +// ----- +// Cycle through RWProbe ports. +firrtl.circuit "RefSink" { + + firrtl.module @RefSource(out %b_ref: !firrtl.rwprobe>, + out %a_rwref: !firrtl.rwprobe>) { + %a, %_a_rwref = firrtl.wire forceable : !firrtl.uint<1>, + !firrtl.rwprobe> + %b, %_b_rwref = firrtl.wire forceable : !firrtl.uint<1>, + !firrtl.rwprobe> + firrtl.ref.define %b_ref, %_b_rwref : !firrtl.rwprobe> + firrtl.ref.define %a_rwref, %_a_rwref : !firrtl.rwprobe> + firrtl.strictconnect %b, %a : !firrtl.uint<1> + } + +// expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: RefSink.{b <- ... <- refSource.b_ref <- refSource.a_rwref <- b}}} + firrtl.module @RefSink( + in %clock: !firrtl.clock, + in %enable: !firrtl.uint<1> + ) { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + %refSource_b_ref, %refSource_a_rwref = + firrtl.instance refSource @RefSource( + out b_ref: !firrtl.rwprobe>, + out a_rwref: !firrtl.rwprobe> + ) + %a_ref_resolve = + firrtl.ref.resolve %refSource_b_ref : !firrtl.rwprobe> + %b = firrtl.node %a_ref_resolve : !firrtl.uint<1> + firrtl.ref.force_initial %c1_ui1, %refSource_a_rwref, %b : + !firrtl.uint<1>, !firrtl.uint<1> + } +} + +// ----- + +// Loop between two RWProbes referring to the same base value. +firrtl.circuit "RefSink" { + firrtl.module @RefSource(out %a_rwref1: !firrtl.rwprobe>, + out %a_rwref2: !firrtl.rwprobe>) { + %a, %_a_rwref = firrtl.wire forceable : !firrtl.uint<1>, + !firrtl.rwprobe> + firrtl.ref.define %a_rwref1, %a_rwref2 : !firrtl.rwprobe> + firrtl.ref.define %a_rwref2, %_a_rwref : !firrtl.rwprobe> + } + +// expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: RefSink.{b <- ... <- refSource.a_rwref2 <- refSource.a_rwref1 <- b}}} + firrtl.module @RefSink( + in %clock: !firrtl.clock, + in %enable: !firrtl.uint<1> + ) { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + %refSource_a_rwref1, %refSource_a_rwref2 = + firrtl.instance refSource @RefSource( + out a_rwref1: !firrtl.rwprobe>, + out a_rwref2: !firrtl.rwprobe> + ) + %a_ref_resolve = + firrtl.ref.resolve %refSource_a_rwref2 : !firrtl.rwprobe> + %b = firrtl.node %a_ref_resolve : !firrtl.uint<1> + firrtl.ref.force_initial %c1_ui1, %refSource_a_rwref1, %b : + !firrtl.uint<1>, !firrtl.uint<1> + } +} + +// ----- + +// Ensure deterministic error messages, in the presence of multiple probes. +firrtl.circuit "RefSink" { + + firrtl.module @RefSource(out %a_rwref1: !firrtl.rwprobe>, + out %a_rwref2: !firrtl.rwprobe>, + out %a_rwref3: !firrtl.rwprobe>, + out %a_rwref4: !firrtl.rwprobe>, + out %a_rwref5: !firrtl.rwprobe>) { + %a, %_a_rwref = firrtl.wire forceable : !firrtl.uint<1>, + !firrtl.rwprobe> + firrtl.ref.define %a_rwref1, %a_rwref2 : !firrtl.rwprobe> + firrtl.ref.define %a_rwref2, %a_rwref3 : !firrtl.rwprobe> + firrtl.ref.define %a_rwref3, %a_rwref4 : !firrtl.rwprobe> + firrtl.ref.define %a_rwref4, %a_rwref5 : !firrtl.rwprobe> + firrtl.ref.define %a_rwref5, %_a_rwref : !firrtl.rwprobe> + } + +// expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: RefSink.{b <- ... <- refSource.a_rwref2 <- refSource.a_rwref1 <- b}}} + firrtl.module @RefSink( + in %clock: !firrtl.clock, + in %enable: !firrtl.uint<1> + ) { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + %rwref1, %rwref2, %rwref3, %rwref4, %rwref5 = + firrtl.instance refSource @RefSource( + out a_rwref1: !firrtl.rwprobe>, + out a_rwref2: !firrtl.rwprobe>, + out a_rwref3: !firrtl.rwprobe>, + out a_rwref4: !firrtl.rwprobe>, + out a_rwref5: !firrtl.rwprobe> + ) + %a_ref_resolve = + firrtl.ref.resolve %rwref2 : !firrtl.rwprobe> + %b = firrtl.node %a_ref_resolve : !firrtl.uint<1> + firrtl.ref.force_initial %c1_ui1, %rwref5, %b : + !firrtl.uint<1>, !firrtl.uint<1> + } +} + +// ----- + +// Incorrect visit of instance op results was resulting in missed cycles. firrtl.circuit "Bug5442" { firrtl.module private @Bar(in %a: !firrtl.uint<1>, out %b: !firrtl.uint<1>) { firrtl.strictconnect %b, %a : !firrtl.uint<1> @@ -617,3 +903,186 @@ firrtl.circuit "Bug5442" { firrtl.strictconnect %baz_a, %bar_b : !firrtl.uint<1> } } + +// ----- + +firrtl.circuit "References" { + firrtl.module private @Child(in %in: !firrtl.probe>, out %out: !firrtl.probe>) { + firrtl.ref.define %out, %in : !firrtl.probe> + } + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: References.{child0.in <- child0.out <- child0.in}}} + firrtl.module @References() { + %in, %out = firrtl.instance child0 @Child(in in: !firrtl.probe>, out out: !firrtl.probe>) + firrtl.ref.define %in, %out : !firrtl.probe> + } +} + +// ----- + +firrtl.circuit "RefSubLoop" { + firrtl.module private @Child(in %bundle: !firrtl.bundle, b: uint<1>>, out %p: !firrtl.rwprobe, b: uint<1>>>) { + %n, %n_ref = firrtl.node interesting_name %bundle forceable : !firrtl.bundle, b: uint<1>> + firrtl.ref.define %p, %n_ref : !firrtl.rwprobe, b: uint<1>>> + } + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: RefSubLoop.{c.bundle.b <- ... <- c.p.b <- c.bundle.b}}} + firrtl.module @RefSubLoop(in %x: !firrtl.uint<1>) { + %c_bundle, %c_p = firrtl.instance c interesting_name @Child(in bundle: !firrtl.bundle, b: uint<1>>, out p: !firrtl.rwprobe, b: uint<1>>>) + %0 = firrtl.ref.sub %c_p[1] : !firrtl.rwprobe, b: uint<1>>> + %1 = firrtl.subfield %c_bundle[b] : !firrtl.bundle, b: uint<1>> + %2 = firrtl.subfield %c_bundle[a] : !firrtl.bundle, b: uint<1>> + firrtl.strictconnect %2, %x : !firrtl.uint<1> + %3 = firrtl.ref.resolve %0 : !firrtl.rwprobe> + firrtl.strictconnect %1, %3 : !firrtl.uint<1> + } +} + +// ----- + +firrtl.circuit "Issue4691" { + firrtl.module private @Send(in %val: !firrtl.uint<2>, out %x: !firrtl.probe>) { + %ref_val = firrtl.ref.send %val : !firrtl.uint<2> + firrtl.ref.define %x, %ref_val : !firrtl.probe> + } + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: Issue4691.{sub.val <- ... <- sub.x <- sub.val}}} + firrtl.module @Issue4691(out %x : !firrtl.uint<2>) { + %sub_val, %sub_x = firrtl.instance sub @Send(in val: !firrtl.uint<2>, out x: !firrtl.probe>) + %res = firrtl.ref.resolve %sub_x : !firrtl.probe> + firrtl.connect %sub_val, %res : !firrtl.uint<2>, !firrtl.uint<2> + firrtl.strictconnect %x, %sub_val : !firrtl.uint<2> + } +} + +// ----- + +firrtl.circuit "Issue5462" { + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: Issue5462.{n.a <- w.a <- n.a}}} + firrtl.module @Issue5462() attributes {convention = #firrtl} { + %w = firrtl.wire : !firrtl.bundle> + %n = firrtl.node %w : !firrtl.bundle> + %0 = firrtl.subfield %n[a] : !firrtl.bundle> + %1 = firrtl.subfield %w[a] : !firrtl.bundle> + firrtl.strictconnect %1, %0 : !firrtl.uint<8> + } +} + +// ----- + +firrtl.circuit "Issue5462" { + firrtl.module private @Child(in %bundle: !firrtl.bundle, b: uint<1>>, out %p: !firrtl.bundle, b: uint<1>>) { + %n = firrtl.node %bundle : !firrtl.bundle, b: uint<1>> + %0 = firrtl.subfield %n[a] : !firrtl.bundle, b: uint<1>> + %1 = firrtl.subfield %p[a] : !firrtl.bundle, b: uint<1>> + firrtl.strictconnect %1, %0 : !firrtl.uint<1> + %2 = firrtl.subfield %n[b] : !firrtl.bundle, b: uint<1>> + %3 = firrtl.subfield %p[b] : !firrtl.bundle, b: uint<1>> + firrtl.strictconnect %3, %2 : !firrtl.uint<1> + } + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: Issue5462.{c.bundle.b <- c.p.b <- c.bundle.b}}} + firrtl.module @Issue5462(in %x: !firrtl.uint<1>) attributes {convention = #firrtl} { + %c_bundle, %c_p = firrtl.instance c @Child(in bundle: !firrtl.bundle, b: uint<1>>, out p: !firrtl.bundle, b: uint<1>>) + %0 = firrtl.subfield %c_p[b] : !firrtl.bundle, b: uint<1>> + %1 = firrtl.subfield %c_bundle[b] : !firrtl.bundle, b: uint<1>> + %2 = firrtl.subfield %c_bundle[a] : !firrtl.bundle, b: uint<1>> + firrtl.strictconnect %2, %x : !firrtl.uint<1> + firrtl.strictconnect %1, %0 : !firrtl.uint<1> + } +} + +// ----- + +firrtl.circuit "Issue5462" { + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: Issue5462.{a <- n.a <- w.a <- a}}} + firrtl.module @Issue5462(in %in_a: !firrtl.uint<8>, out %out_a: !firrtl.uint<8>, in %c: !firrtl.uint<1>) attributes {convention = #firrtl} { + %w = firrtl.wire : !firrtl.bundle> + %n = firrtl.node %w : !firrtl.bundle> + %0 = firrtl.bundlecreate %in_a : (!firrtl.uint<8>) -> !firrtl.bundle> + %1 = firrtl.mux(%c, %n, %0) : (!firrtl.uint<1>, !firrtl.bundle>, !firrtl.bundle>) -> !firrtl.bundle> + %2 = firrtl.subfield %1[a] : !firrtl.bundle> + %3 = firrtl.subfield %w[a] : !firrtl.bundle> + firrtl.strictconnect %3, %2 : !firrtl.uint<8> + %4 = firrtl.subfield %w[a] : !firrtl.bundle> + firrtl.strictconnect %out_a, %4 : !firrtl.uint<8> + } +} + +// ----- +firrtl.circuit "FlipConnect1" { + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: FlipConnect1.{w.a <- x.a <- w.a}}} + firrtl.module @FlipConnect1(){ + %w = firrtl.wire : !firrtl.bundle> + %x = firrtl.wire : !firrtl.bundle> + // x.a <= w.a + firrtl.connect %w, %x: !firrtl.bundle>, !firrtl.bundle> + // w.a <= x.a + %w_a = firrtl.subfield %w[a] : !firrtl.bundle> + %x_a = firrtl.subfield %x[a] : !firrtl.bundle> + firrtl.strictconnect %w_a, %x_a : !firrtl.uint<8> + } +} + +// ----- + +firrtl.circuit "FlipConnect2" { + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: FlipConnect2.{in.a.a <- out.a.a <- in.a.a}}} + firrtl.module @FlipConnect2() { + %in = firrtl.wire : !firrtl.bundle>> + %out = firrtl.wire : !firrtl.bundle>> + firrtl.connect %out, %in : !firrtl.bundle>>, + !firrtl.bundle>> + %0 = firrtl.subfield %in[a] : !firrtl.bundle>> + %1 = firrtl.subfield %out[a] : !firrtl.bundle>> + firrtl.connect %1, %0 : !firrtl.bundle>,!firrtl.bundle> + } +} + +// ----- + +firrtl.circuit "UnrealizedConversionCast" { + // expected-error @below {{detected combinational cycle in a FIRRTL module, sample path: UnrealizedConversionCast.{b <- b <- b}}} + firrtl.module @UnrealizedConversionCast() { + // Casts have cast-like behavior + %b = firrtl.wire : !firrtl.uint<32> + %a = builtin.unrealized_conversion_cast %b : !firrtl.uint<32> to !firrtl.uint<32> + firrtl.strictconnect %b, %a : !firrtl.uint<32> + } +} + +// ----- + +firrtl.circuit "OutsideDialect" { + firrtl.module @OutsideDialect() { + // Other dialects might need to close loops in their own ops. Ignore + // ops from other dialects + %b = firrtl.wire : !firrtl.uint<32> + // expected-remark @below {{Non-firrtl operations detected, combinatorial loop checking may miss some loops.}} + %a = "foo"(%b) : (!firrtl.uint<32>) -> !firrtl.uint<32> + // Should only trigger once + %c = "foo"(%b) : (!firrtl.uint<32>) -> !firrtl.uint<32> + firrtl.strictconnect %b, %a : !firrtl.uint<32> + } +} + +// ----- + +// Foreign op looks sink like, no loop +firrtl.circuit "OutsideDialectSink" { + firrtl.module @OutsideDialectSink() { + // Other dialects might need to close loops in their own ops. Ignore + // ops from other dialects + %b = firrtl.wire : !firrtl.uint<32> + "foo"(%b) : (!firrtl.uint<32>) -> () + } +} + +// ----- + +// Foreign op looks source like, no loop +firrtl.circuit "OutsideDialectSource" { + firrtl.module @OutsideDialectSource() { + // Other dialects might need to close loops in their own ops. Ignore + // ops from other dialects + %b = firrtl.wire : !firrtl.uint<32> + %a = "foo"() : () -> !firrtl.uint<32> + firrtl.strictconnect %b, %a : !firrtl.uint<32> + } +} diff --git a/test/Dialect/FIRRTL/emit-basic.mlir b/test/Dialect/FIRRTL/emit-basic.mlir index 5989c26fdc9e..b2e2354a505c 100644 --- a/test/Dialect/FIRRTL/emit-basic.mlir +++ b/test/Dialect/FIRRTL/emit-basic.mlir @@ -16,11 +16,14 @@ // CHECK-LABEL: circuit Foo : // PRETTY-LABEL: circuit Foo : firrtl.circuit "Foo" { - // CHECK-LABEL: module Foo : + // CHECK-LABEL: public module Foo : firrtl.module @Foo() {} + // CHECK-LABEL: {{^ *}} module PrivateModule : + firrtl.module private @PrivateModule() {} + // CHECK-LABEL: module PortsAndTypes : - firrtl.module @PortsAndTypes( + firrtl.module private @PortsAndTypes( // CHECK-NEXT: input a00 : Clock // CHECK-NEXT: input a01 : Reset // CHECK-NEXT: input a02 : AsyncReset @@ -73,7 +76,7 @@ firrtl.circuit "Foo" { } // CHECK-LABEL: module Statements : - firrtl.module @Statements(in %ui1: !firrtl.uint<1>, in %someAddr: !firrtl.uint<8>, in %someClock: !firrtl.clock, in %someReset: !firrtl.reset, out %someOut: !firrtl.uint<1>, out %ref: !firrtl.probe>) { + firrtl.module private @Statements(in %ui1: !firrtl.uint<1>, in %someAddr: !firrtl.uint<8>, in %someClock: !firrtl.clock, in %someReset: !firrtl.reset, out %someOut: !firrtl.uint<1>, out %ref: !firrtl.probe>) { // CHECK: when ui1 : // CHECK: skip firrtl.when %ui1 : !firrtl.uint<1> { @@ -440,9 +443,9 @@ firrtl.circuit "Foo" { firrtl.ref.define %out_b_0_y_2, %b_0_y_2 : !firrtl.probe> } - firrtl.extmodule @MyParameterizedExtModule(in in: !firrtl.uint, out out: !firrtl.uint<8>) attributes {defname = "name_thing"} + firrtl.extmodule @MyParameterizedExtModule(in in: !firrtl.uint<1>, out out: !firrtl.uint<8>) attributes {defname = "name_thing"} // CHECK-LABEL: extmodule MyParameterizedExtModule : - // CHECK-NEXT: input in : UInt + // CHECK-NEXT: input in : UInt<1> // CHECK-NEXT: output out : UInt<8> // CHECK-NEXT: defname = name_thing // CHECK-NEXT: parameter DEFAULT = 0 @@ -469,7 +472,7 @@ firrtl.circuit "Foo" { // CHECK-NEXT: parameter DEPTH = 32.42 // CHECK-LABEL: module ConstTypes : - firrtl.module @ConstTypes( + firrtl.module private @ConstTypes( // CHECK-NEXT: input a00 : const Clock // CHECK-NEXT: input a01 : const Reset // CHECK-NEXT: input a02 : const AsyncReset @@ -510,7 +513,7 @@ firrtl.circuit "Foo" { in %_0: !firrtl.uint<1> ) attributes {portNames = ["0"]} {} // CHECK-LABEL: module `0Foo` : - firrtl.module @"0Foo"( + firrtl.module private @"0Foo"( // CHECK-NEXT: input `0` : Clock // CHECK-NEXT: input `1` : Reset // CHECK-NEXT: input `2` : AsyncReset @@ -704,13 +707,18 @@ firrtl.circuit "Foo" { } } // CHECK: module ModuleWithGroups : - // CHECK-NEXT: layerblock GroupA : + // CHECK-NEXT: output a : Probe, GroupA> + // CHECK-NEXT: output b : RWProbe, GroupA.GroupB> + // CHECK: layerblock GroupA : // CHECK-NEXT: layerblock GroupB : // CHECK-NEXT: layerblock GroupC : // CHECK-NEXT: layerblock GroupD : // CHECK-NEXT: layerblock GroupE : // CHECK-NEXT: layerblock GroupF : - firrtl.module @ModuleWithGroups() { + firrtl.module @ModuleWithGroups( + out %a: !firrtl.probe, @GroupA>, + out %b: !firrtl.rwprobe, @GroupA::@GroupB> + ) { firrtl.layerblock @GroupA { firrtl.layerblock @GroupA::@GroupB { firrtl.layerblock @GroupA::@GroupB::@GroupC { @@ -725,6 +733,75 @@ firrtl.circuit "Foo" { } } + // Test line-breaks for very large layer associations. + firrtl.layer @Group1234567890 bind { + firrtl.layer @Group1234567890 bind { + firrtl.layer @Group1234567890 bind { + firrtl.layer @Group1234567890 bind { + firrtl.layer @Group1234567890 bind { + firrtl.layer @Group1234567890 bind { + firrtl.layer @Group1234567890 bind { + firrtl.layer @Group1234567890 bind {} + } + } + } + } + } + } + } + + // CHECK: module ModuleWithLongProbeColor + // CHECK-NEXT: output o : Probe< + // CHECK-NEXT: UInt<1>, + // CHECK-NEXT: Group1234567890.Group1234567890.Group1234567890.Group1234567890 + // CHECK-NEXT: .Group1234567890.Group1234567890.Group1234567890.Group1234567890 + // CHECK-NEXT: > + firrtl.module @ModuleWithLongProbeColor( + out %o: !firrtl.probe, @Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890> + ) {} + + // CHECK: module ModuleWithEnabledLayers enablelayer GroupA enablelayer GroupA.GroupB : + firrtl.module private @ModuleWithEnabledLayers() attributes { + layers = [ + @GroupA, + @GroupA::@GroupB + ] + } {} + + // CHECK: extmodule ExtModuleWithEnabledLayers + // CHECK-NEXT: enablelayer GroupA + // CHECK-NEXT: enablelayer GroupA.GroupB : + firrtl.extmodule @ExtModuleWithEnabledLayers() attributes { + layers = [ + @GroupA, + @GroupA::@GroupB + ] + } + + // CHECK: intmodule IntModuleWithEnabledLayers + // CHECK-NEXT: enablelayer GroupA + // CHECK-NEXT: enablelayer GroupA.GroupB : + firrtl.intmodule @IntModuleWithEnabledLayers() attributes { + layers = [ + @GroupA, + @GroupA::@GroupB + ] + } + + // CHECK: module ModuleWithLargeEnabledLayers + // CHECK-NEXT: enablelayer Group1234567890.Group1234567890 + // CHECK-NEXT: enablelayer + // CHECK-NEXT: Group1234567890.Group1234567890.Group1234567890.Group1234567890 + // CHECK-NEXT: enablelayer + // CHECK-NEXT: Group1234567890.Group1234567890.Group1234567890.Group1234567890 + // CHECK-NEXT: .Group1234567890.Group1234567890.Group1234567890.Group1234567890 : + firrtl.module @ModuleWithLargeEnabledLayers() attributes { + layers = [ + @Group1234567890::@Group1234567890, + @Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890, + @Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890::@Group1234567890 + ]} {} + // CHECK: module RWProbe : // CHECK-NEXT: input in : { a : UInt<1> }[2] // CHECK-NEXT: output p : RWProbe> diff --git a/test/Dialect/FIRRTL/emit-metadata.mlir b/test/Dialect/FIRRTL/emit-metadata.mlir index 3c8586b0b9d1..b8974be1bad1 100644 --- a/test/Dialect/FIRRTL/emit-metadata.mlir +++ b/test/Dialect/FIRRTL/emit-metadata.mlir @@ -188,6 +188,20 @@ firrtl.circuit "DualReadsSMem" { // CHECK{LITERAL}: sv.verbatim "[\0A {\0A \22module_name\22: \22{{0}}\22,\0A \22depth\22: 12,\0A \22width\22: 42,\0A \22masked\22: false,\0A \22read\22: 2,\0A \22write\22: 1,\0A \22readwrite\22: 0,\0A \22extra_ports\22: [],\0A \22hierarchy\22: [\0A \22{{1}}.DualReads_ext\22\0A ]\0A }\0A]" // CHECK: symbols = [@DualReads_ext, @DualReadsSMem]} // CHECK{LITERAL}: sv.verbatim "name {{0}} depth 12 width 42 ports write,read,read\0A" {output_file = #hw.output_file<"'dut.conf'", excludeFromFileList>, symbols = [@DualReads_ext]} + +} + +// ----- + +// CHECK-LABEL: firrtl.circuit "ReadOnlyMemory" +firrtl.circuit "ReadOnlyMemory" { + firrtl.module @ReadOnlyMemory() { + %0:4 = firrtl.instance rom_ext sym @rom_ext_0 @rom_ext(in R0_addr: !firrtl.uint<9>, in R0_en: !firrtl.uint<1>, in R0_clk: !firrtl.clock, out R0_data: !firrtl.uint<32>) + } + firrtl.memmodule @rom_ext(in R0_addr: !firrtl.uint<9>, in R0_en: !firrtl.uint<1>, in R0_clk: !firrtl.clock, out R0_data: !firrtl.uint<32>) attributes {dataWidth = 32 : ui32, depth = 512 : ui64, extraPorts = [], maskBits = 0 : ui32, numReadPorts = 1 : ui32, numReadWritePorts = 0 : ui32, numWritePorts = 0 : ui32, readLatency = 1 : ui32, writeLatency = 1 : ui32} + // CHECK{LITERAL}: sv.verbatim "[\0A {\0A \22module_name\22: \22{{0}}\22,\0A \22depth\22: 512,\0A \22width\22: 32,\0A \22masked\22: false,\0A \22read\22: 1,\0A \22write\22: 0,\0A \22readwrite\22: 0,\0A \22extra_ports\22: [],\0A \22hierarchy\22: [\0A \22{{1}}.rom_ext\22\0A ]\0A }\0A]" + // CHECK: symbols = [@rom_ext, @ReadOnlyMemory]} + // CHECK{LITERAL}: sv.verbatim "name {{0}} depth 512 width 32 ports read\0A" {output_file = #hw.output_file<"'dut.conf'", excludeFromFileList>, symbols = [@rom_ext]} } // ----- diff --git a/test/Dialect/FIRRTL/errors.mlir b/test/Dialect/FIRRTL/errors.mlir index d84aaf493ca9..4bdf5b75a103 100644 --- a/test/Dialect/FIRRTL/errors.mlir +++ b/test/Dialect/FIRRTL/errors.mlir @@ -353,6 +353,17 @@ firrtl.circuit "InstanceCannotHavePortSymbols" { // ----- +firrtl.circuit "InstanceMissingLayers" { + // expected-note @below {{original module declared here}} + firrtl.extmodule @Ext(in in : !firrtl.uint<1>) attributes {layers = [@A]} + firrtl.module @InstanceMissingLayers() { + // expected-error @below {{'firrtl.instance' op layers must be [@A], but got []}} + %foo_in = firrtl.instance foo @Ext(in in : !firrtl.uint<1>) + } +} + +// ----- + firrtl.circuit "EmptySym" { firrtl.module @EmptySym() { // expected-error @below {{inner symbol cannot have empty name}} @@ -1869,22 +1880,6 @@ firrtl.circuit "WrongLayerBlockNesting" { // ----- -// A layer block captures a type which is not a FIRRTL base type. -firrtl.circuit "NonBaseTypeCapture" { - firrtl.layer @A bind {} - firrtl.module @NonBaseTypeCapture(in %in: !firrtl.uint<1>) { - // expected-note @below {{operand is defined here}} - %ref = firrtl.ref.send %in : !firrtl.uint<1> - // expected-error @below {{'firrtl.layerblock' op captures an operand which is not a FIRRTL base type}} - firrtl.layerblock @A { - // expected-note @below {{operand is used here}} - %b = firrtl.ref.resolve %ref : !firrtl.probe> - } - } -} - -// ----- - // A layer block captures a non-passive type. firrtl.circuit "NonPassiveCapture" { firrtl.layer @A bind {} @@ -1922,6 +1917,79 @@ firrtl.circuit "LayerBlockDrivesSinksOutside" { // ----- +firrtl.circuit "IllegalRefResolve_NotInLayerBlock" { + firrtl.layer @A bind { + } + firrtl.module @IllegalRefResolve_NotInLayerBlock() { + %0 = firrtl.wire : !firrtl.probe, @A> + // expected-error @below {{'firrtl.ref.resolve' op ambient layers are insufficient to resolve reference}} + // expected-note @below {{missing layer requirements: @A}} + %1 = firrtl.ref.resolve %0 : !firrtl.probe, @A> + } +} + +// ----- + +firrtl.circuit "IllegalRefResolve_IllegalLayer" { + firrtl.layer @A bind {} + firrtl.layer @B bind {} + firrtl.module @IllegalRefResolve_IllegalLayer() { + %0 = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @B { + // expected-error @below {{'firrtl.ref.resolve' op ambient layers are insufficient to resolve reference}} + // expected-note @below {{missing layer requirements: @A}} + %1 = firrtl.ref.resolve %0 : !firrtl.probe, @A> + } + } +} + +// ----- + +firrtl.circuit "InvalidProbeAssociationPort_SymbolDoesNotExist" { + // expected-error @below {{probe port 'a' is associated with layer '@B', but this layer was not defined}} + firrtl.module @InvalidProbeAssociationPort_SymbolDoesNotExist(out %a: !firrtl.probe, @B>) { + } +} + +// ----- + +firrtl.circuit "InvalidProbeAssociationPort_SymbolIsNotALayer" { + // expected-note @below {{symbol refers to this op}} + firrtl.module @B() {} + // expected-error @below {{probe port 'a' is associated with layer '@B', but symbol '@B' does not refer to a 'firrtl.layer' op}} + firrtl.module @InvalidProbeAssociationPort_SymbolIsNotALayer(out %a: !firrtl.probe, @B>) { + } +} + +// ----- + +firrtl.circuit "InvalidProbeAssociationWire_SymbolDoesNotExist" { + firrtl.module @InvalidProbeAssociationWire_SymbolDoesNotExist() { + // expected-error @below {{'firrtl.wire' op is associated with layer '@B', but this layer was not defined}} + %a = firrtl.wire : !firrtl.probe, @B> + } +} + +// ----- + +firrtl.circuit "InvalidProbeAssociationWire_SymbolIsNotALayer" { + // expected-note @below {{symbol refers to this op}} + firrtl.module @B() {} + firrtl.module @InvalidProbeAssociationWire_SymbolIsNotALayer() { + // expected-error @below {{'firrtl.wire' op is associated with layer '@B', but symbol '@B' does not refer to a 'firrtl.layer' op}} + %a = firrtl.wire : !firrtl.probe, @B> + } +} + +// ----- + +firrtl.circuit "UnknownEnabledLayer" { + // expected-error @below {{'firrtl.module' op enables unknown layer '@A'}} + firrtl.module @UnknownEnabledLayer() attributes {layers = [@A]} {} +} + +// ----- + firrtl.circuit "RWProbeRemote" { firrtl.module @Other() { %w = firrtl.wire sym @x : !firrtl.uint<1> @@ -2307,7 +2375,8 @@ firrtl.circuit "NoCases" { portDirections = 0 : i0, portNames = [], annotations = [], - portAnnotations = [] + portAnnotations = [], + layers = [] } : () -> () } } @@ -2329,7 +2398,7 @@ firrtl.circuit "MismatchedCases" { firrtl.module @MismatchedCases() { // expected-error @below {{op case @Perf::@Fast is not in the same option group as @Platform::@ASIC}} - "firrtl.instance_choice"() { + "firrtl.instance_choice"() { moduleNames = [@Target, @Target, @Target], caseNames = [@Platform::@ASIC, @Perf::@Fast], name = "inst", @@ -2337,7 +2406,47 @@ firrtl.circuit "MismatchedCases" { portDirections = 0 : i0, portNames = [], annotations = [], - portAnnotations = [] + portAnnotations = [], + layers = [] } : () -> () } } + +// ----- + +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.option @O { + firrtl.option_case @C + } + // expected-note @below {{original module declared here}} + firrtl.module @Foo() attributes {layers = [@A]} {} + firrtl.module @Bar() attributes {layers = [@A]} {} + firrtl.module @Top() { + // expected-error @below {{'firrtl.instance_choice' op layers must be [@A], but got []}} + firrtl.instance_choice foo @Foo alternatives @O { + @C -> @Bar + } () + } +} + + +// ----- + +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.layer @B bind {} + firrtl.option @O { + firrtl.option_case @C + } + firrtl.module @Foo() attributes {layers = [@A]} {} + // expected-note @below {{original module declared here}} + firrtl.module @Bar() attributes {layers = [@B]} {} + firrtl.module @Top() attributes {layers = [@A, @B]} { + // expected-error @below {{'firrtl.instance_choice' op layers must be [@B], but got [@A]}} + firrtl.instance_choice foo {layers = [@A]} @Foo alternatives @O { + @C -> @Bar + } () + } +} + diff --git a/test/Dialect/FIRRTL/infer-resets-errors.mlir b/test/Dialect/FIRRTL/infer-resets-errors.mlir index ecab5221dbde..108867feae15 100644 --- a/test/Dialect/FIRRTL/infer-resets-errors.mlir +++ b/test/Dialect/FIRRTL/infer-resets-errors.mlir @@ -163,6 +163,27 @@ firrtl.circuit "top" { } } +// ----- +// Reset annotation cannot target synchronous reset signals +firrtl.circuit "top" { + firrtl.module @top() { + // expected-error @below {{'IgnoreFullAsyncResetAnnotation' must target async reset, but targets '!firrtl.uint<1>'}} + %innerReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.uint<1> + } +} + +// ----- +// Reset annotation cannot target reset signals which are inferred to be synchronous +firrtl.circuit "top" { + firrtl.module @top() { + // expected-error @below {{'IgnoreFullAsyncResetAnnotation' must target async reset, but targets '!firrtl.uint<1>'}} + %innerReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.reset + %invalid = firrtl.invalidvalue : !firrtl.reset + firrtl.strictconnect %innerReset, %invalid : !firrtl.reset + } +} + + // ----- // Ignore reset annotation cannot target port firrtl.circuit "top" { diff --git a/test/Dialect/FIRRTL/infer-widths.mlir b/test/Dialect/FIRRTL/infer-widths.mlir index 1f8535596f4b..b3b4ee20a9b3 100644 --- a/test/Dialect/FIRRTL/infer-widths.mlir +++ b/test/Dialect/FIRRTL/infer-widths.mlir @@ -399,7 +399,7 @@ firrtl.circuit "Foo" { // CHECK: %1 = firrtl.shl {{.*}} -> !firrtl.sint<8> // CHECK: %2 = firrtl.shr {{.*}} -> !firrtl.uint<2> // CHECK: %3 = firrtl.shr {{.*}} -> !firrtl.sint<2> - // CHECK: %4 = firrtl.shr {{.*}} -> !firrtl.uint<1> + // CHECK: %4 = firrtl.shr {{.*}} -> !firrtl.uint<0> // CHECK: %5 = firrtl.shr {{.*}} -> !firrtl.sint<1> %ui = firrtl.wire : !firrtl.uint %si = firrtl.wire : !firrtl.sint @@ -415,6 +415,25 @@ firrtl.circuit "Foo" { %c0_si5 = firrtl.constant 0 : !firrtl.sint<5> firrtl.connect %ui, %c0_ui5 : !firrtl.uint, !firrtl.uint<5> firrtl.connect %si, %c0_si5 : !firrtl.sint, !firrtl.sint<5> + + // CHECK: firrtl.connect %u0, %0 : !firrtl.uint<8>, !firrtl.uint<8> + %u0 = firrtl.wire : !firrtl.uint + firrtl.connect %u0, %0 : !firrtl.uint, !firrtl.uint + // CHECK: firrtl.connect %s1, %1 : !firrtl.sint<8>, !firrtl.sint<8> + %s1 = firrtl.wire : !firrtl.sint + firrtl.connect %s1, %1 : !firrtl.sint, !firrtl.sint + // CHECK: firrtl.connect %u2, %2 : !firrtl.uint<2>, !firrtl.uint<2> + %u2 = firrtl.wire : !firrtl.uint + firrtl.connect %u2, %2 : !firrtl.uint, !firrtl.uint + // CHECK: firrtl.connect %s3, %3 : !firrtl.sint<2>, !firrtl.sint<2> + %s3 = firrtl.wire : !firrtl.sint + firrtl.connect %s3, %3 : !firrtl.sint, !firrtl.sint + // CHECK: firrtl.connect %u4, %4 : !firrtl.uint<0>, !firrtl.uint<0> + %u4 = firrtl.wire : !firrtl.uint + firrtl.connect %u4, %4 : !firrtl.uint, !firrtl.uint + // CHECK: firrtl.connect %s5, %5 : !firrtl.sint<1>, !firrtl.sint<1> + %s5 = firrtl.wire : !firrtl.sint + firrtl.connect %s5, %5 : !firrtl.sint, !firrtl.sint } // CHECK-LABEL: @PassiveCastOp diff --git a/test/Dialect/FIRRTL/inject-dut-hierarchy.mlir b/test/Dialect/FIRRTL/inject-dut-hierarchy.mlir index 02543229b4bc..9535012a90d4 100644 --- a/test/Dialect/FIRRTL/inject-dut-hierarchy.mlir +++ b/test/Dialect/FIRRTL/inject-dut-hierarchy.mlir @@ -157,3 +157,27 @@ firrtl.circuit "Refs" attributes { %dut_in, %dut_tap = firrtl.instance dut sym @dut @DUT(in in: !firrtl.uint<1>, out out: !firrtl.ref>) } } + +// ----- + +// CHECK-LABEL: firrtl.circuit "Properties" +firrtl.circuit "Properties" attributes { + annotations = [{class = "sifive.enterprise.firrtl.InjectDUTHierarchyAnnotation", name = "Foo"}] + } { + + firrtl.module private @DUT( + in %in: !firrtl.integer, out %out: !firrtl.integer + ) attributes { + annotations = [ + {class = "sifive.enterprise.firrtl.MarkDUTAnnotation"} + ]} + { + // CHECK: [[IN:%.+]], [[OUT:%.+]] = firrtl.instance Foo + // CHECK: firrtl.propassign [[IN]], %in + // CHECK: firrtl.propassign %out, [[OUT]] + firrtl.propassign %out, %in : !firrtl.integer + } + firrtl.module @Properties() { + %dut_in, %dut_out = firrtl.instance dut sym @dut @DUT(in in: !firrtl.integer, out out: !firrtl.integer) + } +} diff --git a/test/Dialect/FIRRTL/inliner.mlir b/test/Dialect/FIRRTL/inliner.mlir index a84fc438ddc1..eb67d29bd456 100644 --- a/test/Dialect/FIRRTL/inliner.mlir +++ b/test/Dialect/FIRRTL/inliner.mlir @@ -824,6 +824,58 @@ firrtl.circuit "CollidingSymbolsMultiInline" { } } +// CHECK-LABEL: firrtl.circuit "TrackInliningInDebugInfo" +firrtl.circuit "TrackInliningInDebugInfo" { + // CHECK: firrtl.module @TrackInliningInDebugInfo + firrtl.module @TrackInliningInDebugInfo() { + // CHECK: [[SCOPE_FOO:%.+]] = dbg.scope "foo", "Foo" + // CHECK: [[SCOPE_BAR:%.+]] = dbg.scope "bar", "Bar" scope [[SCOPE_FOO]] + // CHECK: dbg.variable "a", {{%.+}} scope [[SCOPE_BAR]] + // CHECK: [[SCOPE_IMPL:%.+]] = dbg.scope "impl", "Bugu" scope [[SCOPE_BAR]] + // CHECK: dbg.variable "b", {{%.+}} scope [[SCOPE_IMPL]] + firrtl.instance foo @Foo() + } + // CHECK-NOT: @Foo + firrtl.module private @Foo() attributes {annotations = [{class = "firrtl.passes.InlineAnnotation"}]} { + firrtl.instance bar @Bar() + } + // CHECK-NOT: @Bar + firrtl.module private @Bar() attributes {annotations = [{class = "firrtl.passes.InlineAnnotation"}]} { + %wire = firrtl.wire : !firrtl.uint<1> + dbg.variable "a", %wire : !firrtl.uint<1> + firrtl.when %wire : !firrtl.uint<1> { + %0 = dbg.scope "impl", "Bugu" + dbg.variable "b", %wire scope %0 : !firrtl.uint<1> + } + } +} + +// CHECK-LABEL: firrtl.circuit "TrackFlatteningInDebugInfo" +firrtl.circuit "TrackFlatteningInDebugInfo" { + // CHECK: firrtl.module @TrackFlatteningInDebugInfo + firrtl.module @TrackFlatteningInDebugInfo() attributes {annotations = [{class = "firrtl.transforms.FlattenAnnotation"}]} { + // CHECK: [[SCOPE_FOO:%.+]] = dbg.scope "foo", "Foo" + // CHECK: [[SCOPE_BAR:%.+]] = dbg.scope "bar", "Bar" scope [[SCOPE_FOO]] + // CHECK: dbg.variable "a", {{%.+}} scope [[SCOPE_BAR]] + // CHECK: [[SCOPE_IMPL:%.+]] = dbg.scope "impl", "Bugu" scope [[SCOPE_BAR]] + // CHECK: dbg.variable "b", {{%.+}} scope [[SCOPE_IMPL]] + firrtl.instance foo @Foo() + } + // CHECK-NOT: @Foo + firrtl.module private @Foo() { + firrtl.instance bar @Bar() + } + // CHECK-NOT: @Bar + firrtl.module private @Bar() { + %wire = firrtl.wire : !firrtl.uint<1> + dbg.variable "a", %wire : !firrtl.uint<1> + firrtl.when %wire : !firrtl.uint<1> { + %0 = dbg.scope "impl", "Bugu" + dbg.variable "b", %wire scope %0 : !firrtl.uint<1> + } + } +} + // ----- // Test proper hierarchical inlining of RefType diff --git a/test/Dialect/FIRRTL/layers-errors.mlir b/test/Dialect/FIRRTL/layers-errors.mlir new file mode 100644 index 000000000000..c2ce129482e7 --- /dev/null +++ b/test/Dialect/FIRRTL/layers-errors.mlir @@ -0,0 +1,171 @@ +// RUN: circt-opt %s -split-input-file -verify-diagnostics + +// Cannot cast away a probe color. +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.module @Top() { + %w = firrtl.wire : !firrtl.probe, @A> + // expected-error @below {{cannot discard layer requirements of input ref}} + // expected-note @below {{discarding layer requirements: @A}} + %s = firrtl.ref.cast %w : (!firrtl.probe, @A>) -> !firrtl.probe> + } +} + +// ----- + +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.module @Top(out %o : !firrtl.probe>) { + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + firrtl.layerblock @A { + // expected-error @below {{'firrtl.ref.define' op has more layer requirements than destination}} + // expected-note @below {{additional layers required: @A}} + firrtl.ref.define %o, %r : !firrtl.probe> + } + } +} + +// ----- + +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.module @Top(out %o : !firrtl.probe>) { + firrtl.layerblock @A { + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + // expected-error @below {{'firrtl.ref.define' op has more layer requirements than destination}} + // expected-note @below {{additional layers required: @A}} + firrtl.ref.define %o, %r : !firrtl.probe> + } + } +} + +// ----- + +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.layer @B bind {} + firrtl.extmodule @Foo(out o : !firrtl.probe, @B>) + firrtl.module @Top(out %o : !firrtl.probe, @B>) { + firrtl.layerblock @A { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @B>) + // expected-error @below {{'firrtl.ref.define' op has more layer requirements than destination}} + // expected-note @below {{additional layers required: @A}} + firrtl.ref.define %o, %foo_o : !firrtl.probe, @B> + } + } +} + +// ----- + +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.module @Top(out %o : !firrtl.probe>) { + %w = firrtl.wire : !firrtl.openbundle>> + firrtl.layerblock @A { + %f = firrtl.opensubfield %w[f] : !firrtl.openbundle>> + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + // expected-error @below {{'firrtl.ref.define' op has more layer requirements than destination}} + // expected-note @below {{additional layers required: @A}} + firrtl.ref.define %f, %r : !firrtl.probe> + } + } +} + +// ----- + +// ref.resolve under insufficient layers. +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.module @Top() { + %w = firrtl.wire : !firrtl.probe, @A> + // expected-error @below {{'firrtl.ref.resolve' op ambient layers are insufficient to resolve reference}} + // expected-note @below {{missing layer requirements: @A}} + %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> + } +} + +// ----- + +// ref.resolve under insufficient layers (with nested layers). +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.module @Top() { + %w = firrtl.wire : !firrtl.probe, @A::@B> + firrtl.layerblock @A { + // expected-error @below {{'firrtl.ref.resolve' op ambient layers are insufficient to resolve reference}} + // expected-note @below {{missing layer requirements: @A}} + %0 = firrtl.ref.resolve %w : !firrtl.probe, @A::@B> + } + } +} + +// ----- + +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.module @Foo() attributes {layers = [@A]} {} + firrtl.module @Top() { + // expected-error @below {{'firrtl.instance' op ambient layers are insufficient to instantiate module}} + // expected-note @below {{missing layer requirements: @A}} + firrtl.instance foo {layers = [@A]} @Foo() + } +} + +// ----- + +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.option @O { + firrtl.option_case @C1 + } + firrtl.module @Foo() attributes {layers = [@A]} {} + firrtl.module @Bar() attributes {layers = [@A]} {} + firrtl.module @Top() { + // expected-error @below {{'firrtl.instance_choice' op ambient layers are insufficient to instantiate module}} + // expected-note @below {{missing layer requirements: @A}} + firrtl.instance_choice foo {layers = [@A]} @Foo alternatives @O { + @C1 -> @Bar + } () + } +} + +// ----- + +// Capturing an outer property from inside a layerblock is not allowed. +// Eventually, we may like to relax this, but we need time to convince +// ourselves whether this is actually safe and can be lowered. +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.extmodule @WithInputProp(in i : !firrtl.string) attributes {layers=[@A]} + firrtl.module @Top(out %o : !firrtl.probe>) { + // expected-note @below {{operand is defined here}} + %str = firrtl.string "whatever" + // expected-error @below {{'firrtl.layerblock' op captures a property operand}} + firrtl.layerblock @A { + %foo_in = firrtl.instance foo @WithInputProp(in i : !firrtl.string) + // expected-note @below {{operand is used here}} + firrtl.propassign %foo_in, %str : !firrtl.string + } + } +} + +// ----- + +// Driving an outer property from inside a layerblock is not allowed. +firrtl.circuit "Top" { + firrtl.layer @A bind {} + firrtl.extmodule @WithInputProp(in i : !firrtl.string) attributes {layers=[@A]} + firrtl.module @Top(out %o : !firrtl.probe>) { + // expected-note @below {{operand is defined here}} + %foo_in = firrtl.instance foo @WithInputProp(in i : !firrtl.string) + // expected-error @below {{'firrtl.layerblock' op captures a property operand}} + firrtl.layerblock @A { + %str = firrtl.string "whatever" + // expected-note @below {{operand is used here}} + firrtl.propassign %foo_in, %str : !firrtl.string + } + } +} diff --git a/test/Dialect/FIRRTL/layers.mlir b/test/Dialect/FIRRTL/layers.mlir new file mode 100644 index 000000000000..f63770c759b0 --- /dev/null +++ b/test/Dialect/FIRRTL/layers.mlir @@ -0,0 +1,204 @@ +// RUN: circt-opt %s + +firrtl.circuit "Test" { + + firrtl.module @Test() {} + + firrtl.layer @A bind { + firrtl.layer @B bind { + } + } + + firrtl.layer @B bind {} + + firrtl.module @Foo(out %o : !firrtl.probe, @A>) { + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + %s = firrtl.ref.cast %r : (!firrtl.probe>) -> !firrtl.probe, @A> + firrtl.ref.define %o, %s : !firrtl.probe, @A> + } + + firrtl.module @Bar(out %o : !firrtl.probe, @B>) { + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + %s = firrtl.ref.cast %r : (!firrtl.probe>) -> !firrtl.probe, @B> + firrtl.ref.define %o, %s : !firrtl.probe, @B> + } + + //===--------------------------------------------------------------------===// + // Basic Casting Tests + //===--------------------------------------------------------------------===// + + firrtl.module @CastToAnyLayer() { + // Safe to take an uncolored probe and colorize it. + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + %s = firrtl.ref.cast %r : (!firrtl.probe>) -> !firrtl.probe, @A> + } + + firrtl.module @CastToSublayer() { + // Safe to take a colored probe and cast it to a nested/child/sub-layer. + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + %s = firrtl.ref.cast %r : (!firrtl.probe>) -> !firrtl.probe, @A> + %t = firrtl.ref.cast %s : (!firrtl.probe, @A>) -> !firrtl.probe, @A::@B> + } + + firrtl.module @CastToAmbientLayer(out %o : !firrtl.probe, @A::@B>) { + // safe to take a value under layerblock @A and cast it to @A::B + firrtl.layerblock @A { + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + %s = firrtl.ref.cast %r : (!firrtl.probe>) -> !firrtl.probe, @A::@B> + firrtl.ref.define %o, %s : !firrtl.probe, @A::@B> + } + } + + firrtl.module @CastAwayColorUnderLayerBlock(out %o : !firrtl.probe>) { + %w = firrtl.wire : !firrtl.probe, @A> + + // define %w, a wire colored by @A + firrtl.layerblock @A { + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + } + + // erase color from %w, safe under layerblock @A + firrtl.layerblock @A { + %r = firrtl.ref.cast %w : (!firrtl.probe, @A>) -> !firrtl.probe> + %d = firrtl.ref.resolve %r : !firrtl.probe> + } + } + + firrtl.module @CastAwayPortColorUnderLayerBlock() { + firrtl.layerblock @A { + %o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + // safe to cast away @A from %o under layerblock @A + %r = firrtl.ref.cast %o : (!firrtl.probe, @A>) -> !firrtl.probe> + } + } + + firrtl.module @CastAwayColorUnderEnabledLayer(out %o : !firrtl.probe>) attributes {layers = [@A]} { + %w = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + %s = firrtl.ref.cast %w : (!firrtl.probe, @A>) -> !firrtl.probe> + %d = firrtl.ref.resolve %s : !firrtl.probe> + } + // cast away the @A from %w, safe under enabled layers. + %r = firrtl.ref.cast %w : (!firrtl.probe, @A>) -> !firrtl.probe> + firrtl.ref.define %o, %r : !firrtl.probe> + } + + //===--------------------------------------------------------------------===// + // Enabled Layers Tests + //===--------------------------------------------------------------------===// + + firrtl.module @CastAwayPortColorUnderEnabledLayer(out %o : !firrtl.probe>) attributes {layers = [@A]} { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + // safe to cast away @A from %foo_o under enabled layers + %r = firrtl.ref.cast %foo_o : (!firrtl.probe, @A>) -> !firrtl.probe> + firrtl.ref.define %o, %r : !firrtl.probe> + } + + firrtl.module @DefineUncoloredProbeUnderEnabledLayerWithReferentUnderLayerBlock(out %o : !firrtl.probe>) + attributes {layers = [@A]} { + firrtl.layerblock @A { + %c = firrtl.constant 0 : !firrtl.uint<1> + %r = firrtl.ref.send %c : !firrtl.uint<1> + firrtl.ref.define %o, %r : !firrtl.probe> + } + } + + firrtl.module @CastAwayMultipleColorsUnderEnabledLayers(out %o : !firrtl.probe>) + attributes {layers = [@A, @B]} { + firrtl.layerblock @A { + %bar_o = firrtl.instance bar @Bar(out o : !firrtl.probe, @B>) + %r = firrtl.ref.cast %bar_o : (!firrtl.probe, @B>) -> !firrtl.probe> + firrtl.ref.define %o, %r : !firrtl.probe> + } + } + + //===--------------------------------------------------------------------===// + // Ref Resolve Tests + //===--------------------------------------------------------------------===// + + firrtl.module @ResolveColoredRefUnderLayerBlock() { + %w = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> + } + } + + firrtl.module @ResolveColoredRefUnderEnabledLayer() attributes {layers=[@A]} { + %w = firrtl.wire : !firrtl.probe, @A> + %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> + } + + firrtl.module @ResolveColoredRefPortUnderLayerBlock1() { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + firrtl.layerblock @A { + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } + } + + firrtl.module @ResolveColoredRefPortUnderLayerBlock2() { + firrtl.layerblock @A { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } + } + + firrtl.module @ResolveColoredRefPortUnderEnabledLayer() attributes {layers=[@A]} { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } + + //===--------------------------------------------------------------------===// + // Regression Tests + //===--------------------------------------------------------------------===// + + firrtl.module @WhenUnderLayer(in %test: !firrtl.uint<1>) { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A { + %w = firrtl.wire : !firrtl.uint<1> + firrtl.when %test : !firrtl.uint<1> { + firrtl.strictconnect %w, %c0_ui1 : !firrtl.uint<1> + } + } + } + + firrtl.module @ProbeEscapeLayer(out %p: !firrtl.probe, @A>) { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A { + %0 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1> + %1 = firrtl.ref.cast %0 : (!firrtl.probe>) -> !firrtl.probe, @A> + firrtl.ref.define %p, %1 : !firrtl.probe, @A> + } + } + + firrtl.module @ProbeIntoOpenBundle(out %o: !firrtl.openbundle, @A>>) { + firrtl.layerblock @A { + %0 = firrtl.opensubfield %o[p] : !firrtl.openbundle, @A>> + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %1 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1> + %2 = firrtl.ref.cast %1 : (!firrtl.probe>) -> !firrtl.probe, @A> + firrtl.ref.define %0, %2 : !firrtl.probe, @A> + } + } + + //===--------------------------------------------------------------------===// + // Properties Under Layers + //===--------------------------------------------------------------------===// + + firrtl.extmodule @WithInputProp(in i : !firrtl.string) + + firrtl.module @InstWithInputPropUnderLayerBlock() { + firrtl.layerblock @A { + %foo_in = firrtl.instance foo @WithInputProp(in i : !firrtl.string) + %str = firrtl.string "whatever" + firrtl.propassign %foo_in, %str : !firrtl.string + } + } +} diff --git a/test/Dialect/FIRRTL/lower-classes.mlir b/test/Dialect/FIRRTL/lower-classes.mlir index e30aff21a455..2b270121b354 100644 --- a/test/Dialect/FIRRTL/lower-classes.mlir +++ b/test/Dialect/FIRRTL/lower-classes.mlir @@ -327,3 +327,50 @@ firrtl.circuit "AnyCast" { firrtl.propassign %foo, %0 : !firrtl.anyref } } + +// CHECK-LABEL: firrtl.circuit "ModuleWithPropertySubmodule" +firrtl.circuit "ModuleWithPropertySubmodule" { + // CHECK: om.class @ModuleWithPropertySubmodule_Class + firrtl.module private @ModuleWithPropertySubmodule() { + %c0 = firrtl.integer 0 + // CHECK: om.object @SubmoduleWithProperty_Class + %inst.prop = firrtl.instance inst @SubmoduleWithProperty(in prop: !firrtl.integer) + firrtl.propassign %inst.prop, %c0 : !firrtl.integer + } + // CHECK: om.class @SubmoduleWithProperty_Class + firrtl.module private @SubmoduleWithProperty(in %prop: !firrtl.integer) { + } +} + +// CHECK-LABEL: firrtl.circuit "DownwardReferences" +firrtl.circuit "DownwardReferences" { + firrtl.class @MyClass() { + } + firrtl.module @MyClassUser(in %myClassIn: !firrtl.class<@MyClass()>) { + } + firrtl.module @DownwardReferences() { + // CHECK: [[OBJ:%.+]] = om.object @MyClass + %myClass = firrtl.object @MyClass() + // CHECK: [[BP:%.+]] = om.basepath_create + // CHECK: om.object @MyClassUser_Class([[BP]], [[OBJ]]) + %myClassUser.myClassIn = firrtl.instance myClassUser @MyClassUser(in myClassIn: !firrtl.class<@MyClass()>) + firrtl.propassign %myClassUser.myClassIn, %myClass : !firrtl.class<@MyClass()> + } +} + +// CHECK-LABEL: firrtl.circuit "IntegerArithmetic" +firrtl.circuit "IntegerArithmetic" { + firrtl.module @IntegerArithmetic() { + %0 = firrtl.integer 1 + %1 = firrtl.integer 2 + + // CHECK: om.integer.add %0, %1 : !om.integer + %2 = firrtl.integer.add %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer + + // CHECK: om.integer.mul %0, %1 : !om.integer + %3 = firrtl.integer.mul %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer + + // CHECK: om.integer.shr %0, %1 : !om.integer + %4 = firrtl.integer.shr %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer + } +} diff --git a/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir b/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir new file mode 100644 index 000000000000..b59ec30fb3a8 --- /dev/null +++ b/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir @@ -0,0 +1,20 @@ +// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-intrinsics{fixup-eicg-wrapper}))' --split-input-file --verify-diagnostics %s + +firrtl.circuit "EICGWithInstAnno" { + firrtl.extmodule @EICG_wrapper(in in: !firrtl.clock, in test_en: !firrtl.uint<1>, in en: !firrtl.uint<1>, out out: !firrtl.clock) attributes {defname = "EICG_wrapper"} + hw.hierpath private @nla [@EICGWithInstAnno::@ckg, @EICG_wrapper] + firrtl.module @EICGWithInstAnno(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) { + // expected-error @below {{EICG_wrapper instance cannot have annotations since it is an intrinsic}} + firrtl.instance ckg sym @ckg {annotations = [{circt.nonlocal = @nla, class = "DummyA"}]} @EICG_wrapper(in in: !firrtl.clock, in test_en: !firrtl.uint<1>, in en: !firrtl.uint<1>, out out: !firrtl.clock) + } +} + +// ----- + +firrtl.circuit "EICGWithModuleAnno" { + // expected-error @below {{EICG_wrapper cannot have annotations since it is an intrinsic}} + firrtl.extmodule @EICG_wrapper(in in: !firrtl.clock, in test_en: !firrtl.uint<1>, in en: !firrtl.uint<1>, out out: !firrtl.clock) attributes {defname = "EICG_wrapper", annotations = [{class = "DummyA"}]} + firrtl.module @EICGWithModuleAnno(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) { + firrtl.instance ckg @EICG_wrapper(in in: !firrtl.clock, in test_en: !firrtl.uint<1>, in en: !firrtl.uint<1>, out out: !firrtl.clock) + } +} diff --git a/test/Dialect/FIRRTL/lower-intrinsics-unknown.mlir b/test/Dialect/FIRRTL/lower-intrinsics-unknown.mlir new file mode 100644 index 000000000000..8001205b851d --- /dev/null +++ b/test/Dialect/FIRRTL/lower-intrinsics-unknown.mlir @@ -0,0 +1,11 @@ +// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-intrinsics{fixup-eicg-wrapper}))' -verify-diagnostics %s + +firrtl.circuit "UnknownIntrinsic" { + // expected-error @below {{intrinsic not recognized}} + firrtl.intmodule private @UnknownIntrinsicMod(in data: !firrtl.uint<32>) attributes {intrinsic = "unknown_intrinsic"} + + firrtl.module private @UnknownIntrinsic(in %data : !firrtl.uint<32>) { + %mod_data = firrtl.instance mod @UnknownIntrinsicMod(in data: !firrtl.uint<32>) + firrtl.strictconnect %mod_data, %data : !firrtl.uint<32> + } +} \ No newline at end of file diff --git a/test/Dialect/FIRRTL/lower-intrinsics.mlir b/test/Dialect/FIRRTL/lower-intrinsics.mlir index d4243c8440d3..cae7fe456474 100644 --- a/test/Dialect/FIRRTL/lower-intrinsics.mlir +++ b/test/Dialect/FIRRTL/lower-intrinsics.mlir @@ -101,6 +101,24 @@ firrtl.circuit "Foo" { firrtl.strictconnect %en2, %en : !firrtl.uint<1> } + // CHECK-NOT: ClockInverter0 + // CHECK-NOT: ClockInverter1 + firrtl.extmodule @ClockInverter0(in in: !firrtl.clock, out out: !firrtl.clock) attributes {annotations = [{class = "circt.Intrinsic", intrinsic = "circt.clock_inv"}]} + firrtl.intmodule @ClockInverter1(in in: !firrtl.clock, out out: !firrtl.clock) attributes {intrinsic = "circt.clock_inv"} + + // CHECK: ClockInverter + firrtl.module @ClockInverter(in %clk: !firrtl.clock) { + // CHECK-NOT: ClockInverter0 + // CHECK: firrtl.int.clock_inv + %in1, %out1 = firrtl.instance "" @ClockInverter0(in in: !firrtl.clock, out out: !firrtl.clock) + firrtl.strictconnect %in1, %clk : !firrtl.clock + + // CHECK-NOT: ClockInverter1 + // CHECK: firrtl.int.clock_inv + %in2, %out2 = firrtl.instance "" @ClockInverter1(in in: !firrtl.clock, out out: !firrtl.clock) + firrtl.strictconnect %in2, %clk : !firrtl.clock + } + // CHECK-NOT: LTLAnd // CHECK-NOT: LTLOr // CHECK-NOT: LTLDelay1 @@ -236,4 +254,127 @@ firrtl.circuit "Foo" { firrtl.strictconnect %ckg_test_en, %en : !firrtl.uint<1> firrtl.strictconnect %ckg_en, %en : !firrtl.uint<1> } + + // CHECK-NOT: CirctAssert1 + // CHECK-NOT: CirctAssert2 + // CHECK-NOT: VerifAssume + // CHECK-NOT: VerifCover +// TODO: + firrtl.intmodule private @AssertAssume(in clock: !firrtl.clock, in predicate: !firrtl.uint<1>, in enable: !firrtl.uint<1>) attributes {intrinsic = "circt.chisel_assert_assume"} + firrtl.intmodule private @AssertAssumeFormat( + in clock: !firrtl.clock, + in predicate: !firrtl.uint<1>, + in enable: !firrtl.uint<1>, + in val: !firrtl.uint<1> + ) attributes {intrinsic = "circt.chisel_assert_assume"} + firrtl.intmodule private @IfElseFatalFormat( + in clock: !firrtl.clock, + in predicate: !firrtl.uint<1>, + in enable: !firrtl.uint<1>, + in val: !firrtl.uint<1> + ) attributes {intrinsic = "circt.chisel_ifelsefatal"} + firrtl.intmodule private @Assume( + in clock: !firrtl.clock, + in predicate: !firrtl.uint<1>, + in enable: !firrtl.uint<1>, + in val: !firrtl.uint<1> + ) attributes {intrinsic = "circt.chisel_assume"} + firrtl.intmodule private @CoverLabel( + in clock: !firrtl.clock, + in predicate: !firrtl.uint<1>, + in enable: !firrtl.uint<1> + ) attributes {intrinsic = "circt.chisel_cover"} + // CHECK-NOT: @AssertAssume + // CHECK-NOT: @AssertAssumeFormat + // CHECK-NOT: @IfElseFatalFormat + // CHECK-NOT: @Assume + // CHECK-NOT: @CoverLabel + + // CHECK: firrtl.module @ChiselVerif( + firrtl.module @ChiselVerif(in %clock: !firrtl.clock, + in %cond: !firrtl.uint<1>, + in %enable: !firrtl.uint<1>) { + // CHECK-NOT: firrtl.instance + // CHECK: firrtl.assert %{{.+}}, %{{.+}}, %{{.+}}, "testing" : + // CHECK-SAME: isConcurrent = true + %assert_clock, %assert_predicate, %assert_enable = firrtl.instance assert interesting_name @AssertAssume(in clock: !firrtl.clock, in predicate: !firrtl.uint<1>, in enable: !firrtl.uint<1>) + firrtl.strictconnect %assert_clock, %clock : !firrtl.clock + firrtl.strictconnect %assert_predicate, %cond : !firrtl.uint<1> + firrtl.strictconnect %assert_enable, %enable : !firrtl.uint<1> + // CHECK-NOT: firrtl.instance + // CHECK: firrtl.assert %{{.+}}, %{{.+}}, %{{.+}}, "message: %d"( + // CHECK-SAME: guards = ["MACRO_GUARD", "ASDF"] + // CHECK-SAME: isConcurrent = true + // CHECK-SAME: name = "label for assert with format string" + %assertFormat_clock, %assertFormat_predicate, %assertFormat_enable, %assertFormat_val = firrtl.instance assertFormat interesting_name @AssertAssumeFormat(in clock: !firrtl.clock, in predicate: !firrtl.uint<1>, in enable: !firrtl.uint<1>, in val: !firrtl.uint<1>) + firrtl.strictconnect %assertFormat_clock, %clock : !firrtl.clock + firrtl.strictconnect %assertFormat_predicate, %cond : !firrtl.uint<1> + firrtl.strictconnect %assertFormat_enable, %enable : !firrtl.uint<1> + firrtl.strictconnect %assertFormat_val, %cond : !firrtl.uint<1> + // CHECK-NOT: firrtl.instance + // CHECK: firrtl.assert %{{.+}}, %{{.+}}, %{{.+}}, "ief: %d"( + // CHECK-SAME: format = "ifElseFatal" + // CHECK-SAME: guards = ["MACRO_GUARD", "ASDF"] + // CHECK-SAME: isConcurrent = true + // CHECK-SAME: name = "label for ifelsefatal assert" + %ief_clock, %ief_predicate, %ief_enable, %ief_val = firrtl.instance ief interesting_name @IfElseFatalFormat(in clock: !firrtl.clock, in predicate: !firrtl.uint<1>, in enable: !firrtl.uint<1>, in val: !firrtl.uint<1>) + firrtl.strictconnect %ief_clock, %clock : !firrtl.clock + firrtl.strictconnect %ief_predicate, %cond : !firrtl.uint<1> + firrtl.strictconnect %ief_enable, %enable : !firrtl.uint<1> + firrtl.strictconnect %ief_val, %enable : !firrtl.uint<1> + // CHECK-NOT: firrtl.instance + // CHECK: firrtl.assume %{{.+}}, %{{.+}}, %{{.+}}, "text: %d"( + // CHECK-SAME: isConcurrent = true + // CHECK-SAME: name = "label for assume" + %assume_clock, %assume_predicate, %assume_enable, %assume_val = firrtl.instance assume interesting_name @Assume(in clock: !firrtl.clock, in predicate: !firrtl.uint<1>, in enable: !firrtl.uint<1>, in val: !firrtl.uint<1>) + firrtl.strictconnect %assume_clock, %clock : !firrtl.clock + firrtl.strictconnect %assume_predicate, %cond : !firrtl.uint<1> + firrtl.strictconnect %assume_enable, %enable : !firrtl.uint<1> + firrtl.strictconnect %assume_val, %enable : !firrtl.uint<1> + // CHECK-NOT: firrtl.instance + // CHECK: firrtl.cover %{{.+}}, %{{.+}}, %{{.+}}, "" : + // CHECK-SAME: isConcurrent = true + // CHECK-SAME: name = "label for cover" + %cover_clock, %cover_predicate, %cover_enable = firrtl.instance cover interesting_name @CoverLabel(in clock: !firrtl.clock, in predicate: !firrtl.uint<1>, in enable: !firrtl.uint<1>) + firrtl.strictconnect %cover_clock, %clock : !firrtl.clock + firrtl.strictconnect %cover_predicate, %cond : !firrtl.uint<1> + firrtl.strictconnect %cover_enable, %enable : !firrtl.uint<1> + } + + // CHECK-NOT: firrtl.intmodule private @FPGAProbeIntrinsic + firrtl.intmodule private @FPGAProbeIntrinsic(in data: !firrtl.uint, in clock: !firrtl.clock) attributes {intrinsic = "circt_fpga_probe"} + + // CHECK-LABEL: firrtl.module private @ProbeIntrinsicTest + firrtl.module private @ProbeIntrinsicTest(in %clock : !firrtl.clock, in %data : !firrtl.uint<32>) { + // CHECK: [[DATA:%.+]] = firrtl.wire : !firrtl.uint + // CHECK-NEXT: [[CLOCK:%.+]] = firrtl.wire : !firrtl.clock + // CHECK-NEXT: firrtl.int.fpga_probe [[CLOCK]], [[DATA]] : !firrtl.uint + // CHECK-NEXT: firrtl.strictconnect [[CLOCK]], %clock : !firrtl.clock + // CHECK-NEXT: firrtl.connect [[DATA]], %data : !firrtl.uint, !firrtl.uint<32> + %mod_data, %mod_clock = firrtl.instance mod @FPGAProbeIntrinsic(in data: !firrtl.uint, in clock: !firrtl.clock) + firrtl.strictconnect %mod_clock, %clock : !firrtl.clock + firrtl.connect %mod_data, %data : !firrtl.uint, !firrtl.uint<32> + } +} + +// CHECK-LABEL: "FixupEICGWrapper2" +firrtl.circuit "FixupEICGWrapper2" { + // CHECK-NOEICG: LegacyClockGateNoTestEn + // CHECK-EICG-NOT: LegacyClockGateNoTestEn + firrtl.extmodule @LegacyClockGateNoTestEn(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock) attributes {defname = "EICG_wrapper"} + + // CHECK: FixupEICGWrapper2 + firrtl.module @FixupEICGWrapper2(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) { + // CHECK-NOEICG: firrtl.instance + // CHECK-EICG-NOT: firrtl.instance + // CHECK-EICG: firrtl.int.clock_gate + %ckg_in, %ckg_en, %ckg_out = firrtl.instance ckg @LegacyClockGateNoTestEn(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock) + firrtl.strictconnect %ckg_in, %clock : !firrtl.clock + firrtl.strictconnect %ckg_en, %en : !firrtl.uint<1> + } } diff --git a/test/Dialect/FIRRTL/lower-layers.mlir b/test/Dialect/FIRRTL/lower-layers.mlir index 393310bff829..37b284e12bab 100644 --- a/test/Dialect/FIRRTL/lower-layers.mlir +++ b/test/Dialect/FIRRTL/lower-layers.mlir @@ -1,11 +1,366 @@ // RUN: circt-opt -firrtl-lower-layers -split-input-file %s | FileCheck %s +firrtl.circuit "Test" { + firrtl.module @Test() {} + + firrtl.layer @A bind { + firrtl.layer @B bind { + firrtl.layer @C bind {} + } + } + firrtl.layer @B bind {} + + firrtl.extmodule @Foo(out o : !firrtl.probe, @A>) + + //===--------------------------------------------------------------------===// + // Removal of Probe Colors + //===--------------------------------------------------------------------===// + + // CHECK-LABEL: @ColoredPorts(out %o: !firrtl.probe>) + firrtl.module @ColoredPorts(out %o: !firrtl.probe, @A>) {} + + // CHECK-LABEL: @ExtColoredPorts(out o: !firrtl.probe>) + firrtl.extmodule @ExtColoredPorts(out o: !firrtl.probe, @A>) + + // CHECK-LABEL: @ColoredPortsOnInstances + firrtl.module @ColoredPortsOnInstances() { + // CHECK: %foo_o = firrtl.instance foo @ColoredPorts(out o: !firrtl.probe>) + %foo_o = firrtl.instance foo @ColoredPorts(out o: !firrtl.probe, @A>) + } + + // CHECK-LABEL: @ColoredThings + firrtl.module @ColoredThings() { + // CHECK: %0 = firrtl.wire : !firrtl.probe>> + %0 = firrtl.wire : !firrtl.probe>, @A> + // CHECK: %1 = firrtl.ref.sub %0[0] : !firrtl.probe>> + %1 = firrtl.ref.sub %0[0] : !firrtl.probe>, @A> + // CHECK-NOT: firrtl.cast + %2 = firrtl.ref.cast %1 : (!firrtl.probe, @A>) -> !firrtl.probe, @A::@B> + } + + // CHECK-LABEL: @ColoredThingUnderWhen + firrtl.module @ColoredThingUnderWhen(in %b : !firrtl.uint<1>) { + // CHECK: firrtl.when %b : !firrtl.uint<1> + firrtl.when %b : !firrtl.uint<1> { + // CHECK: %0 = firrtl.wire : !firrtl.probe>> + %0 = firrtl.wire : !firrtl.probe>, @A> + // CHECK: %1 = firrtl.ref.sub %0[0] : !firrtl.probe>> + %1 = firrtl.ref.sub %0[0] : !firrtl.probe>, @A> + // CHECK-NOT: firrtl.cast + %2 = firrtl.ref.cast %1 : (!firrtl.probe, @A>) -> !firrtl.probe, @A::@B> + } + } + + //===--------------------------------------------------------------------===// + // Removal of Enabled Layers + //===--------------------------------------------------------------------===// + + // CHECK-LABEL: @EnabledLayers() { + firrtl.module @EnabledLayers() attributes {layers = [@A]} {} + + // CHECK-LABEL: @EnabledLayersOnInstance() + firrtl.module @EnabledLayersOnInstance() attributes {layers = [@A]} { + // CHECK: firrtl.instance enabledLayers @EnabledLayers() + firrtl.instance enabledLayers {layers = [@A]} @EnabledLayers() + } + + //===--------------------------------------------------------------------===// + // Removal of Layerblocks and Layers + //===--------------------------------------------------------------------===// + + // CHECK-NOT: firrtl.layer @GoodbyeCruelWorld + firrtl.layer @GoodbyeCruelWorld bind {} + + // CHECK-LABEL @WithLayerBlock + firrtl.module @WithLayerBlock() { + // CHECK-NOT firrtl.layerblock @GoodbyeCruelWorld + firrtl.layerblock @GoodbyeCruelWorld { + } + } + + //===--------------------------------------------------------------------===// + // Capture + //===--------------------------------------------------------------------===// + + // CHECK: firrtl.module private @[[A:.+]](in %[[x:.+]]: !firrtl.uint<1>, in %[[y:.+]]: !firrtl.uint<1>) + // CHECK: %0 = firrtl.add %[[x]], %[[y]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK: } + // CHECK: firrtl.module @CaptureHardware() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + // CHECK: %[[p:.+]], %[[q:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.strictconnect %[[q]], %c1_ui1 : !firrtl.uint<1> + // CHECK: firrtl.strictconnect %[[p]], %c0_ui1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @CaptureHardware() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + firrtl.layerblock @A { + %0 = firrtl.add %c0_ui1, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %x = firrtl.node %[[p]] : !firrtl.uint<1> + // CHECK: } + // CHECK: firrtl.module @CapturePort(in %in: !firrtl.uint<1>) { + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.strictconnect %[[p]], %in : !firrtl.uint<1> + // CHECK: } + firrtl.module @CapturePort(in %in: !firrtl.uint<1>){ + firrtl.layerblock @A { + %x = firrtl.node %in : !firrtl.uint<1> + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %w = firrtl.wire : !firrtl.uint<1> + // CHECK: firrtl.connect %w, %[[p]] : !firrtl.uint<1>, !firrtl.uint<1> + // CHECK: } + // CHECK: firrtl.module @CaptureHardwareViaConnect() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.strictconnect %[[p]], %c0_ui1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @CaptureHardwareViaConnect() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A { + %w = firrtl.wire : !firrtl.uint<1> + firrtl.connect %w, %c0_ui1 : !firrtl.uint<1>, !firrtl.uint<1> + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @CaptureProbeSrc() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %w = firrtl.wire : !firrtl.uint<1> + // CHECK: %0 = firrtl.ref.send %w : !firrtl.uint<1> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[p]], %1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @CaptureProbeSrc() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %w = firrtl.wire : !firrtl.uint<1> + %r = firrtl.ref.send %w : !firrtl.uint<1> + firrtl.layerblock @A { + firrtl.ref.resolve %r : !firrtl.probe> + } + } + + // CHECK: firrtl.module private @[[B:.+]](in %[[p:.+]]: !firrtl.uint<1>, in %[[q:.+]]: !firrtl.uint<1>) + // CHECK: %0 = firrtl.add %[[p]], %[[q]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK: } + // CHECK: firrtl.module private @[[A:.+]](out %[[p:.+]]: !firrtl.probe>, out %[[q:.+]]: !firrtl.probe>) attributes { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %0 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1> + // CHECK: firrtl.ref.define %[[q]], %0 : !firrtl.probe> + // CHECK: %c0_ui1_1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.send %c0_ui1_1 : !firrtl.uint<1> + // CHECK: firrtl.ref.define %[[p]], %1 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @NestedCaptureHardware() { + // CHECK: %[[b1:.+]], %[[b2:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A_B.sv", excludeFromFileList>} @[[B]] + // CHECK: %[[a1:.+]], %[[a2:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %0 = firrtl.ref.resolve %[[a2]] : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[b1]], %0 : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %[[a1]] : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[b2]], %1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @NestedCaptureHardware() { + firrtl.layerblock @A { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A::@B { + %0 = firrtl.add %c0_ui1, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + } + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + // CHECK: firrtl.when %[[p]] : !firrtl.uint<1> { + // CHECK: %0 = firrtl.add %[[p]], %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK: } + // CHECK: } + // CHECK: firrtl.module @WhenUnderLayer() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.strictconnect %[[p]], %c0_ui1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @WhenUnderLayer() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A { + %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + firrtl.when %c0_ui1 : !firrtl.uint<1> { + %0 = firrtl.add %c0_ui1, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + } + } + } + + //===--------------------------------------------------------------------===// + // Connecting/Defining Refs + //===--------------------------------------------------------------------===// + + // Src and Dst Outside Layerblock. + // + // CHECK: firrtl.module private @[[A:.+]]() { + // CHECK: } + // CHECK: firrtl.module @SrcDstOutside() { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: %1 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %1, %0 : !firrtl.probe> + // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A:.+]]() + // CHECK: } + firrtl.module @SrcDstOutside() { + %0 = firrtl.wire : !firrtl.probe, @A> + %1 = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + firrtl.ref.define %1, %0 : !firrtl.probe, @A> + } + } + + // Src Outside Layerblock. + // + // CHECK: firrtl.module private @[[A:.+]](in %_: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.ref.send %_ : !firrtl.uint<1> + // CHECK: %1 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %1, %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @SrcOutside() { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[p]], %1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @SrcOutside() { + %0 = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + %1 = firrtl.wire : !firrtl.probe, @A> + firrtl.ref.define %1, %0 : !firrtl.probe, @A> + } + } + + // Dst Outside Layerblock. + // + // CHECK: firrtl.module private @[[A:.+]](out %[[p:.+]]: !firrtl.probe>) { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %[[p]], %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @DestOutside() { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.ref.define %0, %[[p]] : !firrtl.probe> + // CHECK: } + firrtl.module @DestOutside() { + %0 = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + %1 = firrtl.wire : !firrtl.probe, @A> + firrtl.ref.define %0, %1 : !firrtl.probe, @A> + } + } + + // Src and Dst Inside Layerblock. + // + // CHECK: firrtl.module private @[[A:.+]]() { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: %1 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %1, %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @SrcDstInside() { + // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]]() + // CHECK: } + firrtl.module @SrcDstInside() { + firrtl.layerblock @A { + %0 = firrtl.wire : !firrtl.probe, @A> + %1 = firrtl.wire : !firrtl.probe, @A> + firrtl.ref.define %1, %0 : !firrtl.probe, @A> + } + } + + //===--------------------------------------------------------------------===// + // Resolving Colored Probes + //===--------------------------------------------------------------------===// + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @ResolveColoredRefUnderLayerBlock() { + // CHECK: %w = firrtl.wire : !firrtl.probe> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %0 = firrtl.ref.resolve %w : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[p]], %0 : !firrtl.uint<1> + // CHECK: } + firrtl.module @ResolveColoredRefUnderLayerBlock() { + %w = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> + } + } + + // CHECK: firrtl.module @ResolveColoredRefUnderEnabledLayer() { + // CHECK: %w = firrtl.wire : !firrtl.probe> + // CHECK: %0 = firrtl.ref.resolve %w : !firrtl.probe> + // CHECK: } + firrtl.module @ResolveColoredRefUnderEnabledLayer() attributes {layers=[@A]} { + %w = firrtl.wire : !firrtl.probe, @A> + %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @ResolveColoredRefPortUnderLayerBlock1() { + // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[p]], %0 : !firrtl.uint<1> + // CHECK: } + firrtl.module @ResolveColoredRefPortUnderLayerBlock1() { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + firrtl.layerblock @A { + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } + } + + // CHECK: firrtl.module private @[[A:.+]]() { + // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) + // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @ResolveColoredRefPortUnderLayerBlock2() { + // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers_Test_A.sv", excludeFromFileList>} @[[A]]() + // CHECK: } + firrtl.module @ResolveColoredRefPortUnderLayerBlock2() { + firrtl.layerblock @A { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } + } + + // CHECK: firrtl.module @ResolveColoredRefPortUnderEnabledLayer() { + // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) + // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> + // CHECK: } + firrtl.module @ResolveColoredRefPortUnderEnabledLayer() attributes {layers=[@A]} { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } +} + +// ----- + firrtl.circuit "Simple" { firrtl.layer @A bind { firrtl.layer @B bind { firrtl.layer @C bind {} } } + firrtl.module @Simple() { %a = firrtl.wire : !firrtl.uint<1> %b = firrtl.wire : !firrtl.uint<2> @@ -25,18 +380,18 @@ firrtl.circuit "Simple" { // CHECK-LABEL: firrtl.circuit "Simple" // -// CHECK: sv.verbatim "`include \22groups_Simple_A.sv\22\0A -// CHECK-SAME: `include \22groups_Simple_A_B.sv\22\0A -// CHECK-SAME: `ifndef groups_Simple_A_B_C\0A -// CHECK-SAME: define groups_Simple_A_B_C" -// CHECK-SAME: output_file = #hw.output_file<"groups_Simple_A_B_C.sv", excludeFromFileList> -// CHECK: sv.verbatim "`include \22groups_Simple_A.sv\22\0A -// CHECK-SAME: `ifndef groups_Simple_A_B\0A -// CHECK-SAME: define groups_Simple_A_B" -// CHECK-SAME: output_file = #hw.output_file<"groups_Simple_A_B.sv", excludeFromFileList> -// CHECK: sv.verbatim "`ifndef groups_Simple_A\0A -// CHECK-SAME: define groups_Simple_A" -// CHECK-SAME: output_file = #hw.output_file<"groups_Simple_A.sv", excludeFromFileList> +// CHECK: sv.verbatim "`include \22layers_Simple_A.sv\22\0A +// CHECK-SAME: `include \22layers_Simple_A_B.sv\22\0A +// CHECK-SAME: `ifndef layers_Simple_A_B_C\0A +// CHECK-SAME: define layers_Simple_A_B_C" +// CHECK-SAME: output_file = #hw.output_file<"layers_Simple_A_B_C.sv", excludeFromFileList> +// CHECK: sv.verbatim "`include \22layers_Simple_A.sv\22\0A +// CHECK-SAME: `ifndef layers_Simple_A_B\0A +// CHECK-SAME: define layers_Simple_A_B" +// CHECK-SAME: output_file = #hw.output_file<"layers_Simple_A_B.sv", excludeFromFileList> +// CHECK: sv.verbatim "`ifndef layers_Simple_A\0A +// CHECK-SAME: define layers_Simple_A" +// CHECK-SAME: output_file = #hw.output_file<"layers_Simple_A.sv", excludeFromFileList> // // CHECK: firrtl.module private @Simple_A_B_C( // CHECK-NOT: firrtl.module @@ -69,29 +424,29 @@ firrtl.circuit "Simple" { // CHECK-NOT: firrtl.layerblock // CHECK: %[[A_B_C_cc:[_a-zA-Z0-9]+]] = firrtl.instance simple_A_B_C { // CHECK-SAME: lowerToBind -// CHECK-SAME: output_file = #hw.output_file<"groups_Simple_A_B_C.sv" +// CHECK-SAME: output_file = #hw.output_file<"layers_Simple_A_B_C.sv" // CHECK-SAME: excludeFromFileList // CHECK-SAME: @Simple_A_B_C( // CHECK-NEXT: %[[A_B_b:[_a-zA-Z0-9]+]], %[[A_B_c:[_a-zA-Z0-9]+]], %[[A_B_cc:[_a-zA-Z0-9]+]] = firrtl.instance simple_A_B { // CHECK-SAME: lowerToBind -// CHECK-SAME: output_file = #hw.output_file<"groups_Simple_A_B.sv", excludeFromFileList> +// CHECK-SAME: output_file = #hw.output_file<"layers_Simple_A_B.sv", excludeFromFileList> // CHECK-SAME: @Simple_A_B( // CHECK-NEXT: %[[A_B_cc_resolve:[_a-zA-Z0-9]+]] = firrtl.ref.resolve %[[A_B_cc]] // CHECK-NEXT: firrtl.strictconnect %[[A_B_C_cc]], %[[A_B_cc_resolve]] // CHECK-NEXT: firrtl.strictconnect %[[A_B_b]], %b // CHECK-NEXT: %[[A_a:[_a-zA-Z0-9]+]], %[[A_c:[_a-zA-Z0-9]+]] = firrtl.instance simple_A { // CHECK-SAME: lowerToBind -// CHECK-SAME: output_file = #hw.output_file<"groups_Simple_A.sv", excludeFromFileList> +// CHECK-SAME: output_file = #hw.output_file<"layers_Simple_A.sv", excludeFromFileList> // CHECK-SAME: @Simple_A( // CHECK-NEXT: %[[A_c_resolve:[_a-zA-Z0-9]+]] = firrtl.ref.resolve %[[A_c]] // CHECK-NEXT: firrtl.strictconnect %[[A_B_c]], %[[A_c_resolve]] // CHECK-NEXT: firrtl.strictconnect %[[A_a]], %a // CHECK: } // -// CHECK-DAG: sv.verbatim "`endif // groups_Simple_A" -// CHECK-SAME: output_file = #hw.output_file<"groups_Simple_A.sv", excludeFromFileList> -// CHECK-DAG: sv.verbatim "`endif // groups_Simple_A_B" -// CHECK-SAME: output_file = #hw.output_file<"groups_Simple_A_B.sv", excludeFromFileList> +// CHECK-DAG: sv.verbatim "`endif // layers_Simple_A" +// CHECK-SAME: output_file = #hw.output_file<"layers_Simple_A.sv", excludeFromFileList> +// CHECK-DAG: sv.verbatim "`endif // layers_Simple_A_B" +// CHECK-SAME: output_file = #hw.output_file<"layers_Simple_A_B.sv", excludeFromFileList> // ----- @@ -116,3 +471,110 @@ firrtl.circuit "ModuleNameConflict" { // CHECK: firrtl.instance foo @ModuleNameConflict_A() // CHECK-NEXT: firrtl.instance {{[_a-zA-Z0-9]+}} {lowerToBind, // CHECK-SAME: @[[groupModule]]( + +// ----- +// Layerblock lowering must allow a value to be captured twice. +// https://github.com/llvm/circt/issues/6694 + +firrtl.circuit "CaptureHardwareMultipleTimes" { + firrtl.layer @A bind { + firrtl.layer @B bind {} + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.add %[[p]], %[[p]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK: } + // CHECK: firrtl.module @CaptureSrcTwice() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} @[[A]] + // CHECK: firrtl.strictconnect %[[p]], %c0_ui1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @CaptureSrcTwice() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A { + %0 = firrtl.add %c0_ui1, %c0_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + } + } + + // CHECK: firrtl.module private @[[A:.+]](out %[[dst:.+]]: !firrtl.probe>, in %[[src:.+]]: !firrtl.uint<1>) + // CHECK: %0 = firrtl.ref.send %[[src]] : !firrtl.uint<1> + // CHECK: %w2 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %[[dst]], %w2 : !firrtl.probe> + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @CaptureAsDstThenSrc() { + // CHECK: %w1 = firrtl.wire : !firrtl.probe> + // CHECK: %[[out:.+]], %[[in:.+]] = firrtl.instance {{.+}} @[[A]](out {{.+}}: !firrtl.probe>, in {{.+}}: !firrtl.uint<1>) + // CHECK: %0 = firrtl.ref.resolve %w1 : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[in]], %0 : !firrtl.uint<1> + // CHECK: firrtl.ref.define %w1, %[[out]] : !firrtl.probe> + // CHECK: } + firrtl.module @CaptureAsDstThenSrc() { + %w1 = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + // capture first as a sink. + %w2 = firrtl.wire : !firrtl.probe, @A> + firrtl.ref.define %w1, %w2 : !firrtl.probe, @A> + // Capture again, as a source. + %2 = firrtl.ref.resolve %w1 : !firrtl.probe, @A> + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[src:.+]]: !firrtl.uint<1>, out %[[dst:.+]]: !firrtl.probe>) + // CHECK: %0 = firrtl.ref.send %[[src]] : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: %w2 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %[[dst]], %w2 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @CaptureAsSrcThenDst() { + // CHECK: %w1 = firrtl.wire : !firrtl.probe> + // CHECK: %[[in:.+]], %[[out:.+]] = firrtl.instance {{.+}} @[[A]] + // CHECK: firrtl.ref.define %w1, %[[out]] : !firrtl.probe> + // CHECK: %0 = firrtl.ref.resolve %w1 : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[in]], %0 : !firrtl.uint<1> + // CHECK: } + firrtl.module @CaptureAsSrcThenDst() { + %w1 = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + // capture first as a source. + %2 = firrtl.ref.resolve %w1 : !firrtl.probe, @A> + // capture again, as a sink. + %w2 = firrtl.wire : !firrtl.probe, @A> + firrtl.ref.define %w1, %w2 : !firrtl.probe, @A> + } + } +} + +// ----- +// HierPathOps are rewritten when operations with inner symbols inside +// layerblocks are moved into new modules. +// https://github.com/llvm/circt/issues/6717 + +firrtl.circuit "HierPathOps" { + hw.hierpath @nla1 [@Foo::@foo_A] + hw.hierpath @nla2 [@Foo::@bar, @Bar] + hw.hierpath private @nla3 [@Foo::@baz] + firrtl.layer @A bind {} + firrtl.module @Bar() {} + firrtl.module @Foo() { + %0 = firrtl.wire sym @foo_A : !firrtl.uint<1> + firrtl.layerblock @A { + firrtl.instance bar sym @bar @Bar() + %1 = firrtl.wire sym @baz : !firrtl.uint<1> + } + } +} + +// CHECK-LABEL: firrtl.circuit "HierPathOps" +// +// CHECK: hw.hierpath @nla1 [@Foo::@foo_A] +// CHECK-NEXT: hw.hierpath @nla2 [@Foo::@[[inst_sym:[_A-Za-z0-9]+]], @[[mod_sym:[_A-Za-z0-9]+]]::@bar, @Bar] +// CHECK-NEXT: hw.hierpath private @nla3 [@Foo::@[[inst_sym]], @[[mod_sym]]::@baz] +// +// CHECK: firrtl.module {{.*}} @[[mod_sym]]() +// CHECK-NEXT: firrtl.instance bar sym @bar +// CHECK-NEXT: firrtl.wire sym @baz +// +// CHECK: firrtl.module @Foo() +// CHECK-NEXT: firrtl.wire sym @foo_A : +// CHECK-NEXT: firrtl.instance {{.*}} sym @[[inst_sym]] diff --git a/test/Dialect/FIRRTL/lower-open-aggs.mlir b/test/Dialect/FIRRTL/lower-open-aggs.mlir index 0210eaf71aa9..8c5e70437294 100644 --- a/test/Dialect/FIRRTL/lower-open-aggs.mlir +++ b/test/Dialect/FIRRTL/lower-open-aggs.mlir @@ -304,8 +304,8 @@ firrtl.circuit "WireProbes" { firrtl.circuit "WireSymbols" { // CHECK-LABEL: module{{.*}} @WireSymbols firrtl.module @WireSymbols() { - // CHECk-NEXT: %a = firrtl.wire sym [<@sym_a_c, 1, public>] : !firrtl.bundle> - // CHECk-NEXT: %a_b = firrtl.wire : !firrtl.string + // CHECK-NEXT: %a = firrtl.wire sym [<@sym_a_c,1,public>] : !firrtl.bundle> + // CHECK-NEXT: %a_b = firrtl.wire : !firrtl.string %a = firrtl.wire sym [<@sym_a_c, 2, public>] : !firrtl.openbundle> } } diff --git a/test/Dialect/FIRRTL/lower-signatures.mlir b/test/Dialect/FIRRTL/lower-signatures.mlir index 77ad06cae086..a5a2c4cc4d94 100644 --- a/test/Dialect/FIRRTL/lower-signatures.mlir +++ b/test/Dialect/FIRRTL/lower-signatures.mlir @@ -1,4 +1,5 @@ // RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-signatures))' %s | FileCheck --check-prefixes=CHECK %s +// RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-signatures))' --mlir-print-debuginfo %s | FileCheck --check-prefixes=CHECK-LOC %s firrtl.circuit "Prop" { // CHECK-LABEL @Prop(out %y: !firrtl.string) @@ -19,18 +20,83 @@ firrtl.circuit "Prop" { firrtl.module private @Annos( in %x: !firrtl.uint<1> [{circt.fieldID = 0 : i64, class = "circt.test", pin = "pin0"}], in %y: !firrtl.bundle, b: uint<2>> [{circt.fieldID = 2 : i64, class = "circt.test", pin = "pin2"}, {circt.fieldID = 1 : i64, class = "circt.test", pin = "pin1"}] - ) attributes {convention = #firrtl} { + ) attributes {convention = #firrtl} { } // CHECK-LABEL: @AnalogBlackBox firrtl.extmodule private @AnalogBlackBox(out bus: !firrtl.analog<32>) attributes {convention = #firrtl, defname = "AnalogBlackBox"} firrtl.module @AnalogBlackBoxModule(out %io: !firrtl.bundle>) attributes {convention = #firrtl} { - // CHECK: %io = firrtl.wire interesting_name : !firrtl.bundle> - // CHECK: %0 = firrtl.subfield %io[bus] : !firrtl.bundle> - // CHECK: firrtl.attach %0, %io_bus : !firrtl.analog<32>, !firrtl.analog<32> + // CHECK: %io = firrtl.wire interesting_name : !firrtl.bundle> + // CHECK: %0 = firrtl.subfield %io[bus] : !firrtl.bundle> + // CHECK: firrtl.attach %0, %io_bus : !firrtl.analog<32>, !firrtl.analog<32> %0 = firrtl.subfield %io[bus] : !firrtl.bundle> %impl_bus = firrtl.instance impl interesting_name @AnalogBlackBox(out bus: !firrtl.analog<32>) firrtl.attach %0, %impl_bus : !firrtl.analog<32>, !firrtl.analog<32> } + // CHECK-LABEL: firrtl.module private @Bar + firrtl.module private @Bar(out %in1: !firrtl.bundle, b flip: uint<1>>, in %in2: !firrtl.bundle>, in %out: !firrtl.bundle, e flip: uint<1>>) attributes {convention = #firrtl} { + // CHECK-NEXT: %in1 = firrtl.wire + // CHECK-NEXT: %in2 = firrtl.wire + // CHECK-NEXT: %out = firrtl.wire + } + + // CHECK-LABEL: firrtl.module private @Foo + firrtl.module private @Foo() attributes {convention = #firrtl} { + %bar_in1, %bar_in2, %bar_out = firrtl.instance bar interesting_name @Bar(out in1: !firrtl.bundle, b flip: uint<1>>, in in2: !firrtl.bundle>, in out: !firrtl.bundle, e flip: uint<1>>) + // CHECK: %bar.in1 = firrtl.wire + // CHECK: %bar.in2 = firrtl.wire + // CHECK: %bar.out = firrtl.wire + } + +} + +// Instances should preserve their location. +// See https://github.com/llvm/circt/issues/6535 +firrtl.circuit "PreserveLocation" { + firrtl.extmodule @Foo() + // CHECK-LOC-LABEL: firrtl.module @PreserveLocation + firrtl.module @PreserveLocation() { + // CHECK-LOC: firrtl.instance foo @Foo() loc([[LOC:#.+]]) + firrtl.instance foo @Foo() loc(#instLoc) + } loc(#moduleLoc) +} +// CHECK-LOC: [[LOC]] = loc("someLoc":9001:1) +#moduleLoc = loc("wrongLoc":42:1) +#instLoc = loc("someLoc":9001:1) + +// Internal paths should be expanded +firrtl.circuit "InternalPaths" { + // CHECK: firrtl.extmodule private @BlackBox + // CHECK-SAME: out bundle_a: !firrtl.uint<32>, out bundle_b: !firrtl.uint<23> + // CHECK-SAME: out array_0: !firrtl.uint<1>, out array_1: !firrtl.uint<1> + // CHECK-SAME: out probe: !firrtl.probe> + // CHECK-SAME: internalPaths = [ + // CHECK-SAME: #firrtl.internalpath + // CHECK-SAME: #firrtl.internalpath + // CHECK-SAME: #firrtl.internalpath + // CHECK-SAME: #firrtl.internalpath + // CHECK-SAME: #firrtl.internalpath<"some_probe"> + // CHECK-SAME: ] + firrtl.extmodule private @BlackBox( + out bundle : !firrtl.bundle, b: uint<23>>, + out array : !firrtl.vector, 2>, + out probe : !firrtl.probe> + ) attributes { + convention = #firrtl, + internalPaths = [ + #firrtl.internalpath, + #firrtl.internalpath, + #firrtl.internalpath<"some_probe"> + ] + } + + // CHECK-LABEL: @InternalPaths + firrtl.module @InternalPaths() { + %bundle, %array, %probe = firrtl.instance blackbox @BlackBox( + out bundle : !firrtl.bundle, b: uint<23>>, + out array : !firrtl.vector, 2>, + out probe : !firrtl.probe> + ) + } } diff --git a/test/Dialect/FIRRTL/lowerXMR.mlir b/test/Dialect/FIRRTL/lowerXMR.mlir index b83a9639389c..803b5035e3f4 100644 --- a/test/Dialect/FIRRTL/lowerXMR.mlir +++ b/test/Dialect/FIRRTL/lowerXMR.mlir @@ -3,8 +3,8 @@ // Test for same module lowering // CHECK-LABEL: firrtl.circuit "xmr" firrtl.circuit "xmr" { - // CHECK : #hw.innerNameRef<@xmr::@[[wSym]]> - // CHECK-LABEL: firrtl.module @xmr(out %o: !firrtl.uint<2>) + // CHECK-DAG: @xmr::@[[wSym:[a-zA-Z0-9]+]] + // CHECK: firrtl.module @xmr(out %o: !firrtl.uint<2>) firrtl.module @xmr(out %o: !firrtl.uint<2>) { %w = firrtl.wire : !firrtl.uint<2> %1 = firrtl.ref.send %w : !firrtl.uint<2> @@ -12,7 +12,7 @@ firrtl.circuit "xmr" { // CHECK-NOT: firrtl.ref.resolve firrtl.strictconnect %o, %x : !firrtl.uint<2> // CHECK: %w = firrtl.wire : !firrtl.uint<2> - // CHECK: %w_probe = firrtl.node sym @[[wSym:[a-zA-Z0-9_]+]] interesting_name %w : !firrtl.uint<2> + // CHECK: %w_probe = firrtl.node sym @[[wSym]] interesting_name %w : !firrtl.uint<2> // CHECK-NEXT: %[[#xmr:]] = firrtl.xmr.deref @xmrPath : !firrtl.uint<2> // CHECK: firrtl.strictconnect %o, %[[#xmr]] : !firrtl.uint<2> } @@ -689,32 +689,32 @@ firrtl.circuit "ForceRelease" { // CHECK-LABEL: firrtl.circuit "Top" firrtl.circuit "Top" { - // CHECK: sv.macro.decl @ref_Top_Top_a - // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_Top_a "{{0}}" - // CHECK-SAME: ([@[[XMR1:.*]]]) {output_file = #hw.output_file<"ref_Top_Top.sv">} + // CHECK: sv.macro.decl @ref_Top_a + // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_a "{{0}}" + // CHECK-SAME: ([@[[XMR1:.*]]]) {output_file = #hw.output_file<"ref_Top.sv">} - // CHECK-NEXT: sv.macro.decl @ref_Top_Top_b - // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_Top_b "{{0}}" - // CHECK-SAME: ([@[[XMR2:.*]]]) {output_file = #hw.output_file<"ref_Top_Top.sv">} + // CHECK-NEXT: sv.macro.decl @ref_Top_b + // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_b "{{0}}" + // CHECK-SAME: ([@[[XMR2:.*]]]) {output_file = #hw.output_file<"ref_Top.sv">} - // CHECK-NEXT: sv.macro.decl @ref_Top_Top_c - // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_Top_c "{{0}}.internal.path" - // CHECK-SAME: ([@[[XMR3:.*]]]) {output_file = #hw.output_file<"ref_Top_Top.sv">} + // CHECK-NEXT: sv.macro.decl @ref_Top_c + // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_c "{{0}}.internal.path" + // CHECK-SAME: ([@[[XMR3:.*]]]) {output_file = #hw.output_file<"ref_Top.sv">} - // CHECK-NEXT: sv.macro.decl @ref_Top_Top_d - // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_Top_d "{{0}}" - // CHECK-SAME: ([@[[XMR4:.+]]]) {output_file = #hw.output_file<"ref_Top_Top.sv">} + // CHECK-NEXT: sv.macro.decl @ref_Top_d + // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_d "{{0}}" + // CHECK-SAME: ([@[[XMR4:.+]]]) {output_file = #hw.output_file<"ref_Top.sv">} - // CHECK-NOT: sv.macro.decl @ref_Top_Top_e + // CHECK-NOT: sv.macro.decl @ref_Top_e // CHECK: hw.hierpath private @[[XMR5:.+]] [@Foo::@[[FOO_X_SYM:.+]]] - // CHECK: sv.macro.decl @ref_Top_Foo_x - // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Top_Foo_x "{{0}}" - // CHECK-SAME: ([@[[XMR5]]]) {output_file = #hw.output_file<"ref_Top_Foo.sv">} + // CHECK: sv.macro.decl @ref_Foo_x + // CHECK-NEXT{LITERAL}: sv.macro.def @ref_Foo_x "{{0}}" + // CHECK-SAME: ([@[[XMR5]]]) {output_file = #hw.output_file<"ref_Foo.sv">} - // CHECK-NEXT: sv.macro.decl @ref_Top_Foo_y - // CHECK-NEXT: sv.macro.def @ref_Top_Foo_y "internal.path" + // CHECK-NEXT: sv.macro.decl @ref_Foo_y + // CHECK-NEXT: sv.macro.def @ref_Foo_y "internal.path" // CHECK-NOT: ([ - // CHECK-SAME: {output_file = #hw.output_file<"ref_Top_Foo.sv">} + // CHECK-SAME: {output_file = #hw.output_file<"ref_Foo.sv">} // CHECK: hw.hierpath private @[[XMR1]] [@Top::@[[TOP_W_SYM:.+]]] // CHECK: hw.hierpath private @[[XMR2]] [@Top::@foo, @Foo::@[[FOO_X_SYM]]] @@ -804,11 +804,11 @@ firrtl.circuit "RefABI" { out r2: !firrtl.probe>, 3>>) firrtl.strictconnect %ext_in, %in : !firrtl.uint<1> - // CHECK: %[[XMR_R:.+]] = firrtl.xmr.deref @xmrPath, ".`ref_RefExtMore_RefExtMore_r" : !firrtl.uint<1> + // CHECK: %[[XMR_R:.+]] = firrtl.xmr.deref @xmrPath, ".`ref_RefExtMore_r" : !firrtl.uint<1> // CHECK: %node_r = firrtl.node %[[XMR_R]] %read_r = firrtl.ref.resolve %ext_r : !firrtl.probe> %node_r = firrtl.node %read_r : !firrtl.uint<1> - // CHECK: %[[XMR_R2:.+]] = firrtl.xmr.deref @xmrPath, ".`ref_RefExtMore_RefExtMore_r2" : !firrtl.vector>, 3> + // CHECK: %[[XMR_R2:.+]] = firrtl.xmr.deref @xmrPath, ".`ref_RefExtMore_r2" : !firrtl.vector>, 3> // CHECK: %node_r2 = firrtl.node %[[XMR_R2]] %read_r2 = firrtl.ref.resolve %ext_r2 : !firrtl.probe>, 3>> %node_r2 = firrtl.node %read_r2 : !firrtl.vector>, 3> @@ -846,13 +846,13 @@ firrtl.circuit "BasicRefSub" { // CHECK-LABEL: circuit "RWProbe_field" firrtl.circuit "RWProbe_field" { // CHECK: hw.hierpath private @[[XMRPATH:.+]] [@RWProbe_field::@[[SYM:[^,]+]]] - // CHECK-NEXT: sv.macro.decl @ref_RWProbe_field_RWProbe_field_rw + // CHECK-NEXT: sv.macro.decl @ref_RWProbe_field_rw // e.g., "n[0]" - // CHECK-NEXT{LITERAL}: sv.macro.def @ref_RWProbe_field_RWProbe_field_rw "{{0}}[0]" + // CHECK-NEXT{LITERAL}: sv.macro.def @ref_RWProbe_field_rw "{{0}}[0]" // CHECK-SAME: ([@[[XMRPATH]]]) - // CHECK-NEXT: sv.macro.decl @ref_RWProbe_field_RWProbe_field_rw_narrow + // CHECK-NEXT: sv.macro.decl @ref_RWProbe_field_rw_narrow // e.g., "n[0].a" - // CHECK-NEXT{LITERAL}: sv.macro.def @ref_RWProbe_field_RWProbe_field_rw_narrow "{{0}}[0].a" + // CHECK-NEXT{LITERAL}: sv.macro.def @ref_RWProbe_field_rw_narrow "{{0}}[0].a" // CHECK-SAME: ([@[[XMRPATH]]]) firrtl.module @RWProbe_field(in %x: !firrtl.vector>, 2>, out %rw: !firrtl.rwprobe>>, out %rw_narrow : !firrtl.rwprobe>) { %n, %n_ref = firrtl.node %x forceable : !firrtl.vector>, 2> @@ -869,8 +869,8 @@ firrtl.circuit "RWProbe_field" { // CHECK-LABEL: circuit "RefSubLayers" firrtl.circuit "RefSubLayers" { // CHECK: hw.hierpath private @[[XMRPATH:.+]] [@RefSubLayers::@[[TOP_SYM:[^,]+]], @Mid::@[[MID_SYM:[^,]+]], @Leaf::@[[LEAF_SYM:.+]]] - // CHECK-NEXT: sv.macro.decl @ref_RefSubLayers_RefSubLayers_rw - // CHECK-NEXT{LITERAL}: sv.macro.def @ref_RefSubLayers_RefSubLayers_rw "{{0}}.`ref_ExtRef_ExtRef_out.b[1].a" + // CHECK-NEXT: sv.macro.decl @ref_RefSubLayers_rw + // CHECK-NEXT{LITERAL}: sv.macro.def @ref_RefSubLayers_rw "{{0}}.`ref_ExtRef_out.b[1].a" // CHECK-SAME: ([@[[XMRPATH]]]) firrtl.extmodule @ExtRef(out out: !firrtl.probe, b: vector, b: uint<1>>, 2>>>) firrtl.module @RefSubLayers(out %rw : !firrtl.probe>) { @@ -939,7 +939,7 @@ firrtl.circuit "RefSubZeroWidth" { firrtl.circuit "RWProbePort" { // CHECK: hw.hierpath private @[[XMRPATH:.+]] [@RWProbePort::@target] - // CHECK{LITERAL}: sv.macro.def @ref_RWProbePort_RWProbePort_p "{{0}}" + // CHECK{LITERAL}: sv.macro.def @ref_RWProbePort_p "{{0}}" // CHECK-SAME: ([@[[XMRPATH]]]) // CHECK: module @RWProbePort( // CHECK-NOT: firrtl.ref.rwprobe @@ -957,15 +957,15 @@ firrtl.circuit "RWProbePort" { // CHECK-LABEL: circuit "RefSubOutputPort" firrtl.circuit "RefSubOutputPort" { // CHECK: hw.hierpath private @[[XMRPATH:.+]] [@RefSubOutputPort::@[[CHILD_SYM:.+]], @Child::@[[WIRE_SYM:.+]]] - // CHECK: sv.macro.def @ref_RefSubOutputPort_RefSubOutputPort_outVec + // CHECK: sv.macro.def @ref_RefSubOutputPort_outVec // CHECK-SAME{LITERAL}: "{{0}}.x" - // CHECK-SAME: ([@[[XMRPATH]]]) {output_file = #hw.output_file<"ref_RefSubOutputPort_RefSubOutputPort.sv">} - // CHECK: sv.macro.def @ref_RefSubOutputPort_RefSubOutputPort_outElem + // CHECK-SAME: ([@[[XMRPATH]]]) {output_file = #hw.output_file<"ref_RefSubOutputPort.sv">} + // CHECK: sv.macro.def @ref_RefSubOutputPort_outElem // CHECK-SAME{LITERAL}: "{{0}}.x[1]" - // CHECK-SAME: ([@[[XMRPATH]]]) {output_file = #hw.output_file<"ref_RefSubOutputPort_RefSubOutputPort.sv">} - // CHECK: sv.macro.def @ref_RefSubOutputPort_RefSubOutputPort_outElemDirect + // CHECK-SAME: ([@[[XMRPATH]]]) {output_file = #hw.output_file<"ref_RefSubOutputPort.sv">} + // CHECK: sv.macro.def @ref_RefSubOutputPort_outElemDirect // CHECK-SAME{LITERAL}: "{{0}}.x[1]" - // CHECK-SAME: ([@[[XMRPATH]]]) {output_file = #hw.output_file<"ref_RefSubOutputPort_RefSubOutputPort.sv">} + // CHECK-SAME: ([@[[XMRPATH]]]) {output_file = #hw.output_file<"ref_RefSubOutputPort.sv">} // CHECK: module private @Child // CHECK-NEXT: firrtl.wire sym @[[WIRE_SYM]] forceable diff --git a/test/Dialect/FIRRTL/parse-basic.fir b/test/Dialect/FIRRTL/parse-basic.fir index fd2aaa44fa98..803827026339 100644 --- a/test/Dialect/FIRRTL/parse-basic.fir +++ b/test/Dialect/FIRRTL/parse-basic.fir @@ -308,7 +308,7 @@ circuit MyModule : ; CHECK: firrtl.circuit "MyModule" { node n4 = bits(i8, 4, 2) ; CHECK: firrtl.shl %i8, 4 : (!firrtl.uint<8>) -> !firrtl.uint<12> - ; CHECK: firrtl.shr %i8, 8 : (!firrtl.uint<8>) -> !firrtl.uint<1> + ; CHECK: firrtl.shr %i8, 8 : (!firrtl.uint<8>) -> !firrtl.uint<0> node n5 = or(shl(i8, 4), shr(i8, 8)) ; CHECK: firrtl.dshl %i8, %{{.*}} : (!firrtl.uint<8>, !firrtl.const.uint<4>) -> !firrtl.uint<23> @@ -436,10 +436,14 @@ circuit MyModule : ; CHECK: firrtl.circuit "MyModule" { assert(clock, pred, en, "X equals Y when Z is valid") ; CHECK: firrtl.assert %clock, %pred, %en, "X equals Y when Z is valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false, name = "assert_0"} assert(clock, pred, en, "X equals Y when Z is valid") : assert_0 + ; CHECK: firrtl.assert %clock, %pred, %en, "pred=%d, en=%d"(%pred, %en) : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false, name = "assert_1"} + assert(clock, pred, en, "pred=%d, en=%d", pred, en) : assert_1 ; CHECK: firrtl.assume %clock, %pred, %en, "X equals Y when Z is valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false} assume(clock, pred, en, "X equals Y when Z is valid") ; CHECK: firrtl.assume %clock, %pred, %en, "X equals Y when Z is valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false, name = "assume_0"} assume(clock, pred, en, "X equals Y when Z is valid") : assume_0 + ; CHECK: firrtl.assume %clock, %pred, %en, "pred=%d, en=%d"(%pred, %en) : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false, name = "assume_1"} + assume(clock, pred, en, "pred=%d, en=%d", pred, en) : assume_1 ; CHECK: firrtl.cover %clock, %pred, %en, "X equals Y when Z is valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false} cover(clock, pred, en, "X equals Y when Z is valid") ; CHECK: firrtl.cover %clock, %pred, %en, "X equals Y when Z is valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false, name = "cover_0"} @@ -483,7 +487,7 @@ circuit MyModule : ; CHECK: firrtl.circuit "MyModule" { ; CHECK-LABEL: firrtl.module private @oversize_shift( module oversize_shift : wire value : UInt<2> - ; CHECK: firrtl.shr %value, 5 : (!firrtl.uint<2>) -> !firrtl.uint<1> + ; CHECK: firrtl.shr %value, 5 : (!firrtl.uint<2>) -> !firrtl.uint<0> node n = shr(value, 5) ; CHECK-LABEL: firrtl.module private @when_else_ambiguity( @@ -1045,7 +1049,7 @@ circuit MyModule : ; CHECK: firrtl.circuit "MyModule" { stop(clock, enable, 1) ; CHECK: [[CONDINV:%.+]] = firrtl.not %cond ; CHECK: [[TMP1:%.+]] = firrtl.xorr [[CONDINV]] - ; CHECK-NEXT: [[TMP2:%.+]] = firrtl.verbatim.expr "{{[{][{]0[}][}]}} === 1'bx"([[TMP1]]) + ; CHECK-NEXT: [[TMP2:%.+]] = firrtl.int.isX [[TMP1]] ; CHECK-NEXT: [[TMP:%.+]] = firrtl.or [[CONDINV]], [[TMP2]] ; CHECK-NEXT: firrtl.assert %clock, [[TMP]], %enable, "Hello Assert"(%value) : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<42> ; CHECK-SAME: format = "sva" @@ -1069,7 +1073,7 @@ circuit MyModule : ; CHECK: firrtl.circuit "MyModule" { stop(clock, enable, 1) ; CHECK: [[CONDINV:%.+]] = firrtl.not %cond ; CHECK: [[TMP1:%.+]] = firrtl.xorr [[CONDINV]] - ; CHECK-NEXT: [[TMP2:%.+]] = firrtl.verbatim.expr "{{[{][{]0[}][}]}} === 1'bx"([[TMP1]]) + ; CHECK-NEXT: [[TMP2:%.+]] = firrtl.int.isX [[TMP1]] ; CHECK-NEXT: [[TMP:%.+]] = firrtl.or [[CONDINV]], [[TMP2]] ; CHECK-NEXT: firrtl.assume %clock, [[TMP]], %enable, "Hello Assume"(%value) : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.uint<42> ; CHECK-SAME: guards = ["USE_UNR_ONLY_CONSTRAINTS", "USE_FORMAL_ONLY_CONSTRAINTS"] @@ -1153,8 +1157,12 @@ circuit MyModule : ; CHECK: firrtl.circuit "MyModule" { ; CHECK: %c0_ui8 = firrtl.constant 0 : !firrtl.const.uint<8> ; CHECK: %0 = firrtl.enumcreate Some(%c0_ui8) : (!firrtl.const.uint<8>) -> !firrtl.enum, None: uint<0>> - ; CHECK: %n = firrtl.node interesting_name %0 : !firrtl.enum, None: uint<0>> - node n = {|Some : UInt<8>, None|}(Some, UInt<8>(0)) + ; CHECK: %some = firrtl.node interesting_name %0 : !firrtl.enum, None: uint<0>> + node some = {|Some : UInt<8>, None|}(Some, UInt<8>(0)) + ; CHECK: %c0_ui0 = firrtl.constant 0 : !firrtl.const.uint<0> + ; CHECK: %1 = firrtl.enumcreate None(%c0_ui0) : (!firrtl.const.uint<0>) -> !firrtl.enum, None: uint<0>> + ; CHECK: %none = firrtl.node {{.*}} %1 : !firrtl.enum, None: uint<0>> + node none = {|Some : UInt<8>, None|}(None) ; CHECK: firrtl.match %i : !firrtl.enum, None: uint<0>> { match i: @@ -1671,9 +1679,13 @@ circuit Layers: ; CHECK-NEXT: } ; CHECK-NEXT: } - ; CHECK: firrtl.module @Layers + ; CHECK: firrtl.module @Layers + ; CHECK-SAME: out %b: !firrtl.probe, @A> + ; CHECK-SAME: out %c: !firrtl.rwprobe, @A::@B> module Layers: input a: UInt<1> + output b: Probe, A> + output c: RWProbe, A.B> layerblock A: node A_a = a @@ -1841,6 +1853,30 @@ circuit BasicProps : ; CHECK-NEXT: firrtl.propassign %nested, %[[NESTED]] propassign nested, List>(List(), List(String("test")), List()) +;// ----- +FIRRTL version 4.0.0 + +; CHECK-LABEL: firrtl.circuit "IntegerArithmetic" +circuit IntegerArithmetic : + module IntegerArithmetic : + input a : Integer + input b : Integer + output c : Integer + output d : Integer + output e : Integer + + ; CHECK: [[C:%.+]] = firrtl.integer.add %a, %b + ; CHECK: firrtl.propassign %c, [[C]] + propassign c, integer_add(a, b) + + ; CHECK: [[D:%.+]] = firrtl.integer.mul %a, %b + ; CHECK: firrtl.propassign %d, [[D]] + propassign d, integer_mul(a, b) + + ; CHECK: [[E:%.+]] = firrtl.integer.shr %a, %b + ; CHECK: firrtl.propassign %e, [[E]] + propassign e, integer_shr(a, b) + ;// ----- FIRRTL version 3.1.0 @@ -1908,3 +1944,139 @@ circuit PublicModules: module Bar: ; CHECK: firrtl.module @PublicModules public module PublicModules: + +;// ----- + +FIRRTL version 4.0.0 +; CHECK-LABEL: firrtl.circuit "LayerEnabledModule" +circuit LayerEnabledModule: + layer A, bind: + layer B, bind: + layer C, bind: + ; CHECK: firrtl.module @LayerEnabledModule + ; CHECK-SAME: layers = [@A, @B::@C] + module LayerEnabledModule enablelayer A enablelayer B.C: + + ; CHECK: firrtl.module private @UserOfLayerEnabledModule + ; CHECK-SAME: layers = [@A, @B::@C] + module UserOfLayerEnabledModule enablelayer A enablelayer B.C: + ; CHECK: firrtl.instance i interesting_name {layers = [@A, @B::@C]} @LayerEnabledModule() + inst i of LayerEnabledModule + +;// ----- + +FIRRTL version 3.3.0 +; CHECK-LABEL: circuit "StaticShiftRight" +circuit StaticShiftRight: + ; CHECK: firrtl.module @StaticShiftRight + module StaticShiftRight: + input a : UInt<8> + input b : UInt<0> + input c : SInt<8> + input d : SInt<0> + + wire w : UInt + connect w, a + + wire x : SInt + connect x, c + + ; CHECK: %0 = firrtl.shr %a, 1 + ; CHECK: %1 = firrtl.pad %0, 1 + ; CHECK: %a_1 = firrtl.node {{.*}} %1 : !firrtl.uint<7> + node a_1 = shr(a, 1) + ; CHECK: %2 = firrtl.shr %a, 8 + ; CHECK: %3 = firrtl.pad %2, 1 + ; CHECK: %a_2 = firrtl.node {{.*}} %3 : !firrtl.uint<1> + node a_2 = shr(a, 8) + ; CHECK: %4 = firrtl.shr %a, 10 + ; CHECK: %5 = firrtl.pad %4, 1 + ; CHECK: %a_3 = firrtl.node {{.*}} %5 : !firrtl.uint<1> + node a_3 = shr(a, 10) + ; CHECK: %6 = firrtl.shr %b, 0 + ; CHECK: %7 = firrtl.pad %6, 1 + ; CHECK: %b_1 = firrtl.node {{.*}} %7 : !firrtl.uint<1> + node b_1 = shr(b, 0) + ; CHECK: %8 = firrtl.shr %b, 1 + ; CHECK: %9 = firrtl.pad %8, 1 + ; CHECK: %b_2 = firrtl.node {{.*}} %9 : !firrtl.uint<1> + node b_2 = shr(b, 1) + ; CHECK: %10 = firrtl.shr %w, 10 + ; CHECK: %11 = firrtl.pad %10, 1 + ; CHECK: %w_1 = firrtl.node {{.*}} %11 : !firrtl.uint + node w_1 = shr(w, 10) + + ; CHECK: %12 = firrtl.shr %c, 1 + ; CHECK: %c_1 = firrtl.node {{.*}} %12 : !firrtl.sint<7> + node c_1 = shr(c, 1) + ; CHECK: %13 = firrtl.shr %c, 8 + ; CHECK: %c_2 = firrtl.node {{.*}} %13 : !firrtl.sint<1> + node c_2 = shr(c, 8) + ; CHECK: %14 = firrtl.shr %c, 10 + ; CHECK: %c_3 = firrtl.node {{.*}} %14 : !firrtl.sint<1> + node c_3 = shr(c, 10) + ; CHECK: %15 = firrtl.shr %d, 0 + ; CHECK: %d_1 = firrtl.node {{.*}} %15 : !firrtl.sint<1> + node d_1 = shr(d, 0) + ; CHECK: %16 = firrtl.shr %d, 1 + ; CHECK: %d_2 = firrtl.node {{.*}} %16 : !firrtl.sint<1> + node d_2 = shr(d, 1) + ; CHECK: %17 = firrtl.shr %x, 10 + ; CHECK: %x_1 = firrtl.node {{.*}} %17 : !firrtl.sint + node x_1 = shr(x, 10) + +;// ----- + +FIRRTL version 4.0.0 +; CHECK-LABEL: circuit "StaticShiftRight" +circuit StaticShiftRight: + ; CHECK: firrtl.module @StaticShiftRight + module StaticShiftRight: + input a : UInt<8> + input b : UInt<0> + input c : SInt<8> + input d : SInt<0> + + wire w : UInt + connect w, a + + wire x : SInt + connect x, c + + ; CHECK: %0 = firrtl.shr %a, 1 + ; CHECK: %a_1 = firrtl.node {{.*}} %0 : !firrtl.uint<7> + node a_1 = shr(a, 1) + ; CHECK: %1 = firrtl.shr %a, 8 + ; CHECK: %a_2 = firrtl.node {{.*}} %1 : !firrtl.uint<0> + node a_2 = shr(a, 8) + ; CHECK: %2 = firrtl.shr %a, 10 + ; CHECK: %a_3 = firrtl.node {{.*}} %2 : !firrtl.uint<0> + node a_3 = shr(a, 10) + ; CHECK: %3 = firrtl.shr %b, 0 + ; CHECK: %b_1 = firrtl.node {{.*}} %3 : !firrtl.uint<0> + node b_1 = shr(b, 0) + ; CHECK: %4 = firrtl.shr %b, 1 + ; CHECK: %b_2 = firrtl.node {{.*}} %4 : !firrtl.uint<0> + node b_2 = shr(b, 1) + ; CHECK: %5 = firrtl.shr %w, 10 + ; CHECK: %w_1 = firrtl.node {{.*}} %5 : !firrtl.uint + node w_1 = shr(w, 10) + + ; CHECK: %6 = firrtl.shr %c, 1 + ; CHECK: %c_1 = firrtl.node {{.*}} %6 : !firrtl.sint<7> + node c_1 = shr(c, 1) + ; CHECK: %7 = firrtl.shr %c, 8 + ; CHECK: %c_2 = firrtl.node {{.*}} %7 : !firrtl.sint<1> + node c_2 = shr(c, 8) + ; CHECK: %8 = firrtl.shr %c, 10 + ; CHECK: %c_3 = firrtl.node {{.*}} %8 : !firrtl.sint<1> + node c_3 = shr(c, 10) + ; CHECK: %9 = firrtl.shr %d, 0 + ; CHECK: %d_1 = firrtl.node {{.*}} %9 : !firrtl.sint<1> + node d_1 = shr(d, 0) + ; CHECK: %10 = firrtl.shr %d, 1 + ; CHECK: %d_2 = firrtl.node {{.*}} %10 : !firrtl.sint<1> + node d_2 = shr(d, 1) + ; CHECK: %11 = firrtl.shr %x, 10 + ; CHECK: %x_1 = firrtl.node {{.*}} %11 : !firrtl.sint + node x_1 = shr(x, 10) diff --git a/test/Dialect/FIRRTL/parse-errors.fir b/test/Dialect/FIRRTL/parse-errors.fir index 5e63b774cd48..5c67e4707a5b 100644 --- a/test/Dialect/FIRRTL/parse-errors.fir +++ b/test/Dialect/FIRRTL/parse-errors.fir @@ -330,7 +330,14 @@ circuit ProbeFlipType: circuit NestedProbes: module NestedProbes: - output p : Probe> ; expected-error {{cannot nest reference types}} + output p : Probe> ; expected-error {{invalid probe inner type, must be base-type}} + +;// ----- +FIRRTL version 3.1.0 + +circuit ProbeOfProp: + module ProbeOfProp: + output p : Probe ; expected-error {{invalid probe inner type, must be base-type}} ;// ----- @@ -862,6 +869,39 @@ circuit Top: output out : Integer propassign out, in +;// ----- +FIRRTL version 3.1.0 + +circuit Top: + module Top: + input a : Integer + input b : Integer + output c : Integer + ; expected-error @below {{Integer arithmetic expressions are a FIRRTL 4.0.0+ feature, but the specified FIRRTL version was 3.1.0}} + propassign c, integer_add(a, b) + +;// ----- +FIRRTL version 3.1.0 + +circuit Top: + module Top: + input a : Integer + input b : Integer + output c : Integer + ; expected-error @below {{Integer arithmetic expressions are a FIRRTL 4.0.0+ feature, but the specified FIRRTL version was 3.1.0}} + propassign c, integer_mul(a, b) + +;// ----- +FIRRTL version 3.1.0 + +circuit Top: + module Top: + input a : Integer + input b : Integer + output c : Integer + ; expected-error @below {{Integer arithmetic expressions are a FIRRTL 4.0.0+ feature, but the specified FIRRTL version was 3.1.0}} + propassign c, integer_shr(a, b) + ;// ----- FIRRTL version 3.3.0 @@ -1246,3 +1286,85 @@ circuit Foo: option Platform: FPGA FPGA + +;// ----- +FIRRTL version 3.1.0 +circuit Foo: + + module Foo: + ; expected-error @below {{colored probes are a FIRRTL 3.2.0+ feature, but the specified FIRRTL version was 3.1.0}} + output a: Probe, A> + +;// ----- +FIRRTL version 4.0.0 +circuit Foo: + layer A bind: + module Foo: + output a: Probe< + ; expected-error @below {{expected probe data type}} +;// ----- +FIRRTL version 4.0.0 +circuit Foo: + layer A bind: + module Foo: + ; expected-error @below {{expected '<' in reference type}} + output a: Probe X + +;// ----- +FIRRTL version 4.0.0 +circuit Foo: + layer A bind: + module Foo: + output a: Probe + ; expected-error @below {{expected '<' in reference type}} +;// ----- +FIRRTL version 4.0.0 +circuit Foo: + layer A bind: + module Foo: + ; expected-error @below {{expected probe data type}} + output a: Probe<> + +;// ----- +FIRRTL version 4.0.0 +circuit Foo: + layer A bind: + module Foo: + ; expected-error @below {{expected '>' to end reference type}} + output a: Probe +;// ----- +FIRRTL version 4.0.0 +circuit Foo: + layer A bind: + module Foo: + ; expected-error @below {{expected '>' to end reference type}} + output a: Probe A + +;// ----- +FIRRTL version 4.0.0 +circuit Foo: + layer A bind: + module Foo: + ; expected-error @below {{expected layer name}} + output a: Probe A.> + +;// ----- +FIRRTL version 4.0.0 +circuit UnknownWidthPublic: + public module UnknownWidthPublic: + ; expected-error @below {{public module port must have known width}} + output a: UInt + +;// ----- +FIRRTL version 4.0.0 +circuit UnknownWidthExt: + extmodule UnknownWidthExt: + ; expected-error @below {{extmodule port must have known width}} + output a: { x: UInt } + +;// ----- +FIRRTL version 4.0.0 +circuit AbstractResetPublic: + module AbstractResetPublic: + ; expected-error @below {{public module port must have concrete reset type}} + input r: Reset diff --git a/test/Dialect/FIRRTL/remove-unused-ports.mlir b/test/Dialect/FIRRTL/remove-unused-ports.mlir index 38a94e490e04..eb8659392636 100644 --- a/test/Dialect/FIRRTL/remove-unused-ports.mlir +++ b/test/Dialect/FIRRTL/remove-unused-ports.mlir @@ -1,7 +1,7 @@ // RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-remove-unused-ports))' %s -split-input-file | FileCheck %s firrtl.circuit "Top" { // CHECK-LABEL: firrtl.module @Top(in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, - // CHECK-SAME : out %d_unused: !firrtl.uint<1>, out %d_invalid: !firrtl.uint<1>, out %d_constant: !firrtl.uint<1>) + // CHECK-SAME: out %d_unused: !firrtl.uint<1>, out %d_invalid: !firrtl.uint<1>, out %d_constant: !firrtl.uint<1>) firrtl.module @Top(in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, out %d_unused: !firrtl.uint<1>, out %d_invalid: !firrtl.uint<1>, out %d_constant: !firrtl.uint<1>) { %A_a, %A_b, %A_c, %A_d_unused, %A_d_invalid, %A_d_constant = firrtl.instance A @UseBar(in a: !firrtl.uint<1>, in b: !firrtl.uint<1>, out c: !firrtl.uint<1>, out d_unused: !firrtl.uint<1>, out d_invalid: !firrtl.uint<1>, out d_constant: !firrtl.uint<1>) @@ -72,7 +72,7 @@ firrtl.circuit "Top" { // Strict connect version. firrtl.circuit "Top" { // CHECK-LABEL: firrtl.module @Top(in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, - // CHECK-SAME : out %d_unused: !firrtl.uint<1>, out %d_invalid: !firrtl.uint<1>, out %d_constant: !firrtl.uint<1>) + // CHECK-SAME: out %d_unused: !firrtl.uint<1>, out %d_invalid: !firrtl.uint<1>, out %d_constant: !firrtl.uint<1>) firrtl.module @Top(in %a: !firrtl.uint<1>, in %b: !firrtl.uint<1>, out %c: !firrtl.uint<1>, out %d_unused: !firrtl.uint<1>, out %d_invalid: !firrtl.uint<1>, out %d_constant: !firrtl.uint<1>) { %A_a, %A_b, %A_c, %A_d_unused, %A_d_invalid, %A_d_constant = firrtl.instance A @UseBar(in a: !firrtl.uint<1>, in b: !firrtl.uint<1>, out c: !firrtl.uint<1>, out d_unused: !firrtl.uint<1>, out d_invalid: !firrtl.uint<1>, out d_constant: !firrtl.uint<1>) diff --git a/test/Dialect/FIRRTL/round-trip.mlir b/test/Dialect/FIRRTL/round-trip.mlir index 721bfe53874c..160356e2137a 100644 --- a/test/Dialect/FIRRTL/round-trip.mlir +++ b/test/Dialect/FIRRTL/round-trip.mlir @@ -23,6 +23,16 @@ firrtl.module @Intrinsics(in %ui : !firrtl.uint, in %clock: !firrtl.clock, in %u %cg1 = firrtl.int.clock_gate %clock, %ui1, %ui1 } +// CHECK-LABEL: firrtl.module @FPGAProbe +firrtl.module @FPGAProbe( + in %clock: !firrtl.clock, + in %reset: !firrtl.uint<1>, + in %in: !firrtl.uint<8> +) { + // CHECK: firrtl.int.fpga_probe %clock, %in : !firrtl.uint<8> + firrtl.int.fpga_probe %clock, %in : !firrtl.uint<8> +} + // CHECK-LABEL: firrtl.option @Platform firrtl.option @Platform { // CHECK:firrtl.option_case @FPGA @@ -44,4 +54,36 @@ firrtl.module @Foo(in %clock: !firrtl.clock) { firrtl.strictconnect %inst_clock, %clock : !firrtl.clock } +firrtl.layer @LayerA bind { + firrtl.layer @LayerB bind {} +} + +// CHECK-LABEL: firrtl.module @Layers +// CHECK-SAME: out %a: !firrtl.probe, @LayerA> +// CHECK-SAME: out %b: !firrtl.rwprobe, @LayerA::@LayerB> +firrtl.module @Layers( + out %a: !firrtl.probe, @LayerA>, + out %b: !firrtl.rwprobe, @LayerA::@LayerB> +) {} + +// CHECK-LABEL: firrtl.module @LayersEnabled +// CHECK-SAME: layers = [@LayerA] +firrtl.module @LayersEnabled() attributes {layers = [@LayerA]} { +} + +// CHECK-LABEL: firrtl.module @PropertyArithmetic +firrtl.module @PropertyArithmetic() { + %0 = firrtl.integer 1 + %1 = firrtl.integer 2 + + // CHECK: firrtl.integer.add %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer + %2 = firrtl.integer.add %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer + + // CHECK: firrtl.integer.mul %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer + %3 = firrtl.integer.mul %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer + + // CHECK: firrtl.integer.shr %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer + %4 = firrtl.integer.shr %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer +} + } diff --git a/test/Dialect/FIRRTL/sfc-compat.mlir b/test/Dialect/FIRRTL/sfc-compat.mlir index 65179d12c97c..5613a140d963 100644 --- a/test/Dialect/FIRRTL/sfc-compat.mlir +++ b/test/Dialect/FIRRTL/sfc-compat.mlir @@ -218,3 +218,19 @@ firrtl.circuit "NonConstantAsyncReset_Aggregate1" { %r0 = firrtl.regreset %clock, %reset, %value : !firrtl.clock, !firrtl.asyncreset, !firrtl.vector, 2>, !firrtl.vector, 2> } } + +// ----- + +// CHECK-LABEL: "WalksNestedRegions" +firrtl.circuit "WalksNestedRegions" { + firrtl.module @WalksNestedRegions(in %a: !firrtl.uint<1>) { + // CHECK: firrtl.when + firrtl.when %a : !firrtl.uint<1> { + // CHECK-NOT: firrtl.invalidvalue + // CHECK-NEXT: %[[zero:[_A-Za-z0-9]+]] = firrtl.constant 0 + %invalid_ui1 = firrtl.invalidvalue : !firrtl.uint<1> + // CHECK-NEXT: %0 = firrtl.node %[[zero]] + %0 = firrtl.node %invalid_ui1 : !firrtl.uint<1> + } + } +} diff --git a/test/Dialect/HW/canonicalization.mlir b/test/Dialect/HW/canonicalization.mlir index 928bbb7f00b5..3c16ae8a3b23 100644 --- a/test/Dialect/HW/canonicalization.mlir +++ b/test/Dialect/HW/canonicalization.mlir @@ -79,8 +79,8 @@ hw.module @mul_cstfold(in %arg0 : i7, out result : i7) { } // CHECK-LABEL: hw.module @div_cstfold(in %arg0 : i7, out result : i7, out a : i7, out b : i7, out c : i7) { -// CHECK-NEXT: %c-3_i7 = hw.constant -3 : i7 // CHECK-NEXT: %c2_i7 = hw.constant 2 : i7 +// CHECK-NEXT: %c-3_i7 = hw.constant -3 : i7 // CHECK-NEXT: hw.output %c2_i7, %arg0, %c-3_i7, %arg0 : i7, i7, i7, i7 hw.module @div_cstfold(in %arg0 : i7, out result : i7, out a: i7, out b: i7, out c: i7) { %c1_i7 = hw.constant 1 : i7 @@ -1114,8 +1114,8 @@ hw.module @struct_extract1(in %a0: i3, in %a1: i5, out r0: i3) { } // CHECK-LABEL: hw.module @struct_extract2 -// CHECK-NEXT: %c3_i7 = hw.constant 3 : i7 // CHECK-NEXT: %c1_i3 = hw.constant 1 : i3 +// CHECK-NEXT: %c3_i7 = hw.constant 3 : i7 // CHECK-NEXT: hw.output %c1_i3, %c3_i7 : i3, i7 hw.module @struct_extract2(out r0: i3, out r1: i7) { %s = hw.aggregate_constant [1 : i3, [3 : i7]] : !hw.struct> @@ -1741,18 +1741,23 @@ hw.module @Wires(in %a: i42) { // Wires should push their name or name hint onto their input when folding. %2 = comb.mul %a, %a : i42 %3 = comb.mul %a, %a : i42 - %4 = comb.mul %a, %a : i42 + %4 = comb.mul %a, %a {sv.namehint = "preserve"} : i42 + %5 = comb.mul %a, %a : i42 %someName1 = hw.wire %2 : i42 - %5 = hw.wire %3 {sv.namehint = "someName2"} : i42 - %someName3 = hw.wire %4 {sv.namehint = "ignoredName"} : i42 + %6 = hw.wire %3 {sv.namehint = "someName2"} : i42 + %7 = hw.wire %4 {sv.namehint = "_ignored"} : i42 + %someName3 = hw.wire %5 {sv.namehint = "someName3"} : i42 hw.instance "names1" @WiresKeep(keep: %someName1: i42) -> () - hw.instance "names2" @WiresKeep(keep: %5: i42) -> () - hw.instance "names3" @WiresKeep(keep: %someName3: i42) -> () + hw.instance "names2" @WiresKeep(keep: %6: i42) -> () + hw.instance "names3" @WiresKeep(keep: %7: i42) -> () + hw.instance "names4" @WiresKeep(keep: %someName3: i42) -> () // CHECK-NEXT: %2 = comb.mul %a, %a {sv.namehint = "someName1"} // CHECK-NEXT: %3 = comb.mul %a, %a {sv.namehint = "someName2"} - // CHECK-NEXT: %4 = comb.mul %a, %a {sv.namehint = "someName3"} + // CHECK-NEXT: %4 = comb.mul %a, %a {sv.namehint = "preserve"} + // CHECK-NEXT: %5 = comb.mul %a, %a {sv.namehint = "someName3"} // CHECK-NEXT: hw.instance "names1" @WiresKeep(keep: %2: i42) // CHECK-NEXT: hw.instance "names2" @WiresKeep(keep: %3: i42) // CHECK-NEXT: hw.instance "names3" @WiresKeep(keep: %4: i42) + // CHECK-NEXT: hw.instance "names4" @WiresKeep(keep: %5: i42) } hw.module.extern @WiresKeep(in %keep: i42) diff --git a/test/Dialect/HW/errors.mlir b/test/Dialect/HW/errors.mlir index ec1f1bdf2e9a..d7e7f5c916f0 100644 --- a/test/Dialect/HW/errors.mlir +++ b/test/Dialect/HW/errors.mlir @@ -1,20 +1,20 @@ // RUN: circt-opt %s -split-input-file -verify-diagnostics -func.func private @test_extract(%arg0: i4) { +hw.module private @test_extract(in %arg0: i4) { // expected-error @+1 {{'comb.extract' op from bit too large for input}} %a = comb.extract %arg0 from 6 : (i4) -> i3 } // ----- -func.func private @test_extract(%arg0: i4) { +hw.module private @test_extract(in %arg0: i4) { // expected-error @+1 {{'comb.extract' op from bit too large for input}} %b = comb.extract %arg0 from 2 : (i4) -> i3 } // ----- -func.func private @test_and() { +hw.module private @test_and() { // expected-error @+1 {{'comb.and' op expected 1 or more operands}} %b = comb.and : i111 } @@ -28,7 +28,7 @@ hw.module @InnerSymVisibility() { // ----- -func.func private @notModule () { +func.func @notModule () { return } @@ -65,12 +65,12 @@ hw.module @A(out "": i1) { } // ----- // expected-error @+1 {{expected non-function type}} -func.func private @arrayDims(%a: !hw.array<3 x 4 x i5>) { } +hw.module private @arrayDims(in %a: !hw.array<3 x 4 x i5>) { } // ----- // expected-error @+1 {{invalid element for hw.inout type}} -func.func private @invalidInout(%arg0: !hw.inout>) { } +hw.module private @invalidInout(in %arg0: !hw.inout>) { } // ----- @@ -498,6 +498,6 @@ builtin.module @Nested { hw.module @Foo () { // expected-error @+1 {{Cannot find module definition 'DoesNotExist'}} - hw.instance_choice "inst" @DoesNotExist () -> () + hw.instance_choice "inst" option "foo" @DoesNotExist () -> () } diff --git a/test/Dialect/HW/flatten-io.mlir b/test/Dialect/HW/flatten-io.mlir index 4cfaf440d290..60a615bf4f73 100644 --- a/test/Dialect/HW/flatten-io.mlir +++ b/test/Dialect/HW/flatten-io.mlir @@ -71,7 +71,7 @@ hw.module @instance_extern2(in %arg0 : i32, in %arg1 : !Struct1, out out : !Stru } // EXTERN-LABEL: hw.module.extern @level1_extern -// EXERN-SAME: (in %arg0 : i32, in %in_a : i1, in %in_b : i2, in %arg1 : i32, out out0 : i32, out out_a : i1, out out_b : i2, out out1 : i32) +// EXTERN-SAME: (in %arg0 : i32, in %in_a : i1, in %in_b : i2, in %arg1 : i32, out out0 : i32, out out_a : i1, out out_b : i2, out out1 : i32) // BASIC-LABEL: hw.module.extern @level1_extern(in %arg0 : i32, in %in : !hw.struct, in %arg1 : i32, out out0 : i32, out out : !hw.struct, out out1 : i32) hw.module.extern @level1_extern(in %arg0 : i32, in %in : !Struct1, in %arg1: i32, out out0 : i32, out out: !Struct1, out out1: i32) diff --git a/test/Dialect/HW/round-trip.mlir b/test/Dialect/HW/round-trip.mlir index 7152496c426a..9b4e058b5f8d 100644 --- a/test/Dialect/HW/round-trip.mlir +++ b/test/Dialect/HW/round-trip.mlir @@ -13,9 +13,9 @@ hw.module private @TargetDefault(in %a: i32, out b: i32) { } hw.module public @top(in %a: i32) { - // CHECK: hw.instance_choice "inst1" sym @inst1 @TargetDefault or @TargetA if "A" or @TargetB if "B"(a: %a: i32) -> (b: i32) - hw.instance_choice "inst1" sym @inst1 @TargetDefault or @TargetA if "A" or @TargetB if "B"(a: %a: i32) -> (b: i32) - // CHECK: hw.instance_choice "inst2" @TargetDefault(a: %a: i32) -> (b: i32) - hw.instance_choice "inst2" @TargetDefault(a: %a: i32) -> (b: i32) + // CHECK: hw.instance_choice "inst1" sym @inst1 option "bar" @TargetDefault or @TargetA if "A" or @TargetB if "B"(a: %a: i32) -> (b: i32) + hw.instance_choice "inst1" sym @inst1 option "bar" @TargetDefault or @TargetA if "A" or @TargetB if "B"(a: %a: i32) -> (b: i32) + // CHECK: hw.instance_choice "inst2" option "baz" @TargetDefault(a: %a: i32) -> (b: i32) + hw.instance_choice "inst2" option "baz" @TargetDefault(a: %a: i32) -> (b: i32) } diff --git a/test/Dialect/HW/verify-irn.mlir b/test/Dialect/HW/verify-irn.mlir index cf249f2f28ba..d3c88dba539d 100644 --- a/test/Dialect/HW/verify-irn.mlir +++ b/test/Dialect/HW/verify-irn.mlir @@ -39,5 +39,5 @@ hw.module @XMRRefD() { %a = sv.wire sym @a : !hw.inout } hw.module @XMRRefOp() { - hw.instance_choice "foo" sym @foo @XMRRefA or @XMRRefB if "B" or @XMRRefC if "C"() -> () + hw.instance_choice "foo" sym @foo option "bar" @XMRRefA or @XMRRefB if "B" or @XMRRefC if "C"() -> () } diff --git a/test/Dialect/Handshake/lower-extmem-esi.mlir b/test/Dialect/Handshake/lower-extmem-esi.mlir index 4da2263f96fb..1516df66c06b 100644 --- a/test/Dialect/Handshake/lower-extmem-esi.mlir +++ b/test/Dialect/Handshake/lower-extmem-esi.mlir @@ -27,10 +27,10 @@ // CHECK-SAME: in %reset : i1, // CHECK-SAME: out out0 : !esi.channel // CHECK-SAME: ) { -//CHECK-NEXT: %bundle, %data = esi.bundle.pack %main.mem_ld0.addr : !esi.bundle<[!esi.channel to "address", !esi.channel from "data"]> -//CHECK-NEXT: esi.service.req.to_server %bundle -> <@mem::@read>(#esi.appid<"load">) : !esi.bundle<[!esi.channel to "address", !esi.channel from "data"]> -//CHECK-NEXT: %bundle_0, %ack = esi.bundle.pack %main.mem_st0 : !esi.bundle<[!esi.channel> to "req", !esi.channel from "ack"]> -//CHECK-NEXT: esi.service.req.to_server %bundle_0 -> <@mem::@write>(#esi.appid<"store">) : !esi.bundle<[!esi.channel> to "req", !esi.channel from "ack"]> +//CHECK-NEXT: [[B0:%.+]] = esi.service.req <@mem::@read>(#esi.appid<"load">) : !esi.bundle<[!esi.channel from "address", !esi.channel to "data"]> +//CHECK-NEXT: %data = esi.bundle.unpack %main.mem_ld0.addr from [[B0]] : !esi.bundle<[!esi.channel from "address", !esi.channel to "data"]> +//CHECK-NEXT: [[B1:%.+]] = esi.service.req <@mem::@write>(#esi.appid<"store">) : !esi.bundle<[!esi.channel> from "req", !esi.channel to "ack"]> +//CHECK-NEXT: %ack = esi.bundle.unpack %main.mem_st0 from [[B1]] : !esi.bundle<[!esi.channel> from "req", !esi.channel to "ack"]> //CHECK-NEXT: %main.out0, %main.mem_ld0.addr, %main.mem_st0 = hw.instance "main" @__main_hw(arg0: %arg0: !esi.channel, arg1: %arg1: !esi.channel, v: %v: !esi.channel, mem_ld0.data: %data: !esi.channel, mem_st0.done: %ack: !esi.channel, argCtrl: %argCtrl: !esi.channel, clock: %clock: !seq.clock, reset: %reset: i1) -> (out0: !esi.channel, mem_ld0.addr: !esi.channel, mem_st0: !esi.channel>) //CHECK-NEXT: hw.output %main.out0 : !esi.channel //CHECK-NEXT: } diff --git a/test/Dialect/Ibis/Transforms/containerize.mlir b/test/Dialect/Ibis/Transforms/containerize.mlir index 35b5ef354f05..cf85cbee26ae 100644 --- a/test/Dialect/Ibis/Transforms/containerize.mlir +++ b/test/Dialect/Ibis/Transforms/containerize.mlir @@ -1,3 +1,5 @@ +// XFAIL: * +// See https://github.com/llvm/circt/issues/6658 // RUN: circt-opt --ibis-containerize %s | FileCheck %s // CHECK-LABEL: ibis.container @A_B diff --git a/test/Dialect/Ibis/Transforms/methods_to_containers.mlir b/test/Dialect/Ibis/Transforms/methods_to_containers.mlir index 4215a64b16bb..358286cd9bcc 100644 --- a/test/Dialect/Ibis/Transforms/methods_to_containers.mlir +++ b/test/Dialect/Ibis/Transforms/methods_to_containers.mlir @@ -1,3 +1,5 @@ +// XFAIL: * +// See https://github.com/llvm/circt/issues/6658 // RUN: circt-opt --pass-pipeline='builtin.module(ibis.class(ibis-convert-methods-to-containers))' %s | FileCheck %s // CHECK-LABEL: ibis.class @ToContainers { diff --git a/test/Dialect/Ibis/round-trip.mlir b/test/Dialect/Ibis/round-trip.mlir index 2873911c2c39..dc2cc2f7bba0 100644 --- a/test/Dialect/Ibis/round-trip.mlir +++ b/test/Dialect/Ibis/round-trip.mlir @@ -1,3 +1,5 @@ +// XFAIL: * +// See https://github.com/llvm/circt/issues/6658 // RUN: circt-opt %s | circt-opt | FileCheck %s // CHECK-LABEL: ibis.class @HighLevel { diff --git a/test/Dialect/LLHD/IR/entity.mlir b/test/Dialect/LLHD/IR/entity.mlir index 24090f6a651a..1ecc91cf7a87 100644 --- a/test/Dialect/LLHD/IR/entity.mlir +++ b/test/Dialect/LLHD/IR/entity.mlir @@ -29,5 +29,5 @@ // CHECK-NEXT: llhd.entity @out_of_names () -> () { "llhd.entity"() ({ ^body: -// CHECK-NEXT : } +// CHECK-NEXT: } }) {sym_name="out_of_names", ins=0, function_type=()->()} : () -> () diff --git a/test/Dialect/LTL/canonicalization.mlir b/test/Dialect/LTL/canonicalization.mlir index 1f2962d07dd9..1f8e1e99a49b 100644 --- a/test/Dialect/LTL/canonicalization.mlir +++ b/test/Dialect/LTL/canonicalization.mlir @@ -6,10 +6,11 @@ func.func private @Prop(%arg0: !ltl.property) // CHECK-LABEL: @DelayFolds func.func @DelayFolds(%arg0: !ltl.sequence) { + // TODO: This can't happen because the fold changes type, need to be able to cast i1 to sequence // delay(s, 0, 0) -> s - // CHECK-NEXT: call @Seq(%arg0) - %0 = ltl.delay %arg0, 0, 0 : !ltl.sequence - call @Seq(%0) : (!ltl.sequence) -> () + // COM: CHECK-NEXT: call @Seq(%arg0) + //%0 = ltl.delay %arg0, 0, 0 : !ltl.sequence + //call @Seq(%0) : (!ltl.sequence) -> () // delay(delay(s, 1), 2) -> delay(s, 3) // CHECK-NEXT: ltl.delay %arg0, 3 : diff --git a/test/Dialect/LoopSchedule/errors.mlir b/test/Dialect/LoopSchedule/errors.mlir index 88b2aa78b5b3..ea0ece448670 100644 --- a/test/Dialect/LoopSchedule/errors.mlir +++ b/test/Dialect/LoopSchedule/errors.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt %s -split-input-file -verify-diagnostics +// RUN: circt-opt %s -split-input-file -verify-diagnostics -allow-unregistered-dialect func.func @combinational_condition() { %c0_i32 = arith.constant 0 : i32 @@ -67,11 +67,11 @@ func.func @only_stages() { func.func @only_stages() { %false = arith.constant 0 : i1 - // expected-error @+1 {{'loopschedule.pipeline' op stages may only contain 'loopschedule.pipeline.stage' or 'loopschedule.terminator' ops, found %1 = "arith.addi"(%arg0, %arg0) : (i1, i1) -> i1}} + // expected-error @+1 {{'loopschedule.pipeline' op stages may only contain 'loopschedule.pipeline.stage' or 'loopschedule.terminator' ops, found "foo"() : () -> ()}} loopschedule.pipeline II = 1 iter_args(%arg0 = %false) : (i1) -> () { loopschedule.register %arg0 : i1 } do { - %0 = arith.addi %arg0, %arg0 : i1 + "foo"() : () -> () loopschedule.terminator iter_args(), results() : () -> () } return diff --git a/test/Dialect/MSFT/constructs.mlir b/test/Dialect/MSFT/constructs.mlir index 9b9d339b299a..215830e7b433 100644 --- a/test/Dialect/MSFT/constructs.mlir +++ b/test/Dialect/MSFT/constructs.mlir @@ -50,18 +50,6 @@ hw.module @PE(in %clk: !seq.clock, in %a: i8, in %b: i8, out sum: i8) { hw.output %sumDelay1 : i8 } -// CHECK-LABEL: hw.module @ChannelExample(in %clk : !seq.clock, in %a : i8, out out : i8) { -// CHECK: [[REG0:%.+]] = msft.constructs.channel %a %clk "chEx"(2) : i8 -// CHECK: hw.output [[REG0]] : i8 -// LOWER-LABEL: hw.module @ChannelExample(in %clk : !seq.clock, in %a : i8, out out : i8) { -// LOWER: %chEx_0 = seq.compreg sym @chEx_0 %a, %clk : i8 -// LOWER: %chEx_1 = seq.compreg sym @chEx_1 %chEx_0, %clk : i8 -// LOWER: hw.output %chEx_1 : i8 -hw.module @ChannelExample (in %clk: !seq.clock, in %a : i8, out out: i8) { - %out = msft.constructs.channel %a %clk "chEx" (2) : i8 - hw.output %out : i8 -} - // CHECK-LABEL: hw.module @foo(in %in0 : i32, in %in1 : i32, in %in2 : i32, in %clk : !seq.clock, out out : i32) { // CHECK: %0 = msft.hlc.linear clock %clk : i32 { // CHECK: %1 = comb.mul %in0, %in1 : i32 diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index db9a82d44017..9d2deb16914f 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -1,21 +1,29 @@ -// RUN: circt-opt %s | circt-opt | FileCheck %s +// RUN: circt-opt %s -verify-diagnostics | circt-opt -verify-diagnostics | FileCheck %s + +// CHECK-LABEL: moore.module @Foo +moore.module @Foo { + // CHECK: moore.instance "foo" @Foo + moore.instance "foo" @Foo () -> () : () -> () +} + +// CHECK-LABEL: moore.module @Bar +moore.module @Bar { +} // CHECK-LABEL: func @Expressions func.func @Expressions(%a: !moore.bit, %b: !moore.logic, %c: !moore.packed>) { - // CHECK: %0 = moore.mir.concat - // CHECK: %1 = moore.mir.concat - %0 = moore.mir.concat %a, %a : (!moore.bit, !moore.bit) -> !moore.packed> - %1 = moore.mir.concat %b, %b : (!moore.logic, !moore.logic) -> !moore.packed> + // CHECK: moore.concat + // CHECK: moore.concat + moore.concat %a, %a : (!moore.bit, !moore.bit) -> !moore.packed> + moore.concat %b, %b : (!moore.logic, !moore.logic) -> !moore.packed> - // CHECK: %2 = moore.mir.shl % - // CHECK: %3 = moore.mir.shl arithmetic % - %2 = moore.mir.shl %b, %a : !moore.logic, !moore.bit - %3 = moore.mir.shl arithmetic %c, %a : !moore.packed>, !moore.bit + // CHECK: moore.shl % + moore.shl %b, %a : !moore.logic, !moore.bit - // CHECK: %4 = moore.mir.shr % - // CHECK: %5 = moore.mir.shr arithmetic % - %4 = moore.mir.shr %b, %a : !moore.logic, !moore.bit - %5 = moore.mir.shr arithmetic %c, %a : !moore.packed>, !moore.bit + // CHECK: moore.shr + // CHECK: moore.ashr + moore.shr %b, %a : !moore.logic, !moore.bit + moore.ashr %c, %a : !moore.packed>, !moore.bit return } diff --git a/test/Dialect/Moore/errors.mlir b/test/Dialect/Moore/errors.mlir new file mode 100644 index 000000000000..4879dc19dd27 --- /dev/null +++ b/test/Dialect/Moore/errors.mlir @@ -0,0 +1,10 @@ +// RUN: circt-opt %s --verify-diagnostics --split-input-file + +func.func @Foo() { + return +} + +moore.module @Bar { + // expected-error @below {{symbol 'Foo' must reference a 'moore.module', but got a 'func.func' instead}} + moore.instance "foo" @Foo () -> () : () -> () +} diff --git a/test/Dialect/OM/round-trip.mlir b/test/Dialect/OM/round-trip.mlir index 6dc385d0286a..f00cc9c9f732 100644 --- a/test/Dialect/OM/round-trip.mlir +++ b/test/Dialect/OM/round-trip.mlir @@ -280,3 +280,18 @@ om.class @Any(%in: !om.class.type<@Empty>) { // CHECK: om.class.field @field, %[[CAST]] om.class.field @field, %0 : !om.any } + +// CHECK-LABEL: @IntegerArithmetic +om.class @IntegerArithmetic() { + %0 = om.constant #om.integer<1 : si3> : !om.integer + %1 = om.constant #om.integer<2 : si3> : !om.integer + + // CHECK: om.integer.add %0, %1 : !om.integer + %2 = om.integer.add %0, %1 : !om.integer + + // CHECK: om.integer.mul %0, %1 : !om.integer + %3 = om.integer.mul %0, %1 : !om.integer + + // CHECK: om.integer.shr %0, %1 : !om.integer + %4 = om.integer.shr %0, %1 : !om.integer +} diff --git a/test/Dialect/Seq/canonicalization.mlir b/test/Dialect/Seq/canonicalization.mlir index 46b38fbfbc26..872dbc6bd336 100644 --- a/test/Dialect/Seq/canonicalization.mlir +++ b/test/Dialect/Seq/canonicalization.mlir @@ -158,8 +158,8 @@ hw.module @FirMem(in %addr : i4, in %clock : !seq.clock, in %data : i42, out out %c0_i3 = hw.constant 0 : i3 %c-1_i3 = hw.constant -1 : i3 - // CHECK: [[CLK_TRUE:%.+]] = seq.const_clock high // CHECK: [[CLK_FALSE:%.+]] = seq.const_clock low + // CHECK: [[CLK_TRUE:%.+]] = seq.const_clock high %clk_false = seq.to_clock %false %clk_true = seq.to_clock %true @@ -219,8 +219,8 @@ hw.module @through_wire(in %clock : i1, out out: i1) { // CHECK-LABEL: @const_clock hw.module @const_clock(out clock_true : !seq.clock, out clock_false : !seq.clock) { - // CHECK: [[CLOCK_FALSE:%.+]] = seq.const_clock low // CHECK: [[CLOCK_TRUE:%.+]] = seq.const_clock high + // CHECK: [[CLOCK_FALSE:%.+]] = seq.const_clock low %true = hw.constant 1 : i1 %clock_true = seq.to_clock %true @@ -238,4 +238,23 @@ hw.module @const_clock_reg(in %clock : !seq.clock, out r_data : !seq.clock) { %0 = seq.const_clock low %1 = seq.firreg %1 clock %0 : !seq.clock hw.output %1 : !seq.clock -} \ No newline at end of file +} + +// CHECK-LABEL: @clock_inv +hw.module @clock_inv(in %clock : !seq.clock, out clock_true : !seq.clock, out clock_false : !seq.clock, out same_clock: !seq.clock) { + %clk_low = seq.const_clock low + %clk_high = seq.const_clock high + + %clk_inv_low = seq.clock_inv %clk_low + %clk_inv_high = seq.clock_inv %clk_high + + %clk_inv = seq.clock_inv %clock + %clk_orig = seq.clock_inv %clk_inv + + + // CHECK: [[CLK_HIGH:%.+]] = seq.const_clock high + // CHECK: [[CLK_LOW:%.+]] = seq.const_clock low + // CHECK: hw.output [[CLK_HIGH]], [[CLK_LOW]], %clock + hw.output %clk_inv_low, %clk_inv_high, %clk_orig : !seq.clock, !seq.clock, !seq.clock + +} diff --git a/test/Dialect/Seq/clock-inv.mlir b/test/Dialect/Seq/clock-inv.mlir new file mode 100644 index 000000000000..27796d8f4887 --- /dev/null +++ b/test/Dialect/Seq/clock-inv.mlir @@ -0,0 +1,17 @@ +// RUN: circt-opt --lower-seq-to-sv %s | FileCheck %s + +// CHECK-LABEL: @clock_inv +hw.module @clock_inv(in %clk_in : !seq.clock, out clk_out : !seq.clock) { + // CHECK: [[INV:%.+]] = comb.xor %clk_in, %true : i1 + // CHECK: hw.output [[INV]] : i1 + %0 = seq.clock_inv %clk_in + hw.output %0 : !seq.clock +} + +// CHECK-LABEL: @clock_inv_with_hint +hw.module @clock_inv_with_hint(in %clk_in : !seq.clock, out clk_out : !seq.clock) { + // CHECK: [[INV:%.+]] = comb.xor %clk_in, %true {sv.namehint = "hint"} : i1 + // CHECK: hw.output [[INV]] : i1 + %0 = seq.clock_inv %clk_in { sv.namehint = "hint" } + hw.output %0 : !seq.clock +} diff --git a/test/Dialect/Seq/clock-type.mlir b/test/Dialect/Seq/clock-type.mlir index fc25500dda07..b3a5c3295d9e 100644 --- a/test/Dialect/Seq/clock-type.mlir +++ b/test/Dialect/Seq/clock-type.mlir @@ -68,7 +68,7 @@ hw.module public @CrossReferences() { // CHECK-LABEL: hw.module @ClockAgg(in %c : !hw.struct, out oc : !hw.struct) // CHECK: [[CLOCK:%.+]] = hw.struct_extract %c["clock"] : !hw.struct // CHECK: [[STRUCT:%.+]] = hw.struct_create ([[CLOCK]]) : !hw.struct -// CHECL: hw.output [[STRUCT]] : !hw.struct +// CHECK: hw.output [[STRUCT]] : !hw.struct hw.module @ClockAgg(in %c: !hw.struct, out oc: !hw.struct) { %clock = hw.struct_extract %c["clock"] : !hw.struct %0 = hw.struct_create (%clock) : !hw.struct @@ -85,3 +85,18 @@ hw.module @ClockArray(in %c: !hw.array<1x!seq.clock>, out oc: !hw.array<1x!seq.c %0 = hw.array_create %clock : !seq.clock hw.output %0 : !hw.array<1x!seq.clock> } + +hw.module.extern private @ClockSource(out clock : !seq.clock) +hw.module.extern private @ClockSink(in %clock : !seq.clock) +hw.module.extern private @WireSink(in %clock : i1) + +// CHECK-LABEL: hw.module @ClockCastUse +hw.module @ClockCastUse() { + // CHECK: hw.instance "" @WireSink(clock: %clk.clock: i1) -> () + // CHECK: hw.instance "" @ClockSink(clock: %clk.clock: i1) -> () + // CHECK: %clk.clock = hw.instance "clk" @ClockSource() -> (clock: i1) + hw.instance "" @WireSink(clock : %wire : i1) -> () + %wire = seq.from_clock %clk {sv.namehint = "X"} + hw.instance "" @ClockSink(clock : %clk : !seq.clock) -> () + %clk = hw.instance "clk" @ClockSource() -> (clock : !seq.clock) +} diff --git a/test/Dialect/Seq/lower-fifo.mlir b/test/Dialect/Seq/lower-fifo.mlir index 45ae94ba98c3..56ce6c245bf9 100644 --- a/test/Dialect/Seq/lower-fifo.mlir +++ b/test/Dialect/Seq/lower-fifo.mlir @@ -5,8 +5,8 @@ // CHECK: hw.module @fifo1(in %[[CLOCK:.*]] : !seq.clock, in %[[VAL_1:.*]] : i1, in %[[VAL_2:.*]] : i32, in %[[VAL_3:.*]] : i1, in %[[VAL_4:.*]] : i1, out out : i32) { -// CHECK: %[[VAL_5:.*]] = hw.constant true // CHECK: %[[VAL_6:.*]] = hw.constant -1 : i2 +// CHECK: %[[VAL_5:.*]] = hw.constant true // CHECK: %[[VAL_7:.*]] = hw.constant -2 : i2 // CHECK: %[[VAL_8:.*]] = hw.constant 1 : i2 // CHECK: %[[VAL_9:.*]] = hw.constant 0 : i2 @@ -49,9 +49,9 @@ hw.module @fifo1(in %clk : !seq.clock, in %rst : i1, in %in : i32, in %rdEn : i1 // CHECK: hw.module @fifo2(in %[[CLOCK:.*]] : !seq.clock, in %[[VAL_1:.*]] : i1, in %[[VAL_2:.*]] : i32, in %[[VAL_3:.*]] : i1, in %[[VAL_4:.*]] : i1, out out : i32, out empty : i1, out full : i1, out almost_empty : i1, out almost_full : i1) { // CHECK: %[[VAL_5:.*]] = hw.constant 2 : i3 -// CHECK: %[[VAL_6:.*]] = hw.constant 0 : i2 -// CHECK: %[[VAL_7:.*]] = hw.constant true // CHECK: %[[VAL_8:.*]] = hw.constant -1 : i3 +// CHECK: %[[VAL_7:.*]] = hw.constant true +// CHECK: %[[VAL_6:.*]] = hw.constant 0 : i2 // CHECK: %[[VAL_9:.*]] = hw.constant 3 : i3 // CHECK: %[[VAL_10:.*]] = hw.constant 1 : i3 // CHECK: %[[VAL_11:.*]] = hw.constant 0 : i3 diff --git a/test/Dialect/Seq/round-trip.mlir b/test/Dialect/Seq/round-trip.mlir index 639bb21db507..8be9ae7c305d 100644 --- a/test/Dialect/Seq/round-trip.mlir +++ b/test/Dialect/Seq/round-trip.mlir @@ -91,4 +91,9 @@ hw.module @clock_const() { %high = seq.const_clock high // CHECK: seq.const_clock low %low = seq.const_clock low -} \ No newline at end of file +} + +hw.module @clock_inv(in %clock: !seq.clock) { + // CHECK: seq.clock_inv %clock + %inv = seq.clock_inv %clock +} diff --git a/test/Dialect/Sim/round-trip.mlir b/test/Dialect/Sim/round-trip.mlir new file mode 100644 index 000000000000..d3854ee8310f --- /dev/null +++ b/test/Dialect/Sim/round-trip.mlir @@ -0,0 +1,18 @@ +// RUN: circt-opt %s -verify-diagnostics | circt-opt -verify-diagnostics | FileCheck %s + + +// CHECK-LABEL: hw.module @plusargs_value +hw.module @plusargs_value() { + // CHECK: sim.plusargs.test "foo" + %0 = sim.plusargs.test "foo" + // CHECK: sim.plusargs.value "bar" : i5 + %1, %2 = sim.plusargs.value "bar" : i5 +} + +// CHECK-LABEL: hw.module @stop_finish +hw.module @stop_finish(in %clock : !seq.clock, in %cond : i1) { + // sim.finish %clock, %cond + sim.finish %clock, %cond + // sim.fatal %clock, %cond + sim.fatal %clock, %cond +} \ No newline at end of file diff --git a/test/circt-verilog/commandline.sv b/test/circt-verilog/commandline.sv new file mode 100644 index 000000000000..32b94be04aaf --- /dev/null +++ b/test/circt-verilog/commandline.sv @@ -0,0 +1,6 @@ +// RUN: circt-verilog -h | FileCheck %s --check-prefix=CHECK-HELP +// RUN: circt-verilog --version | FileCheck %s --check-prefix=CHECK-VERSION +// REQUIRES: slang + +// CHECK-HELP: OVERVIEW: Verilog and SystemVerilog frontend +// CHECK-VERSION: slang version 3. diff --git a/test/circt-verilog/include/other.sv b/test/circt-verilog/include/other.sv index cef493d19ac9..22bebd710776 100644 --- a/test/circt-verilog/include/other.sv +++ b/test/circt-verilog/include/other.sv @@ -1,3 +1,5 @@ +// This file is included from `preprocess.sv`. Empty `RUN` line such that it +// does not get picked up by lit. // RUN: localparam Z = 9001; diff --git a/test/circt-verilog/preprocess-errros.sv b/test/circt-verilog/preprocess-errors.sv similarity index 87% rename from test/circt-verilog/preprocess-errros.sv rename to test/circt-verilog/preprocess-errors.sv index 08eb1cdae525..814345e30ffa 100644 --- a/test/circt-verilog/preprocess-errros.sv +++ b/test/circt-verilog/preprocess-errors.sv @@ -1,4 +1,5 @@ // RUN: circt-verilog %s -E --verify-diagnostics +// REQUIRES: slang // expected-error @below {{could not find or open include file}} `include "unknown.sv" diff --git a/test/circt-verilog/preprocess-multiple-files.sv b/test/circt-verilog/preprocess-multiple-files.sv new file mode 100644 index 000000000000..05d58f051c33 --- /dev/null +++ b/test/circt-verilog/preprocess-multiple-files.sv @@ -0,0 +1,19 @@ +// RUN: split-file %s %t +// RUN: circt-verilog %t/a.sv %t/b.sv -E | FileCheck %s --check-prefixes=CHECK-MULTI-UNIT +// RUN: circt-verilog %t/a.sv %t/b.sv -E --single-unit | FileCheck %s --check-prefixes=CHECK-SINGLE-UNIT +// REQUIRES: slang + +// CHECK-MULTI-UNIT: import hello::undefined; +// CHECK-SINGLE-UNIT: import hello::defined; + +//--- a.sv + +`define HELLO + +//--- b.sv + +`ifdef HELLO +import hello::defined; +`else +import hello::undefined; +`endif diff --git a/test/circt-verilog/preprocess.sv b/test/circt-verilog/preprocess.sv index 1f61385999d1..ffad26a7b4b8 100644 --- a/test/circt-verilog/preprocess.sv +++ b/test/circt-verilog/preprocess.sv @@ -1,4 +1,5 @@ // RUN: circt-verilog %s -E -DBAR=1337 -DHELLO -I%S/include | FileCheck %s +// REQUIRES: slang // CHECK-NOT: define FOO // CHECK: localparam X = 42 diff --git a/test/firtool/chisel-interface.fir b/test/firtool/chisel-interface.fir index c8acf68291dc..d8f183844b43 100644 --- a/test/firtool/chisel-interface.fir +++ b/test/firtool/chisel-interface.fir @@ -5,6 +5,10 @@ ; RUN: firtool %s --disable-output --export-chisel-interface --chisel-interface-out-dir %t ; RUN: FileCheck %s -input-file=%t/Foo.scala +; Test exporting a Chisel interface to the default directory. +; RUN: firtool %s --split-verilog --export-chisel-interface -o %t +; RUN: FileCheck %s -input-file=%t/Foo.scala + ; CHECK-LABEL: // Generated by CIRCT ; CHECK-LABEL: package shelf.foo ; CHECK-LABEL: import chisel3._ diff --git a/test/firtool/chisel_assert.fir b/test/firtool/chisel_assert.fir new file mode 100644 index 000000000000..38834c32392a --- /dev/null +++ b/test/firtool/chisel_assert.fir @@ -0,0 +1,103 @@ +; RUN: firtool %s | FileCheck %s + +FIRRTL version 4.0.0 + +circuit ChiselVerif: + intmodule AssertAssume: + input clock: Clock + input predicate: UInt<1> + input enable: UInt<1> + intrinsic = circt_chisel_assert_assume + + intmodule AssertAssumeFormat: + input clock: Clock + input predicate: UInt<1> + input enable: UInt<1> + input val: UInt<1> + intrinsic = circt_chisel_assert_assume + parameter format = "message: %d" + parameter label = "label for assert with format string" + parameter guards = "MACRO_GUARD;ASDF" + + intmodule IfElseFatalFormat: + input clock: Clock + input predicate: UInt<1> + input enable: UInt<1> + input val: UInt<1> + intrinsic = circt_chisel_ifelsefatal + parameter format = "ief: %d" + ; In normal emission these are unused, but allow them to be set for now. + parameter label = "label for ifelsefatal assert" + parameter guards = "MACRO_GUARD;ASDF" + + intmodule Assume: + input clock: Clock + input predicate: UInt<1> + input enable: UInt<1> + input val: UInt<1> + intrinsic = circt_chisel_assume + parameter format = "text: %d" + parameter label = "label for assume" + + intmodule CoverLabel: + input clock: Clock + input predicate: UInt<1> + input enable: UInt<1> + intrinsic = circt_chisel_cover + parameter label = "label for cover" + + ; CHECK: module ChiselVerif + module ChiselVerif: + input clock: Clock + input cond: UInt<1> + input enable: UInt<1> + + ; CHECK: assert property + ; CHECK-NOT: $error + ; CHECK: PROPERTY_AS_CONSTRAINT + ; CHECK: assume + inst assert of AssertAssume + connect assert.clock, clock + connect assert.predicate, cond + connect assert.enable, enable + + ; CHECK: `ifdef MACRO_GUARD + ; CHECK-NEXT: `ifdef ASDF + ; CHECK: label_for_assert_with_format_string + ; CHECK: assert property + ; CHECK: "message: %d" + ; CHECK: $sampled(cond) + ; CHECK: PROPERTY_AS_CONSTRAINT + ; CHECK: assume + inst assertFormat of AssertAssumeFormat + connect assertFormat.clock, clock + connect assertFormat.predicate, cond + connect assertFormat.enable, enable + connect assertFormat.val, cond + + ; Special if-else-fatal pattern, assert-like. + ; No guards or labels for normal emission flow. + ; CHECK: $error("ief: %d" + ; CHECK: $fatal + inst ief of IfElseFatalFormat + connect ief.clock, clock + connect ief.predicate, cond + connect ief.enable, enable + connect ief.val, enable + + ; CHECK: label_for_assume + ; CHECK: assume property + ; CHECK: "text: %d" + ; CHECK: $sampled(enable) + inst assume of Assume + connect assume.clock, clock + connect assume.predicate, cond + connect assume.enable, enable + connect assume.val, enable + + ; CHECK: label_for_cover + ; CHECK: cover property + inst cover of CoverLabel + connect cover.clock, clock + connect cover.predicate, cond + connect cover.enable, enable diff --git a/test/firtool/classes-dedupe.fir b/test/firtool/classes-dedupe.fir new file mode 100644 index 000000000000..ed4f38897978 --- /dev/null +++ b/test/firtool/classes-dedupe.fir @@ -0,0 +1,116 @@ +; RUN: firtool %s -ir-verilog | FileCheck %s + +FIRRTL version 3.3.0 + +circuit Test : %[[ +{ + "class": "firrtl.transforms.MustDeduplicateAnnotation", + "modules": ["~Test|CPU_1", "~Test|CPU_2"] +} +]] + ; CHECK: hw.hierpath private [[NLA1:@.+]] [@Test::@sym, @CPU_1::[[SYM1:@.+]]] + ; CHECK: hw.hierpath private [[NLA2:@.+]] [@Test::@sym, @CPU_1::[[SYM2:@.+]], @Fetch_1::[[SYM3:@.+]]] + module Test : + input in : UInt<1> + output out_1 : UInt<1> + output out_2 : UInt<1> + output om_out_1 : AnyRef + output om_out_2 : AnyRef + inst cpu_1 of CPU_1 + inst cpu_2 of CPU_2 + connect cpu_1.in, in + connect cpu_2.in, in + connect out_1, cpu_1.out + connect out_2, cpu_2.out + propassign om_out_1, cpu_1.om_out + propassign om_out_2, cpu_2.om_out + + ; CHECK-LABEL: hw.module private @CPU_1 + ; CHECK-SAME: out out : i1 {hw.exportPort = #hw} + module CPU_1 : + input in : UInt<1> + output out : UInt<1> + output om_out : AnyRef + + object om of OM_1 + propassign om_out, om + + ; CHECK: hw.instance "fetch_1" sym [[SYM2]] + inst fetch_1 of Fetch_1 + inst fetch_2 of Fetch_1 + connect fetch_1.in, in + connect fetch_2.in, in + connect out, fetch_1.out + + ; CHECK-NOT: CPU_2 + module CPU_2 : + input in : UInt<1> + output out : UInt<1> + output om_out : AnyRef + + object om of OM_2 + propassign om_out, om + + inst fetch_1 of Fetch_2 + inst fetch_2 of Fetch_2 + connect fetch_1.in, in + connect fetch_2.in, in + connect out, fetch_1.out + + module Fetch_1 : + input in : UInt<1> + output out : UInt<1> + ; CHECK: %foo = sv.wire sym [[SYM3]] + wire foo : UInt<1> + connect foo, in + connect out, foo + + ; CHECK-NOT: Fetch_2 + module Fetch_2 : + input in : UInt<1> + output out : UInt<1> + wire foo : UInt<1> + connect foo, in + connect out, foo + + class Foo_1 : + output out_foo : Integer + propassign out_foo, Integer(1) + + class Foo_2 : + output out_bar : Integer + propassign out_bar, Integer(1) + + ; CHECK-LABEL: om.class @OM_1(%basepath: !om.basepath) + class OM_1 : + output out_1 : Path + output out_2 : Path + output out_foo_1 : Inst + output out_foo_2 : Inst + + object foo_1 of Foo_1 + propassign out_foo_1, foo_1 + + object foo_2 of Foo_2 + propassign out_foo_2, foo_2 + + ; CHECK: om.path_create reference %basepath [[NLA1]] + propassign out_1, path("OMReferenceTarget:~Test|CPU_1>out") + ; CHECK: om.path_create reference %basepath [[NLA2]] + propassign out_2, path("OMReferenceTarget:~Test|CPU_1/fetch_1:Fetch_1>foo") + + ; CHECK-NOT: OM_2 + class OM_2 : + output out_1 : Path + output out_2 : Path + output out_foo_1 : Inst + output out_foo_2 : Inst + + object foo_1 of Foo_1 + propassign out_foo_1, foo_1 + + object foo_2 of Foo_2 + propassign out_foo_2, foo_2 + + propassign out_1, path("OMReferenceTarget:~Test|CPU_2>out") + propassign out_2, path("OMReferenceTarget:~Test|CPU_2/fetch_1:Fetch_2>foo") diff --git a/test/firtool/clocking.fir b/test/firtool/clocking.fir new file mode 100644 index 000000000000..bc7a51d55e48 --- /dev/null +++ b/test/firtool/clocking.fir @@ -0,0 +1,18 @@ +; RUN: firtool %s | FileCheck %s + +circuit Foo: + intmodule ClockInverter: + input in: Clock + output out: Clock + intrinsic = circt_clock_inv + + module Foo: + input clk: Clock + output inverted_clk: Clock + + inst inv of ClockInverter + inv.in <= clk + inverted_clk <= inv.out + + ; CHECK-LABEL: module Foo + ; CHECK: assign inverted_clk = ~clk; diff --git a/test/firtool/export-ref.fir b/test/firtool/export-ref.fir index dcea96746e13..3e9886357923 100644 --- a/test/firtool/export-ref.fir +++ b/test/firtool/export-ref.fir @@ -1,10 +1,11 @@ +; RUN: rm -rf %t ; RUN: firtool %s -split-verilog -o %t -; RUN: cat %t/ref_Top_Top.sv | FileCheck %s +; RUN: cat %t/ref_Top.sv | FileCheck %s -; CHECK: `define ref_Top_Top_direct_probe _GEN{{(_[[0-9]+])?}} -; CHECK-NEXT: `define ref_Top_Top_inner_x_probe inner.x_probe -; CHECK-NEXT: `define ref_Top_Top_inner_y_probe _GEN{{(_[0-9]+])?}} -; CHECK-NEXT: `define ref_Top_Top_keyword_probe _GEN{{(_[[0-9]+])?}} +; CHECK: `define ref_Top_direct_probe _GEN{{(_[[0-9]+])?}} +; CHECK-NEXT: `define ref_Top_inner_x_probe inner.x_probe +; CHECK-NEXT: `define ref_Top_inner_y_probe _GEN{{(_[0-9]+])?}} +; CHECK-NEXT: `define ref_Top_keyword_probe _GEN{{(_[[0-9]+])?}} FIRRTL version 3.0.0 circuit Top: %[[ diff --git a/test/firtool/firtool.mlir b/test/firtool/firtool.mlir index 84b1b803e683..1b1321340ab3 100644 --- a/test/firtool/firtool.mlir +++ b/test/firtool/firtool.mlir @@ -20,16 +20,20 @@ firrtl.circuit "Top" { // MLIR-NEXT: } // VERILOG-LABEL: module Top( -// VERILOG-NEXT : input [7:0] in, -// VERILOG-NEXT : output [7:0] out); -// VERILOG-NEXT : assign out = in; -// VERILOG-NEXT : endmodule +// VERILOG-NEXT: input [7:0] in, +// VERILOG-NEXT: output [7:0] out +// VERILOG-NEXT: ); +// VERILOG-EMPTY: +// VERILOG-NEXT: assign out = in; +// VERILOG-NEXT: endmodule // VERILOG-WITH-MLIR-LABEL: module Top( -// VERILOG-WITH-MLIR-NEXT : input [7:0] in, -// VERILOG-WITH-MLIR-NEXT : output [7:0] out); -// VERILOG-WITH-MLIR-NEXT : assign out = in; -// VERILOG-WITH-MLIR-NEXT : endmodule +// VERILOG-WITH-MLIR-NEXT: input [7:0] in, +// VERILOG-WITH-MLIR-NEXT: output [7:0] out +// VERILOG-WITH-MLIR-NEXT: ); +// VERILOG-WITH-MLIR-EMPTY: +// VERILOG-WITH-MLIR-NEXT: assign out = in; +// VERILOG-WITH-MLIR-NEXT: endmodule // VERILOG-WITH-MLIR-OUT-NOT: sv.verbatim{{.*}}output_file = {{.*}}meta.omir.json diff --git a/test/firtool/import-ref.fir b/test/firtool/import-ref.fir index 0091b738e935..f9ea6e0d2156 100644 --- a/test/firtool/import-ref.fir +++ b/test/firtool/import-ref.fir @@ -16,10 +16,10 @@ circuit TestHarness: dut.clock <= clock ; CHECK: fwrite - ; CHECK-SAME: "%x", TestHarness.dut.`ref_DUT_DUT_read) + ; CHECK-SAME: "%x", TestHarness.dut.`ref_DUT_read) printf(clock, UInt<1>(1) "%x", read(dut.read)) ; CHECK: initial - ; CHECK: force TestHarness.dut.`ref_DUT_DUT_write = 32'hDEADBEEF; + ; CHECK: force TestHarness.dut.`ref_DUT_write = 32'hDEADBEEF; force_initial(dut.write, UInt<32>("hdeadbeef")) ; CHECK: endmodule @@ -47,11 +47,11 @@ circuit DUT: define read = i.read define write = i.write -; CHECK-LABEL: FILE "ref_DUT_DUT.sv" +; CHECK-LABEL: FILE "ref_DUT.sv" ; CHECK-EMPTY: ; CHECK-NEXT: Generated by -; CHECK-NEXT: `define ref_DUT_DUT_read i.`ref_Inner_Inner_read -; CHECK-NEXT: `define ref_DUT_DUT_write i.`ref_Inner_Inner_write +; CHECK-NEXT: `define ref_DUT_read i.`ref_Inner_read +; CHECK-NEXT: `define ref_DUT_write i.`ref_Inner_write ; // ----- @@ -70,8 +70,8 @@ circuit Inner: ; CHECK: reg [31:0] [[REG:.+]]; ; CHECK: wire [31:0] [[REG_READ:.+]] = [[REG]]; -; CHECK-LABEL: FILE "ref_Inner_Inner.sv" +; CHECK-LABEL: FILE "ref_Inner.sv" ; CHECK-EMPTY: ; CHECK-NEXT: Generated by -; CHECK-NEXT: `define ref_Inner_Inner_read [[REG_READ]] -; CHECK-NEXT: `define ref_Inner_Inner_write [[REG]] +; CHECK-NEXT: `define ref_Inner_read [[REG_READ]] +; CHECK-NEXT: `define ref_Inner_write [[REG]] diff --git a/test/firtool/groups.fir b/test/firtool/layers.fir similarity index 63% rename from test/firtool/groups.fir rename to test/firtool/layers.fir index bc6df1a1efc0..403d798ec29a 100644 --- a/test/firtool/groups.fir +++ b/test/firtool/layers.fir @@ -1,6 +1,6 @@ ; RUN: firtool %s | FileCheck %s -FIRRTL version 3.2.0 +FIRRTL version 4.0.0 circuit Foo: %[[ { "class": "firrtl.transforms.DontTouchAnnotation", @@ -11,16 +11,16 @@ circuit Foo: %[[ "target": "~Foo|Foo>y" } ]] - declgroup A, bind: - declgroup B, bind: + layer A, bind: + layer B, bind: module Foo: input in: UInt<1> - group A: + layerblock A: node x = in - group B: + layerblock B: node y = x ; CHECK-LABEL: module Foo_A_B( @@ -35,19 +35,19 @@ circuit Foo: %[[ ; CHECK-NEXT: wire x_probe = x; ; CHECK-NEXT: endmodule -; CHECK-LABEL: FILE "groups_Foo_A_B.sv" -; CHECK: `include "groups_Foo_A.sv" -; CHECK-NEXT: `ifndef groups_Foo_A_B -; CHECK-NEXT: `define groups_Foo_A_B +; CHECK-LABEL: FILE "layers_Foo_A_B.sv" +; CHECK: `include "layers_Foo_A.sv" +; CHECK-NEXT: `ifndef layers_Foo_A_B +; CHECK-NEXT: `define layers_Foo_A_B ; CHECK-NEXT: bind Foo Foo_A_B foo_A_B ( ; CHECK-NEXT: _x (Foo.foo_A.x_probe) ; CHECK-NEXT: ); -; CHECK-NEXT: `endif // groups_Foo_A_B +; CHECK-NEXT: `endif // layers_Foo_A_B -; CHECK-LABEL: FILE "groups_Foo_A.sv" -; CHECK: `ifndef groups_Foo_A -; CHECK-NEXT: `define groups_Foo_A +; CHECK-LABEL: FILE "layers_Foo_A.sv" +; CHECK: `ifndef layers_Foo_A +; CHECK-NEXT: `define layers_Foo_A ; CHECK-NEXT: bind Foo Foo_A foo_A ( ; CHECK-NEXT: ._in (in) ; CHECK-NEXT: ); -; CHECK-NEXT: `endif // groups_Foo_A +; CHECK-NEXT: `endif // layers_Foo_A diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir new file mode 100644 index 000000000000..00ad1be57074 --- /dev/null +++ b/test/firtool/lower-layers.fir @@ -0,0 +1,40 @@ +; RUN: firtool -verilog %s | FileCheck %s + +; This is an end-to-end example of a test-bench (Foo) enabling verification, +; probing into a device-under-test (Bar), and reading from hardware which is +; only present if the verification layer is enabled. + +FIRRTL version 4.0.0 + +circuit Foo: %[[ + {"class": "firrtl.transforms.DontTouchAnnotation", "target": "~Foo|Bar>c"}, + {"class": "firrtl.transforms.DontTouchAnnotation", "target": "~Foo|Foo>d"} +]] + layer Verification, bind: + + ; CHECK: module Bar_Verification(); + ; CHECK: wire c = 1'h0; + ; CHECK: wire c_probe = c; + ; CHECK: endmodule + + ; CHECK: module Bar(); + ; CHECK: endmodule + module Bar: + input a: UInt<1> + output b: Probe, Verification> + + layerblock Verification: + node c = UInt<1>(0) + define b = probe(c) + + ; CHECK: module Foo(); + ; CHECK: wire d = Foo.bar.bar_Verification.c_probe; + ; CHECK: Bar bar (); + ; CHECK: endmodule + public module Foo enablelayer Verification: + + inst bar of Bar + + node d = read(bar.b) + connect bar.a, d + diff --git a/test/firtool/lower-layers2.fir b/test/firtool/lower-layers2.fir new file mode 100644 index 000000000000..4cd549226250 --- /dev/null +++ b/test/firtool/lower-layers2.fir @@ -0,0 +1,106 @@ +; RUN: firtool -verilog -disable-all-randomization %s | FileCheck %s + +; This is an end-to-end example of a test-harness enabling verification, probing +; into a device-under-test, and reading from hardware which is only present if +; the verification layer is enabled. + +FIRRTL version 4.0.0 + +circuit TestHarness: + + layer Verification bind: + + ; CHECK: module DUT_Verification( + ; CHECK: input _clock, + ; CHECK: input [31:0] _a + ; CHECK: ); + ; CHECK: reg [31:0] pc_d; + ; CHECK: wire [31:0] pc_d_probe = pc_d; + ; CHECK: always @(posedge _clock) + ; CHECK: pc_d <= _a; + ; CHECK: endmodule + + ; CHECK: module DUT( + ; CHECK: input clock, + ; CHECK: input [31:0] a, + ; CHECK: output [31:0] b + ; CHECK: ); + ; CHECK: reg [31:0] pc; + ; CHECK: always @(posedge clock) + ; CHECK: pc <= a; + ; CHECK: assign b = pc; + ; CHECK: endmodule + module DUT: + input clock: Clock + input reset: UInt<1> + input a: UInt<32> + output b: UInt<32> + output trace: Probe, Verification> + + reg pc: UInt<32>, clock + connect pc, a + connect b, pc + + wire x : Probe, Verification> + + layerblock Verification: + reg pc_d: UInt<32>, clock + connect pc_d, a + define x = probe(pc_d) + + layerblock Verification: + define trace = x + + ; CHECK: module TestHarness_Verification( + ; CHECK: input [31:0] _dut_trace, + ; CHECK: input _clock, + ; CHECK: _reset + ; CHECK: ); + ; CHECK: `ifndef SYNTHESIS + ; CHECK: always @(posedge _clock) begin + ; CHECK: if ((`PRINTF_COND_) & _reset) + ; CHECK: $fwrite(32'h80000002, "The last PC was: %x", _dut_trace); + ; CHECK: end // always @(posedge) + ; CHECK: `endif // not def SYNTHESIS + ; CHECK: endmodule + + ; CHECK: module TestHarness( + ; CHECK: input clock, + ; CHECK: reset, + ; CHECK: input [31:0] a, + ; CHECK: output [31:0] b + ; CHECK: ); + ; CHECK: DUT dut ( + ; CHECK: .clock (clock), + ; CHECK: .a (a), + ; CHECK: .b (b) + ; CHECK: ); + ; CHECK: endmodule + module TestHarness: + input clock: Clock + input reset: UInt<1> + input a: UInt<32> + output b: UInt<32> + + inst dut of DUT + connect dut.clock, clock + connect dut.reset, reset + connect dut.a, a + connect b, dut.b + + layerblock Verification: + printf(clock, reset, "The last PC was: %x", read(dut.trace)) + +; CHECK: // ----- 8< ----- FILE "layers_TestHarness_Verification.sv" ----- 8< ----- +; CHECK: `ifndef layers_TestHarness_Verification +; CHECK: `define layers_TestHarness_Verification +; CHECK: bind DUT DUT_Verification dUT_Verification ( +; CHECK: ._clock (clock), +; CHECK: ._a (a) +; CHECK: ); +; CHECK: bind TestHarness TestHarness_Verification testHarness_Verification ( +; CHECK: ._dut_trace (TestHarness.dut.dUT_Verification.pc_d_probe), +; CHECK: ._clock (clock), +; CHECK: ._reset (reset) +; CHECK: ); +; CHECK: `endif // layers_TestHarness_Verification diff --git a/test/firtool/plusargs.fir b/test/firtool/plusargs.fir new file mode 100644 index 000000000000..cc4a9d67b502 --- /dev/null +++ b/test/firtool/plusargs.fir @@ -0,0 +1,56 @@ +; RUN: firtool %s --format=fir --ir-sv | FileCheck %s + +circuit PlusArgTest: + intmodule PlusArgFooTest : + output found : UInt<1> + intrinsic = circt_plusargs_test + parameter FORMAT = "foo" + + intmodule PlusArgBarValue : + output found : UInt<1> + output result : UInt<32> + intrinsic = circt_plusargs_value + parameter FORMAT = "foo=%d" + + + ; CHECK-LABEL: @PlusArgTest + module PlusArgTest : + output foo_found : UInt<1> + output bar_found : UInt<1> + output bar_result : UInt<32> + + + inst foo of PlusArgFooTest + foo_found <= foo.found + + inst bar of PlusArgBarValue + bar_found <= bar.found + bar_result <= bar.result + + + ; CHECK: [[FORMAT_FOO:%.+]] = sv.constantStr "foo" + ; CHECK-NEXT: [[FOUND_FOO_REG:%.+]] = sv.reg : !hw.inout + ; CHECK-NEXT: sv.initial { + ; CHECK-NEXT: [[FOUND_FOO_VAL:%.+]] = sv.system "test$plusargs"([[FORMAT_FOO]]) : (!hw.string) -> i1 + ; CHECK-NEXT: sv.bpassign [[FOUND_FOO_REG]], [[FOUND_FOO_VAL]] : i1 + ; CHECK-NEXT: } + ; CHECK-NEXT: [[FOUND_FOO:%.+]] = sv.read_inout [[FOUND_FOO_REG]] : !hw.inout + + ; CHECK: [[RESULT_BAR_REG:%.+]] = sv.reg : !hw.inout + ; CHECK-NEXT: [[FOUND_BAR_REG:%.+]] = sv.reg : !hw.inout + ; CHECK-NEXT: sv.ifdef "SYNTHESIS" { + ; CHECK-NEXT: %z_i32 = sv.constantZ : i32 + ; CHECK-NEXT: sv.assign [[RESULT_BAR_REG]], %z_i32 {sv.attributes = [#sv.attribute<"This dummy assignment exists to avoid undriven lint warnings (e.g., Verilator UNDRIVEN).", emitAsComment>]} : i32 + ; CHECK-NEXT: sv.assign [[FOUND_BAR_REG]], %false : i1 + ; CHECK-NEXT: } else { + ; CHECK-NEXT: sv.initial { + ; CHECK-NEXT: [[FORMAT_BAR:%.+]] = sv.constantStr "foo=%d" + ; CHECK-NEXT: [[TMP_BAR:%.+]] = sv.system "value$plusargs"([[FORMAT_BAR]], [[RESULT_BAR_REG]]) : (!hw.string, !hw.inout) -> i32 + ; CHECK-NEXT: [[FOUND_BAR_VAL:%.+]] = comb.icmp bin ne [[TMP_BAR]], %c0_i32 : i32 + ; CHECK-NEXT: sv.bpassign [[FOUND_BAR_REG]], [[FOUND_BAR_VAL]] : i1 + ; CHECK-NEXT: } + ; CHECK-NEXT: } + ; CHECK-NEXT: [[FOUND_BAR:%.+]] = sv.read_inout [[FOUND_BAR_REG]] : !hw.inout + ; CHECK-NEXT: [[RESULT_BAR:%.+]] = sv.read_inout [[RESULT_BAR_REG]] : !hw.inout + + ; CHECK: [[FOUND_FOO]], [[FOUND_BAR]], [[RESULT_BAR]] : i1, i1, i32 diff --git a/test/firtool/stop.fir b/test/firtool/stop.fir new file mode 100644 index 000000000000..1bb933807353 --- /dev/null +++ b/test/firtool/stop.fir @@ -0,0 +1,30 @@ +; RUN: firtool %s --format=fir --ir-sv | FileCheck %s + +; CHECK: sv.ifdef "STOP_COND_" { +; CHECK: } else { +; CHECK: sv.ifdef "STOP_COND" { +; CHECK: sv.macro.def @STOP_COND_ "(`STOP_COND)" +; CHECK: } else { +; CHECK: sv.macro.def @STOP_COND_ "1" +; CHECK: } +; CHECK: } + +circuit StopAndFinishTest: + module StopAndFinishTest : + input clock : Clock + input cond : UInt<1> + + ; CHECK: [[STOP_COND:%.+]] = sv.macro.ref @STOP_COND_() : () -> i1 + ; CHECK: [[COND:%.+]] = comb.and bin [[STOP_COND:%.+]], %cond : i1 + ; CHECK: sv.ifdef "SYNTHESIS" { + ; CHECK: } else { + ; CHECK: sv.always posedge %clock { + ; CHECK: sv.if [[COND]] { + ; CHECK: sv.finish 1 + ; CHECK: sv.fatal 1 + ; CHECK: } + ; CHECK: } + ; CHECK: } + + stop(clock, cond, 0) + stop(clock, cond, 1) diff --git a/test/ibistool/output_format.mlir b/test/ibistool/output_format.mlir index d159e0a5fa6c..e29a42960b8d 100644 --- a/test/ibistool/output_format.mlir +++ b/test/ibistool/output_format.mlir @@ -1,3 +1,5 @@ +// XFAIL: * +// See https://github.com/llvm/circt/issues/6658 // RUN: ibistool %s --lo --post-ibis-ir | FileCheck %s --check-prefix=CHECK-POST-IBIS // RUN: ibistool %s --lo --ir | FileCheck %s --check-prefix=CHECK-IR // RUN: ibistool %s --lo --verilog | FileCheck %s --check-prefix=CHECK-VERILOG diff --git a/test/lit.cfg.py b/test/lit.cfg.py index 6af560d214df..6f9e2a773bfd 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -58,8 +58,8 @@ tools = [ 'arcilator', 'circt-as', 'circt-capi-ir-test', 'circt-capi-om-test', 'circt-capi-firrtl-test', 'circt-capi-firtool-test', 'circt-dis', - 'circt-opt', 'circt-reduce', 'circt-translate', 'circt-verilog', 'firtool', - 'hlstool', 'om-linker', 'ibistool' + 'circt-opt', 'circt-reduce', 'circt-translate', 'firtool', 'hlstool', + 'om-linker', 'ibistool' ] # Enable Verilator if it has been detected. @@ -84,4 +84,9 @@ config.available_features.add('llhd-sim') tools.append('llhd-sim') +# Add circt-verilog if the Slang frontend is enabled. +if config.slang_frontend_enabled: + config.available_features.add('slang') + tools.append('circt-verilog') + llvm_config.add_tool_substitutions(tools, tool_dirs) diff --git a/test/lit.site.cfg.py.in b/test/lit.site.cfg.py.in index 2b2baf03ea57..8fc0c2384f73 100644 --- a/test/lit.site.cfg.py.in +++ b/test/lit.site.cfg.py.in @@ -40,6 +40,7 @@ config.esi_capnp = "@ESI_CAPNP@" config.zlib = "@HAVE_ZLIB@" config.scheduling_or_tools = "@SCHEDULING_OR_TOOLS@" config.llhd_sim_enabled = @CIRCT_LLHD_SIM_ENABLED@ +config.slang_frontend_enabled = @CIRCT_SLANG_FRONTEND_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/tools/CMakeLists.txt b/tools/CMakeLists.txt index 97e8e055b9c7..3183ba08706b 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -8,7 +8,6 @@ add_subdirectory(circt-opt) add_subdirectory(circt-reduce) add_subdirectory(circt-rtl-sim) add_subdirectory(circt-translate) -add_subdirectory(circt-verilog) add_subdirectory(esi) add_subdirectory(firtool) add_subdirectory(handshake-runner) @@ -17,3 +16,7 @@ add_subdirectory(ibistool) add_subdirectory(llhd-sim) add_subdirectory(om-linker) add_subdirectory(py-split-input-file) + +if(CIRCT_SLANG_FRONTEND_ENABLED) + add_subdirectory(circt-verilog) +endif() diff --git a/tools/arcilator/CMakeLists.txt b/tools/arcilator/CMakeLists.txt index a418cdc40aff..83d6258f6551 100644 --- a/tools/arcilator/CMakeLists.txt +++ b/tools/arcilator/CMakeLists.txt @@ -8,6 +8,7 @@ target_link_libraries(arcilator CIRCTArcTransforms CIRCTCombToArith CIRCTConvertToArcs + CIRCTExportArc CIRCTOM CIRCTSeqToSV CIRCTSeqTransforms diff --git a/tools/arcilator/arcilator.cpp b/tools/arcilator/arcilator.cpp index c5b3ee3414e2..22c931bb1f32 100644 --- a/tools/arcilator/arcilator.cpp +++ b/tools/arcilator/arcilator.cpp @@ -17,6 +17,8 @@ #include "circt/Dialect/Arc/ArcInterfaces.h" #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/Arc/ModelInfo.h" +#include "circt/Dialect/Arc/ModelInfoExport.h" #include "circt/Dialect/Seq/SeqPasses.h" #include "circt/InitAllDialects.h" #include "circt/InitAllPasses.h" @@ -60,6 +62,7 @@ using namespace llvm; using namespace mlir; using namespace circt; +using namespace arc; //===----------------------------------------------------------------------===// // Command Line Arguments @@ -190,13 +193,13 @@ static cl::opt outputFormat( // Main Tool Logic //===----------------------------------------------------------------------===// -/// Populate a pass manager with the arc simulator pipeline for the given -/// command line options. -static void populatePipeline(PassManager &pm) { - auto untilReached = [](Until until) { - return until >= runUntilBefore || until > runUntilAfter; - }; +static bool untilReached(Until until) { + return until >= runUntilBefore || until > runUntilAfter; +} +/// Populate a pass manager with the arc simulator pipeline for the given +/// command line options. This pipeline lowers modules to the Arc dialect. +static void populateHwModuleToArcPipeline(PassManager &pm) { if (verbosePassExecutions) pm.addInstrumentation( std::make_unique>( @@ -301,13 +304,15 @@ static void populatePipeline(PassManager &pm) { return; pm.addPass(arc::createLowerArcsToFuncsPass()); pm.nest().addPass(arc::createAllocateStatePass()); - if (!stateFile.empty()) - pm.addPass(arc::createPrintStateInfoPass(stateFile)); pm.addPass(arc::createLowerClocksToFuncsPass()); // no CSE between state alloc // and clock func lowering pm.addPass(createCSEPass()); pm.addPass(arc::createArcCanonicalizerPass()); +} +/// Populate a pass manager with the Arc to LLVM pipeline for the given +/// command line options. This pipeline lowers modules to LLVM IR. +static void populateArcToLLVMPipeline(PassManager &pm) { // Lower the arcs and update functions to LLVM. if (untilReached(UntilLLVMLowering)) return; @@ -328,17 +333,46 @@ static LogicalResult processBuffer( if (!module) return failure(); - PassManager pm(&context); - pm.enableVerifier(verifyPasses); - pm.enableTiming(ts); - if (failed(applyPassManagerCLOptions(pm))) + // Lower HwModule to Arc model. + PassManager pmArc(&context); + pmArc.enableVerifier(verifyPasses); + pmArc.enableTiming(ts); + if (failed(applyPassManagerCLOptions(pmArc))) + return failure(); + populateHwModuleToArcPipeline(pmArc); + + if (failed(pmArc.run(module.get()))) + return failure(); + + // Output state info as JSON if requested. + if (!stateFile.empty() && !untilReached(UntilStateLowering)) { + std::error_code ec; + llvm::ToolOutputFile outputFile(stateFile, ec, + llvm::sys::fs::OpenFlags::OF_None); + if (ec) { + llvm::errs() << "unable to open state file: " << ec.message() << '\n'; + return failure(); + } + if (failed(collectAndExportModelInfo(module.get(), outputFile.os()))) { + llvm::errs() << "failed to collect model info\n"; + return failure(); + } + + outputFile.keep(); + } + + // Lower Arc model to LLVM IR. + PassManager pmLlvm(&context); + pmLlvm.enableVerifier(verifyPasses); + pmLlvm.enableTiming(ts); + if (failed(applyPassManagerCLOptions(pmLlvm))) return failure(); - populatePipeline(pm); + populateArcToLLVMPipeline(pmLlvm); if (printDebugInfo && outputFormat == OutputLLVM) - pm.nest().addPass(LLVM::createDIScopeForLLVMFuncOpPass()); + pmLlvm.addPass(LLVM::createDIScopeForLLVMFuncOpPass()); - if (failed(pm.run(module.get()))) + if (failed(pmLlvm.run(module.get()))) return failure(); // Handle MLIR output. diff --git a/tools/circt-opt/CMakeLists.txt b/tools/circt-opt/CMakeLists.txt index 484ae77cade3..ceb08d75ec38 100644 --- a/tools/circt-opt/CMakeLists.txt +++ b/tools/circt-opt/CMakeLists.txt @@ -28,6 +28,7 @@ target_link_libraries(circt-opt CIRCTDCToHW CIRCTDCTransforms CIRCTDebug + CIRCTEmit CIRCTESI CIRCTExportChiselInterface CIRCTExportVerilog @@ -71,6 +72,7 @@ target_link_libraries(circt-opt CIRCTSeq CIRCTSeqToSV CIRCTSeqTransforms + CIRCTSimToSV CIRCTSSP CIRCTSSPTransforms CIRCTCFToHandshake diff --git a/tools/circt-translate/CMakeLists.txt b/tools/circt-translate/CMakeLists.txt index 42e8b928e781..322e6373396d 100644 --- a/tools/circt-translate/CMakeLists.txt +++ b/tools/circt-translate/CMakeLists.txt @@ -1,13 +1,17 @@ get_property(dialect_libs GLOBAL PROPERTY CIRCT_DIALECT_LIBS) get_property(translation_libs GLOBAL PROPERTY CIRCT_TRANSLATION_LIBS) +if(CIRCT_SLANG_FRONTEND_ENABLED) + add_compile_definitions(CIRCT_SLANG_FRONTEND_ENABLED) +endif() + set(LLVM_LINK_COMPONENTS Support - ) +) add_circt_tool(circt-translate circt-translate.cpp - ) +) llvm_update_compile_flags(circt-translate) @@ -18,4 +22,4 @@ target_link_libraries(circt-translate MLIRIR MLIRSupport MLIRTranslateLib - ) +) diff --git a/tools/circt-translate/circt-translate.cpp b/tools/circt-translate/circt-translate.cpp index 4b658a20f057..c7daf06e3c8d 100644 --- a/tools/circt-translate/circt-translate.cpp +++ b/tools/circt-translate/circt-translate.cpp @@ -18,12 +18,20 @@ #include "mlir/Tools/mlir-translate/MlirTranslateMain.h" #include "llvm/Support/PrettyStackTrace.h" +#ifdef CIRCT_SLANG_FRONTEND_ENABLED +#include "circt/Conversion/ImportVerilog.h" +#endif + int main(int argc, char **argv) { // Set the bug report message to indicate users should file issues on // llvm/circt and not llvm/llvm-project. llvm::setBugReportMsg(circt::circtBugReportMsg); circt::registerAllTranslations(); +#ifdef CIRCT_SLANG_FRONTEND_ENABLED + circt::registerFromVerilogTranslation(); +#endif + return mlir::failed( mlir::mlirTranslateMain(argc, argv, "CIRCT Translation Testing Tool")); } diff --git a/tools/circt-verilog/CMakeLists.txt b/tools/circt-verilog/CMakeLists.txt index e180b38cc0e3..03e3685dc63a 100644 --- a/tools/circt-verilog/CMakeLists.txt +++ b/tools/circt-verilog/CMakeLists.txt @@ -1,14 +1,11 @@ add_circt_tool(circt-verilog circt-verilog.cpp - DEPENDS - SUPPORT_PLUGINS ) llvm_update_compile_flags(circt-verilog) target_link_libraries(circt-verilog PRIVATE CIRCTImportVerilog + CIRCTSupport MLIRIR ) - -export_executable_symbols_for_plugins(circt-verilog) diff --git a/tools/circt-verilog/circt-verilog.cpp b/tools/circt-verilog/circt-verilog.cpp index a8a9189ac0d8..45a2cab8654a 100644 --- a/tools/circt-verilog/circt-verilog.cpp +++ b/tools/circt-verilog/circt-verilog.cpp @@ -31,6 +31,8 @@ using namespace circt; //===----------------------------------------------------------------------===// namespace { +enum class LoweringMode { OnlyPreprocess, OnlyLint, OnlyParse, Full }; + struct CLOptions { cl::OptionCategory cat{"Verilog Frontend Options"}; @@ -47,24 +49,19 @@ struct CLOptions { "corresponding line"), cl::init(false), cl::Hidden, cl::cat(cat)}; - cl::opt onlyPreprocess{ - "E", cl::desc("Only run the preprocessor (and print preprocessed files)"), - cl::init(false), cl::cat(cat)}; - cl::alias onlyPreprocessLong{"preprocess-only", cl::desc("Alias for -E"), - cl::aliasopt(onlyPreprocess), cl::NotHidden, - cl::cat(cat)}; - - cl::opt onlyLint{ - "lint-only", - cl::desc( - "Only lint the input, without elaboration and mapping to CIRCT IR"), - cl::init(false), cl::cat(cat)}; - - cl::opt onlyParse{ - "parse-only", - cl::desc( - "Only parse and elaborate the input, without mapping to CIRCT IR"), - cl::init(false), cl::cat(cat)}; + cl::opt loweringMode{ + cl::desc("Specify how to process the input:"), + cl::values( + clEnumValN( + LoweringMode::OnlyPreprocess, "E", + "Only run the preprocessor (and print preprocessed files)"), + clEnumValN(LoweringMode::OnlyLint, "lint-only", + "Only lint the input, without elaboration and mapping to " + "CIRCT IR"), + clEnumValN(LoweringMode::OnlyParse, "parse-only", + "Only parse and elaborate the input, without mapping to " + "CIRCT IR")), + cl::init(LoweringMode::Full), cl::cat(cat)}; //===--------------------------------------------------------------------===// // Include paths @@ -106,7 +103,7 @@ struct CLOptions { cl::list defines{ "D", - cl::desc("Define to (or 1 if ommitted) in all " + cl::desc("Define to (or 1 if omitted) in all " "source files"), cl::value_desc("="), cl::Prefix, cl::cat(cat)}; cl::alias definesLong{"define-macro", cl::desc("Alias for -D"), @@ -125,11 +122,9 @@ struct CLOptions { cl::opt librariesInheritMacros{ "libraries-inherit-macros", - cl::desc( - "If true, library files will inherit macro definitions from the " - "primary " - "source " - "files. --single-unit must also be passed when this option is used."), + cl::desc("If true, library files will inherit macro definitions from the " + "primary source files. --single-unit must also be passed when " + "this option is used."), cl::init(false), cl::cat(cat)}; //===--------------------------------------------------------------------===// @@ -218,8 +213,10 @@ static LogicalResult executeWithSources(MLIRContext *context, // Map the command line options to `ImportVerilog`'s conversion options. ImportVerilogOptions options; - options.onlyLint = opts.onlyLint; - options.onlyParse = opts.onlyParse; + if (opts.loweringMode == LoweringMode::OnlyLint) + options.mode = ImportVerilogOptions::Mode::OnlyLint; + else if (opts.loweringMode == LoweringMode::OnlyParse) + options.mode = ImportVerilogOptions::Mode::OnlyParse; options.includeDirs = opts.includeDirs; options.includeSystemDirs = opts.includeSystemDirs; @@ -237,7 +234,7 @@ static LogicalResult executeWithSources(MLIRContext *context, options.timeScale = opts.timeScale; options.allowUseBeforeDeclare = opts.allowUseBeforeDeclare; options.ignoreUnknownModules = opts.ignoreUnknownModules; - if (!opts.onlyLint) + if (opts.loweringMode != LoweringMode::OnlyLint) options.topModules = opts.topModules; options.paramOverrides = opts.paramOverrides; @@ -259,7 +256,7 @@ static LogicalResult executeWithSources(MLIRContext *context, // If the user requested for the files to be only preprocessed, do so and // print the results to the configured output file. - if (opts.onlyPreprocess) { + if (opts.loweringMode == LoweringMode::OnlyPreprocess) { auto result = preprocessVerilog(sourceMgr, context, ts, outputFile->os(), &options); if (succeeded(result)) @@ -281,7 +278,7 @@ static LogicalResult executeWithSources(MLIRContext *context, static LogicalResult execute(MLIRContext *context) { // Open the input files. llvm::SourceMgr sourceMgr; - for (auto inputFilename : opts.inputFilenames) { + for (const auto &inputFilename : opts.inputFilenames) { std::string errorMessage; auto buffer = openInputFile(inputFilename, &errorMessage); if (!buffer) { @@ -298,10 +295,9 @@ static LogicalResult execute(MLIRContext *context) { context->printOpOnDiagnostic(false); (void)executeWithSources(context, sourceMgr); return handler.verify(); - } else { - SourceMgrDiagnosticHandler handler(sourceMgr, context); - return executeWithSources(context, sourceMgr); } + SourceMgrDiagnosticHandler handler(sourceMgr, context); + return executeWithSources(context, sourceMgr); } int main(int argc, char **argv) { diff --git a/tools/firtool/CMakeLists.txt b/tools/firtool/CMakeLists.txt index 45f1f513535e..37fb822eb18c 100644 --- a/tools/firtool/CMakeLists.txt +++ b/tools/firtool/CMakeLists.txt @@ -16,6 +16,7 @@ target_link_libraries(firtool PRIVATE CIRCTHWTransforms CIRCTOMTransforms CIRCTSeq + CIRCTSim CIRCTSVTransforms CIRCTTransforms CIRCTFirtool diff --git a/tools/firtool/firtool.cpp b/tools/firtool/firtool.cpp index 02c6565e68bc..5a0cf7251aec 100644 --- a/tools/firtool/firtool.cpp +++ b/tools/firtool/firtool.cpp @@ -16,6 +16,7 @@ #include "circt/Conversion/Passes.h" #include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/Debug/DebugDialect.h" +#include "circt/Dialect/Emit/EmitDialect.h" #include "circt/Dialect/FIRRTL/CHIRRTLDialect.h" #include "circt/Dialect/FIRRTL/FIRParser.h" #include "circt/Dialect/FIRRTL/FIRRTLDialect.h" @@ -31,6 +32,7 @@ #include "circt/Dialect/SV/SVDialect.h" #include "circt/Dialect/SV/SVPasses.h" #include "circt/Dialect/Seq/SeqDialect.h" +#include "circt/Dialect/Sim/SimDialect.h" #include "circt/Dialect/Verif/VerifDialect.h" #include "circt/Support/LoweringOptions.h" #include "circt/Support/LoweringOptionsParser.h" @@ -594,10 +596,10 @@ static LogicalResult executeFirtool(MLIRContext &context, // Figure out the input format if unspecified. if (inputFormat == InputUnspecified) { - if (StringRef(inputFilename).endswith(".fir")) + if (StringRef(inputFilename).ends_with(".fir")) inputFormat = InputFIRFile; - else if (StringRef(inputFilename).endswith(".mlir") || - StringRef(inputFilename).endswith(".mlirbc") || + else if (StringRef(inputFilename).ends_with(".mlir") || + StringRef(inputFilename).ends_with(".mlirbc") || mlir::isBytecode(*input)) inputFormat = InputMLIRFile; else { @@ -634,10 +636,11 @@ static LogicalResult executeFirtool(MLIRContext &context, } // Register our dialects. - context.loadDialect(); + context.loadDialect(); // Process the input. if (failed(processInput(context, firtoolOptions, ts, std::move(input), @@ -702,9 +705,11 @@ int main(int argc, char **argv) { // Conversion passes: registerPrepareForEmissionPass(); + registerHWLowerInstanceChoicesPass(); registerLowerFIRRTLToHWPass(); registerLegalizeAnonEnumsPass(); registerLowerSeqToSVPass(); + registerLowerSimToSVPass(); registerLowerVerifToSVPass(); } diff --git a/tools/ibistool/ibistool.cpp b/tools/ibistool/ibistool.cpp index a100fa863655..8c0c70ab4625 100644 --- a/tools/ibistool/ibistool.cpp +++ b/tools/ibistool/ibistool.cpp @@ -11,7 +11,8 @@ // //===----------------------------------------------------------------------===// -#include "mlir/Conversion/Passes.h" +#include "mlir/Conversion/AffineToStandard/AffineToStandard.h" +#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h" #include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" diff --git a/unittests/Dialect/HW/CMakeLists.txt b/unittests/Dialect/HW/CMakeLists.txt index 5613696c1c3b..01bc86e78549 100644 --- a/unittests/Dialect/HW/CMakeLists.txt +++ b/unittests/Dialect/HW/CMakeLists.txt @@ -8,4 +8,5 @@ add_circt_unittest(CIRCTHWTests target_link_libraries(CIRCTHWTests PRIVATE CIRCTHW + CIRCTCAPIHW ) diff --git a/unittests/Dialect/HW/InstanceGraphTest.cpp b/unittests/Dialect/HW/InstanceGraphTest.cpp index 6aad951767b0..fe4a41086349 100644 --- a/unittests/Dialect/HW/InstanceGraphTest.cpp +++ b/unittests/Dialect/HW/InstanceGraphTest.cpp @@ -7,7 +7,11 @@ //===----------------------------------------------------------------------===// #include "GraphFixture.h" +#include "circt-c/Dialect/HW.h" #include "circt/Dialect/HW/HWInstanceGraph.h" +#include "mlir-c/BuiltinAttributes.h" +#include "mlir/CAPI/IR.h" +#include "mlir/CAPI/Support.h" #include "llvm/ADT/PostOrderIterator.h" #include "gtest/gtest.h" @@ -37,4 +41,55 @@ TEST(InstanceGraphTest, PostOrderTraversal) { ASSERT_EQ(range.end(), it); } +TEST(InstanceGraphCAPITest, PostOrderTraversal) { + MLIRContext context; + + HWInstanceGraph instanceGraph = + hwInstanceGraphGet(wrap(fixtures::createModule(&context).getOperation())); + + struct Context { + size_t i; + HWInstanceGraphNode topLevelNode; + }; + auto ctx = Context{0, hwInstanceGraphGetTopLevelNode(instanceGraph)}; + + hwInstanceGraphForEachNode( + instanceGraph, + [](HWInstanceGraphNode node, void *userData) { + Context *ctx = reinterpret_cast(userData); + ctx->i++; + + if (ctx->i == 5) { + ASSERT_EQ(hwInstanceGraphNodeEqual(ctx->topLevelNode, node), true); + return; + } + + MlirOperation moduleOp = hwInstanceGraphNodeGetModuleOp(node); + MlirAttribute moduleNameAttr = mlirOperationGetAttributeByName( + moduleOp, mlirStringRefCreateFromCString("sym_name")); + StringRef moduleName = unwrap(mlirStringAttrGetValue(moduleNameAttr)); + + switch (ctx->i) { + case 1: + ASSERT_EQ("Cat", moduleName); + break; + case 2: + ASSERT_EQ("Bear", moduleName); + break; + case 3: + ASSERT_EQ("Alligator", moduleName); + break; + case 4: + ASSERT_EQ("Top", moduleName); + break; + default: + llvm_unreachable("unexpected i value"); + break; + } + }, + &ctx); + + ASSERT_EQ(ctx.i, 5UL); +} + } // namespace diff --git a/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp b/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp index 4559e8b471bc..0a2000a056fa 100644 --- a/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp +++ b/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp @@ -613,4 +613,276 @@ TEST(EvaluatorTests, InstantiateCycle) { ASSERT_TRUE(failed(result)); } +TEST(EvaluatorTests, IntegerBinaryArithmeticAdd) { + StringRef mod = "om.class @IntegerBinaryArithmeticAdd() {" + " %0 = om.constant #om.integer<1 : si3> : !om.integer" + " %1 = om.constant #om.integer<2 : si3> : !om.integer" + " %2 = om.integer.add %0, %1 : !om.integer" + " om.class.field @result, %2 : !om.integer" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = evaluator.instantiate( + StringAttr::get(&context, "IntegerBinaryArithmeticAdd"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + ASSERT_EQ(3, llvm::cast(fieldValue.get()) + ->getAs() + .getValue() + .getValue()); +} + +TEST(EvaluatorTests, IntegerBinaryArithmeticMul) { + StringRef mod = "om.class @IntegerBinaryArithmeticMul() {" + " %0 = om.constant #om.integer<2 : si3> : !om.integer" + " %1 = om.constant #om.integer<3 : si3> : !om.integer" + " %2 = om.integer.mul %0, %1 : !om.integer" + " om.class.field @result, %2 : !om.integer" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = evaluator.instantiate( + StringAttr::get(&context, "IntegerBinaryArithmeticMul"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + ASSERT_EQ(6, llvm::cast(fieldValue.get()) + ->getAs() + .getValue() + .getValue()); +} + +TEST(EvaluatorTests, IntegerBinaryArithmeticShr) { + StringRef mod = "om.class @IntegerBinaryArithmeticShr() {" + " %0 = om.constant #om.integer<8 : si5> : !om.integer" + " %1 = om.constant #om.integer<2 : si3> : !om.integer" + " %2 = om.integer.shr %0, %1 : !om.integer" + " om.class.field @result, %2 : !om.integer" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = evaluator.instantiate( + StringAttr::get(&context, "IntegerBinaryArithmeticShr"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + ASSERT_EQ(2, llvm::cast(fieldValue.get()) + ->getAs() + .getValue() + .getValue()); +} + +TEST(EvaluatorTests, IntegerBinaryArithmeticShrNegative) { + StringRef mod = "om.class @IntegerBinaryArithmeticShrNegative() {" + " %0 = om.constant #om.integer<8 : si5> : !om.integer" + " %1 = om.constant #om.integer<-2 : si3> : !om.integer" + " %2 = om.integer.shr %0, %1 : !om.integer" + " om.class.field @result, %2 : !om.integer" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + context.getDiagEngine().registerHandler([&](Diagnostic &diag) { + if (StringRef(diag.str()).starts_with("'om.integer.shr'")) + ASSERT_EQ(diag.str(), + "'om.integer.shr' op shift amount must be non-negative"); + if (StringRef(diag.str()).starts_with("failed")) + ASSERT_EQ(diag.str(), "failed to evaluate integer operation"); + }); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = evaluator.instantiate( + StringAttr::get(&context, "IntegerBinaryArithmeticShrNegative"), {}); + + ASSERT_TRUE(failed(result)); +} + +TEST(EvaluatorTests, IntegerBinaryArithmeticShrTooLarge) { + StringRef mod = "om.class @IntegerBinaryArithmeticShrTooLarge() {" + " %0 = om.constant #om.integer<8 : si5> : !om.integer" + " %1 = om.constant #om.integer<36893488147419100000 : si66> " + ": !om.integer" + " %2 = om.integer.shr %0, %1 : !om.integer" + " om.class.field @result, %2 : !om.integer" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + context.getDiagEngine().registerHandler([&](Diagnostic &diag) { + if (StringRef(diag.str()).starts_with("'om.integer.shr'")) + ASSERT_EQ( + diag.str(), + "'om.integer.shr' op shift amount must be representable in 64 bits"); + if (StringRef(diag.str()).starts_with("failed")) + ASSERT_EQ(diag.str(), "failed to evaluate integer operation"); + }); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = evaluator.instantiate( + StringAttr::get(&context, "IntegerBinaryArithmeticShrTooLarge"), {}); + + ASSERT_TRUE(failed(result)); +} + +TEST(EvaluatorTests, IntegerBinaryArithmeticObjects) { + StringRef mod = "om.class @Class1() {" + " %0 = om.constant #om.integer<1 : si3> : !om.integer" + " om.class.field @value, %0 : !om.integer" + "}" + "" + "om.class @Class2() {" + " %0 = om.constant #om.integer<2 : si3> : !om.integer" + " om.class.field @value, %0 : !om.integer" + "}" + "" + "om.class @IntegerBinaryArithmeticObjects() {" + " %0 = om.object @Class1() : () -> !om.class.type<@Class1>" + " %1 = om.object.field %0, [@value] : " + "(!om.class.type<@Class1>) -> !om.integer" + "" + " %2 = om.object @Class2() : () -> !om.class.type<@Class2>" + " %3 = om.object.field %2, [@value] : " + "(!om.class.type<@Class2>) -> !om.integer" + "" + " %5 = om.integer.add %1, %3 : !om.integer" + " om.class.field @result, %5 : !om.integer" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = evaluator.instantiate( + StringAttr::get(&context, "IntegerBinaryArithmeticObjects"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + ASSERT_EQ(3, llvm::cast(fieldValue.get()) + ->getAs() + .getValue() + .getValue()); +} + +TEST(EvaluatorTests, IntegerBinaryArithmeticObjectsDelayed) { + StringRef mod = + "om.class @Class1(%input: !om.integer) {" + " %0 = om.constant #om.integer<1 : si3> : !om.integer" + " om.class.field @value, %0 : !om.integer" + " om.class.field @input, %input : !om.integer" + "}" + "" + "om.class @Class2() {" + " %0 = om.constant #om.integer<2 : si3> : !om.integer" + " om.class.field @value, %0 : !om.integer" + "}" + "" + "om.class @IntegerBinaryArithmeticObjectsDelayed() {" + " %0 = om.object @Class1(%5) : (!om.integer) -> !om.class.type<@Class1>" + " %1 = om.object.field %0, [@value] : " + "(!om.class.type<@Class1>) -> !om.integer" + "" + " %2 = om.object @Class2() : () -> !om.class.type<@Class2>" + " %3 = om.object.field %2, [@value] : " + "(!om.class.type<@Class2>) -> !om.integer" + "" + " %5 = om.integer.add %1, %3 : !om.integer" + " om.class.field @result, %5 : !om.integer" + "}"; + + DialectRegistry registry; + registry.insert(); + + MLIRContext context(registry); + context.getOrLoadDialect(); + + OwningOpRef owning = + parseSourceString(mod, ParserConfig(&context)); + + Evaluator evaluator(owning.release()); + + auto result = evaluator.instantiate( + StringAttr::get(&context, "IntegerBinaryArithmeticObjectsDelayed"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + ASSERT_EQ(3, llvm::cast(fieldValue.get()) + ->getAs() + .getValue() + .getValue()); +} + } // namespace