diff --git a/CMakeLists.txt b/CMakeLists.txt index 44437c9932f9..059cb2337faa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -591,6 +591,13 @@ if(CIRCT_SLANG_FRONTEND_ENABLED) # harder than it ought to be. set_property( GLOBAL APPEND PROPERTY CIRCT_EXPORTS slang_slang unordered_dense fmt) + + # Disable the installation of headers coming from third-party libraries. We + # won't use those APIs directly. Just make them static libraries for the sake + # of running slang normally. + set_target_properties(fmt PROPERTIES PUBLIC_HEADER "") + set_target_properties(unordered_dense PROPERTIES PUBLIC_HEADER "") + install(TARGETS slang_slang unordered_dense fmt EXPORT CIRCTTargets) else() find_package(slang 3.0 REQUIRED) diff --git a/README.md b/README.md index 0e2b7f2893cd..83b3ad4a7667 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # ⚡️ "CIRCT" / Circuit IR Compilers and Tools -"CIRCT" stands for "Circuit IR Compilers and Tools". One might also interpret +"CIRCT" stands for "Circuit Intermediate Representations (IR) Compilers and Tools". One might also interpret it as the recursively as "CIRCT IR Compiler and Tools". The T can be selectively expanded as Tool, Translator, Team, Technology, Target, Tree, Type, ... we're ok with the ambiguity. diff --git a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md index 34fb19caffb6..0cbac6f916ad 100644 --- a/docs/Dialects/FIRRTL/FIRRTLAnnotations.md +++ b/docs/Dialects/FIRRTL/FIRRTLAnnotations.md @@ -474,8 +474,55 @@ Example: } ``` +### FullResetAnnotation + +| Property | Type | Description | +| ---------- | ------ | ------------- | +| class | string | `circt.FullAsyncResetAnnotation` | +| target | string | Reference target | +| resetType | string | "async" or "sync" | + + +The target must be a signal that is a reset. The type of the signal must be (or inferred +to be) the same as the reset type specified in the annotation. + +Indicates that all reset-less registers which are children of the module containing +the target will have the reset targeted attached, with a reset value of 0. + +The module containing the target of this annotation is not allowed to reside in multiple +hierarchies. + +Example: +```json +{ + "class": "circt.FullResetAnnotation", + "target": "~Foo|Bar/d:Baz>reset", + "resetType": "async" +} +``` + +### ExcludeFromFullResetAnnotation + +| Property | Type | Description | +| ---------- | ------ | ------------- | +| class | string | `circt.ExcludeFromFullResetAnnotation` | +| target | string | Reference target | + +This annotation indicates that the target moudle should be excluded from the +FullResetAnnotation of a parent module. + +Example: +```json +{ + "class": "circt.IgnoreFullAsyncResetAnnotation", + "target": "~Foo|Bar/d:Baz" +} +``` + ### FullAsyncResetAnnotation +**Deprecated, use FullResetAnnotation** + | Property | Type | Description | | ---------- | ------ | ------------- | | class | string | `sifive.enterprise.firrtl.FullAsyncResetAnnotation` | @@ -500,6 +547,8 @@ Example: ### IgnoreFullAsyncResetAnnotation +**Deprecated, use ExcludeFromFullResetAnnotation** + | Property | Type | Description | | ---------- | ------ | ------------- | | class | string | `sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation` | diff --git a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md index 1ba3aacf4eb4..4ca5cdbec72c 100644 --- a/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md +++ b/docs/Dialects/FIRRTL/FIRRTLIntrinsics.md @@ -252,7 +252,8 @@ Function Declaration: Types: * Operand and result types must be passive. -* Aggregate types are lowered into corresponding verilog aggregate. +* A vector is lowered to an unpacked open array type, e.g. `a: Vec<4, UInt<8>>` to `byte a []`. +* A bundle is lowered to a packed struct. * Integer types are lowered into into 2-state types. * Small integer types (< 64 bit) must be compatible to C-types and arguments are passed by values. Users are required to use specific integer types for small integers shown in the table below. Large integers are lowered to `bit` and passed by a reference. @@ -267,12 +268,13 @@ Types: Example SV output: ```firrtl -node result = intrinsic(circt_dpi_call : UInt<64>, clock, enable, uint_8_value, uint_32_value) +node result = intrinsic(circt_dpi_call : UInt<64>, clock, enable, uint_8_value, uint_32_value, uint_8_vector) ``` ```verilog import "DPI-C" function void dpi_func( input byte in_0, int in_1, + byte in_2[], output longint out_0 ); diff --git a/docs/Dialects/FIRRTL/_index.md b/docs/Dialects/FIRRTL/_index.md index a617ede06af3..4b2dec99bf29 100644 --- a/docs/Dialects/FIRRTL/_index.md +++ b/docs/Dialects/FIRRTL/_index.md @@ -22,6 +22,10 @@ page](https://github.com/freechipsproject/firrtl). [include "Dialects/FIRRTLExpressionOps.md"] +## Operation Definitions -- Intrinsics + +[include "Dialects/FIRRTLIntrinsicOps.md"] + ## Type Definitions [include "Dialects/FIRRTLTypes.md"] diff --git a/docs/Dialects/HWArith/RationaleHWArith.md b/docs/Dialects/HWArith/RationaleHWArith.md index 86442c2deb9d..dfbea368cbe3 100644 --- a/docs/Dialects/HWArith/RationaleHWArith.md +++ b/docs/Dialects/HWArith/RationaleHWArith.md @@ -483,19 +483,18 @@ can be simplified to: %4 = hwarith.icmp lt %0, %1 : si3, ui5 ``` -Note that the result of the comparison is *always* of type `ui1`, regardless of -the operands. So if the `i1` type is needed, the result must be cast -accordingly. +Note that the result of the comparison is *always* of type `i1` since the +logical result is a boolean, which doesn't have signedness semantics. #### Overview | | LHS type | RHS type | Comparison type | Result type | | - | :------- | :------- | :--------------------------------------- | :---------- | -|(U)| `ui` | `ui` | `ui`, *r* = max(*a*, *b*) | `ui1` | -|(S)| `si` | `si` | `si`, *r* = max(*a*, *b*) | `ui1` | -|(M)| `ui` | `si` | `si`, *r* = *a* + 1 **if** *a* ≥ *b* | `ui1` | -| | | | `si`, *r* = *b* **if** *a* < *b* | `ui1` | -|(M)| `si` | `ui` | Same as `ui si` | `ui1` | +|(U)| `ui` | `ui` | `ui`, *r* = max(*a*, *b*) | `i1` | +|(S)| `si` | `si` | `si`, *r* = max(*a*, *b*) | `i1` | +|(M)| `ui` | `si` | `si`, *r* = *a* + 1 **if** *a* ≥ *b* | `i1` | +| | | | `si`, *r* = *b* **if** *a* < *b* | `i1` | +|(M)| `si` | `ui` | Same as `ui si` | `i1` | #### Examples ```mlir diff --git a/docs/VerilogGeneration.md b/docs/VerilogGeneration.md index 5efb26373422..0a006d3b0888 100644 --- a/docs/VerilogGeneration.md +++ b/docs/VerilogGeneration.md @@ -136,6 +136,9 @@ The current set of "lint warnings fix" Lowering Options is: collisions with Verilog keywords insensitively. E.g., this will treat a variable called `WIRE` as a collision with the keyword and rename it to `WIRE_0` (or similar). When set to `false`, then `WIRE` will not be renamed. + * `fixUpEmptyModules` (default=`false`). If true, then add a dummy wire to + empty modules since some vendor tools consider empty modules as a blackbox and + raise synthesis errors. ## Recommended `LoweringOptions` by Target diff --git a/frontends/PyCDE/integration_test/esi_test.py b/frontends/PyCDE/integration_test/esi_test.py index 2c78c15a41c1..ddc1cd4ced6a 100644 --- a/frontends/PyCDE/integration_test/esi_test.py +++ b/frontends/PyCDE/integration_test/esi_test.py @@ -7,6 +7,7 @@ import pycde from pycde import (AppID, Clock, Module, Reset, modparams, generator) from pycde.bsp import cosim +from pycde.common import Constant from pycde.constructs import Reg, Wire from pycde.esi import FuncService, MMIO, MMIOReadWriteCmdType from pycde.types import (Bits, Channel, UInt) @@ -15,21 +16,23 @@ import sys -class LoopbackInOutAdd7(Module): +class LoopbackInOutAdd(Module): """Loopback the request from the host, adding 7 to the first 15 bits.""" clk = Clock() rst = Reset() + add_amt = Constant(UInt(16), 11) + @generator def construct(ports): loopback = Wire(Channel(UInt(16))) - args = FuncService.get_call_chans(AppID("loopback_add7"), + args = FuncService.get_call_chans(AppID("add"), arg_type=UInt(24), result=loopback) ready = Wire(Bits(1)) data, valid = args.unwrap(ready) - plus7 = data + 7 + plus7 = data + LoopbackInOutAdd.add_amt.value data_chan, data_ready = loopback.type.wrap(plus7.as_uint(16), valid) data_chan_buffered = data_chan.buffer(ports.clk, ports.rst, 5) ready.assign(data_ready) @@ -96,7 +99,7 @@ class Top(Module): @generator def construct(ports): - LoopbackInOutAdd7(clk=ports.clk, rst=ports.rst) + LoopbackInOutAdd(clk=ports.clk, rst=ports.rst, appid=AppID("loopback")) for i in range(4, 18, 5): MMIOClient(i)() MMIOReadWriteClient(clk=ports.clk, rst=ports.rst) diff --git a/frontends/PyCDE/integration_test/test_software/esi_ram.py b/frontends/PyCDE/integration_test/test_software/esi_ram.py index cf18fc16dafe..c9f61b54b511 100644 --- a/frontends/PyCDE/integration_test/test_software/esi_ram.py +++ b/frontends/PyCDE/integration_test/test_software/esi_ram.py @@ -1,3 +1,4 @@ +from multiprocessing import dummy import time from typing import cast import esiaccel as esi @@ -26,8 +27,12 @@ # assert len(m.type_table) == len(m_alt.type_table) info = m.module_infos -assert len(info) == 3 -assert info[1].name == "Dummy" +dummy_info = None +for i in info: + if i.name == "Dummy": + dummy_info = i + break +assert dummy_info is not None def read(addr: int) -> bytearray: diff --git a/frontends/PyCDE/integration_test/test_software/esi_test.py b/frontends/PyCDE/integration_test/test_software/esi_test.py index f264cf923e06..4eeeabbe8cb1 100644 --- a/frontends/PyCDE/integration_test/test_software/esi_test.py +++ b/frontends/PyCDE/integration_test/test_software/esi_test.py @@ -67,13 +67,21 @@ def read_offset_check(i: int, add_amt: int): print(m.type_table) d = acc.build_accelerator() - -recv = d.ports[esi.AppID("loopback_add7")].read_port("result") +loopback = d.children[esi.AppID("loopback")] +recv = loopback.ports[esi.AppID("add")].read_port("result") recv.connect() -send = d.ports[esi.AppID("loopback_add7")].write_port("arg") +send = loopback.ports[esi.AppID("add")].write_port("arg") send.connect() +loopback_info = None +for mod_info in m.module_infos: + if mod_info.name == "LoopbackInOutAdd": + loopback_info = mod_info + break +assert loopback_info is not None +add_amt = mod_info.constants["add_amt"].value + ################################################################################ # Loopback add 7 tests ################################################################################ @@ -85,6 +93,6 @@ def read_offset_check(i: int, add_amt: int): print(f"data: {data}") print(f"resp: {resp}") -assert resp == data + 7 +assert resp == data + add_amt print("PASS") diff --git a/frontends/PyCDE/src/pycde/common.py b/frontends/PyCDE/src/pycde/common.py index b2e993164e8a..2fce095417e0 100644 --- a/frontends/PyCDE/src/pycde/common.py +++ b/frontends/PyCDE/src/pycde/common.py @@ -135,6 +135,23 @@ def __repr__(self) -> str: return f"{self.name}[{self.index}]" +class Constant: + """A constant value associated with a module. Gets added to the ESI system + manifest so it is accessible at runtime. + + Example usage: + + ``` + def ExampleModule(Module): + const_name = Constant(UInt(16), 42) + ``` + """ + + def __init__(self, type: Type, value: object): + self.type = type + self.value = value + + class _PyProxy: """Parent class for a Python object which has a corresponding IR op (i.e. a proxy class).""" diff --git a/frontends/PyCDE/src/pycde/module.py b/frontends/PyCDE/src/pycde/module.py index 46904adbbeb9..5f76cc9f8da1 100644 --- a/frontends/PyCDE/src/pycde/module.py +++ b/frontends/PyCDE/src/pycde/module.py @@ -6,9 +6,9 @@ from dataclasses import dataclass from typing import Any, List, Optional, Set, Tuple, Dict -from .common import (AppID, Clock, Input, ModuleDecl, Output, PortError, - _PyProxy, Reset) -from .support import (get_user_loc, _obj_to_attribute, create_type_string, +from .common import (AppID, Clock, Constant, Input, ModuleDecl, Output, + PortError, _PyProxy, Reset) +from .support import (get_user_loc, _obj_to_attribute, obj_to_typed_attribute, create_const_zero) from .signals import ClockSignal, Signal, _FromCirctValue from .types import ClockType, Type, _FromCirctType @@ -237,6 +237,7 @@ def scan_cls(self): clock_ports = set() reset_ports = set() generators = {} + constants = {} num_inputs = 0 num_outputs = 0 for attr_name, attr in self.cls_dct.items(): @@ -273,11 +274,14 @@ def scan_cls(self): ports.append(attr) elif isinstance(attr, Generator): generators[attr_name] = attr + elif isinstance(attr, Constant): + constants[attr_name] = attr self.ports = ports self.clocks = clock_ports self.resets = reset_ports self.generators = generators + self.constants = constants def create_port_proxy(self) -> PortProxyBase: """Create a proxy class for generators to use in order to access module @@ -475,6 +479,17 @@ def create_op(self, sys, symbol): else: self.add_metadata(sys, symbol, None) + # If there are associated constants, add them to the manifest. + if len(self.constants) > 0: + constants_dict: Dict[str, ir.Attribute] = {} + for name, constant in self.constants.items(): + constant_attr = obj_to_typed_attribute(constant.value, constant.type) + constants_dict[name] = constant_attr + with ir.InsertionPoint(sys.mod.body): + from .dialects.esi import esi + esi.SymbolConstantsOp(symbolRef=ir.FlatSymbolRefAttr.get(symbol), + constants=ir.DictAttr.get(constants_dict)) + if len(self.generators) > 0: if hasattr(self, "parameters") and self.parameters is not None: self.attributes["pycde.parameters"] = self.parameters diff --git a/frontends/PyCDE/src/pycde/support.py b/frontends/PyCDE/src/pycde/support.py index a276f444d680..b29db4634a2a 100644 --- a/frontends/PyCDE/src/pycde/support.py +++ b/frontends/PyCDE/src/pycde/support.py @@ -1,6 +1,12 @@ +from __future__ import annotations + from .circt import support from .circt import ir +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .types import Type + import os @@ -43,6 +49,13 @@ def _obj_to_attribute(obj) -> ir.Attribute: "This is required for parameters.") +def obj_to_typed_attribute(obj: object, type: Type) -> ir.Attribute: + from .types import BitVectorType + if isinstance(type, BitVectorType): + return ir.IntegerAttr.get(type._type, obj) + raise ValueError(f"Type '{type}' conversion to attribute not supported yet.") + + __dir__ = os.path.dirname(__file__) _local_files = set([os.path.join(__dir__, x) for x in os.listdir(__dir__)]) _hidden_filenames = set(["functools.py"]) diff --git a/frontends/PyCDE/test/test_esi.py b/frontends/PyCDE/test/test_esi.py index cf4766897304..84f7878cc267 100644 --- a/frontends/PyCDE/test/test_esi.py +++ b/frontends/PyCDE/test/test_esi.py @@ -4,7 +4,7 @@ from pycde import (Clock, Input, InputChannel, Output, OutputChannel, Module, Reset, generator, types) from pycde import esi -from pycde.common import AppID, RecvBundle, SendBundle +from pycde.common import AppID, Constant, RecvBundle, SendBundle from pycde.constructs import Wire from pycde.esi import MMIO from pycde.module import Metadata @@ -36,6 +36,7 @@ class HostComms: # CHECK: esi.manifest.sym @LoopbackInOutTop name "LoopbackInOut" {{.*}}version "0.1" {bar = "baz", foo = 1 : i64} +# CHECK: esi.manifest.constants @LoopbackInOutTop {c1 = 54 : ui8} # CHECK-LABEL: hw.module @LoopbackInOutTop(in %clk : !seq.clock, in %rst : i1) @@ -59,6 +60,8 @@ class LoopbackInOutTop(Module): }, ) + c1 = Constant(UInt(8), 54) + @generator def construct(self): # Use Cosim to implement the 'HostComms' service. diff --git a/include/circt-c/Dialect/HW.h b/include/circt-c/Dialect/HW.h index 4b2d7b3abb59..4310190d03a9 100644 --- a/include/circt-c/Dialect/HW.h +++ b/include/circt-c/Dialect/HW.h @@ -161,6 +161,7 @@ MLIR_CAPI_EXPORTED MlirStringRef hwTypeAliasTypeGetScope(MlirType typeAlias); MLIR_CAPI_EXPORTED bool hwAttrIsAInnerSymAttr(MlirAttribute); MLIR_CAPI_EXPORTED MlirAttribute hwInnerSymAttrGet(MlirAttribute symName); +MLIR_CAPI_EXPORTED MlirAttribute hwInnerSymAttrGetEmpty(MlirContext ctx); MLIR_CAPI_EXPORTED MlirAttribute hwInnerSymAttrGetSymName(MlirAttribute); MLIR_CAPI_EXPORTED bool hwAttrIsAInnerRefAttr(MlirAttribute); diff --git a/include/circt-c/Dialect/OM.h b/include/circt-c/Dialect/OM.h index 2248113d98c1..d9089bb0d38c 100644 --- a/include/circt-c/Dialect/OM.h +++ b/include/circt-c/Dialect/OM.h @@ -25,6 +25,12 @@ MLIR_DECLARE_CAPI_DIALECT_REGISTRATION(OM, om); // Type API. //===----------------------------------------------------------------------===// +/// Is the Type an AnyType. +MLIR_CAPI_EXPORTED bool omTypeIsAAnyType(MlirType type); + +/// Get the TypeID for an AnyType. +MLIR_CAPI_EXPORTED MlirTypeID omAnyTypeGetTypeID(void); + /// Is the Type a ClassType. MLIR_CAPI_EXPORTED bool omTypeIsAClassType(MlirType type); @@ -46,6 +52,15 @@ MLIR_CAPI_EXPORTED bool omTypeIsAFrozenPathType(MlirType type); /// Get the TypeID for a FrozenPathType. MLIR_CAPI_EXPORTED MlirTypeID omFrozenPathTypeGetTypeID(void); +/// Is the Type a ListType. +MLIR_CAPI_EXPORTED bool omTypeIsAListType(MlirType type); + +/// Get the TypeID for a ListType. +MLIR_CAPI_EXPORTED MlirTypeID omListTypeGetTypeID(void); + +// Return a element type of a ListType. +MLIR_CAPI_EXPORTED MlirType omListTypeGetElementType(MlirType type); + /// Is the Type a MapType. MLIR_CAPI_EXPORTED bool omTypeIsAMapType(MlirType type); diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 684e6616cb01..6eb67b865093 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -516,7 +516,8 @@ def ConvertMooreToCore : Pass<"convert-moore-to-core", "mlir::ModuleOp"> { }]; let constructor = "circt::createConvertMooreToCorePass()"; let dependentDialects = ["comb::CombDialect", "hw::HWDialect", - "llhd::LLHDDialect"]; + "llhd::LLHDDialect", "mlir::cf::ControlFlowDialect", + "mlir::scf::SCFDialect"]; } //===----------------------------------------------------------------------===// @@ -653,7 +654,7 @@ def ConvertToArcs : Pass<"convert-to-arcs", "mlir::ModuleOp"> { latency. }]; let constructor = "circt::createConvertToArcsPass()"; - let dependentDialects = ["circt::arc::ArcDialect"]; + let dependentDialects = ["circt::arc::ArcDialect", "circt::hw::HWDialect"]; let options = [ Option<"tapRegisters", "tap-registers", "bool", "true", "Make registers observable">, diff --git a/include/circt/Dialect/Arc/ArcCostModel.h b/include/circt/Dialect/Arc/ArcCostModel.h new file mode 100644 index 000000000000..769a3ceb5e2b --- /dev/null +++ b/include/circt/Dialect/Arc/ArcCostModel.h @@ -0,0 +1,62 @@ +//===- ArcCostModel.h -----------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_ARC_ARCCOSTMODEL_H +#define CIRCT_DIALECT_ARC_ARCCOSTMODEL_H + +#include "circt/Dialect/Arc/ArcOps.h" +#include "mlir/IR/Operation.h" +#include "mlir/Pass/AnalysisManager.h" + +using namespace mlir; + +namespace circt { +namespace arc { + +struct OperationCosts { + size_t normalCost{0}; + size_t packingCost{0}; + size_t shufflingCost{0}; + size_t vectorizeOpsBodyCost{0}; + size_t totalCost() const { + return normalCost + packingCost + shufflingCost + vectorizeOpsBodyCost; + } + OperationCosts &operator+=(const OperationCosts &other) { + this->normalCost += other.normalCost; + this->packingCost += other.packingCost; + this->shufflingCost += other.shufflingCost; + this->vectorizeOpsBodyCost += other.vectorizeOpsBodyCost; + return *this; + } +}; + +class ArcCostModel { +public: + OperationCosts getCost(Operation *op); + +private: + OperationCosts computeOperationCost(Operation *op); + + // gets the cost to pack the vectors we have some cases we need to consider: + // 1: the input is scalar so we can give it a cost of 1 + // 2: the input is a result of another vector but with no shuffling so the + // is 0 + // 3: the input is a result of another vector but with some shuffling so + // the cost is the (number of out of order elements) * 2 + // 4: the input is a mix of some vectors: + // a) same order we multiply by 2 + // b) shuffling we multiply by 3 + OperationCosts getInputVectorsCost(VectorizeOp vecOp); + size_t getShufflingCost(const ValueRange &inputVec, bool isSame = false); + DenseMap opCostCache; +}; + +} // namespace arc +} // namespace circt + +#endif // CIRCT_DIALECT_ARC_ARCCOSTMODEL_H diff --git a/include/circt/Dialect/Arc/ArcOps.td b/include/circt/Dialect/Arc/ArcOps.td index bcaf65e56b85..02781979d217 100644 --- a/include/circt/Dialect/Arc/ArcOps.td +++ b/include/circt/Dialect/Arc/ArcOps.td @@ -134,6 +134,9 @@ def StateOp : ArcOp<"state", [ DeclareOpInterfaceMethods, AttrSizedOperandSegments, DeclareOpInterfaceMethods, + PredOpTrait<"types of initial arguments match result types", + CPred<[{getInitials().empty() || + llvm::equal(getInitials().getType(), getResults().getType())}]>> ]> { let summary = "State transfer arc"; @@ -143,13 +146,15 @@ def StateOp : ArcOp<"state", [ Optional:$enable, Optional:$reset, I32Attr:$latency, - Variadic:$inputs); + Variadic:$inputs, + Variadic:$initials); let results = (outs Variadic:$outputs); let assemblyFormat = [{ $arc `(` $inputs `)` (`clock` $clock^)? (`enable` $enable^)? - (`reset` $reset^)? `latency` $latency attr-dict - `:` functional-type($inputs, results) + (`reset` $reset^)? + ( `initial` ` ` `(` $initials^ `:` type($initials) `)`)? + `latency` $latency attr-dict `:` functional-type($inputs, results) }]; let hasFolder = 1; @@ -157,21 +162,24 @@ def StateOp : ArcOp<"state", [ let builders = [ OpBuilder<(ins "DefineOp":$arc, "mlir::Value":$clock, "mlir::Value":$enable, - "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs), [{ + "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials), [{ build($_builder, $_state, mlir::SymbolRefAttr::get(arc), arc.getFunctionType().getResults(), clock, enable, latency, - inputs); + inputs, initials); }]>, OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, arc, results, clock, enable, Value(), latency, - inputs); + inputs, initials); }]>, OpBuilder<(ins "mlir::SymbolRefAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "mlir::Value":$reset, - "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs + "unsigned":$latency, CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ if (clock) $_state.addOperands(clock); @@ -180,6 +188,7 @@ def StateOp : ArcOp<"state", [ if (reset) $_state.addOperands(reset); $_state.addOperands(inputs); + $_state.addOperands(initials); $_state.addAttribute("arc", arc); $_state.addAttribute("latency", $_builder.getI32IntegerAttr(latency)); $_state.addAttribute(getOperandSegmentSizeAttr(), @@ -187,23 +196,26 @@ def StateOp : ArcOp<"state", [ clock ? 1 : 0, enable ? 1 : 0, reset ? 1 : 0, - static_cast(inputs.size())})); + static_cast(inputs.size()), + static_cast(initials.size())})); $_state.addTypes(results); }]>, OpBuilder<(ins "mlir::StringAttr":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, mlir::SymbolRefAttr::get(arc), results, clock, - enable, latency, inputs); + enable, latency, inputs, initials); }]>, OpBuilder<(ins "mlir::StringRef":$arc, "mlir::TypeRange":$results, "mlir::Value":$clock, "mlir::Value":$enable, "unsigned":$latency, - CArg<"mlir::ValueRange", "{}">:$inputs + CArg<"mlir::ValueRange", "{}">:$inputs, + CArg<"mlir::ValueRange", "{}">:$initials ), [{ build($_builder, $_state, mlir::StringAttr::get($_builder.getContext(), arc), - results, clock, enable, latency, inputs); + results, clock, enable, latency, inputs, initials); }]> ]; let skipDefaultBuilders = 1; @@ -429,26 +441,37 @@ def ClockDomainOp : ArcOp<"clock_domain", [ let hasCanonicalizeMethod = 1; } -def ClockTreeOp : ArcOp<"clock_tree", [NoTerminator, NoRegionArguments]> { +//===----------------------------------------------------------------------===// +// (Pseudo) Clock Trees +//===----------------------------------------------------------------------===// + +class ClockTreeLikeOp traits = []>: + ArcOp +])> { + let regions = (region SizedRegion<1>:$body); +} + +def ClockTreeOp : ClockTreeLikeOp<"clock_tree"> { let summary = "A clock tree"; let arguments = (ins I1:$clock); - let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ $clock attr-dict-with-keyword $body }]; - let extraClassDeclaration = [{ - mlir::Block &getBodyBlock() { return getBody().front(); } - }]; } -def PassThroughOp : ArcOp<"passthrough", [NoTerminator, NoRegionArguments]> { +def PassThroughOp : ClockTreeLikeOp<"passthrough"> { let summary = "Clock-less logic that is on the pass-through path"; - let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ attr-dict-with-keyword $body }]; - let extraClassDeclaration = [{ - mlir::Block &getBodyBlock() { return getBody().front(); } +} + +def InitialOp : ClockTreeLikeOp<"initial"> { + let summary = "Clock-less logic called at the start of simulation"; + let assemblyFormat = [{ + attr-dict-with-keyword $body }]; } @@ -651,19 +674,22 @@ def TapOp : ArcOp<"tap"> { let assemblyFormat = [{ $value attr-dict `:` type($value) }]; } -def ModelOp : ArcOp<"model", [RegionKindInterface, IsolatedFromAbove, - NoTerminator, Symbol]> { +def ModelOp : ArcOp<"model", [ + RegionKindInterface, IsolatedFromAbove, NoTerminator, Symbol, + DeclareOpInterfaceMethods +]> { let summary = "A model with stratified clocks"; let description = [{ A model with stratified clocks. The `io` optional attribute specifies the I/O of the module associated to this model. }]; let arguments = (ins SymbolNameAttr:$sym_name, - TypeAttrOf:$io); + TypeAttrOf:$io, + OptionalAttr:$initialFn); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ - $sym_name `io` $io attr-dict-with-keyword $body + $sym_name `io` $io (`initializer` $initialFn^)? attr-dict-with-keyword $body }]; let extraClassDeclaration = [{ @@ -814,7 +840,7 @@ def VectorizeOp : ArcOp<"vectorize", [ ``` or SIMD vectors. ```mlir - %0:2 = arc.vectorize (%in0, %in1), (%in2, %in3) : + %0:2 = arc.vectorize (%in0, %in1), (%in2, %in3) : (i1, i1, i1, i1) -> (i1, i1) { ^bb0(%arg0: vector<2xi1>, %arg1: vector<2xi1>): %1 = arith.and %arg0, %arg1 : vector<2xi1> diff --git a/include/circt/Dialect/Arc/ArcPasses.h b/include/circt/Dialect/Arc/ArcPasses.h index d8cbb6f0c19a..bd185fa147ef 100644 --- a/include/circt/Dialect/Arc/ArcPasses.h +++ b/include/circt/Dialect/Arc/ArcPasses.h @@ -46,6 +46,7 @@ std::unique_ptr createLowerVectorizationsPass( LowerVectorizationsModeEnum mode = LowerVectorizationsModeEnum::Full); std::unique_ptr createMakeTablesPass(); std::unique_ptr createMuxToControlFlowPass(); +std::unique_ptr createPrintCostModelPass(); 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 3a44ce80b354..6a89f69c7b02 100644 --- a/include/circt/Dialect/Arc/ArcPasses.td +++ b/include/circt/Dialect/Arc/ArcPasses.td @@ -58,6 +58,24 @@ def Dedup : Pass<"arc-dedup", "mlir::ModuleOp"> { ]; } +def PrintCostModel : Pass<"arc-print-cost-model", "mlir::ModuleOp"> { + let summary = "A dymmy pass to test analysis passes"; + let constructor = "circt::arc::createPrintCostModelPass()"; + let dependentDialects = ["arc::ArcDialect"]; + let statistics = [ + Statistic<"moduleCost", "Operation(s)", + "Number of operations in the module">, + Statistic<"packingCost", "Pack operations(s)", + "Number of scalar to vector packking in the module">, + Statistic<"shufflingCost", "Shuffle operation(s)", + "Number of shuffles done to set up the VectorizeOps">, + Statistic<"vectoroizeOpsBodyCost", "VectorizeOps Body Cost", + "Number of operations inside the body of the VectorizeOps">, + Statistic<"allVectorizeOpsCost", "All VectorizeOps Cost", + "Total Cost of all VectorizeOps in the module"> + ]; +} + def FindInitialVectors : Pass<"arc-find-initial-vectors", "mlir::ModuleOp"> { let summary = "Find initial groups of vectorizable ops"; let constructor = "circt::arc::createFindInitialVectorsPass()"; diff --git a/include/circt/Dialect/Arc/ModelInfo.h b/include/circt/Dialect/Arc/ModelInfo.h index 322f38b774c5..ca3918a772f0 100644 --- a/include/circt/Dialect/Arc/ModelInfo.h +++ b/include/circt/Dialect/Arc/ModelInfo.h @@ -36,11 +36,13 @@ struct ModelInfo { std::string name; size_t numStateBytes; llvm::SmallVector states; + mlir::FlatSymbolRefAttr initialFnSym; ModelInfo(std::string name, size_t numStateBytes, - llvm::SmallVector states) + llvm::SmallVector states, + mlir::FlatSymbolRefAttr initialFnSym) : name(std::move(name)), numStateBytes(numStateBytes), - states(std::move(states)) {} + states(std::move(states)), initialFnSym(initialFnSym) {} }; /// Collects information about states within the provided Arc model storage diff --git a/include/circt/Dialect/ESI/ESIInterfaces.td b/include/circt/Dialect/ESI/ESIInterfaces.td index f37ccea5bccb..764a718ce6db 100644 --- a/include/circt/Dialect/ESI/ESIInterfaces.td +++ b/include/circt/Dialect/ESI/ESIInterfaces.td @@ -68,6 +68,14 @@ def IsManifestData : OpInterface<"IsManifestData"> { "Get the class name for this op.", "StringRef", "getManifestClass", (ins) >, + InterfaceMethod< + "Get the symbol to which this manifest data is referring, if any.", + "FlatSymbolRefAttr", "getSymbolRefAttr", (ins), + /*methodBody=*/"", + /*defaultImplementation=*/[{ + return FlatSymbolRefAttr(); + }] + >, InterfaceMethod< "Populate results with the manifest data.", "void", "getDetails", (ins "SmallVectorImpl&":$results), diff --git a/include/circt/Dialect/ESI/ESIManifest.td b/include/circt/Dialect/ESI/ESIManifest.td index 1fd3ef9089dc..1913016bd4d5 100644 --- a/include/circt/Dialect/ESI/ESIManifest.td +++ b/include/circt/Dialect/ESI/ESIManifest.td @@ -169,6 +169,23 @@ def AppIDHierNodeOp : ESI_Op<"manifest.hier_node", [ }]; } +def SymbolConstantsOp : ESI_Op<"manifest.constants", [ + DeclareOpInterfaceMethods]> { + let summary = "Constant values associated with a symbol"; + + let arguments = (ins FlatSymbolRefAttr:$symbolRef, + DictionaryAttr:$constants); + let assemblyFormat = [{ + $symbolRef $constants attr-dict + }]; + + let extraClassDeclaration = [{ + // Get information which needs to appear in the manifest for the host to + // connect to this service. + void getDetails(SmallVectorImpl &results); + }]; +} + def SymbolMetadataOp : ESI_Op<"manifest.sym", [ DeclareOpInterfaceMethods]> { let summary = "Metadata about a symbol"; diff --git a/include/circt/Dialect/FIRRTL/AnnotationDetails.h b/include/circt/Dialect/FIRRTL/AnnotationDetails.h index b35215619ad7..90da1f5abff8 100644 --- a/include/circt/Dialect/FIRRTL/AnnotationDetails.h +++ b/include/circt/Dialect/FIRRTL/AnnotationDetails.h @@ -159,6 +159,11 @@ constexpr const char *elaborationArtefactsDirectoryAnnoClass = constexpr const char *testHarnessPathAnnoClass = "sifive.enterprise.firrtl.TestHarnessPathAnnotation"; /// Annotation that marks a reset (port or wire) and domain. +constexpr const char *fullResetAnnoClass = "circt.FullResetAnnotation"; +/// Annotation that marks a module as not belonging to any reset domain. +constexpr const char *excludeFromFullResetAnnoClass = + "circt.ExcludeFromFullResetAnnotation"; +/// Annotation that marks a reset (port or wire) and domain. constexpr const char *fullAsyncResetAnnoClass = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"; /// Annotation that marks a module as not belonging to any reset domain. diff --git a/include/circt/Dialect/FIRRTL/CMakeLists.txt b/include/circt/Dialect/FIRRTL/CMakeLists.txt index 6b598d85a012..c1bee8852f5c 100644 --- a/include/circt/Dialect/FIRRTL/CMakeLists.txt +++ b/include/circt/Dialect/FIRRTL/CMakeLists.txt @@ -36,6 +36,7 @@ add_circt_doc(FIRRTLStructure Dialects/FIRRTLStructureOps -gen-op-doc) add_circt_doc(FIRRTLDeclarations Dialects/FIRRTLDeclarationOps -gen-op-doc) add_circt_doc(FIRRTLStatements Dialects/FIRRTLStatementOps -gen-op-doc) add_circt_doc(FIRRTLExpressions Dialects/FIRRTLExpressionOps -gen-op-doc) +add_circt_doc(FIRRTLIntrinsics Dialects/FIRRTLIntrinsicOps -gen-op-doc) add_circt_doc(FIRRTLTypes Dialects/FIRRTLTypes -gen-typedef-doc) add_circt_doc(FIRRTLTypesImpl Dialects/FIRRTLTypesImpl -gen-typedef-doc) add_circt_doc(FIRRTLOpInterfaces Dialects/FIRRTLOpInterfaces -gen-op-interface-docs) diff --git a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td index 1fd8847385ac..9b184a96868d 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLDeclarations.td @@ -36,10 +36,50 @@ class ReferableDeclOp traits = []> : /// located in a hardware-creating context, such as the body of a module. class HardwareDeclOp traits = []> : ReferableDeclOp]> {} +def FormalOp : FIRRTLOp<"formal", [ + HasParent<"firrtl::CircuitOp">, + DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods +]> { + let summary = "A formal test definition."; + let description = [{ + The `firrtl.formal` operation defines a formal verification problem in the same + context as the rest of the design. This problem is solved using bounded model + checking and should be given a bound k, which represents the number of cycles considered + during model checking. This definition marks a test harness defined as an internal module to + be verified using bounded model checking. + + Example: + ```mlir + // DUT + firrtl.module @Foo(in %bar: !firrtl.uint<8>, out %out: !firrtl.uint<8>) { ... } + + // Test harness + firrtl.module @FooTest(in %bar_s: !firrtl.uint<8>) { + %bar, %out = firrtl.instance foo @Foo(in bar: %bar_s: !firrtl.uint<8>, out out: !firrtl.uint<8>) + %c42_8 = firrtl.constant 42 : !firrtl.uint<8> + firrtl.connect %bar, %c42_8: !firrtl.uint<8>, !firrtl.uint<8> + %c69_8 = firrtl.constant 69 : !firrtl.uint<8> + %cond = firrtl.eq %c69_8, %out : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<1> + firrtl.assert %cond + } + + // Mark test harness as formal test + firrtl.formal @formal1 of @FooTest bound 20 + ``` + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, FlatSymbolRefAttr:$moduleName, UI64Attr:$bound); + let results = (outs); + let assemblyFormat = [{ + $sym_name `of` $moduleName `bound` $bound attr-dict + }]; +} + def InstanceOp : HardwareDeclOp<"instance", [ DeclareOpInterfaceMethods, DeclareOpInterfaceMethods, diff --git a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td index f435e86ef7ce..359e64d86571 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLExpressions.td @@ -1154,6 +1154,23 @@ def ListCreateOp : FIRRTLOp<"list.create", [Pure, SameTypeOperands]> { let hasVerifier = 1; } +def ListConcatOp : FIRRTLOp<"list.concat", [Pure, SameOperandsAndResultType]> { + let summary = "Concatenate multiple lists to produce a new list"; + let description = [{ + Produces a value of list type by concatenating the provided lists. + + Example: + ```mlir + %3 = firrtl.list.concat %0, %1, %2 : !firrtl.list + ``` + }]; + + let arguments = (ins Variadic:$subLists); + let results = (outs ListType:$result); + + let assemblyFormat = "$subLists attr-dict `:` type($result)"; +} + def BoolConstantOp : FIRRTLOp<"bool", [Pure, ConstantLike]> { let summary = "Produce a constant boolean value"; let description = [{ diff --git a/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td b/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td index 4e6082eb37d1..99aa2770d51c 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLIntrinsics.td @@ -13,7 +13,11 @@ #ifndef CIRCT_DIALECT_FIRRTL_FIRRTLINTRINSICS_TD #define CIRCT_DIALECT_FIRRTL_FIRRTLINTRINSICS_TD +include "FIRRTLAttributes.td" include "FIRRTLDialect.td" +include "FIRRTLTypes.td" +include "circt/Dialect/HW/HWOpInterfaces.td" +include "mlir/Interfaces/SideEffectInterfaces.td" //===----------------------------------------------------------------------===// // Generic intrinsic operation for parsing into before lowering. diff --git a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td index 80ad66b5082e..dbd23c955ff3 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLStructure.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLStructure.td @@ -445,7 +445,7 @@ def LayerOp : FIRRTLOp< let results = (outs); let regions = (region SizedRegion<1>:$body); let assemblyFormat = [{ - $sym_name $convention attr-dict-with-keyword $body + $sym_name `` $convention attr-dict-with-keyword $body }]; } diff --git a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h index 03a2466d2b52..5f9aa0ddac9e 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h +++ b/include/circt/Dialect/FIRRTL/FIRRTLVisitors.h @@ -65,8 +65,8 @@ class ExprVisitor { UninferredResetCastOp, ConstCastOp, RefCastOp, // Property expressions. StringConstantOp, FIntegerConstantOp, BoolConstantOp, - DoubleConstantOp, ListCreateOp, UnresolvedPathOp, PathOp, - IntegerAddOp, IntegerMulOp, IntegerShrOp>( + DoubleConstantOp, ListCreateOp, ListConcatOp, UnresolvedPathOp, + PathOp, IntegerAddOp, IntegerMulOp, IntegerShrOp>( [&](auto expr) -> ResultType { return thisCast->visitExpr(expr, args...); }) @@ -219,6 +219,7 @@ class ExprVisitor { HANDLE(BoolConstantOp, Unhandled); HANDLE(DoubleConstantOp, Unhandled); HANDLE(ListCreateOp, Unhandled); + HANDLE(ListConcatOp, Unhandled); HANDLE(PathOp, Unhandled); HANDLE(UnresolvedPathOp, Unhandled); HANDLE(IntegerAddOp, Unhandled); diff --git a/include/circt/Dialect/FIRRTL/Passes.h b/include/circt/Dialect/FIRRTL/Passes.h index d3dd11735179..1ae1e07e5358 100644 --- a/include/circt/Dialect/FIRRTL/Passes.h +++ b/include/circt/Dialect/FIRRTL/Passes.h @@ -214,6 +214,8 @@ std::unique_ptr createLowerDPIPass(); std::unique_ptr createAssignOutputDirsPass(mlir::StringRef outputDir = ""); +std::unique_ptr createCheckRecursiveInstantiation(); + /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION #include "circt/Dialect/FIRRTL/Passes.h.inc" diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index 4af7367449b7..ec7043fb6a97 100644 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -397,11 +397,13 @@ def InferResets : Pass<"firrtl-infer-resets", "firrtl::CircuitOp"> { let summary = "Infer reset synchronicity and add implicit resets"; let description = [{ This pass infers whether resets are synchronous or asynchronous, and extends - reset-less registers with an asynchronous reset based on the following + reset-less registers with a reset based on the following annotations: - - `sifive.enterprise.firrtl.FullAsyncResetAnnotation` - - `sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation` + - `circt.FullResetAnnotation` + - `circt.ExcludeFromFullResetAnnotation` + - `sifive.enterprise.firrtl.FullAsyncResetAnnotation` (deprecated) + - `sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation` (deprecated) }]; let constructor = "circt::firrtl::createInferResetsPass()"; } @@ -968,4 +970,18 @@ def ProbesToSignals : Pass<"firrtl-probes-to-signals", "firrtl::CircuitOp"> { let constructor = "circt::firrtl::createProbesToSignalsPass()"; } +def CheckRecursiveInstantiation : Pass<"firrtl-check-recursive-instantiation", + "firrtl::CircuitOp"> { + let summary = "Check for illegal recursive instantiation"; + let description = [{ + This pass checks for illegal recursive module instantion. Recursive + instantiation is when a module instantiates itself, either directly or + indirectly through other modules it instantiates. Recursive module + instantiation is illegal because it would require infinite hardware to + synthesize. Recursive class instantiation is illegal as it would create an + infinite loop. + }]; + let constructor = "circt::firrtl::createCheckRecursiveInstantiation()"; +} + #endif // CIRCT_DIALECT_FIRRTL_PASSES_TD diff --git a/include/circt/Dialect/HW/HWTypes.td b/include/circt/Dialect/HW/HWTypes.td index f2f8c1c8bceb..431ed6c34857 100644 --- a/include/circt/Dialect/HW/HWTypes.td +++ b/include/circt/Dialect/HW/HWTypes.td @@ -39,7 +39,7 @@ def HWNonInOutType : DialectType($_self)">, - "InOutType", "InOutType">; + "InOutType", "::circt::hw::InOutType">; class InOutTypeOf allowedTypes> : ContainerType, CPred<"::circt::hw::type_isa<::circt::hw::InOutType>($_self)">, @@ -62,7 +62,7 @@ def StructType : DialectType($_self)">, - "a UnionType", "::circt::hw::TypeAliasOr">; + "a UnionType", "::circt::hw::TypeAliasOr<::circt::hw::UnionType>">; // A handle to refer to circt::hw::EnumType in ODS. def EnumType : DialectType { let description = [{ The `icmp` operation compares two integers using a predicate. If the predicate is true, returns 1, otherwise returns 0. This operation always - returns a one bit wide result of type `ui1`. Both operand types may be + returns a one bit wide result of type `i1`. Both operand types may be signed or unsigned scalar integer types of arbitrary bitwidth. | LHS type | RHS type | Comparison type | Result type | | :------- | :------- | :--------------------------------------- | :---------- | - | `ui` | `ui` | `ui`, *r* = max(*a*, *b*) | `ui1` | - | `si` | `si` | `si`, *r* = max(*a*, *b*) | `ui1` | - | `ui` | `si` | `si`, *r* = *a* + 1 **if** *a* ≥ *b* | `ui1` | - | | | `si`, *r* = *b* **if** *a* < *b* | `ui1` | - | `si` | `ui` | Same as `ui si` | `ui1` | + | `ui` | `ui` | `ui`, *r* = max(*a*, *b*) | `i1` | + | `si` | `si` | `si`, *r* = max(*a*, *b*) | `i1` | + | `ui` | `si` | `si`, *r* = *a* + 1 **if** *a* ≥ *b* | `i1` | + | | | `si`, *r* = *b* **if** *a* < *b* | `i1` | + | `si` | `ui` | Same as `ui si` | `i1` | Examples: ```mlir @@ -248,7 +248,7 @@ def ICmpOp : HWArithOp<"icmp", [Pure]> { let arguments = (ins ICmpPredicate:$predicate, HWArithIntegerType:$lhs, HWArithIntegerType:$rhs); - let results = (outs UI1:$result); + let results = (outs I1:$result); let assemblyFormat = "$predicate $lhs `,` $rhs attr-dict `:` type($lhs) `,` type($rhs)"; } diff --git a/include/circt/Dialect/LLHD/IR/CMakeLists.txt b/include/circt/Dialect/LLHD/IR/CMakeLists.txt index 0f62129db938..93d0696c504d 100644 --- a/include/circt/Dialect/LLHD/IR/CMakeLists.txt +++ b/include/circt/Dialect/LLHD/IR/CMakeLists.txt @@ -5,7 +5,9 @@ set(LLVM_TARGET_DEFINITIONS LLHD.td) mlir_tablegen(LLHDEnums.h.inc -gen-enum-decls) mlir_tablegen(LLHDEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(CIRCTLLHDEnumsIncGen) +add_dependencies(circt-headers CIRCTLLHDEnumsIncGen) mlir_tablegen(LLHDAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect=llhd) mlir_tablegen(LLHDAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect=llhd) add_public_tablegen_target(CIRCTLLHDAttributesIncGen) +add_dependencies(circt-headers CIRCTLLHDAttributesIncGen) diff --git a/include/circt/Dialect/LLHD/IR/LLHD.td b/include/circt/Dialect/LLHD/IR/LLHD.td index 8c3b1031b907..0c6a6bc2734e 100644 --- a/include/circt/Dialect/LLHD/IR/LLHD.td +++ b/include/circt/Dialect/LLHD/IR/LLHD.td @@ -10,8 +10,8 @@ // //===----------------------------------------------------------------------===// -#ifndef CIRCT_DIALECT_LLHD_IR_LLHD -#define CIRCT_DIALECT_LLHD_IR_LLHD +#ifndef CIRCT_DIALECT_LLHD_IR_LLHD_TD +#define CIRCT_DIALECT_LLHD_IR_LLHD_TD include "circt/Dialect/HW/HWTypes.td" include "mlir/IR/AttrTypeBase.td" @@ -24,97 +24,18 @@ include "mlir/Interfaces/InferTypeOpInterface.td" include "mlir/Interfaces/FunctionInterfaces.td" include "mlir/IR/SymbolInterfaces.td" -//===----------------------------------------------------------------------===// -// LLHD dialect definition -//===----------------------------------------------------------------------===// - -def LLHD_Dialect : Dialect { - let name = "llhd"; - let cppNamespace = "::circt::llhd"; - let dependentDialects = ["circt::hw::HWDialect"]; - - let description = [{ - A low-level hardware description dialect in MLIR. - }]; - - let hasConstantMaterializer = 1; - let useDefaultTypePrinterParser = 1; - let useDefaultAttributePrinterParser = 1; - - // Opt-out of properties for now, must migrate by LLVM 19. #5273. - let usePropertiesForAttributes = 0; - - let extraClassDeclaration = [{ - /// Register all LLHD types. - void registerTypes(); - /// Register all LLHD attributes. - void registerAttributes(); - }]; -} - -//===----------------------------------------------------------------------===// -// Import HW Types -//===----------------------------------------------------------------------===// - +include "circt/Dialect/LLHD/IR/LLHDDialect.td" include "circt/Dialect/HW/HWTypes.td" - -//===----------------------------------------------------------------------===// -// LLHD type definitions -//===----------------------------------------------------------------------===// - -include "LLHDTypesImpl.td" - -// LLHD Time Type -def LLHD_TimeType : DialectType($_self)">, "LLHD time type", "TimeType">, - BuildableType<"TimeType::get($_builder.getContext())">; - -// Legal underlying types for signals and pointers. -def LLHD_AnyElementType : - AnyTypeOf<[HWIntegerType, ArrayType, StructType]>; - -// LLHD ptr type -class LLHD_PtrType allowedTypes> - : ContainerType, CPred<"llvm::isa($_self)">, - "llvm::cast($_self).getElementType()", "LLHD pointer type">; - -def LLHD_AnyPtrType : LLHD_PtrType<[LLHD_AnyElementType]>; - -def LLHD_AnySigOrPtrType : AnyTypeOf<[LLHD_AnyPtrType, InOutType]>; - -//===----------------------------------------------------------------------===// -// LLHD op definition -//===----------------------------------------------------------------------===// - -// Base class for all LLHD ops. -class LLHD_Op traits = []> - : Op { - - // For each LLHD op, the following static functions need to be defined in - // LLHDOps.cpp: - // - // * static ParseResult parse(OpAsmParser &parser, - // OperationState &state); - // * static void print(OpAsmPrinter &p, op) - let hasCustomAssemblyFormat = 1; -} - -//===----------------------------------------------------------------------===// -// LLHD trait definitions -//===----------------------------------------------------------------------===// - -class SameTypeArbitraryWidth - : PredOpTrait>; +include "circt/Dialect/LLHD/IR/LLHDTypes.td" //===----------------------------------------------------------------------===// // LLHD Operations //===----------------------------------------------------------------------===// -include "ValueOps.td" -include "SignalOps.td" -include "ExtractOps.td" -include "StructureOps.td" -include "MemoryOps.td" +include "circt/Dialect/LLHD/IR/LLHDValueOps.td" +include "circt/Dialect/LLHD/IR/LLHDSignalOps.td" +include "circt/Dialect/LLHD/IR/LLHDExtractOps.td" +include "circt/Dialect/LLHD/IR/LLHDStructureOps.td" +include "circt/Dialect/LLHD/IR/LLHDMemoryOps.td" -#endif // CIRCT_DIALECT_LLHD_IR_LLHD +#endif // CIRCT_DIALECT_LLHD_IR_LLHD_TD diff --git a/include/circt/Dialect/LLHD/IR/LLHDDialect.td b/include/circt/Dialect/LLHD/IR/LLHDDialect.td new file mode 100644 index 000000000000..108e509ea427 --- /dev/null +++ b/include/circt/Dialect/LLHD/IR/LLHDDialect.td @@ -0,0 +1,49 @@ +//===- LLHDDialect.td - LLHD 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 LLHD dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_TD +#define CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_TD + +include "mlir/IR/DialectBase.td" + +//===----------------------------------------------------------------------===// +// LLHD dialect definition +//===----------------------------------------------------------------------===// + +def LLHDDialect : Dialect { + let name = "llhd"; + let cppNamespace = "::circt::llhd"; + let dependentDialects = ["circt::hw::HWDialect"]; + + let description = [{ + A low-level hardware description dialect in MLIR. + }]; + + let hasConstantMaterializer = 1; + let useDefaultTypePrinterParser = 1; + let useDefaultAttributePrinterParser = 1; + + // Opt-out of properties for now, must migrate by LLVM 19. #5273. + let usePropertiesForAttributes = 0; + + let extraClassDeclaration = [{ + /// Register all LLHD types. + void registerTypes(); + /// Register all LLHD attributes. + void registerAttributes(); + }]; +} + +class LLHDOp traits = []> + : Op; + +#endif // CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_TD diff --git a/include/circt/Dialect/LLHD/IR/ExtractOps.td b/include/circt/Dialect/LLHD/IR/LLHDExtractOps.td similarity index 90% rename from include/circt/Dialect/LLHD/IR/ExtractOps.td rename to include/circt/Dialect/LLHD/IR/LLHDExtractOps.td index 121fc80e390b..955e63e0aaf2 100644 --- a/include/circt/Dialect/LLHD/IR/ExtractOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDExtractOps.td @@ -1,4 +1,4 @@ -//===- ExtractOps.td - LLHD extract operations -------------*- tablegen -*-===// +//===- LLHDExtractOps.td - LLHD extract operations ---------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -18,8 +18,8 @@ class SigPtrIndexBitWidthConstraint : TypesMatchWith<"Index width should be exactly clog2 (size of array)", input, index, [{ - IntegerType::get($_self.getContext(), std::max( - llvm::Log2_64_Ceil(llhd::getLLHDTypeWidth($_self)), 1)) + IntegerType::get($_self.getContext(), + llvm::Log2_64_Ceil(llhd::getLLHDTypeWidth($_self))) }]>; class SigArrayElementTypeConstraint @@ -47,7 +47,7 @@ class SmallerOrEqualResultTypeWidthConstraint // Integer Operations //===----------------------------------------------------------------------===// -def LLHD_SigExtractOp : LLHD_Op<"sig.extract", +def SigExtractOp : LLHDOp<"sig.extract", [Pure, SmallerOrEqualResultTypeWidthConstraint<"result", "input">, SigPtrIndexBitWidthConstraint<"lowBit", "input">]> { @@ -74,7 +74,7 @@ def LLHD_SigExtractOp : LLHD_Op<"sig.extract", let hasFolder = true; } -def LLHD_PtrExtractOp : LLHD_Op<"ptr.extract", +def PtrExtractOp : LLHDOp<"ptr.extract", [Pure, SmallerOrEqualResultTypeWidthConstraint<"result", "input">, SigPtrIndexBitWidthConstraint<"lowBit", "input">]> { @@ -86,9 +86,9 @@ def LLHD_PtrExtractOp : LLHD_Op<"ptr.extract", operand. The result length is defined by the result type. }]; - let arguments = (ins LLHD_PtrType<[HWIntegerType]>:$input, + let arguments = (ins LLHDPtrTypeOf<[HWIntegerType]>:$input, HWIntegerType:$lowBit); - let results = (outs LLHD_PtrType<[HWIntegerType]>: $result); + let results = (outs LLHDPtrTypeOf<[HWIntegerType]>: $result); let assemblyFormat = [{ $input `from` $lowBit attr-dict `:` functional-type($input, $result) @@ -105,7 +105,7 @@ def LLHD_PtrExtractOp : LLHD_Op<"ptr.extract", // Array Operations //===----------------------------------------------------------------------===// -def LLHD_SigArraySliceOp : LLHD_Op<"sig.array_slice", +def SigArraySliceOp : LLHDOp<"sig.array_slice", [Pure, SmallerOrEqualResultTypeWidthConstraint<"result", "input">, SameSigPtrArrayElementTypeConstraint<"result", "input">, @@ -156,7 +156,7 @@ def LLHD_SigArraySliceOp : LLHD_Op<"sig.array_slice", let hasCanonicalizeMethod = true; } -def LLHD_PtrArraySliceOp : LLHD_Op<"ptr.array_slice", +def PtrArraySliceOp : LLHDOp<"ptr.array_slice", [Pure, SmallerOrEqualResultTypeWidthConstraint<"result", "input">, SameSigPtrArrayElementTypeConstraint<"result", "input">, @@ -183,9 +183,9 @@ def LLHD_PtrArraySliceOp : LLHD_Op<"ptr.array_slice", ``` }]; - let arguments = (ins LLHD_PtrType<[ArrayType]>: $input, + let arguments = (ins LLHDPtrTypeOf<[ArrayType]>: $input, HWIntegerType: $lowIndex); - let results = (outs LLHD_PtrType<[ArrayType]>: $result); + let results = (outs LLHDPtrTypeOf<[ArrayType]>: $result); let assemblyFormat = [{ $input `at` $lowIndex attr-dict `:` functional-type($input, $result) @@ -207,7 +207,7 @@ def LLHD_PtrArraySliceOp : LLHD_Op<"ptr.array_slice", let hasCanonicalizeMethod = true; } -def LLHD_SigArrayGetOp : LLHD_Op<"sig.array_get", +def SigArrayGetOp : LLHDOp<"sig.array_get", [Pure, SigPtrIndexBitWidthConstraint<"index", "input">, SigArrayElementTypeConstraint<"result", "input">]> { @@ -239,7 +239,7 @@ def LLHD_SigArrayGetOp : LLHD_Op<"sig.array_get", }]; } -def LLHD_PtrArrayGetOp : LLHD_Op<"ptr.array_get", +def PtrArrayGetOp : LLHDOp<"ptr.array_get", [Pure, SigPtrIndexBitWidthConstraint<"index", "input">, PtrArrayElementTypeConstraint<"result", "input">]> { @@ -258,8 +258,8 @@ def LLHD_PtrArrayGetOp : LLHD_Op<"ptr.array_get", ``` }]; - let arguments = (ins LLHD_PtrType<[ArrayType]>:$input, HWIntegerType:$index); - let results = (outs LLHD_PtrType<[HWNonInOutType]>: $result); + let arguments = (ins LLHDPtrTypeOf<[ArrayType]>:$input, HWIntegerType:$index); + let results = (outs LLHDPtrTypeOf<[HWNonInOutType]>: $result); let assemblyFormat = "$input `[` $index `]` attr-dict `:` qualified(type($input))"; @@ -275,7 +275,7 @@ def LLHD_PtrArrayGetOp : LLHD_Op<"ptr.array_get", // Structure Operations //===----------------------------------------------------------------------===// -def LLHD_SigStructExtractOp : LLHD_Op<"sig.struct_extract", [Pure, +def SigStructExtractOp : LLHDOp<"sig.struct_extract", [Pure, DeclareOpInterfaceMethods, InferTypeOpInterface]> { let summary = "Extract a field from a signal of a struct."; let description = [{ @@ -305,7 +305,7 @@ def LLHD_SigStructExtractOp : LLHD_Op<"sig.struct_extract", [Pure, }]; } -def LLHD_PtrStructExtractOp : LLHD_Op<"ptr.struct_extract", [Pure, +def PtrStructExtractOp : LLHDOp<"ptr.struct_extract", [Pure, DeclareOpInterfaceMethods, InferTypeOpInterface]> { let summary = "Extract a field from a pointer to a struct."; let description = [{ @@ -322,8 +322,8 @@ def LLHD_PtrStructExtractOp : LLHD_Op<"ptr.struct_extract", [Pure, ``` }]; - let arguments = (ins LLHD_PtrType<[StructType]>:$input, StrAttr:$field); - let results = (outs LLHD_PtrType<[HWNonInOutType]>:$result); + let arguments = (ins LLHDPtrTypeOf<[StructType]>:$input, StrAttr:$field); + let results = (outs LLHDPtrTypeOf<[HWNonInOutType]>:$result); let assemblyFormat = "$input `[` $field `]` attr-dict `:` qualified(type($input))"; diff --git a/include/circt/Dialect/LLHD/IR/MemoryOps.td b/include/circt/Dialect/LLHD/IR/LLHDMemoryOps.td similarity index 65% rename from include/circt/Dialect/LLHD/IR/MemoryOps.td rename to include/circt/Dialect/LLHD/IR/LLHDMemoryOps.td index 95ff53306758..12c50def8dce 100644 --- a/include/circt/Dialect/LLHD/IR/MemoryOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDMemoryOps.td @@ -1,4 +1,4 @@ -//===- MemoryOps.td - LLHD memory operations ---------------*- tablegen -*-===// +//===- LLHDMemoryOps.td - LLHD memory operations -----------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -def LLHD_VarOp : LLHD_Op<"var", [ +def VarOp : LLHDOp<"var", [ TypesMatchWith< "type of 'init' and underlying type of 'result' have to match.", "init", "result", "PtrType::get($_self)"> @@ -21,25 +21,22 @@ def LLHD_VarOp : LLHD_Op<"var", [ initial value given by `init`, and returns a pointer to the allocated region. - **Examples:** + Example: ``` - %int = llhd.const 0 : i32 - %arr = llhd.array_uniform %int : !llhd.array<3xi32> - + %int = hw.constant 0 : i32 %iPtr = llhd.var %int : i32 - %arrPtr = llhd.var %arr : !llhd.array<3xi32> ``` }]; - let arguments = (ins LLHD_AnyElementType: $init); - let results = (outs Res: $result); let assemblyFormat = "$init attr-dict `:` qualified(type($init))"; } -def LLHD_LoadOp : LLHD_Op<"load", [ +def LoadOp : LLHDOp<"load", [ TypesMatchWith< "type of 'result' and underlying type of 'pointer' have to match.", "pointer", "result", "llvm::cast($_self).getElementType()"> @@ -49,27 +46,24 @@ def LLHD_LoadOp : LLHD_Op<"load", [ The `llhd.load` operation loads a value from a memory region given by `pointer`. - **Examples:** + Example: ``` - %int = llhd.const 0 : i32 - %arr = llhd.array_uniform %int : !llhd.array<3xi32> + %int = hw.constant 0 : i32 %iPtr = llhd.var %int : i32 - %arrPtr = llhd.var %arr : !llhd.array<3xi32> %iLd = llhd.load %iPtr : !llhd.ptr - %arrLd = llhd.load %arrPtr : !llhd.ptr> ``` }]; - let arguments = (ins Arg: $pointer); - let results = (outs LLHD_AnyElementType: $result); + let results = (outs HWValueType: $result); let assemblyFormat = "$pointer attr-dict `:` qualified(type($pointer))"; } -def LLHD_StoreOp : LLHD_Op<"store", [ +def StoreOp : LLHDOp<"store", [ TypesMatchWith< "type of 'value' and underlying type of 'pointer' have to match.", "pointer", "value", "llvm::cast($_self).getElementType()"> @@ -79,22 +73,19 @@ def LLHD_StoreOp : LLHD_Op<"store", [ The `llhd.store` operation stores the value `value` to the memory region given by `pointer`. - **Examples:** + Example: ``` - %int = llhd.const 0 : i32 - %arr = llhd.array_uniform %int : !llhd.array<3xi32> + %int = hw.constant 0 : i32 %iPtr = llhd.var %int : i32 - %arrPtr = llhd.var %arr : !llhd.array<3xi32> llhd.store %iPtr, %int : !llhd.ptr - llhd.store %arrPtr, %arr : !llhd.ptr> ``` }]; - let arguments = (ins Arg: $pointer, - LLHD_AnyElementType: $value); + HWValueType: $value); let assemblyFormat = "$pointer `,` $value attr-dict `:` qualified(type($pointer))"; } diff --git a/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td new file mode 100644 index 000000000000..ae9c60bd0cc8 --- /dev/null +++ b/include/circt/Dialect/LLHD/IR/LLHDSignalOps.td @@ -0,0 +1,174 @@ +//===- LLHDSignalOps.td - LLHD signal operations -----------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This describes the MLIR ops for LLHD signal creation and manipulation. +// +//===----------------------------------------------------------------------===// + +include "mlir/IR/EnumAttr.td" +include "mlir/IR/OpAsmInterface.td" + +def SignalOp : LLHDOp<"sig", [ + DeclareOpInterfaceMethods, + TypesMatchWith< + "type of 'init' and underlying type of 'signal' have to match.", + "init", "result", "hw::InOutType::get($_self)"> +]> { + let summary = "Create a signal."; + let description = [{ + The `llhd.sig` instruction introduces a new signal in the IR. The input + operand determines the initial value carried by the signal, while the + result type will always be a signal carrying the type of the init operand. + A signal defines a unique name within the entity it resides in. + + Example: + + ```mlir + %c123_i64 = hw.constant 123 : i64 + %foo = llhd.sig %c123_i64 : i64 + %0 = llhd.sig name "foo" %c123_i64 : i64 + ``` + + This example creates a new signal named "foo", carrying an `i64` type with + initial value of 123. + }]; + let arguments = (ins + OptionalAttr:$name, + HWValueType:$init + ); + let results = (outs Res:$result); + let assemblyFormat = [{ + `` custom($name) $init attr-dict + `:` type($init) + }]; +} + +def PrbOp : LLHDOp<"prb", [ + TypesMatchWith< + "type of 'result' and underlying type of 'signal' have to match.", + "signal", "result", "llvm::cast($_self).getElementType()"> + ]> { + let summary = "Probe a signal."; + let description = [{ + This operation probes a signal and returns the value it + currently carries as a new SSA operand. The result type is always + the type carried by the signal. + + Example: + + ```mlir + %true = hw.constant true + %sig_i1 = llhd.sig %true : i1 + %prbd = llhd.prb %sig_i1 : !hw.inout + ``` + }]; + + let arguments = (ins Arg: $signal); + let results = (outs HWValueType: $result); + + let assemblyFormat = "$signal attr-dict `:` qualified(type($signal))"; +} + +def OutputOp : LLHDOp<"output", [ + TypesMatchWith< + "type of 'value' and underlying type of 'result' have to match.", + "value", "result", "hw::InOutType::get($_self)"> + ]> { + let summary = "Introduce a new signal and drive a value onto it."; + let description = [{ + The `llhd.output` operation introduces a new signal and continuously + drives a the given value onto it after a given time-delay. The same + value is used to initialize the signal in the same way as the 'init' + value in `llhd.sig`. An optional name can be given to the created signal. + This shows up, e.g., in the simulation trace. + + Example: + + ```mlir + %value = hw.constant true + %time = llhd.constant_time <1ns, 0d, 0e> + %sig = llhd.output "sigName" %value after %time : i1 + + // is equivalent to + + %value = hw.constant true + %time = llhd.constant_time <1ns, 0d, 0e> + %sig = llhd.sig "sigName" %value : i1 + llhd.drv %sig, %value after %time : !hw.inout + ``` + }]; + + let arguments = (ins OptionalAttr: $name, + HWValueType: $value, + LLHDTimeType: $time); + + let results = (outs InOutType: $result); + + let assemblyFormat = [{ + ( $name^ )? $value `after` $time attr-dict `:` qualified(type($value)) + }]; +} + +def DrvOp : LLHDOp<"drv", [ + TypesMatchWith< + "type of 'value' and underlying type of 'signal' have to match.", + "signal", "value", "llvm::cast($_self).getElementType()"> + ]> { + let summary = "Drive a value into a signal."; + let description = [{ + The `llhd.drv` operation drives a new value onto a signal. A time + operand also has to be passed, which specifies the frequency at which + the drive will be performed. An optional enable value can be passed as + last argument. In this case the drive will only be performed if the + value is 1. In case no enable signal is passed the drive will always be + performed. This operation does not define any new SSA operands. + + Example: + + ```mlir + %true = hw.constant true + %false = hw.constant false + %time = llhd.constant_time <1ns, 0d, 0e> + %sig = llhd.sig %true : i1 + + llhd.drv %sig, %false after %time : !hw.inout + llhd.drv %sig, %false after %time if %true : !hw.inout + ``` + }]; + + let arguments = (ins Arg: $signal, + HWValueType: $value, + LLHDTimeType: $time, + Optional: $enable); + + let assemblyFormat = [{ + $signal `,` $value `after` $time ( `if` $enable^ )? attr-dict `:` + qualified(type($signal)) + }]; + + let hasFolder = 1; + let hasCanonicalizeMethod = 1; +} + +def DelayOp : LLHDOp<"delay", [Pure, SameOperandsAndResultType]> { + let summary = "specifies value propagation delay"; + let description = [{ + This operation propagates all value changes of the input to the output after + the specified time delay. + Reference values are not supported (e.g., pointers, inout, etc.) + since the store-like operation used for those types should encode a delayed + store. + }]; + + let arguments = (ins HWNonInOutType:$input, LLHDTimeAttr:$delay); + let results = (outs HWNonInOutType:$result); + + let assemblyFormat = "$input `by` $delay attr-dict `:` type($result)"; +} diff --git a/include/circt/Dialect/LLHD/IR/StructureOps.td b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td similarity index 70% rename from include/circt/Dialect/LLHD/IR/StructureOps.td rename to include/circt/Dialect/LLHD/IR/LLHDStructureOps.td index f56e11ce641a..c288c68e559b 100644 --- a/include/circt/Dialect/LLHD/IR/StructureOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDStructureOps.td @@ -1,4 +1,4 @@ -//===- StructureOps.td - Process and Entity definitions ----*- tablegen -*-===// +//===- LLHDStructureOps.td - Process and Entity defs -------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,8 +10,9 @@ // //===----------------------------------------------------------------------===// -def LLHD_ProcessOp : LLHD_Op<"process", [ +def ProcessOp : LLHDOp<"process", [ NoRegionArguments, + RecursiveMemoryEffects, HasParent<"hw::HWModuleOp"> ]> { let summary = "create a process"; @@ -51,7 +52,40 @@ def LLHD_ProcessOp : LLHD_Op<"process", [ let assemblyFormat = "attr-dict-with-keyword $body"; } -def LLHD_ConnectOp : LLHD_Op<"con", [ +def FinalOp : LLHDOp<"final", [ + NoRegionArguments, + RecursiveMemoryEffects, + HasParent<"hw::HWModuleOp">, +]> { + let summary = "A process that runs at the end of simulation"; + let description = [{ + An `llhd.final` op encapsulates a region of IR that is to be executed after + the last time step of a simulation has completed. This can be used to + implement various forms of state cleanup and tear-down. Some verifications + ops may also want to check that certain final conditions hold at the end of + a simulation run. + + The `llhd.wait` terminator is not allowed in `llhd.final` processes since + there is no later time slot for the execution to resume. Control flow must + eventually end in an `llhd.halt` terminator. + + Execution order between multiple `llhd.final` ops is undefined. + + Example: + ```mlir + hw.module @Foo() { + llhd.final { + func.call @printSimulationStatistics() : () -> () + llhd.halt + } + } + ``` + }]; + let regions = (region MinSizedRegion<1>: $body); + let assemblyFormat = "attr-dict-with-keyword $body"; +} + +def ConnectOp : LLHDOp<"con", [ SameTypeOperands, HasParent<"hw::HWModuleOp"> ]> { @@ -71,7 +105,7 @@ def LLHD_ConnectOp : LLHD_Op<"con", [ let hasCanonicalizeMethod = 1; } -def LLHD_WaitOp : LLHD_Op<"wait", [ +def WaitOp : LLHDOp<"wait", [ Terminator, AttrSizedOperandSegments, HasParent<"ProcessOp">, @@ -96,39 +130,26 @@ def LLHD_WaitOp : LLHD_Op<"wait", [ ``` }]; - let arguments = (ins Variadic:$obs, - Optional:$time, + let arguments = (ins Variadic:$observed, + Optional:$time, Variadic:$destOps); let successors = (successor AnySuccessor:$dest); let assemblyFormat = [{ - (`for` $time^ `,`)? (`(`$obs^ `:` qualified(type($obs))`)` `,`)? + (`for` $time^ `,`)? (`(`$observed^ `:` qualified(type($observed))`)` `,`)? $dest (`(` $destOps^ `:` qualified(type($destOps)) `)`)? attr-dict }]; } -def LLHD_HaltOp : LLHD_Op<"halt", [Terminator, HasParent<"ProcessOp">]> { +def HaltOp : LLHDOp<"halt", [ + Terminator, + ParentOneOf<["ProcessOp", "FinalOp"]> +]> { let summary = "Terminates execution of a process."; let description = [{ The `halt` instruction terminates execution of a process. All processes must halt eventually or consist of an infinite loop. - - * This is a terminator instruction - * This instruction is only allowed in processes (`llhd.process`). - - Syntax: - - ``` - halt-op ::= `llhd.halt` - ``` - - Example: - - ```mlir - llhd.halt - ``` }]; - let assemblyFormat = "attr-dict"; } diff --git a/include/circt/Dialect/LLHD/IR/LLHDTypesImpl.td b/include/circt/Dialect/LLHD/IR/LLHDTypes.td similarity index 76% rename from include/circt/Dialect/LLHD/IR/LLHDTypesImpl.td rename to include/circt/Dialect/LLHD/IR/LLHDTypes.td index a87126f018da..fc2d2d62753d 100644 --- a/include/circt/Dialect/LLHD/IR/LLHDTypesImpl.td +++ b/include/circt/Dialect/LLHD/IR/LLHDTypes.td @@ -10,15 +10,20 @@ // //===----------------------------------------------------------------------===// +#ifndef CIRCT_DIALECT_LLHD_IR_LLHDTYPES_TD +#define CIRCT_DIALECT_LLHD_IR_LLHDTYPES_TD + +include "circt/Dialect/LLHD/IR/LLHDDialect.td" +include "mlir/IR/AttrTypeBase.td" + // Base class for other typedefs. Provides dialact-specific defaults. -class LLHDType : TypeDef { } +class LLHDType : TypeDef { } //===----------------------------------------------------------------------===// // Type declarations //===----------------------------------------------------------------------===// -// Declares the llhd::PtrType in C++. -def PtrTypeImpl : LLHDType<"Ptr"> { +def LLHDPtrType : LLHDType<"Ptr"> { let summary = "pointer type"; let description = [{ Represents a pointer to a memory location holding a value of its element @@ -36,8 +41,7 @@ def PtrTypeImpl : LLHDType<"Ptr"> { ]; } -// Declares the llhd::TimeType in C++. -def TimeTypeImpl : LLHDType<"Time"> { +def LLHDTimeType : LLHDType<"Time"> { let summary = "time type"; let description = [{ Represents a simulation time value as a combination of a real time value in @@ -49,12 +53,21 @@ def TimeTypeImpl : LLHDType<"Time"> { let mnemonic = "time"; } +//===----------------------------------------------------------------------===// +// Type Constraints +//===----------------------------------------------------------------------===// + +class LLHDPtrTypeOf allowedTypes> + : ContainerType, CPred<"llvm::isa($_self)">, + "llvm::cast($_self).getElementType()", "LLHD pointer type">; + +def LLHDAnySigOrPtrType : AnyTypeOf<[LLHDPtrType, InOutType]>; + //===----------------------------------------------------------------------===// // Attribute declarations //===----------------------------------------------------------------------===// -// Declares the llhd::TimeAttr in C++. -def LLHD_TimeAttr : AttrDef { +def LLHDTimeAttr : AttrDef { let summary = "time attribute"; let description = [{ Represents a value of the LLHD time type. @@ -83,3 +96,5 @@ def LLHD_TimeAttr : AttrDef { time, timeUnit, delta, epsilon); }]>]; } + +#endif // CIRCT_DIALECT_LLHD_IR_LLHDTYPES_TD diff --git a/include/circt/Dialect/LLHD/IR/ValueOps.td b/include/circt/Dialect/LLHD/IR/LLHDValueOps.td similarity index 84% rename from include/circt/Dialect/LLHD/IR/ValueOps.td rename to include/circt/Dialect/LLHD/IR/LLHDValueOps.td index 1620095f3b9a..176b0199ee97 100644 --- a/include/circt/Dialect/LLHD/IR/ValueOps.td +++ b/include/circt/Dialect/LLHD/IR/LLHDValueOps.td @@ -1,4 +1,4 @@ -//===- ValueOps.td - LLHD value operations -----------------*- tablegen -*-===// +//===- LLHDValueOps.td - LLHD value operations -------------*- tablegen -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -def LLHD_ConstantTimeOp : LLHD_Op<"constant_time", +def ConstantTimeOp : LLHDOp<"constant_time", [ConstantLike, Pure]> { let summary = "Introduce a new time constant."; let description = [{ @@ -35,8 +35,8 @@ def LLHD_ConstantTimeOp : LLHD_Op<"constant_time", "unsigned":$epsilon)> ]; - let arguments = (ins LLHD_TimeAttr: $value); - let results = (outs LLHD_TimeType: $result); + let arguments = (ins LLHDTimeAttr: $value); + let results = (outs LLHDTimeType: $result); let hasFolder = 1; } diff --git a/include/circt/Dialect/LLHD/IR/SignalOps.td b/include/circt/Dialect/LLHD/IR/SignalOps.td deleted file mode 100644 index 4aa94591770d..000000000000 --- a/include/circt/Dialect/LLHD/IR/SignalOps.td +++ /dev/null @@ -1,309 +0,0 @@ -//===- SignalOps.td - LLHD signal operations ---------------*- tablegen -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This describes the MLIR ops for LLHD signal creation and manipulation. -// -//===----------------------------------------------------------------------===// - -include "mlir/IR/EnumAttr.td" - -def LLHD_SigOp : LLHD_Op<"sig", [ - ParentOneOf<["hw::HWModuleOp", "llhd::ProcessOp"]>, - TypesMatchWith< - "type of 'init' and underlying type of 'signal' have to match.", - "init", "result", "hw::InOutType::get($_self)"> - ]> { - let summary = "Create a signal."; - let description = [{ - The `llhd.sig` instruction introduces a new signal in the IR. The input - operand determines the initial value carried by the signal, while the - result type will always be a signal carrying the type of the init operand. - A signal defines a unique name within the entity it resides in. - - Syntax: - - ``` - sig-op ::= ssa-id `=` `llhd.sig` sig-name ssa-init attr-dict `:` init-type - ``` - - Example: - - ```mlir - %init_i64 = llhd.const 123 : i64 - %sig_i64 = llhd.sig "foo" %init_64 : i64 - - %init_i1 = llhd.const 1 : i1 - %sig_i1 = llhd.sig "bar" %init_i1 : i1 - ``` - - The first `llhd.sig` instruction creates a new signal named "foo", carrying - an `i64` type with initial value of 123, while the second one creates a new - signal named "bar", carrying an `i1` type with initial value of 1. - }]; - - let arguments = (ins StrAttr: $name, LLHD_AnyElementType: $init); - let results = (outs InOutType: $result); - - let assemblyFormat = "$name $init attr-dict `:` qualified(type($init))"; -} - -def LLHD_PrbOp : LLHD_Op<"prb", [ - TypesMatchWith< - "type of 'result' and underlying type of 'signal' have to match.", - "signal", "result", "llvm::cast($_self).getElementType()"> - ]> { - let summary = "Probe a signal."; - let description = [{ - The `llhd.prb` instruction probes a signal and returns the value it - currently carries as a new SSA operand. The result type is always - the type carried by the signal. - - Syntax: - - ``` - prb-op ::= ssa-id `=` `llhd.prb` ssa-sig attr-dict `:` !hw.inout - ``` - - Example: - - ```mlir - %const_i1 = llhd.const 1 : i1 - %sig_i1 = llhd.sig %const_i1 : i1 - %prbd = llhd.prb %sig_i1 : !hw.inout - ``` - }]; - - let arguments = (ins Arg: $signal); - let results = (outs LLHD_AnyElementType: $result); - - let assemblyFormat = "$signal attr-dict `:` qualified(type($signal))"; -} - -def LLHD_OutputOp : LLHD_Op<"output", [ - TypesMatchWith< - "type of 'value' and underlying type of 'result' have to match.", - "value", "result", "hw::InOutType::get($_self)"> - ]> { - let summary = "Introduce a new signal and drive a value onto it."; - let description = [{ - The `llhd.output` operation introduces a new signal and continuously - drives a the given value onto it after a given time-delay. The same - value is used to initialize the signal in the same way as the 'init' - value in `llhd.sig`. An optional name can be given to the created signal. - This shows up, e.g., in the simulation trace. - - Example: - - ```mlir - %value = llhd.const 1 : i1 - %time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time - %sig = llhd.output "sigName" %value after %time : i1 - - // is equivalent to - - %value = llhd.const 1 : i1 - %time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time - %sig = llhd.sig "sigName" %value : i1 - llhd.drv %sig, %value after %time : !hw.inout - ``` - }]; - - let arguments = (ins OptionalAttr: $name, - LLHD_AnyElementType: $value, - LLHD_TimeType: $time); - - let results = (outs InOutType: $result); - - let assemblyFormat = [{ - ( $name^ )? $value `after` $time attr-dict `:` qualified(type($value)) - }]; -} - -def LLHD_DrvOp : LLHD_Op<"drv", [ - TypesMatchWith< - "type of 'value' and underlying type of 'signal' have to match.", - "signal", "value", "llvm::cast($_self).getElementType()"> - ]> { - let summary = "Drive a value into a signal."; - let description = [{ - The `llhd.drv` operation drives a new value onto a signal. A time - operand also has to be passed, which specifies the frequency at which - the drive will be performed. An optional enable value can be passed as - last argument. In this case the drive will only be performed if the - value is 1. In case no enable signal is passed the drive will always be - performed. This operation does not define any new SSA operands. - - Syntax: - - ``` - drv-op ::= `llhd.drv` ssa-signal `,` ssa-const `after` ssa-time - (`if` ssa-enable)? `:` !hw.inout - ``` - - Example: - - ```mlir - %init = llhd.const 1 : i1 - %en = llhd.const 0 : i1 - %time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time - %sig = llhd.sig %init : i1 - %new = llhd.not %init : i1 - - llhd.drv %sig, %new after %time : !hw.inout - llhd.drv %sig, %new after %time if %en : !hw.inout - ``` - }]; - - let arguments = (ins Arg: $signal, - LLHD_AnyElementType: $value, - LLHD_TimeType: $time, - Optional: $enable); - - let assemblyFormat = [{ - $signal `,` $value `after` $time ( `if` $enable^ )? attr-dict `:` - qualified(type($signal)) - }]; - - let hasFolder = 1; - let hasCanonicalizeMethod = 1; -} - -def REG_MODE_LOW : I64EnumAttrCase<"low", 0>; -def REG_MODE_HIGH : I64EnumAttrCase<"high", 1>; -def REG_MODE_RISE : I64EnumAttrCase<"rise", 2>; -def REG_MODE_FALL : I64EnumAttrCase<"fall", 3>; -def REG_MODE_BOTH : I64EnumAttrCase<"both", 4>; - -def LLHD_RegModeAttr : I64EnumAttr<"RegMode", "", [ - REG_MODE_LOW, REG_MODE_HIGH, REG_MODE_RISE, REG_MODE_FALL, REG_MODE_BOTH - ]> { - let cppNamespace = "::circt::llhd"; -} - -def LLHD_RegModeArrayAttr - : TypedArrayAttrBase {} - -def LLHD_RegOp : LLHD_Op<"reg", [ - HasParent<"hw::HWModuleOp">, - AttrSizedOperandSegments - ]> { - let summary = "Represents a storage element"; - let description = [{ - This instruction represents a storage element. It drives its output onto - the 'signal' value. An arbitrary amount of triggers can be added to the - storage element. However, at least one is required. They are quadruples - consisting of the new value to be stored if the trigger applies, the - mode and trigger value which specify when this trigger has to be applied - as well as a delay. Optionally, each triple may also have a gate - condition, in this case the trigger only applies if the gate is one. If - multiple triggers apply the left-most in the list takes precedence. - - There are five modes available: - - | Mode | Meaning | - |--------|-----------------------------------------------------------------| - | "low" | Storage element stores `value` while the `trigger` is low. Models active-low resets and low-transparent latches. - | "high" | Storage element stores `value` while the `trigger` is high. Models active-high resets and high-transparent latches. - | "rise" | Storage element stores `value` upon the rising edge of the `trigger`. Models rising-edge flip-flops. - | "fall" | Storage element stores `value` upon the falling edge of the `trigger`. Models falling-edge flip-flops. - | "both" | Storage element stores `value` upon the a rising or a falling edge of the `trigger`. Models dual-edge flip-flops. - - This instruction may only be used in an LLHD entity. - - Syntax: - - ``` - reg-op ::= `llhd.reg` signal-ssa-value - ( `,` `(` value-ssa-value `,` mode-string trigger-ssa-value `after` - delay-ssa-value ( `if` gate-ssa-value )? `:` value-type )+ - attr-dict `:` signal-type - ``` - - Examples: - - A rising, falling, and dual-edge triggered flip-flop: - - ```mlir - llhd.reg %Q, (%D, "rise" %CLK after %T : !hw.inout) : !hw.inout - llhd.reg %Q, (%D, "fall" %CLK after %T : !hw.inout) : !hw.inout - llhd.reg %Q, (%D, "both" %CLK after %T : !hw.inout) : !hw.inout - ``` - - A rising-edge triggered flip-flop with active-low reset: - - ```mlir - llhd.reg %Q, (%init, "low" %RSTB after %T : !hw.inout), - (%D, "rise" %CLK after %T : !hw.inout) : !hw.inout - ``` - - A rising-edge triggered enable flip-flop with active-low reset: - - ```mlir - llhd.reg %Q, (%init, "low" %RSTB after %T : !hw.inout), - (%D, "rise" %CLK after %T if %EN : !hw.inout) : !hw.inout - ``` - - A transparent-low and transparent-high latch: - - ```mlir - llhd.reg %Q, (%D, "low" %CLK after %T : !hw.inout) : !hw.inout - llhd.reg %Q, (%D, "high" %CLK after %T : !hw.inout) : !hw.inout - ``` - - An SR latch: - - ```mlir - %0 = llhd.const 0 : i1 - %1 = llhd.const 1 : i1 - llhd.reg %Q, (%0, "high" %R after %T : !hw.inout), - (%1, "high" %S after %T : !hw.inout) : !hw.inout - ``` - }]; - - let arguments = (ins - InOutType: $signal, - LLHD_RegModeArrayAttr: $modes, - Variadic>: $values, - Variadic: $triggers, - Variadic: $delays, - Variadic: $gates, - I64ArrayAttr: $gateMask); - - let extraClassDeclaration = [{ - static StringRef getModeAttrName() { return "modes"; } - static RegMode getRegModeByName(StringRef name) { - std::optional optional = symbolizeRegMode(name); - assert(optional && "Invalid RegMode string."); - return *optional; - } - - bool hasGate(unsigned index) { - assert(index < getGateMask().getValue().size() && "Index out of range."); - return llvm::cast( - getGateMask().getValue()[index]).getInt() != 0; - } - - Value getGateAt(unsigned index) { - assert(index < getGateMask().getValue().size() && "Index out of range."); - if (!hasGate(index)) return Value(); - return - getGates()[llvm::cast( - getGateMask().getValue()[index]).getInt()-1]; - } - - RegMode getRegModeAt(unsigned index) { - assert(index < getModes().getValue().size() && "Index out of range."); - return (RegMode) llvm::cast( - getModes().getValue()[index]).getInt(); - } - }]; - - let hasVerifier = 1; -} diff --git a/include/circt/Dialect/LLHD/Transforms/Passes.td b/include/circt/Dialect/LLHD/Transforms/Passes.td index b4db78a4020b..145f921e1d96 100644 --- a/include/circt/Dialect/LLHD/Transforms/Passes.td +++ b/include/circt/Dialect/LLHD/Transforms/Passes.td @@ -40,17 +40,18 @@ def MemoryToBlockArgument : Pass<"llhd-memory-to-block-argument", ```mlir llhd.process { - %c5 = llhd.const 5 : i32 + %c5 = hw.constant 5 : i32 %cond = llhd.prb %condsig : !hw.inout %ptr = llhd.var %c5 : i32 cond_br %cond, ^bb1, ^bb2 ^bb1: - %c6 = llhd.const 6 : i32 + %c6 = hw.constant 6 : i32 llhd.store %ptr, %c6 : !llhd.ptr br ^bb2 ^bb2: %ld = llhd.load %ptr : !llhd.ptr - %res = llhd.not %ld : i32 + %c-1_i32 = hw.constant -1 : i32 + %res = comb.xor %ld, %c-1_i32 : i32 llhd.halt } ``` @@ -59,14 +60,15 @@ def MemoryToBlockArgument : Pass<"llhd-memory-to-block-argument", ```mlir llhd.process { - %c5 = llhd.const 5 : i32 + %c5 = hw.constant 5 : i32 %cond = llhd.prb %condsig : !hw.inout cond_br %cond, ^bb1, ^bb2(%c5 : i32) ^bb1: - %c6 = llhd.const 6 : i32 + %c6 = hw.constant 6 : i32 br ^bb2(%c6 : i32) ^bb2(%arg : i32): - %res = llhd.not %arg : i32 + %c-1_i32 = hw.constant -1 : i32 + %res = comb.xor %arg, %c-1_i32 : i32 llhd.halt } ``` diff --git a/include/circt/Dialect/Moore/CMakeLists.txt b/include/circt/Dialect/Moore/CMakeLists.txt index 0fdc97c38bf8..38609ece9bcf 100644 --- a/include/circt/Dialect/Moore/CMakeLists.txt +++ b/include/circt/Dialect/Moore/CMakeLists.txt @@ -9,10 +9,8 @@ mlir_tablegen(MooreEnums.cpp.inc -gen-enum-defs) add_public_tablegen_target(CIRCTMooreEnumsIncGen) add_dependencies(circt-headers CIRCTMooreEnumsIncGen) -mlir_tablegen(MooreAttributes.h.inc -gen-attrdef-decls - -attrdefs-dialect MooreDialect) -mlir_tablegen(MooreAttributes.cpp.inc -gen-attrdef-defs - -attrdefs-dialect MooreDialect) +mlir_tablegen(MooreAttributes.h.inc -gen-attrdef-decls -attrdefs-dialect moore) +mlir_tablegen(MooreAttributes.cpp.inc -gen-attrdef-defs -attrdefs-dialect moore) add_public_tablegen_target(CIRCTMooreAttributesIncGen) add_dependencies(circt-headers CIRCTMooreAttributesIncGen) diff --git a/include/circt/Dialect/Moore/Moore.td b/include/circt/Dialect/Moore/Moore.td index 5c6284db230b..ab5bb41d3e43 100644 --- a/include/circt/Dialect/Moore/Moore.td +++ b/include/circt/Dialect/Moore/Moore.td @@ -15,6 +15,7 @@ include "circt/Dialect/Moore/MooreDialect.td" include "circt/Dialect/Moore/MooreTypes.td" +include "circt/Dialect/Moore/MooreAttributes.td" include "circt/Dialect/Moore/MooreOps.td" #endif // CIRCT_DIALECT_MOORE_MOORE diff --git a/include/circt/Dialect/Moore/MooreAttributes.h b/include/circt/Dialect/Moore/MooreAttributes.h new file mode 100644 index 000000000000..c2369cc97fa5 --- /dev/null +++ b/include/circt/Dialect/Moore/MooreAttributes.h @@ -0,0 +1,25 @@ +//===- MooreAttributes.h - Declare Moore dialect attributes ------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares the attributes for the Moore dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_MOORE_MOOREATTRIBUTES_H +#define CIRCT_DIALECT_MOORE_MOOREATTRIBUTES_H + +#include "circt/Support/FVInt.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinTypes.h" + +// Include generated attributes. +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/Moore/MooreAttributes.h.inc" + +#endif // CIRCT_DIALECT_MOORE_MOOREATTRIBUTES_H diff --git a/include/circt/Dialect/Moore/MooreAttributes.td b/include/circt/Dialect/Moore/MooreAttributes.td new file mode 100644 index 000000000000..1b052bf0bc42 --- /dev/null +++ b/include/circt/Dialect/Moore/MooreAttributes.td @@ -0,0 +1,24 @@ +//===- MooreAttributes.td - Moore attribute definitions ----*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_MOORE_MOOREATTRIBUTES +#define CIRCT_DIALECT_MOORE_MOOREATTRIBUTES + +include "circt/Dialect/Moore/MooreDialect.td" +include "mlir/IR/AttrTypeBase.td" + +def FVIntegerAttr : AttrDef { + let mnemonic = "fvint"; + let summary = "An attribute containing a four-valued integer"; + let parameters = (ins "FVInt":$value); + let hasCustomAssemblyFormat = 1; + let convertFromStorage = "$_self.getValue()"; + let returnType = "circt::FVInt"; +} + +#endif // CIRCT_DIALECT_MOORE_MOOREATTRIBUTES diff --git a/include/circt/Dialect/Moore/MooreDialect.td b/include/circt/Dialect/Moore/MooreDialect.td index ffec56edd9c1..a125b7e260bd 100644 --- a/include/circt/Dialect/Moore/MooreDialect.td +++ b/include/circt/Dialect/Moore/MooreDialect.td @@ -29,12 +29,16 @@ def MooreDialect : Dialect { let extraClassDeclaration = [{ /// Register all Moore types. void registerTypes(); + /// Register all Moore attributes. + void registerAttributes(); /// Type parsing and printing. Type parseType(DialectAsmParser &parser) const override; void printType(Type, DialectAsmPrinter &) const override; }]; + let useDefaultAttributePrinterParser = 1; let useDefaultTypePrinterParser = 0; + let hasConstantMaterializer = 1; let dependentDialects = ["hw::HWDialect", "mlir::func::FuncDialect"]; } diff --git a/include/circt/Dialect/Moore/MooreOps.h b/include/circt/Dialect/Moore/MooreOps.h index 626174184dbd..6fa19c29f39e 100644 --- a/include/circt/Dialect/Moore/MooreOps.h +++ b/include/circt/Dialect/Moore/MooreOps.h @@ -14,6 +14,7 @@ #define CIRCT_DIALECT_MOORE_MOOREOPS_H #include "circt/Dialect/HW/HWTypes.h" +#include "circt/Dialect/Moore/MooreAttributes.h" #include "circt/Dialect/Moore/MooreDialect.h" #include "circt/Dialect/Moore/MooreTypes.h" #include "mlir/Dialect/Func/IR/FuncOps.h" diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index dc3402ea366b..b6b222d586ac 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -9,6 +9,7 @@ #ifndef CIRCT_DIALECT_MOORE_MOOREOPS #define CIRCT_DIALECT_MOORE_MOOREOPS +include "circt/Dialect/Moore/MooreAttributes.td" include "circt/Dialect/Moore/MooreDialect.td" include "circt/Dialect/Moore/MooreTypes.td" include "mlir/IR/OpAsmInterface.td" @@ -131,8 +132,6 @@ def ProcedureKindAttr: I32EnumAttr<"ProcedureKind", "Procedure kind", } def ProcedureOp : MooreOp<"procedure", [ - SingleBlock, - NoTerminator, NoRegionArguments, RecursiveMemoryEffects, RecursivelySpeculatable @@ -172,15 +171,22 @@ def ProcedureOp : MooreOp<"procedure", [ See IEEE 1800-2017 § 9.2 "Structured procedures". }]; - let regions = (region SizedRegion<1>:$bodyRegion); let arguments = (ins ProcedureKindAttr:$kind); let results = (outs); + let regions = (region AnyRegion:$body); let assemblyFormat = [{ - $kind attr-dict-with-keyword $bodyRegion + $kind attr-dict-with-keyword $body }]; } +def ReturnOp : MooreOp<"return", [ + Pure, Terminator, HasParent<"ProcedureOp"> +]> { + let summary = "Return from a procedure"; + let assemblyFormat = [{ attr-dict }]; +} + //===----------------------------------------------------------------------===// // Declarations //===----------------------------------------------------------------------===// @@ -260,19 +266,20 @@ def NetOp : MooreOp<"net", [ `` custom($name) $kind ($assignment^)? attr-dict `:` type($result) }]; + let hasCanonicalizeMethod = true; } -def AssignedVarOp : MooreOp<"assigned_variable", [ +def AssignedVariableOp : MooreOp<"assigned_variable", [ DeclareOpInterfaceMethods, - TypesMatchWith<"initial value and variable types match", - "result", "initial", "cast($_self).getNestedType()"> + SameOperandsAndResultType ]> { - let summary = "The copy of variable must have the initial value"; - let arguments = (ins OptionalAttr:$name, UnpackedType:$initial); - let results = (outs Res:$result); + let summary = "A variable with a unique continuously assigned value"; + let arguments = (ins OptionalAttr:$name, UnpackedType:$input); + let results = (outs UnpackedType:$result); let assemblyFormat = [{ - `` custom($name) $initial attr-dict `:` type($result) + `` custom($name) $input attr-dict `:` type($input) }]; + let hasCanonicalizeMethod = true; } def ReadOp : MooreOp<"read", [ @@ -369,58 +376,103 @@ def NonBlockingAssignOp : AssignOpBase<"nonblocking_assign"> { // Statements //===----------------------------------------------------------------------===// -def None: I32EnumAttrCase<"None", 0, "none">; -/// Transit from 0 to x/z/1, and from x/z to 1. +// Any change on the input. +def AnyChange: I32EnumAttrCase<"AnyChange", 0, "any">; +// A transition from 0 to X/Z/1, or from X/Z to 1. def PosEdge: I32EnumAttrCase<"PosEdge", 1, "posedge">; -/// Transit from 1 to x/z/0, and from x/z to 0. +// A transition from 1 to X/Z/0, or from X/Z to 0. def NegEdge: I32EnumAttrCase<"NegEdge", 2, "negedge">; -/// Include the negedge and posedge. +// The combination of `PosEdge` and `NegEdge`. def BothEdges: I32EnumAttrCase<"BothEdges", 3, "edge">; -def EdgeAtrr: I32EnumAttr<"Edge", "Edge kind", - [None, PosEdge, NegEdge, BothEdges]>{ +def EdgeAttr: I32EnumAttr<"Edge", "Edge kind", + [AnyChange, PosEdge, NegEdge, BothEdges]> { let cppNamespace = "circt::moore"; } -def EventOp : MooreOp<"wait_event", [ - HasParent<"ProcedureOp"> +def WaitEventOp : MooreOp<"wait_event", [ + RecursiveMemoryEffects, + NoRegionArguments, + SingleBlock, + NoTerminator ]> { - let summary = "Detecting posedge and negedge"; + let summary = "Suspend execution until an event occurs"; let description = [{ - It is introduced by the symbol `@`, and it allows statement execution to - be delayed until the occurrence of some simulation event occurring in a - procedure executing concurrently with this procedure. - - For the implicit event control(`@(*)`), there are two situations that are - not automatically added to event expression: - 1. Identifiers that only appear in wait or event expressions. - ``` - always @(*) begin // equivalent to @(b) - @(n) kid = b; // n is not added to @(*) - end - ``` - 2. Identifiers that only appear as a hierarchical_variable_identifier - in the variable_lvalue of the left-hand side of assignments. - ``` - always @(*) begin - a = b + c; // a is not added to @(*) - end - ``` + The `moore.wait_event` op suspends execution of the current process until + its body signals that an event has been the detected. Conceptually, the body + of this op is executed whenever any potentially relevant signal has changed. + If one of the contained `moore.detect_event` ops detect an event, execution + resumes after the `moore.wait_event` operation. If no event is detected, the + current process remains suspended. + + Example corresponding to the SystemVerilog `@(posedge x, negedge y iff z)`: + ``` + moore.wait_event { + %0 = moore.read %x : + %1 = moore.read %y : + %2 = moore.read %z : + moore.detect_event posedge %0 : i1 + moore.detect_event negedge %1 if %2 : i1 + } + ``` - Example: + The body may also contain any operations necessary to evaluate the event + conditions. For example, the SV `@(posedge ~x iff i == 42)`: ``` - @(a, b, c) // none - @(posedge clk) // positive edge - @(negedge clk) // negative edge - @(edge clk) // both edge - @(*) // implicit event control + moore.wait_event { + %0 = moore.read %x : + %1 = moore.not %0 : i1 + %2 = moore.read %i : + %3 = moore.constant 42 : i19 + %4 = moore.eq %2, %3 : i19 + moore.detect_event posedge %0 if %4 : i1 + } ``` + See IEEE 1800-2017 § 9.4.2 "Event control". }]; - let arguments = (ins EdgeAtrr:$edge, UnpackedType:$input); - let results = (outs); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ attr-dict-with-keyword $body }]; +} + +def DetectEventOp : MooreOp<"detect_event", [ + HasParent<"WaitEventOp"> +]> { + let summary = "Check if an event occured within a `wait_event` op"; + let description = [{ + The `moore.detect_event` op is used inside the body of a `moore.wait_event` + to check if an interesting value change has occurred on its operand. The + `moore.detect_event` op implicitly stores the previous value of its operand + and compares it against the current value to detect an interesting edge: + + - `posedge` checks for a low-to-high transition + - `negedge` checks for a high-to-low transition + - `edge` checks for either a `posedge` or a `negedge` + - `any` checks for any value change (including e.g. X to Z) + + The edges are detected as follows: + + - `0` to `1 X Z`: `posedge` + - `1` to `0 X Z`: `negedge` + - `X Z` to `1`: `posedge` + - `X Z` to `0`: `negedge` + + | From | To 0 | To 1 | To X | To Z | + |-------|---------|---------|---------|---------| + | 0 | - | posedge | posedge | posedge | + | 1 | negedge | - | negedge | negedge | + | X | negedge | posedge | - | - | + | Z | negedge | posedge | - | - | + + See IEEE 1800-2017 § 9.4.2 "Event control". + }]; + let arguments = (ins + EdgeAttr:$edge, + UnpackedType:$input, + Optional:$condition + ); let assemblyFormat = [{ - $edge $input attr-dict `:` type($input) + $edge $input (`if` $condition^)? attr-dict `:` type($input) }]; } @@ -428,14 +480,15 @@ def EventOp : MooreOp<"wait_event", [ // Expressions //===----------------------------------------------------------------------===// -def ConstantOp : MooreOp<"constant", [Pure]> { +def ConstantOp : MooreOp<"constant", [Pure, ConstantLike]> { let summary = "A constant integer value"; - let arguments = (ins APIntAttr:$value); + let arguments = (ins FVIntegerAttr:$value); let results = (outs IntType:$result); let hasCustomAssemblyFormat = 1; let hasVerifier = 1; let hasFolder = 1; let builders = [ + OpBuilder<(ins "IntType":$type, "const FVInt &":$value)>, OpBuilder<(ins "IntType":$type, "const APInt &":$value)>, OpBuilder<(ins "IntType":$type, "int64_t":$value)>, ]; @@ -472,7 +525,7 @@ def NamedConstantOp : MooreOp<"named_constant", [ }]; } -def StringConstantOp : MooreOp<"string_constant", [Pure, ConstantLike]> { +def StringConstantOp : MooreOp<"string_constant", [Pure]> { let summary = "Produce a constant string value"; let description = [{ Produces a constant value of string type. @@ -696,6 +749,9 @@ class PowOpBase : BinaryOpBase { See IEEE 1800-2017 § 11.4.3 "Arithmetic operators". }]; + + let hasCanonicalizeMethod = 1; + let hasFolder = 1; } def PowUOp : PowOpBase<"powu">; @@ -830,6 +886,16 @@ class CaseEqOpBase : MooreOp : MooreOp { let summary = "Case equality"; } def CaseNeOp : CaseEqOpBase<"case_ne"> { let summary = "Case inequality"; } +def CaseZEqOp : CaseEqOpBase<"casez_eq"> { + let summary = "Case equality with Z as wildcard"; +} +def CaseXZEqOp : CaseEqOpBase<"casexz_eq"> { + let summary = "Case equality with X and Z as wildcard"; +} class WildcardEqOpBase : MooreOp { }]; } +//===----------------------------------------------------------------------===// +// Array Manipulation +//===----------------------------------------------------------------------===// + +def ArrayCreateOp : MooreOp<"array_create", [Pure, SameTypeOperands]> { + let summary = "Create an array value from individual elements"; + let arguments = (ins Variadic:$elements); + let results = (outs AnyStaticArrayType:$result); + let assemblyFormat = [{ + $elements attr-dict `:` type($elements) `->` type($result) + }]; + let hasVerifier = 1; +} + //===----------------------------------------------------------------------===// // Struct Manipulation //===----------------------------------------------------------------------===// @@ -1201,24 +1287,21 @@ def UnionExtractRefOp : MooreOp<"union_extract_ref"> { def ConditionalOp : MooreOp<"conditional",[ RecursiveMemoryEffects, NoRegionArguments, - SingleBlockImplicitTerminator<"moore::YieldOp"> ]> { let summary = "Conditional operation"; let description = [{ - If cond_predicate is true, the operator returns the value of the first - expression without evaluating the second expression; if false, it returns - the value of the second expression without evaluating the first expression. - If cond_predicate evaluates to an ambiguous value (x or z), then both the - first expression and the second expression shall be evaluated, and compared - for logical equivalence. If that comparison is true (1), the operator shall - return either the first or second expression. Otherwise the operator returns - a result based on the data types of the expressions. - - When both the first and second expressions are of integral types, if the - cond_predicate evaluates to an ambiguous value and the expressions are not - logically equivalent, their results shall be combined bit by bit using the - table below to calculate the final result. The first and second expressions - are extended to the same width. + If the condition is true, this op evaluates the first region and returns its + result without evaluating the second region. If the the condition is false, + this op evaluates the second region and returns its result without + evaluating the first region. + + If the condition is unknown (X or Z), _both_ regions are evaluated. If both + results are equal as per `case_eq`, one of the results is returned. If the + results are not equal, this op returns a value based on the data types of + the results. + + In case the results of the first and second region are of an integral type, + they are merged by applying the following bit-wise truth table: |?: | 0 | 1 | X | Z | |---|---|---|---|---| @@ -1227,6 +1310,7 @@ def ConditionalOp : MooreOp<"conditional",[ | X | X | X | X | X | | Z | X | X | X | X | + Non-integral data types define other rules which are not yet implemented. See IEEE 1800-2017 § 11.4.11 "Conditional operator". }]; let arguments = (ins AnySingleBitType:$condition); @@ -1257,10 +1341,10 @@ def YieldOp : MooreOp<"yield", [ yielded. }]; let arguments = (ins UnpackedType:$result); - let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>]; let assemblyFormat = [{ attr-dict $result `:` type($result) }]; let hasVerifier = 1; } + #endif // CIRCT_DIALECT_MOORE_MOOREOPS diff --git a/include/circt/Dialect/Moore/MooreTypes.td b/include/circt/Dialect/Moore/MooreTypes.td index 5830daab8aae..1217bc356713 100644 --- a/include/circt/Dialect/Moore/MooreTypes.td +++ b/include/circt/Dialect/Moore/MooreTypes.td @@ -406,6 +406,12 @@ def BitType : MooreType, + "packed or unpacked static array type", + "moore::UnpackedType">; + /// A packed or unpacked struct type. def AnyStructType : MooreType< Or<[StructType.predicate, UnpackedStructType.predicate]>, diff --git a/include/circt/Dialect/OM/Evaluator/Evaluator.h b/include/circt/Dialect/OM/Evaluator/Evaluator.h index 3834e626c108..2592fbdc7e2b 100644 --- a/include/circt/Dialect/OM/Evaluator/Evaluator.h +++ b/include/circt/Dialect/OM/Evaluator/Evaluator.h @@ -481,6 +481,9 @@ struct Evaluator { FailureOr evaluateListCreate(ListCreateOp op, ActualParameters actualParams, Location loc); + FailureOr evaluateListConcat(ListConcatOp op, + ActualParameters actualParams, + Location loc); FailureOr evaluateTupleCreate(TupleCreateOp op, ActualParameters actualParams, Location loc); diff --git a/include/circt/Dialect/OM/OMOps.td b/include/circt/Dialect/OM/OMOps.td index 769da2623e88..6338218c2ce6 100644 --- a/include/circt/Dialect/OM/OMOps.td +++ b/include/circt/Dialect/OM/OMOps.td @@ -207,6 +207,23 @@ def ListCreateOp : OMOp<"list_create", [Pure, SameTypeOperands]> { let hasCustomAssemblyFormat = 1; } +def ListConcatOp : OMOp<"list_concat", [Pure, SameOperandsAndResultType]> { + let summary = "Concatenate multiple lists to produce a new list"; + let description = [{ + Produces a value of list type by concatenating the provided lists. + + Example: + ``` + %3 = om.list_concat %0, %1, %2 : !om.list + ``` + }]; + + let arguments = (ins Variadic:$subLists); + let results = (outs ListType:$result); + + let assemblyFormat = "$subLists attr-dict `:` type($result)"; +} + def TupleCreateOp : OMOp<"tuple_create", [Pure, InferTypeOpInterface]> { let summary = "Create a tuple of values"; let description = [{ diff --git a/include/circt/Dialect/SMT/SMTIntOps.td b/include/circt/Dialect/SMT/SMTIntOps.td index 8410402115dc..86802fe87d4a 100644 --- a/include/circt/Dialect/SMT/SMTIntOps.td +++ b/include/circt/Dialect/SMT/SMTIntOps.td @@ -52,6 +52,12 @@ class VariadicIntOp : SMTIntOp { let arguments = (ins Variadic:$inputs); let results = (outs IntType:$result); let assemblyFormat = "$inputs attr-dict"; + + let builders = [ + OpBuilder<(ins "mlir::ValueRange":$inputs), [{ + build($_builder, $_state, $_builder.getType(), inputs); + }]>, + ]; } class BinaryIntOp : SMTIntOp { diff --git a/include/circt/Dialect/Seq/SeqEnums.h b/include/circt/Dialect/Seq/SeqEnums.h index 85a7e478f8c5..6007eb060dbf 100644 --- a/include/circt/Dialect/Seq/SeqEnums.h +++ b/include/circt/Dialect/Seq/SeqEnums.h @@ -17,4 +17,4 @@ enum class ReadEnableMode { Zero, Ignore, Undefined }; } // namespace seq } // namespace circt -#endif // CIRCT_DIALECT_SEQ_SEQENUMS_H \ No newline at end of file +#endif // CIRCT_DIALECT_SEQ_SEQENUMS_H diff --git a/include/circt/Dialect/Sim/SimPasses.td b/include/circt/Dialect/Sim/SimPasses.td index ae178e8fd42b..b2e6d8e98e23 100644 --- a/include/circt/Dialect/Sim/SimPasses.td +++ b/include/circt/Dialect/Sim/SimPasses.td @@ -23,4 +23,9 @@ def ProceduralizeSim : Pass<"sim-proceduralize", "hw::HWModuleOp"> { let dependentDialects = ["circt::hw::HWDialect, circt::seq::SeqDialect, mlir::scf::SCFDialect"]; } +def LowerDPIFunc : Pass<"sim-lower-dpi-func", "mlir::ModuleOp"> { + let summary = "Lower sim.dpi.func into func.func for the simulation flow"; + let dependentDialects = ["mlir::func::FuncDialect", "mlir::LLVM::LLVMDialect"]; +} + #endif // CIRCT_DIALECT_SIM_SEQPASSES diff --git a/include/circt/Dialect/Verif/VerifOps.td b/include/circt/Dialect/Verif/VerifOps.td index 299f9c769eac..8333d8fe2157 100644 --- a/include/circt/Dialect/Verif/VerifOps.td +++ b/include/circt/Dialect/Verif/VerifOps.td @@ -251,7 +251,7 @@ def YieldOp : VerifOp<"yield", [ Terminator, ParentOneOf<[ "verif::LogicEquivalenceCheckingOp", "verif::BoundedModelCheckingOp", - "verif::InstanceOp" + "verif::ContractOp" ]> ]> { let summary = "yields values from a region"; @@ -313,7 +313,7 @@ def FormalOp : VerifOp<"formal", [ //===----------------------------------------------------------------------===// def SymbolicInputOp : VerifOp<"symbolic_input", [ - ParentOneOf<["verif::FormalOp", "verif::InstanceOp"]> + ParentOneOf<["verif::FormalOp"]> ]>{ let summary = "declare a symbolic input for formal verification"; let description = [{ @@ -354,70 +354,46 @@ def ConcreteInputOp : VerifOp<"concrete_input", [ // Formal Contract Ops //===----------------------------------------------------------------------===// -class ContractLikeOp traits = []> - : VerifOp, RegionKindInterface, IsolatedFromAbove - ]> { - - let arguments = (ins Variadic:$inputs); - - let regions = (region SizedRegion<1>:$body); - - let extraClassDeclaration = [{ - /// Implement RegionKindInterface. - static RegionKind getRegionKind(unsigned index) { - return RegionKind::Graph; - } - - /// Retrieves the region block arguments - BlockArgument getRegionArg(unsigned index) { - return getBody().front().getArguments()[index]; - } - - /// Retrieves the number of block arguments - unsigned getNumRegionArgs() { - return getBody().front().getNumArguments(); - } - }]; - - let hasRegionVerifier = 1; -} - - -def ContractOp : ContractLikeOp<"contract", [NoTerminator]>{ +def ContractOp : VerifOp<"contract", [ + SingleBlockImplicitTerminator<"verif::YieldOp">, + HasParent<"hw::HWModuleOp">, + RegionKindInterface +]>{ let summary = "declare a formal contract"; let description = [{ This operation declares a formal contract that is used to create precondition and postconditions on a parent module. These are used as an abstraction to better modularize formal verification such that each module containing a contract is checked exactly once. The contract contains a single block where the block arguments - represent the inputs and outputs in the same order as in the module signature. + represent the results of the code block that the contract is abstracting over. The + operands represent the SSA values that this contract's results will replace. e.g. ``` hw.module @Bar(in %foo : i8, out "" : i8, out "1" : i8) { - verif.contract (%foo) : (i8) { - ^bb0(%arg1 : i8, %bar.0 : i8, %bar.1 : i8): + %o0, %o1 = verif.contract (%to0, %to1) : (i8, i8) -> (i8, i8) { + ^bb0(%bar.0 : i8, %bar.1 : i8): %c0_8 = hw.constant 0 : i8 - %prec = comb.icmp bin ugt %arg1, %c0_8 : i8 + %prec = comb.icmp bin ugt %foo, %c0_8 : i8 verif.require %prec : i1 - %post = comb.icmp bin ugt %bar.0, %arg1 : i8 - %post1 = comb.icmp bin ult %bar.1, %arg1 : i8 + %post = comb.icmp bin ugt %bar.0, %foo : i8 + %post1 = comb.icmp bin ult %bar.1, %foo : i8 verif.ensure %post : i1 verif.ensure %post1 : i1 - } + verif.yield %bar.0, %bar.1 : i8, i8 + } /* ... Module definition ... */ } ``` This later is used to replace any instance of Bar during verification: ``` - %bar.0, %bar.1 = hw.instance "bar" @Bar("foo" : %foo : i8) -> ("" : i8, "1" : i8) + %bar.0, %bar.1 = hw.instance "bar" @Bar("foo" : %c42_8 : i8) -> ("" : i8, "1" : i8) /* ... After PrepareForFormal Pass becomes ... */ - %bar.0, %bar.1 = verif.instance (%c42_8) : (i8) -> (i8, i8) { + %bar.0, %bar.1 = verif.contract (%c42_8) : (i8) -> (i8, i8) { ^bb0(%arg1: i8): %c0_8 = hw.constant 0 : i8 %prec = comb.icmp bin ugt %arg1, %c0_8 : i8 @@ -435,29 +411,32 @@ def ContractOp : ContractLikeOp<"contract", [NoTerminator]>{ ``` }]; + let arguments = (ins Variadic:$inputs); + let results = (outs Variadic:$results); + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = [{ - `(` $inputs `)` attr-dict `:` `(` type($inputs) `)` $body + `(` $inputs `)` attr-dict `:` `(` type($inputs) `)` `->` `(` type($results) `)` $body }]; -} -def InstanceOp : ContractLikeOp<"instance", [ - SingleBlockImplicitTerminator<"verif::YieldOp"> -]>{ - let summary = "declare an instance of a formal contract (replace an hw.instance)"; - let description = [{ - This operation declares an instance of an `hw.module` that contains a - `verif.contract` definition. The body of this op will be a modified version - of the body of the referenced module's contract body, with `verif.require` - ops being reoplaced with `verif.assert` and `verif.ensure` being replaced - with `verif.assume`. The `verif.result` ops are converted into `verif.symbolic_input` - and are yielded as a result of the region to be used in the rest of the module. - }]; + let extraClassDeclaration = [{ + /// Implement RegionKindInterface. + static RegionKind getRegionKind(unsigned index) { + return RegionKind::Graph; + } - let results = (outs Variadic:$results); + /// Retrieves the region block arguments + BlockArgument getRegionArg(unsigned index) { + return getBody().front().getArguments()[index]; + } - let assemblyFormat = [{ - `(` $inputs `)` attr-dict `:` functional-type($inputs, $results) $body + /// Retrieves the number of block arguments + unsigned getNumRegionArgs() { + return getBody().front().getNumArguments(); + } }]; + + let hasRegionVerifier = 1; } class RequireLikeOp traits = [ diff --git a/include/circt/Firtool/Firtool.h b/include/circt/Firtool/Firtool.h index 9e6e7fbfb678..64ebefca783a 100644 --- a/include/circt/Firtool/Firtool.h +++ b/include/circt/Firtool/Firtool.h @@ -96,7 +96,7 @@ class FirtoolOptions { return allowAddingPortsOnPublic; } bool shouldConvertProbesToSignals() const { return probesToSignals; } - bool shouldReplicateSequentialMemories() const { return replSeqMem; } + bool shouldReplaceSequentialMemories() const { return replSeqMem; } bool shouldDisableOptimization() const { return disableOptimization; } bool shouldLowerMemories() const { return lowerMemories; } bool shouldDedup() const { return !noDedup; } diff --git a/include/circt/Support/FVInt.h b/include/circt/Support/FVInt.h new file mode 100644 index 000000000000..accc4b263b8b --- /dev/null +++ b/include/circt/Support/FVInt.h @@ -0,0 +1,679 @@ +//===- FVInt.h - Four-valued integer ----------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements a class to represent arbitrary precision integers where +// each bit can be one of four values. This corresponds to SystemVerilog's +// four-valued `logic` type (originally defined in IEEE 1364, later merged into +// IEEE 1800). +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_FVINT_H +#define CIRCT_SUPPORT_FVINT_H + +#include "circt/Support/LLVM.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +namespace circt { + +/// Four-valued arbitrary precision integers. +/// +/// Each bit of the integer can be 0, 1, X, or Z. Internally the bits are stored +/// in a pair of `APInt`s, one of which specifies the value of each bit (0/X or +/// 1/Z), and the other whether the bit is unknown (X or Z). +class FVInt { +public: + /// Default constructor that creates an zero-bit zero value. + explicit FVInt() : FVInt(0, 0) {} + + /// Construct an `FVInt` from a 64-bit value. The result has no X or Z bits. + FVInt(unsigned numBits, uint64_t value, bool isSigned = false) + : FVInt(APInt(numBits, value, isSigned)) {} + + /// Construct an `FVInt` from an `APInt`. The result has no X or Z bits. + FVInt(const APInt &value) + : value(value), unknown(APInt::getZero(value.getBitWidth())) {} + + /// Construct an `FVInt` from two `APInt`s used internally to store the bit + /// data. The first argument specifies whether each bit is 0/X or 1/Z. The + /// second argument specifies whether each bit is 0/1 or X/Z. Both `APInt`s + /// must have the same bitwidth. The two arguments correspond to the results + /// of `getRawValue()` and `getRawUnknown()`. + FVInt(APInt &&rawValue, APInt &&rawUnknown) + : value(rawValue), unknown(rawUnknown) { + assert(rawValue.getBitWidth() == rawUnknown.getBitWidth()); + } + + /// Construct an `FVInt` with all bits set to 0. + static FVInt getZero(unsigned numBits) { + return FVInt(APInt::getZero(numBits)); + } + + /// Construct an `FVInt` with all bits set to 1. + static FVInt getAllOnes(unsigned numBits) { + return FVInt(APInt::getAllOnes(numBits)); + } + + /// Construct an `FVInt` with all bits set to X. + static FVInt getAllX(unsigned numBits) { + return FVInt(APInt::getZero(numBits), APInt::getAllOnes(numBits)); + } + + /// Construct an `FVInt` with all bits set to Z. + static FVInt getAllZ(unsigned numBits) { + return FVInt(APInt::getAllOnes(numBits), APInt::getAllOnes(numBits)); + } + + /// Return the number of bits this integer has. + unsigned getBitWidth() const { return value.getBitWidth(); } + + /// Compute the number of active bits in the value. This is the smallest bit + /// width to which the value can be truncated without losing information in + /// the most significant bits. Or put differently, the value truncated to its + /// active bits and zero-extended back to its original width produces the + /// original value. + unsigned getActiveBits() const { + return std::max(value.getActiveBits(), unknown.getActiveBits()); + } + + /// Compute the minimum bit width necessary to accurately represent this + /// integer's value and sign. This is the smallest bit width to which the + /// value can be truncated without losing information in the most significant + /// bits and without flipping from negative to positive or vice versa. Or put + /// differently, the value truncated to its significant bits and sign-extended + /// back to its original width produces the original value. + unsigned getSignificantBits() const { + return std::max(value.getSignificantBits(), unknown.getSignificantBits()); + } + + /// Return the underlying `APInt` used to store whether a bit is 0/X or 1/Z. + const APInt &getRawValue() const { return value; } + + /// Return the underlying `APInt` used to store whether a bit is unknown (X or + /// Z). + const APInt &getRawUnknown() const { return unknown; } + + /// Convert the four-valued `FVInt` to a two-valued `APInt` by mapping X and Z + /// bits to either 0 or 1. + APInt toAPInt(bool unknownBitMapping) const { + auto v = value; + if (unknownBitMapping) + v |= unknown; // set unknown bits to 1 + else + v &= ~unknown; // set unknown bits to 0 + return v; + } + + //===--------------------------------------------------------------------===// + // Resizing + //===--------------------------------------------------------------------===// + + /// Truncate the integer to a smaller bit width. This simply discards the + /// high-order bits. If the integer is truncated to a bit width less than its + /// "active bits", information will be lost and the resulting integer will + /// have a different value. + FVInt trunc(unsigned bitWidth) const { + assert(bitWidth <= getBitWidth()); + return FVInt(value.trunc(bitWidth), unknown.trunc(bitWidth)); + } + + /// Zero-extend the integer to a new bit width. The additional high-order bits + /// are filled in with zero. + FVInt zext(unsigned bitWidth) const { + assert(bitWidth >= getBitWidth()); + return FVInt(value.zext(bitWidth), unknown.zext(bitWidth)); + } + + /// Sign-extend the integer to a new bit width. The additional high-order bits + /// are filled in with the sign bit (top-most bit) of the original number, + /// also when that sign bit is X or Z. Zero-width integers are extended with + /// zeros. + FVInt sext(unsigned bitWidth) const { + assert(bitWidth >= getBitWidth()); + return FVInt(value.sext(bitWidth), unknown.sext(bitWidth)); + } + + /// Truncate or zero-extend to a target bit width. + FVInt zextOrTrunc(unsigned bitWidth) const { + return bitWidth > getBitWidth() ? zext(bitWidth) : trunc(bitWidth); + } + + /// Truncate or sign-extend to a target bit width. + FVInt sextOrTrunc(unsigned bitWidth) const { + return bitWidth > getBitWidth() ? sext(bitWidth) : trunc(bitWidth); + } + + //===--------------------------------------------------------------------===// + // Value Tests + //===--------------------------------------------------------------------===// + + /// Determine if any bits are X or Z. + bool hasUnknown() const { return !unknown.isZero(); } + + /// Determine if all bits are 0. This is true for zero-width values. + bool isZero() const { return value.isZero() && unknown.isZero(); } + + /// Determine if all bits are 1. This is true for zero-width values. + bool isAllOnes() const { return value.isAllOnes() && unknown.isZero(); } + + /// Determine if all bits are X. This is true for zero-width values. + bool isAllX() const { return value.isZero() && unknown.isAllOnes(); } + + /// Determine if all bits are Z. This is true for zero-width values. + bool isAllZ() const { return value.isAllOnes() && unknown.isAllOnes(); } + + /// Determine whether the integer interpreted as a signed number would be + /// negative. Returns true if the sign bit is 1, and false if it is 0, X, or + /// Z. + bool isNegative() const { + auto idx = getBitWidth() - 1; + return value[idx] && !unknown[idx]; + } + + //===--------------------------------------------------------------------===// + // Bit Manipulation + //===--------------------------------------------------------------------===// + + /// The value of an individual bit. Can be 0, 1, X, or Z. + enum Bit { V0 = 0b00, V1 = 0b01, X = 0b10, Z = 0b11 }; + + /// Get the value of an individual bit. + Bit getBit(unsigned index) const { + return static_cast(value[index] | unknown[index] << 1); + } + + /// Set the value of an individual bit. + void setBit(unsigned index, Bit bit) { + value.setBitVal(index, (bit >> 0) & 1); + unknown.setBitVal(index, (bit >> 1) & 1); + } + + /// Compute a mask of all the 0 bits in this integer. + APInt getZeroBits() const { return ~value & ~unknown; } + + /// Compute a mask of all the 1 bits in this integer. + APInt getOneBits() const { return value & ~unknown; } + + /// Compute a mask of all the X bits in this integer. + APInt getXBits() const { return ~value & unknown; } + + /// Compute a mask of all the Z bits in this integer. + APInt getZBits() const { return value & unknown; } + + /// Compute a mask of all the X and Z bits in this integer. + APInt getUnknownBits() const { return unknown; } + + /// Set the value of all bits in the mask to 0. + template + void setZeroBits(const T &mask) { + value &= ~mask; + unknown &= ~mask; + } + + /// Set the value of all bits in the mask to 1. + template + void setOneBits(const T &mask) { + value |= mask; + unknown &= ~mask; + } + + /// Set the value of all bits in the mask to X. + template + void setXBits(const T &mask) { + value &= ~mask; + unknown |= mask; + } + + /// Set the value of all bits in the mask to Z. + template + void setZBits(const T &mask) { + value |= mask; + unknown |= mask; + } + + /// Set all bits to 0. + void setAllZero() { + value.clearAllBits(); + unknown.clearAllBits(); + } + + /// Set all bits to 1. + void setAllOne() { + value.setAllBits(); + unknown.clearAllBits(); + } + + /// Set all bits to X. + void setAllX() { + value.clearAllBits(); + unknown.setAllBits(); + } + + /// Set all bits to Z. + void setAllZ() { + value.setAllBits(); + unknown.setAllBits(); + } + + /// Replace all Z bits with X. This is useful since most logic operations will + /// treat X and Z bits the same way and produce an X bit in the output. By + /// mapping Z bits to X, these operations can then just handle 0, 1, and X + /// bits. + void replaceZWithX() { + // Z bits have value and unknown set to 1. X bits have value set to 0 and + // unknown set to 1. To convert between the two, make sure that value is 0 + // wherever unknown is 1. + value &= ~unknown; + } + + /// If any bits are X or Z, set the entire integer to X. + void setAllXIfAnyUnknown() { + if (hasUnknown()) + setAllX(); + } + + /// If any bits in this integer or another integer are X or Z, set the entire + /// integer to X. This is useful for binary operators which want to set their + /// result to X if either of the two inputs contained an X or Z bit. + void setAllXIfAnyUnknown(const FVInt &other) { + if (hasUnknown() || other.hasUnknown()) + setAllX(); + } + + //===--------------------------------------------------------------------===// + // Shift Operators + //===--------------------------------------------------------------------===// + + /// Perform a logical left-shift. If any bits in the shift amount are unknown, + /// the entire result is X. + FVInt &operator<<=(const FVInt &amount) { + if (amount.hasUnknown()) { + setAllX(); + } else { + value <<= amount.value; + unknown <<= amount.value; + } + return *this; + } + + /// Perform a logical left-shift by a two-valued amount. + template + FVInt &operator<<=(const T &amount) { + value <<= amount; + unknown <<= amount; + return *this; + } + + //===--------------------------------------------------------------------===// + // Logic Operators + //===--------------------------------------------------------------------===// + + /// Compute the logical NOT of this integer. This implements the following + /// bit-wise truth table: + /// ``` + /// 0 | 1 + /// 1 | 0 + /// X | X + /// Z | X + /// ``` + void flipAllBits() { + value = ~value; + replaceZWithX(); + } + + /// Compute the logical NOT. + FVInt operator~() const { + auto v = *this; + v.flipAllBits(); + return v; + } + + /// Compute the logical AND of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 0 0 0 + /// 1 | 0 1 X X + /// X | 0 X X X + /// Z | 0 X X X + /// ``` + FVInt &operator&=(const FVInt &other) { + auto zeros = getZeroBits() | other.getZeroBits(); + value &= other.value; + unknown |= other.unknown; + unknown &= ~zeros; + replaceZWithX(); + return *this; + } + + /// Compute the logical AND of this integer and a two-valued integer. + template + FVInt &operator&=(T other) { + value &= other; + unknown &= other; // make 0 bits known + replaceZWithX(); + return *this; + } + + /// Compute the logical AND. + template + FVInt operator&(const T &other) const { + auto v = *this; + v &= other; + return v; + } + + /// Compute the logical OR of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 1 X X + /// 1 | 1 1 1 1 + /// X | X 1 X X + /// Z | X 1 X X + /// ``` + FVInt &operator|=(const FVInt &other) { + auto ones = getOneBits() | other.getOneBits(); + value |= other.value; + unknown |= other.unknown; + unknown &= ~ones; + replaceZWithX(); + return *this; + } + + /// Compute the logical OR of this integer and a two-valued integer. + template + FVInt &operator|=(T other) { + value |= other; + unknown &= ~other; // make 1 bits known + replaceZWithX(); + return *this; + } + + /// Compute the logical OR. + template + FVInt operator|(const T &other) const { + auto v = *this; + v |= other; + return v; + } + + /// Compute the logical XOR of this integer and another. This implements the + /// following bit-wise truth table: + /// ``` + /// 0 1 X Z + /// +-------- + /// 0 | 0 1 X X + /// 1 | 1 0 X X + /// X | X X X X + /// Z | X X X X + /// ``` + FVInt &operator^=(const FVInt &other) { + value ^= other.value; + unknown |= other.unknown; + replaceZWithX(); + return *this; + } + + /// Compute the logical XOR of this integer and a two-valued integer. + template + FVInt &operator^=(const T &other) { + value ^= other; + replaceZWithX(); + return *this; + } + + /// Compute the logical XOR. + template + FVInt operator^(const T &other) const { + auto v = *this; + v ^= other; + return v; + } + + //===--------------------------------------------------------------------===// + // Arithmetic Operators + //===--------------------------------------------------------------------===// + + /// Compute the negation of this integer. If any bits are unknown, the entire + /// result is X. + void negate() { + value.negate(); + setAllXIfAnyUnknown(); + } + + /// Compute the negation of this integer. + FVInt operator-() const { + auto v = *this; + v.negate(); + return v; + } + + /// Compute the addition of this integer and another. If any bits in either + /// integer are unknown, the entire result is X. + FVInt &operator+=(const FVInt &other) { + value += other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the addition of this integer and a two-valued integer. If any bit + /// in the integer is unknown, the entire result is X. + template + FVInt &operator+=(const T &other) { + value += other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute an addition. + template + FVInt operator+(const T &other) const { + auto v = *this; + v += other; + return v; + } + + /// Compute the subtraction of this integer and another. If any bits in either + /// integer are unknown, the entire result is X. + FVInt &operator-=(const FVInt &other) { + value -= other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the subtraction of this integer and a two-valued integer. If any + /// bit in the integer is unknown, the entire result is X. + template + FVInt &operator-=(const T &other) { + value -= other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute an subtraction. + template + FVInt operator-(const T &other) const { + auto v = *this; + v -= other; + return v; + } + + /// Compute the multiplication of this integer and another. If any bits in + /// either integer are unknown, the entire result is X. + FVInt &operator*=(const FVInt &other) { + value *= other.value; + setAllXIfAnyUnknown(other); + return *this; + } + + /// Compute the multiplication of this integer and a two-valued integer. If + /// any bit in the integer is unknown, the entire result is X. + template + FVInt &operator*=(const T &other) { + value *= other; + setAllXIfAnyUnknown(); + return *this; + } + + /// Compute a multiplication. + template + FVInt operator*(const T &other) const { + auto v = *this; + v *= other; + return v; + } + + //===--------------------------------------------------------------------===// + // Comparison + //===--------------------------------------------------------------------===// + + /// Determine whether this integer is equal to another. Note that this + /// corresponds to SystemVerilog's `===` operator. Returns false if the two + /// integers have different bit width. + bool operator==(const FVInt &other) const { + if (getBitWidth() != other.getBitWidth()) + return false; + return value == other.value && unknown == other.unknown; + } + + /// Determine whether this integer is equal to a two-valued integer. Note that + /// this corresponds to SystemVerilog's `===` operator. + template + bool operator==(const T &other) const { + return value == other && !hasUnknown(); + } + + /// Determine whether this integer is not equal to another. Returns true if + /// the two integers have different bit width. + bool operator!=(const FVInt &other) const { return !((*this) == other); } + + /// Determine whether this integer is not equal to a two-valued integer. + template + bool operator!=(const T &other) const { + return !((*this) == other); + } + + //===--------------------------------------------------------------------===// + // String Conversion + //===--------------------------------------------------------------------===// + + /// Convert a string into an `FVInt`. + /// + /// The radix can be 2, 8, 10, or 16. For radix 2, the input string may + /// contain the characters `x` or `X` to indicate an unknown X bit, and `z` or + /// `Z` to indicate an unknown Z bit. For radix 8, each X or Z counts as 3 + /// bits. For radix 16, each X and Z counts as 4 bits. When radix is 10 the + /// input cannot contain any X or Z. + /// + /// Returns the parsed integer if the string is non-empty and a well-formed + /// number, otherwise returns none. + static std::optional tryFromString(StringRef str, unsigned radix = 10); + + /// Convert a string into an `FVInt`. Same as `tryFromString`, but aborts if + /// the string is malformed. + static FVInt fromString(StringRef str, unsigned radix = 10) { + auto v = tryFromString(str, radix); + assert(v.has_value() && "string is not a well-formed FVInt"); + return *v; + } + + /// Convert an `FVInt` to a string. + /// + /// The radix can be 2, 8, 10, or 16. For radix 8 or 16, the integer can only + /// contain unknown bits in groups of 3 or 4, respectively, such that a `X` or + /// `Z` can be printed for the entire group of bits. For radix 10, the integer + /// cannot contain any unknown bits. In case the output contains letters, + /// `uppercase` specifies whether they are printed as uppercase letters. + /// + /// Appends the output characters to `str` and returns true if the integer + /// could be printed with the given configuration. Otherwise returns false and + /// leaves `str` in its original state. Always succeeds for radix 2. + bool tryToString(SmallVectorImpl &str, unsigned radix = 10, + bool uppercase = true) const; + + /// Convert an `FVInt` to a string. Same as `tryToString`, but directly + /// returns the string and aborts if the conversion is unsuccessful. + SmallString<16> toString(unsigned radix = 10, bool uppercase = true) const { + SmallString<16> str; + bool success = tryToString(str, radix, uppercase); + assert(success && "radix cannot represent FVInt"); + return str; + } + + /// Print an `FVInt` to an output stream. + void print(raw_ostream &os) const; + +private: + APInt value; + APInt unknown; +}; + +inline FVInt operator&(uint64_t a, const FVInt &b) { return b & a; } +inline FVInt operator|(uint64_t a, const FVInt &b) { return b | a; } +inline FVInt operator^(uint64_t a, const FVInt &b) { return b ^ a; } +inline FVInt operator+(uint64_t a, const FVInt &b) { return b + a; } +inline FVInt operator*(uint64_t a, const FVInt &b) { return b * a; } + +inline FVInt operator&(const APInt &a, const FVInt &b) { return b & a; } +inline FVInt operator|(const APInt &a, const FVInt &b) { return b | a; } +inline FVInt operator^(const APInt &a, const FVInt &b) { return b ^ a; } +inline FVInt operator+(const APInt &a, const FVInt &b) { return b + a; } +inline FVInt operator*(const APInt &a, const FVInt &b) { return b * a; } + +inline FVInt operator-(uint64_t a, const FVInt &b) { + return FVInt(b.getBitWidth(), a) - b; +} + +inline FVInt operator-(const APInt &a, const FVInt &b) { return FVInt(a) - b; } + +inline bool operator==(uint64_t a, const FVInt &b) { return b == a; } +inline bool operator!=(uint64_t a, const FVInt &b) { return b != a; } + +inline raw_ostream &operator<<(raw_ostream &os, const FVInt &value) { + value.print(os); + return os; +} + +llvm::hash_code hash_value(const FVInt &a); + +/// Print a four-valued integer usign an `AsmPrinter`. This produces the +/// following output formats: +/// +/// - Decimal notation if the integer has no unknown bits. The sign bit is used +/// to determine whether the value is printed as negative number or not. +/// - Hexadecimal notation with a leading `h` if the integer the bits in each +/// hex digit are either all known, all X, or all Z. +/// - Binary notation with a leading `b` in all other cases. +void printFVInt(AsmPrinter &p, const FVInt &value); + +/// Parse a four-valued integer using an `AsmParser`. This accepts the following +/// formats: +/// +/// - `42`/`-42`: positive or negative integer in decimal notation. The sign bit +/// of the result indicates whether the value was negative. Cannot contain +/// unknown X or Z digits. +/// - `h123456789ABCDEF0XZ`: signless integer in hexadecimal notation. Can +/// contain unknown X or Z digits. +/// - `b10XZ`: signless integer in binary notation. Can contain unknown X or Z +/// digits. +/// +/// The result has enough bits to fully represent the parsed integer, and to +/// have the sign bit encode whether the integer was written as a negative +/// number in the input. The result's bit width may be larger than the minimum +/// number of bits required to represent its value. +ParseResult parseFVInt(AsmParser &p, FVInt &result); + +} // namespace circt + +#endif // CIRCT_SUPPORT_FVINT_H diff --git a/include/circt/Support/LoweringOptions.h b/include/circt/Support/LoweringOptions.h index 59ce7aadc966..d72fcdfc260d 100644 --- a/include/circt/Support/LoweringOptions.h +++ b/include/circt/Support/LoweringOptions.h @@ -166,6 +166,10 @@ struct LoweringOptions { /// If true, then update the the mlir to include output verilog locations. bool emitVerilogLocations = false; + + /// If true, add a dummy wire to empty modules to prevent tools from regarding + /// the module as blackbox. + bool fixUpEmptyModules = false; }; } // namespace circt diff --git a/include/circt/Support/Namespace.h b/include/circt/Support/Namespace.h index 51dac95129ad..d422991f1eb8 100644 --- a/include/circt/Support/Namespace.h +++ b/include/circt/Support/Namespace.h @@ -35,11 +35,13 @@ class Namespace { nextIndex.insert({"", 0}); } Namespace(const Namespace &other) = default; - Namespace(Namespace &&other) : nextIndex(std::move(other.nextIndex)) {} + Namespace(Namespace &&other) + : nextIndex(std::move(other.nextIndex)), locked(other.locked) {} Namespace &operator=(const Namespace &other) = default; Namespace &operator=(Namespace &&other) { nextIndex = std::move(other.nextIndex); + locked = other.locked; return *this; } @@ -59,8 +61,19 @@ class Namespace { nextIndex.insert({strAttr.getValue(), 0}); } + /// Removes a symbol from the namespace. Returns true if the symbol was + /// removed, false if the symbol was not found. + /// This is only allowed to be called _before_ any call to newName. + bool erase(llvm::StringRef symbol) { + assert(!locked && "Cannot erase names from a locked namespace"); + return nextIndex.erase(symbol); + } + /// Empty the namespace. - void clear() { nextIndex.clear(); } + void clear() { + nextIndex.clear(); + locked = false; + } /// Return a unique name, derived from the input `name`, and add the new name /// to the internal namespace. There are two possible outcomes for the @@ -70,6 +83,7 @@ class Namespace { /// 2. The name is given a `_` suffix where `` is a number starting from /// `0` and incrementing by one each time (`_0`, ...). StringRef newName(const Twine &name) { + locked = true; // Special case the situation where there is no name collision to avoid // messing with the SmallString allocation below. llvm::SmallString<64> tryName; @@ -103,6 +117,7 @@ class Namespace { /// 2. The name is given a suffix `__` where `` is a number /// starting from `0` and incrementing by one each time. StringRef newName(const Twine &name, const Twine &suffix) { + locked = true; // Special case the situation where there is no name collision to avoid // messing with the SmallString allocation below. llvm::SmallString<64> tryName; @@ -142,6 +157,11 @@ class Namespace { // namespace. It follows that all values less than the "next index" value are // already used. llvm::StringMap nextIndex; + + // When true, no names can be erased from the namespace. This is to prevent + // erasing names after they have been used, thus leaving users of the + // namespace in an inconsistent state. + bool locked = false; }; } // namespace circt diff --git a/include/circt/Support/Utils.h b/include/circt/Support/Utils.h new file mode 100644 index 000000000000..f6c76b2ebd14 --- /dev/null +++ b/include/circt/Support/Utils.h @@ -0,0 +1,30 @@ +//===- Utils.h - Miscellaneous utilities ----------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Miscellaneous utilities for CIRCT that do not fit in with other files. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_SUPPORT_UTILS_H +#define CIRCT_SUPPORT_UTILS_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/Operation.h" +#include "mlir/IR/Value.h" + +namespace circt { +/// Return true if a Value is created "underneath" an operation. This is +/// frequently useful when negated as that indicates that a Value is defined +/// "outside" the region of an Operation and that the Operation is thereby +/// "capturing" the value. +inline bool isAncestorOfValueOwner(Operation *op, Value value) { + return op->isAncestor(value.getParentBlock()->getParentOp()); +} +} // namespace circt + +#endif // CIRCT_SUPPORT_UTILS_H diff --git a/integration_test/Bindings/Python/dialects/om.py b/integration_test/Bindings/Python/dialects/om.py index fd6dcf00306e..2e2bce8a4815 100644 --- a/integration_test/Bindings/Python/dialects/om.py +++ b/integration_test/Bindings/Python/dialects/om.py @@ -3,7 +3,7 @@ import circt from circt.dialects import om -from circt.ir import Context, InsertionPoint, Location, Module, IntegerAttr, IntegerType +from circt.ir import Context, InsertionPoint, Location, Module, IntegerAttr, IntegerType, Type from circt.support import var_to_attribute from dataclasses import dataclass @@ -68,6 +68,11 @@ %map = om.map_create %entry1, %entry2: !om.string, !om.integer om.class.field @map_create, %map : !om.map + + %true = om.constant true + om.class.field @true, %true : i1 + %false = om.constant false + om.class.field @false, %false : i1 } om.class @Child(%0: !om.integer) { @@ -157,7 +162,7 @@ # CHECK: 14 print(obj.child.foo) -# CHECK: loc("-":60:7) +# CHECK: loc("-":65:7) print(obj.child.get_field_loc("foo")) # CHECK: ('Root', 'x') print(obj.reference) @@ -224,6 +229,11 @@ # CHECK-NEXT: Y 15 print(k, v) +# CHECK: True +print(obj.true) +# CHECK: False +print(obj.false) + obj = evaluator.instantiate("Client") object_dict: Dict[om.Object, str] = {} for field_name, data in obj: @@ -297,3 +307,12 @@ IntegerAttr.get(IntegerType.get_unsigned(64), -42)) # CHECK: 18446744073709551574 print(str(int_attr6)) + + # Test AnyType + any_type = Type.parse("!om.any") + assert isinstance(any_type, om.AnyType) + + # Test ListType + list_type = Type.parse("!om.list") + assert isinstance(list_type, om.ListType) + assert isinstance(list_type.element_type, om.AnyType) diff --git a/integration_test/Dialect/ESI/runtime/callback.mlir b/integration_test/Dialect/ESI/runtime/callback.mlir index 541fdb5f57e8..35dd8418bdb3 100644 --- a/integration_test/Dialect/ESI/runtime/callback.mlir +++ b/integration_test/Dialect/ESI/runtime/callback.mlir @@ -1,12 +1,17 @@ -// REQUIRES: esi-cosim, esi-runtime, rtl-sim, esitester +// REQUIRES: esi-cosim, esi-runtime, rtl-sim + +// Generate SV files // 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 --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir +// RUN: mkdir hw && cd hw +// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata --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 // RUN: cd .. -// RUN: esi-cosim.py --source %t6 --top top -- esitester cosim env | FileCheck %s -hw.module @EsiTesterTop(in %clk : !seq.clock, in %rst : i1) { +// Test cosimulation +// RUN: esi-cosim.py --source %t6/hw --top top -- esitester cosim env wait | FileCheck %s + +hw.module @top(in %clk : !seq.clock, in %rst : i1) { hw.instance "PrintfExample" sym @PrintfExample @PrintfExample(clk: %clk: !seq.clock, rst: %rst: i1) -> () + esi.service.instance #esi.appid<"cosim_default"> impl as "cosim" (%clk, %rst) : (!seq.clock, i1) -> () } // CHECK: PrintfExample: 7 diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir b/integration_test/Dialect/ESI/runtime/loopback.mlir index f5eec8e5e390..92dd32b7b934 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir @@ -1,11 +1,28 @@ // 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 --lower-hwarith-to-hw --canonicalize --export-split-verilog -o %t3.mlir + +// Generate SV files +// RUN: mkdir hw && cd hw +// RUN: circt-opt %s --esi-connect-services --esi-appid-hier=top=top --esi-build-manifest=top=top --esi-clean-metadata --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: esi-cosim.py --source %t6 --top top -- %python %s.py cosim env + +// Test ESI utils +// RUN: esiquery trace w:%t6/hw/esi_system_manifest.json info | FileCheck %s --check-prefix=QUERY-INFO +// RUN: esiquery trace w:%t6/hw/esi_system_manifest.json hier | FileCheck %s --check-prefix=QUERY-HIER + +// Test cosimulation +// RUN: esi-cosim.py --source %t6/hw --top top -- %python %s.py cosim env + +// Test C++ header generation against the manifest file +// RUN: %python -m esiaccel.cppgen --file %t6/hw/esi_system_manifest.json --output-dir %t6/include/loopback/ +// RUN: %host_cxx -I %t6/include %s.cpp -o %t6/test +// RUN: %t6/test | FileCheck %s --check-prefix=CPP-TEST +// RUN: FileCheck %s --check-prefix=LOOPBACK-H --input-file %t6/include/loopback/LoopbackIP.h + +// Test C++ header generation against a live accelerator +// RUN: esi-cosim.py --source %t6 --top top -- %python -m esiaccel.cppgen --platform cosim --connection env --output-dir %t6/include/loopback/ +// RUN: %host_cxx -I %t6/include %s.cpp -o %t6/test +// RUN: %t6/test | FileCheck %s --check-prefix=CPP-TEST !sendI8 = !esi.bundle<[!esi.channel from "send"]> !recvI8 = !esi.bundle<[!esi.channel to "recv"]> @@ -95,6 +112,7 @@ hw.module @CallableFunc1() { } esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} +esi.manifest.constants @Loopback {depth=5:ui32} hw.module @top(in %clk: !seq.clock, in %rst: i1) { esi.service.instance #esi.appid<"cosim"> svc @HostComms impl as "cosim" (%clk, %rst) : (!seq.clock, i1) -> () @@ -107,6 +125,18 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { hw.instance "loopback_array" @LoopbackArray() -> () } +// CPP-TEST: depth: 0x5 + +// QUERY-INFO: API version: 0 +// QUERY-INFO: ******************************** +// QUERY-INFO: * Module information +// QUERY-INFO: ******************************** +// QUERY-INFO: - LoopbackIP v0.0 : IP which simply echos bytes +// QUERY-INFO: Constants: +// QUERY-INFO: depth: 5 +// QUERY-INFO: Extra metadata: +// QUERY-INFO: foo: 1 + // QUERY-HIER: ******************************** // QUERY-HIER: * Design hierarchy // QUERY-HIER: ******************************** @@ -145,3 +175,14 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // QUERY-HIER: recv: !esi.channel // QUERY-HIER: mysvc_send: // QUERY-HIER: send: !esi.channel + + +// LOOPBACK-H: /// Generated header for esi_system module LoopbackIP. +// LOOPBACK-H-NEXT: #pragma once +// LOOPBACK-H-NEXT: #include "types.h" +// LOOPBACK-H-LABEL: namespace esi_system { +// LOOPBACK-H-LABEL: class LoopbackIP { +// LOOPBACK-H-NEXT: public: +// LOOPBACK-H-NEXT: static constexpr uint32_t depth = 0x5; +// LOOPBACK-H-NEXT: }; +// LOOPBACK-H-NEXT: } // namespace esi_system diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.cpp b/integration_test/Dialect/ESI/runtime/loopback.mlir.cpp new file mode 100644 index 000000000000..f9950359498a --- /dev/null +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.cpp @@ -0,0 +1,5 @@ +#include "loopback/LoopbackIP.h" + +#include + +int main() { printf("depth: 0x%x\n", esi_system::LoopbackIP::depth); } diff --git a/integration_test/Dialect/ESI/runtime/loopback.mlir.py b/integration_test/Dialect/ESI/runtime/loopback.mlir.py index 8bf332af4473..091b02511599 100644 --- a/integration_test/Dialect/ESI/runtime/loopback.mlir.py +++ b/integration_test/Dialect/ESI/runtime/loopback.mlir.py @@ -21,6 +21,13 @@ for esiType in m.type_table: print(f"{esiType}") +for info in m.module_infos: + print(f"{info.name}") + for const_name, const in info.constants.items(): + print(f" {const_name}: {const.value} {const.type}") + if info.name == "LoopbackIP" and const_name == "depth": + assert const.value == 5 + d = acc.build_accelerator() loopback = d.children[esiaccel.AppID("loopback_inst", 0)] @@ -94,4 +101,7 @@ print(f"result: {result}") if platform != "trace": assert result == [-21, -22] + +acc = None + print("PASS") diff --git a/integration_test/arcilator/JIT/dpi.mlir b/integration_test/arcilator/JIT/dpi.mlir new file mode 100644 index 000000000000..bdc3b32d80dc --- /dev/null +++ b/integration_test/arcilator/JIT/dpi.mlir @@ -0,0 +1,55 @@ +// RUN: split-file %s %t +// RUN: %host_cc %t/shared_lib.c --shared -o %t/shared_lib.so +// RUN: arcilator %t/dpi.mlir --run --jit-entry=main --shared-libs=%t/shared_lib.so | FileCheck %s +// REQUIRES: arcilator-jit + + +//--- shared_lib.c +void mul_shared(int a, int b, int *result) { *result = a * b; } + +//--- dpi.mlir +// CHECK: c = 0 +// CHECK-NEXT: d = 0 +// CHECK-NEXT: c = 5 +// CHECK-NEXT: d = 6 +sim.func.dpi @mul_shared(in %a : i32, in %b : i32, out c : i32) +sim.func.dpi @add_mlir(in %a : i32, in %b : i32, out c : i32) attributes {verilogName = "add_mlir_impl"} +func.func @add_mlir_impl(%arg0: i32, %arg1: i32, %arg2: !llvm.ptr) { + %0 = arith.addi %arg0, %arg1 : i32 + llvm.store %0, %arg2 : i32, !llvm.ptr + return +} +hw.module @arith(in %clock : i1, in %a : i32, in %b : i32, out c : i32, out d : i32) { + %seq_clk = seq.to_clock %clock + + %0 = sim.func.dpi.call @add_mlir(%a, %b) clock %seq_clk : (i32, i32) -> i32 + %1 = sim.func.dpi.call @mul_shared(%a, %b) clock %seq_clk : (i32, i32) -> i32 + hw.output %0, %1 : i32, i32 +} +func.func @main() { + %c2_i32 = arith.constant 2 : i32 + %c3_i32 = arith.constant 3 : i32 + %one = arith.constant 1 : i1 + %zero = arith.constant 0 : i1 + arc.sim.instantiate @arith as %arg0 { + arc.sim.set_input %arg0, "a" = %c2_i32 : i32, !arc.sim.instance<@arith> + arc.sim.set_input %arg0, "b" = %c3_i32 : i32, !arc.sim.instance<@arith> + arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@arith> + + arc.sim.step %arg0 : !arc.sim.instance<@arith> + arc.sim.set_input %arg0, "clock" = %zero : i1, !arc.sim.instance<@arith> + %0 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@arith> + %1 = arc.sim.get_port %arg0, "d" : i32, !arc.sim.instance<@arith> + + arc.sim.emit "c", %0 : i32 + arc.sim.emit "d", %1 : i32 + + arc.sim.step %arg0 : !arc.sim.instance<@arith> + arc.sim.set_input %arg0, "clock" = %one : i1, !arc.sim.instance<@arith> + %2 = arc.sim.get_port %arg0, "c" : i32, !arc.sim.instance<@arith> + %3 = arc.sim.get_port %arg0, "d" : i32, !arc.sim.instance<@arith> + arc.sim.emit "c", %2 : i32 + arc.sim.emit "d", %3 : i32 + } + return +} diff --git a/integration_test/arcilator/JIT/initial-shift-reg.mlir b/integration_test/arcilator/JIT/initial-shift-reg.mlir new file mode 100644 index 000000000000..3724962d8a7f --- /dev/null +++ b/integration_test/arcilator/JIT/initial-shift-reg.mlir @@ -0,0 +1,69 @@ +// RUN: arcilator %s --run --jit-entry=main | FileCheck %s +// REQUIRES: arcilator-jit + +// CHECK-LABEL: output = ca +// CHECK-NEXT: output = ca +// CHECK-NEXT: output = 0 +// CHECK-NEXT: output = fe +// CHECK-NEXT: output = ff + +module { + + hw.module @shiftreg(in %clock : i1, in %reset : i1, in %en : i1, in %din : i8, out dout : i8) { + %seq_clk = seq.to_clock %clock + %srA = seq.firreg %0 clock %seq_clk preset 0xFE : i8 + %srB = seq.firreg %1 clock %seq_clk : i8 + %srC = seq.firreg %2 clock %seq_clk preset 0xCA : i8 + %0 = comb.mux bin %en, %din, %srA : i8 + %1 = comb.mux bin %en, %srA, %srB : i8 + %2 = comb.mux bin %en, %srB, %srC : i8 + hw.output %srC : i8 + } + + func.func @main() { + %ff = arith.constant 0xFF : i8 + %false = arith.constant 0 : i1 + %true = arith.constant 1 : i1 + + arc.sim.instantiate @shiftreg as %model { + arc.sim.set_input %model, "en" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "reset" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "din" = %ff : i8, !arc.sim.instance<@shiftreg> + + %res0 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res0 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + + %res1 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res1 : i8 + + arc.sim.set_input %model, "en" = %true : i1, !arc.sim.instance<@shiftreg> + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res2 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res2 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res3 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res3 : i8 + + arc.sim.set_input %model, "clock" = %true : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + arc.sim.set_input %model, "clock" = %false : i1, !arc.sim.instance<@shiftreg> + arc.sim.step %model : !arc.sim.instance<@shiftreg> + %res4 = arc.sim.get_port %model, "dout" : i8, !arc.sim.instance<@shiftreg> + arc.sim.emit "output", %res4 : i8 + } + return + } +} diff --git a/integration_test/arcilator/JIT/initial.mlir b/integration_test/arcilator/JIT/initial.mlir new file mode 100644 index 000000000000..7cde23b21072 --- /dev/null +++ b/integration_test/arcilator/JIT/initial.mlir @@ -0,0 +1,25 @@ +// RUN: arcilator %s --run --jit-entry=main 2>&1 >/dev/null | FileCheck %s +// REQUIRES: arcilator-jit + +// CHECK: - Init - + +module { + llvm.func @_arc_env_get_print_stream(i32) -> !llvm.ptr + llvm.func @_arc_libc_fputs(!llvm.ptr, !llvm.ptr) -> i32 + llvm.mlir.global internal constant @global_init_str(" - Init -\0A\00") {addr_space = 0 : i32} + + arc.model @initmodel io !hw.modty<> { + ^bb0(%arg0: !arc.storage): + arc.initial { + %cst0 = llvm.mlir.constant(0 : i32) : i32 + %stderr = llvm.call @_arc_env_get_print_stream(%cst0) : (i32) -> !llvm.ptr + %str = llvm.mlir.addressof @global_init_str : !llvm.ptr + %0 = llvm.call @_arc_libc_fputs(%str, %stderr) : (!llvm.ptr, !llvm.ptr) -> i32 + } + } + func.func @main() { + arc.sim.instantiate @initmodel as %arg0 { + } + return + } +} diff --git a/integration_test/arcilator/JIT/runtime-environment.mlir b/integration_test/arcilator/JIT/runtime-environment.mlir new file mode 100644 index 000000000000..04ae3e828f14 --- /dev/null +++ b/integration_test/arcilator/JIT/runtime-environment.mlir @@ -0,0 +1,40 @@ +// RUN: arcilator %s --run --jit-entry=main 2>&1 >/dev/null | FileCheck -strict-whitespace %s +// REQUIRES: arcilator-jit + +// CHECK: Hello World!{{[[:space:]]}} + +module { + + llvm.func @_arc_env_get_print_stream(i32) -> !llvm.ptr + llvm.func @_arc_libc_fprintf(!llvm.ptr, !llvm.ptr, ...) -> i32 + llvm.func @_arc_libc_fputc(i32, !llvm.ptr) -> i32 + llvm.func @_arc_libc_fputs(!llvm.ptr, !llvm.ptr) -> i32 + + llvm.mlir.global internal constant @global_hello("He%c%co \00") {addr_space = 0 : i32} + llvm.mlir.global internal constant @global_world("World\00") {addr_space = 0 : i32} + + arc.model @dut io !hw.modty<> { + ^bb0(%arg0: !arc.storage): + %cst0 = llvm.mlir.constant(0 : i32) : i32 + %ascii_em = llvm.mlir.constant(33 : i32) : i32 + %ascii_lf = llvm.mlir.constant(10 : i32) : i32 + %ascii_l = llvm.mlir.constant(108 : i32) : i32 + %stderr = llvm.call @_arc_env_get_print_stream(%cst0) : (i32) -> !llvm.ptr + + %hello = llvm.mlir.addressof @global_hello : !llvm.ptr + %0 = llvm.call @_arc_libc_fprintf(%stderr, %hello, %ascii_l, %ascii_l) vararg(!llvm.func) : (!llvm.ptr, !llvm.ptr, i32, i32) -> i32 + + %world = llvm.mlir.addressof @global_world : !llvm.ptr + %1 = llvm.call @_arc_libc_fputs(%world, %stderr) : (!llvm.ptr, !llvm.ptr) -> i32 + + %2 = llvm.call @_arc_libc_fputc(%ascii_em, %stderr) : (i32, !llvm.ptr) -> i32 + %3 = llvm.call @_arc_libc_fputc(%ascii_lf, %stderr) : (i32, !llvm.ptr) -> i32 + } + + func.func @main() { + arc.sim.instantiate @dut as %arg0 { + arc.sim.step %arg0 : !arc.sim.instance<@dut> + } + return + } +} diff --git a/integration_test/lit.cfg.py b/integration_test/lit.cfg.py index 94d0ab4c8108..f3de280b120e 100644 --- a/integration_test/lit.cfg.py +++ b/integration_test/lit.cfg.py @@ -187,6 +187,10 @@ if config.bindings_tcl_enabled: config.available_features.add('bindings_tcl') +# Add host c/c++ compiler. +config.substitutions.append(("%host_cc", config.host_cc)) +config.substitutions.append(("%host_cxx", config.host_cxx)) + # Enable clang-tidy if it has been detected. if config.clang_tidy_path != "": tool_dirs.append(config.clang_tidy_path) diff --git a/lib/Bindings/Python/OMModule.cpp b/lib/Bindings/Python/OMModule.cpp index 5122c88e8f44..7908867d116a 100644 --- a/lib/Bindings/Python/OMModule.cpp +++ b/lib/Bindings/Python/OMModule.cpp @@ -380,10 +380,20 @@ static PythonPrimitive omPrimitiveToPythonValue(MlirAttribute attr) { return py::str(strRef.data, strRef.length); } + // BoolAttr's are IntegerAttr's, check this first. if (mlirAttributeIsABool(attr)) { return py::bool_(mlirBoolAttrGetValue(attr)); } + if (mlirAttributeIsAInteger(attr)) { + MlirType type = mlirAttributeGetType(attr); + if (mlirTypeIsAIndex(type) || mlirIntegerTypeIsSignless(type)) + return py::int_(mlirIntegerAttrGetValueInt(attr)); + if (mlirIntegerTypeIsSigned(type)) + return py::int_(mlirIntegerAttrGetValueSInt(attr)); + return py::int_(mlirIntegerAttrGetValueUInt(attr)); + } + if (omAttrIsAReferenceAttr(attr)) { auto innerRef = omReferenceAttrGetInnerRef(attr); auto moduleStrRef = @@ -602,6 +612,9 @@ void circt::python::populateDialectOMSubmodule(py::module &m) { .def("__len__", &omMapAttrGetNumElements); PyMapAttrIterator::bind(m); + // Add the AnyType class definition. + mlir_type_subclass(m, "AnyType", omTypeIsAAnyType, omAnyTypeGetTypeID); + // Add the ClassType class definition. mlir_type_subclass(m, "ClassType", omTypeIsAClassType, omClassTypeGetTypeID) .def_property_readonly("name", [](MlirType type) { @@ -613,6 +626,10 @@ void circt::python::populateDialectOMSubmodule(py::module &m) { mlir_type_subclass(m, "BasePathType", omTypeIsAFrozenBasePathType, omFrozenBasePathTypeGetTypeID); + // Add the ListType class definition. + mlir_type_subclass(m, "ListType", omTypeIsAListType, omListTypeGetTypeID) + .def_property_readonly("element_type", omListTypeGetElementType); + // Add the PathType class definition. mlir_type_subclass(m, "PathType", omTypeIsAFrozenPathType, omFrozenPathTypeGetTypeID); diff --git a/lib/Bindings/Python/dialects/om.py b/lib/Bindings/Python/dialects/om.py index bdf51eed84e1..ef1d9fe43871 100644 --- a/lib/Bindings/Python/dialects/om.py +++ b/lib/Bindings/Python/dialects/om.py @@ -5,7 +5,7 @@ from __future__ import annotations from ._om_ops_gen import * -from .._mlir_libs._circt._om import Evaluator as BaseEvaluator, Object as BaseObject, List as BaseList, Tuple as BaseTuple, Map as BaseMap, BasePath as BaseBasePath, BasePathType, Path, PathType, ClassType, ReferenceAttr, ListAttr, MapAttr, OMIntegerAttr +from .._mlir_libs._circt._om import AnyType, Evaluator as BaseEvaluator, Object as BaseObject, List as BaseList, Tuple as BaseTuple, Map as BaseMap, BasePath as BaseBasePath, BasePathType, Path, PathType, ClassType, ReferenceAttr, ListAttr, ListType, MapAttr, OMIntegerAttr from ..ir import Attribute, Diagnostic, DiagnosticSeverity, Module, StringAttr, IntegerAttr, IntegerType from ..support import attribute_to_var, var_to_attribute diff --git a/lib/CAPI/Dialect/HW.cpp b/lib/CAPI/Dialect/HW.cpp index 2ffb1f8e3649..732c322101a7 100644 --- a/lib/CAPI/Dialect/HW.cpp +++ b/lib/CAPI/Dialect/HW.cpp @@ -217,6 +217,10 @@ MlirAttribute hwInnerSymAttrGet(MlirAttribute symName) { return wrap(InnerSymAttr::get(cast(unwrap(symName)))); } +MlirAttribute hwInnerSymAttrGetEmpty(MlirContext ctx) { + return wrap(InnerSymAttr::get(unwrap(ctx))); +} + MlirAttribute hwInnerSymAttrGetSymName(MlirAttribute innerSymAttr) { return wrap((Attribute)cast(unwrap(innerSymAttr)).getSymName()); } diff --git a/lib/CAPI/Dialect/OM.cpp b/lib/CAPI/Dialect/OM.cpp index 1263974d5ab8..5b7f828737b9 100644 --- a/lib/CAPI/Dialect/OM.cpp +++ b/lib/CAPI/Dialect/OM.cpp @@ -29,6 +29,12 @@ MLIR_DEFINE_CAPI_DIALECT_REGISTRATION(OM, om, OMDialect) // Type API. //===----------------------------------------------------------------------===// +/// Is the Type an AnyType. +bool omTypeIsAAnyType(MlirType type) { return isa(unwrap(type)); } + +/// Get the TypeID for an AnyType. +MlirTypeID omAnyTypeGetTypeID(void) { return wrap(AnyType::getTypeID()); } + /// Is the Type a ClassType. bool omTypeIsAClassType(MlirType type) { return isa(unwrap(type)); } @@ -60,6 +66,17 @@ MlirTypeID omFrozenPathTypeGetTypeID(void) { return wrap(FrozenPathType::getTypeID()); } +/// Is the Type a ListType. +bool omTypeIsAListType(MlirType type) { return isa(unwrap(type)); } + +/// Get the TypeID for a ListType. +MlirTypeID omListTypeGetTypeID(void) { return wrap(ListType::getTypeID()); } + +// Return a element type of a ListType. +MlirType omListTypeGetElementType(MlirType type) { + return wrap(cast(unwrap(type)).getElementType()); +} + /// Is the Type a StringType. bool omTypeIsAStringType(MlirType type) { return isa(unwrap(type)); diff --git a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp index 086038148c04..d6e4f5bcc130 100644 --- a/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp +++ b/lib/Conversion/ArcToLLVM/LowerArcToLLVM.cpp @@ -318,6 +318,7 @@ namespace { struct ModelInfoMap { size_t numStateBytes; llvm::DenseMap states; + mlir::FlatSymbolRefAttr initialFnSymbol; }; template @@ -378,6 +379,16 @@ struct SimInstantiateOpLowering Value zero = rewriter.create(loc, rewriter.getI8Type(), 0); rewriter.create(loc, allocated, zero, numStateBytes, false); + + // Call the model's 'initial' function if present. + if (model.initialFnSymbol) { + auto initialFnType = LLVM::LLVMFunctionType::get( + LLVM::LLVMVoidType::get(op.getContext()), + {LLVM::LLVMPointerType::get(op.getContext())}); + rewriter.create(loc, initialFnType, model.initialFnSymbol, + ValueRange{allocated}); + } + rewriter.inlineBlockBefore(&adaptor.getBody().getBlocks().front(), op, {allocated}); rewriter.create(loc, freeFunc, ValueRange{allocated}); @@ -646,7 +657,8 @@ void LowerArcToLLVMPass::runOnOperation() { for (StateInfo &stateInfo : modelInfo.states) states.insert({stateInfo.name, stateInfo}); modelMap.insert({modelInfo.name, - ModelInfoMap{modelInfo.numStateBytes, std::move(states)}}); + ModelInfoMap{modelInfo.numStateBytes, std::move(states), + modelInfo.initialFnSym}}); } patterns.addhasTrait() || isa(op) || + seq::ClockGateOp, sim::DPICallOp>(op) || op->getNumResults() > 1; } @@ -265,6 +266,7 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { auto stateOp = dyn_cast(callOp.getOperation()); Value clock = stateOp ? stateOp.getClock() : Value{}; Value reset; + SmallVector initialValues; SmallVector absorbedRegs; SmallVector absorbedNames(callOp->getNumResults(), {}); if (auto names = callOp->getAttrOfType("names")) @@ -306,6 +308,8 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { } } + initialValues.push_back(regOp.getPowerOnValue()); + absorbedRegs.push_back(regOp); // If we absorb a register into the arc, the arc effectively produces that // register's value. So if the register had a name, ensure that we assign @@ -344,6 +348,28 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { "had a reset."); arc.getResetMutable().assign(reset); } + + bool onlyDefaultInitializers = + llvm::all_of(initialValues, [](auto val) -> bool { return !val; }); + + if (!onlyDefaultInitializers) { + if (!arc.getInitials().empty()) { + return arc.emitError( + "StateOp tried to infer initial values from CompReg, but already " + "had an initial value."); + } + // Create 0 constants for default initialization + for (unsigned i = 0; i < initialValues.size(); ++i) { + if (!initialValues[i]) { + OpBuilder zeroBuilder(arc); + initialValues[i] = zeroBuilder.createOrFold( + arc.getLoc(), + zeroBuilder.getIntegerAttr(arc.getResult(i).getType(), 0)); + } + } + arc.getInitialsMutable().assign(initialValues); + } + if (tapRegisters && llvm::any_of(absorbedNames, [](auto name) { return !cast(name).getValue().empty(); })) @@ -384,6 +410,7 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { SmallVector outputs; SmallVector names; SmallVector types; + SmallVector initialValues; SmallDenseMap mapping; SmallVector regToOutputMapping; for (auto regOp : regOps) { @@ -394,6 +421,7 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { types.push_back(regOp.getType()); outputs.push_back(block->addArgument(regOp.getType(), regOp.getLoc())); names.push_back(regOp->getAttrOfType("name")); + initialValues.push_back(regOp.getPowerOnValue()); } regToOutputMapping.push_back(it->second); } @@ -410,9 +438,22 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { defOp.getBody().push_back(block.release()); builder.setInsertionPoint(module.getBodyBlock()->getTerminator()); + + bool onlyDefaultInitializers = + llvm::all_of(initialValues, [](auto val) -> bool { return !val; }); + + if (onlyDefaultInitializers) + initialValues.clear(); + else + for (unsigned i = 0; i < initialValues.size(); ++i) { + if (!initialValues[i]) + initialValues[i] = builder.createOrFold( + loc, builder.getIntegerAttr(types[i], 0)); + } + auto arcOp = builder.create(loc, defOp, std::get<0>(clockAndResetAndOp), - /*enable=*/Value{}, 1, inputs); + /*enable=*/Value{}, 1, inputs, initialValues); auto reset = std::get<1>(clockAndResetAndOp); if (reset) arcOp.getResetMutable().assign(reset); diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index 74e5760ccae1..a67d02581c64 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -341,6 +341,13 @@ static Type stripUnpackedTypes(Type type) { .Default([](Type type) { return type; }); } +/// Return true if the type has a leading unpacked type. +static bool hasLeadingUnpackedType(Type type) { + assert(isa(type) && "inout type is expected"); + auto elementType = cast(type).getElementType(); + return stripUnpackedTypes(elementType) != elementType; +} + /// Return true if type has a struct type as a subtype. static bool hasStructType(Type type) { return TypeSwitch(type) @@ -5855,8 +5862,11 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { } // Try inlining an assignment into declarations. + // FIXME: Unpacked array is not inlined since several tools doesn't support + // that syntax. See Issue 6363. if (isa(op) && - !op->getParentOp()->hasTrait()) { + !op->getParentOp()->hasTrait() && + !hasLeadingUnpackedType(op->getResult(0).getType())) { // Get a single assignments if any. if (auto singleAssign = getSingleAssignAndCheckUsers(op)) { auto *source = singleAssign.getSrc().getDefiningOp(); @@ -5877,7 +5887,10 @@ LogicalResult StmtEmitter::emitDeclaration(Operation *op) { } // Try inlining a blocking assignment to logic op declaration. - if (isa(op) && op->getParentOp()->hasTrait()) { + // FIXME: Unpacked array is not inlined since several tools doesn't support + // that syntax. See Issue 6363. + if (isa(op) && op->getParentOp()->hasTrait() && + !hasLeadingUnpackedType(op->getResult(0).getType())) { // Get a single assignment which might be possible to inline. if (auto singleAssign = getSingleAssignAndCheckUsers(op)) { // It is necessary for the assignment to dominate users of the op. diff --git a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp index 59d1b660f7d4..4789346f0760 100644 --- a/lib/Conversion/ExportVerilog/PrepareForEmission.cpp +++ b/lib/Conversion/ExportVerilog/PrepareForEmission.cpp @@ -1315,6 +1315,24 @@ static LogicalResult legalizeHWModule(Block &block, return success(); } +static void fixUpEmptyModules(hw::HWEmittableModuleLike module) { + auto outputOp = dyn_cast(module.getBodyBlock()->begin()); + if (!outputOp || outputOp->getNumOperands() > 0) + return; // Not empty so no need to fix up. + OpBuilder builder(module->getContext()); + builder.setInsertionPoint(outputOp); + auto constant = builder.create(module.getLoc(), + builder.getBoolAttr(true)); + auto wire = builder.create(module.getLoc(), builder.getI1Type()); + sv::setSVAttributes(wire, + sv::SVAttributeAttr::get( + builder.getContext(), + "This wire is added to avoid emitting empty modules. " + "See `fixUpEmptyModules` lowering option in CIRCT.", + /*emitAsComment=*/true)); + builder.create(module.getLoc(), wire, constant); +} + // NOLINTNEXTLINE(misc-no-recursion) LogicalResult ExportVerilog::prepareHWModule(hw::HWEmittableModuleLike module, const LoweringOptions &options) { @@ -1325,6 +1343,10 @@ LogicalResult ExportVerilog::prepareHWModule(hw::HWEmittableModuleLike module, // Zero-valued logic pruning. pruneZeroValuedLogic(module); + // Fix up empty modules if necessary. + if (options.fixUpEmptyModules) + fixUpEmptyModules(module); + // Legalization. if (failed(legalizeHWModule(*module.getBodyBlock(), options))) return failure(); diff --git a/lib/Conversion/ImportVerilog/Expressions.cpp b/lib/Conversion/ImportVerilog/Expressions.cpp index 48b268bb7649..8ae630cc9432 100644 --- a/lib/Conversion/ImportVerilog/Expressions.cpp +++ b/lib/Conversion/ImportVerilog/Expressions.cpp @@ -12,6 +12,20 @@ using namespace circt; using namespace ImportVerilog; +using moore::Domain; + +/// Convert a Slang `SVInt` to a CIRCT `FVInt`. +static FVInt convertSVIntToFVInt(const slang::SVInt &svint) { + if (svint.hasUnknown()) { + unsigned numWords = svint.getNumWords() / 2; + auto value = ArrayRef(svint.getRawPtr(), numWords); + auto unknown = ArrayRef(svint.getRawPtr() + numWords, numWords); + return FVInt(APInt(svint.getBitWidth(), value), + APInt(svint.getBitWidth(), unknown)); + } + auto value = ArrayRef(svint.getRawPtr(), svint.getNumWords()); + return FVInt(APInt(svint.getBitWidth(), value)); +} // NOLINTBEGIN(misc-no-recursion) namespace { @@ -28,26 +42,23 @@ struct RvalueExprVisitor { Value convertToSimpleBitVector(Value value) { if (!value) return {}; - if (isa(value.getType()) || - isa( - dyn_cast(value.getType()).getNestedType())) + if (isa(value.getType())) return value; - mlir::emitError(loc, "expression of type ") - << value.getType() << " cannot be cast to a simple bit vector"; - return {}; - } - /// Helper function to convert a value to its "truthy" boolean value. - Value convertToBool(Value value) { - if (!value) - return {}; - if (auto type = dyn_cast_or_null(value.getType())) - if (type.getBitSize() == 1) - return value; - if (auto type = dyn_cast_or_null(value.getType())) - return builder.create(loc, value); + // Some operations in Slang's AST, for example bitwise or `|`, don't cast + // packed struct/array operands to simple bit vectors but directly operate + // on the struct/array. Since the corresponding IR ops operate only on + // simple bit vectors, insert a conversion in this case. + if (auto packed = dyn_cast(value.getType())) { + if (auto bits = packed.getBitSize()) { + auto sbvType = + moore::IntType::get(value.getContext(), *bits, packed.getDomain()); + return builder.create(loc, sbvType, value); + } + } + mlir::emitError(loc, "expression of type ") - << value.getType() << " cannot be cast to a boolean"; + << value.getType() << " cannot be cast to a simple bit vector"; return {}; } @@ -55,16 +66,18 @@ struct RvalueExprVisitor { Value visit(const slang::ast::LValueReferenceExpression &expr) { assert(!context.lvalueStack.empty() && "parent assignments push lvalue"); auto lvalue = context.lvalueStack.back(); - return builder.create( - loc, cast(lvalue.getType()).getNestedType(), lvalue); + return builder.create(loc, lvalue); } // Handle named values, such as references to declared variables. Value visit(const slang::ast::NamedValueExpression &expr) { if (auto value = context.valueSymbols.lookup(&expr.symbol)) { - if (auto refType = dyn_cast(value.getType())) - value = - builder.create(loc, refType.getNestedType(), value); + if (isa(value.getType())) { + auto readOp = builder.create(loc, value); + if (context.rvalueReadCallback) + context.rvalueReadCallback(readOp); + value = readOp.getResult(); + } return value; } @@ -76,7 +89,7 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); if (!type) return {}; - return convertSVInt(constant.integer(), type); + return materializeSVInt(constant.integer(), type); } // Otherwise some other part of ImportVerilog should have added an MLIR @@ -92,19 +105,20 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); if (!type) return {}; - auto operand = context.convertRvalueExpression(expr.operand()); - if (!operand) - return {}; - return builder.create(loc, type, operand); + return context.convertRvalueExpression(expr.operand(), type); } // Handle blocking and non-blocking assignments. Value visit(const slang::ast::AssignmentExpression &expr) { auto lhs = context.convertLvalueExpression(expr.left()); + if (!lhs) + return {}; + context.lvalueStack.push_back(lhs); - auto rhs = context.convertRvalueExpression(expr.right()); + auto rhs = context.convertRvalueExpression( + expr.right(), cast(lhs.getType()).getNestedType()); context.lvalueStack.pop_back(); - if (!lhs || !rhs) + if (!rhs) return {}; if (expr.timingControl) { @@ -135,19 +149,16 @@ struct RvalueExprVisitor { // Helper function to create pre and post increments and decrements. Value createIncrement(Value arg, bool isInc, bool isPost) { - auto preValue = convertToSimpleBitVector(arg); - if (!preValue) - return {}; - preValue = builder.create( - loc, cast(preValue.getType()).getNestedType(), - preValue); + auto preValue = builder.create(loc, arg); auto one = builder.create( loc, cast(preValue.getType()), 1); auto postValue = isInc ? builder.create(loc, preValue, one).getResult() : builder.create(loc, preValue, one).getResult(); builder.create(loc, arg, postValue); - return isPost ? preValue : postValue; + if (isPost) + return preValue; + return postValue; } // Handle unary operators. @@ -196,7 +207,7 @@ struct RvalueExprVisitor { return createReduction(arg, true); case UnaryOperator::LogicalNot: - arg = convertToBool(arg); + arg = context.convertToBool(arg); if (!arg) return {}; return builder.create(loc, arg); @@ -220,8 +231,10 @@ struct RvalueExprVisitor { template Value createBinary(Value lhs, Value rhs) { lhs = convertToSimpleBitVector(lhs); + if (!lhs) + return {}; rhs = convertToSimpleBitVector(rhs); - if (!lhs || !rhs) + if (!rhs) return {}; return builder.create(loc, lhs, rhs); } @@ -229,10 +242,18 @@ struct RvalueExprVisitor { // Handle binary operators. Value visit(const slang::ast::BinaryExpression &expr) { auto lhs = context.convertRvalueExpression(expr.left()); + if (!lhs) + return {}; auto rhs = context.convertRvalueExpression(expr.right()); - if (!lhs || !rhs) + if (!rhs) return {}; + // Determine the domain of the result. + Domain domain = Domain::TwoValued; + if (expr.type->isFourState() || expr.left().type->isFourState() || + expr.right().type->isFourState()) + domain = Domain::FourValued; + using slang::ast::BinaryOperator; switch (expr.op) { case BinaryOperator::Add: @@ -313,35 +334,45 @@ struct RvalueExprVisitor { // See IEEE 1800-2017 § 11.4.7 "Logical operators". case BinaryOperator::LogicalAnd: { - // TODO: This should short-circuit. Put the RHS code into an scf.if. - lhs = convertToBool(lhs); - rhs = convertToBool(rhs); - if (!lhs || !rhs) + // TODO: This should short-circuit. Put the RHS code into a separate + // block. + lhs = context.convertToBool(lhs, domain); + if (!lhs) + return {}; + rhs = context.convertToBool(rhs, domain); + if (!rhs) return {}; return builder.create(loc, lhs, rhs); } case BinaryOperator::LogicalOr: { - // TODO: This should short-circuit. Put the RHS code into an scf.if. - lhs = convertToBool(lhs); - rhs = convertToBool(rhs); - if (!lhs || !rhs) + // TODO: This should short-circuit. Put the RHS code into a separate + // block. + lhs = context.convertToBool(lhs, domain); + if (!lhs) + return {}; + rhs = context.convertToBool(rhs, domain); + if (!rhs) return {}; return builder.create(loc, lhs, rhs); } case BinaryOperator::LogicalImplication: { // `(lhs -> rhs)` equivalent to `(!lhs || rhs)`. - lhs = convertToBool(lhs); - rhs = convertToBool(rhs); - if (!lhs || !rhs) + lhs = context.convertToBool(lhs, domain); + if (!lhs) + return {}; + rhs = context.convertToBool(rhs, domain); + if (!rhs) return {}; auto notLHS = builder.create(loc, lhs); return builder.create(loc, notLHS, rhs); } case BinaryOperator::LogicalEquivalence: { // `(lhs <-> rhs)` equivalent to `(lhs && rhs) || (!lhs && !rhs)`. - lhs = convertToBool(lhs); - rhs = convertToBool(rhs); - if (!lhs || !rhs) + lhs = context.convertToBool(lhs, domain); + if (!lhs) + return {}; + rhs = context.convertToBool(rhs, domain); + if (!rhs) return {}; auto notLHS = builder.create(loc, lhs); auto notRHS = builder.create(loc, rhs); @@ -373,20 +404,17 @@ struct RvalueExprVisitor { return {}; } - // Materialize a Slang integer literal as a constant op. - Value convertSVInt(const slang::SVInt &value, Type type) { - if (value.hasUnknown()) { - mlir::emitError(loc, "literals with X or Z bits not supported"); - return {}; - } - auto intType = - moore::IntType::get(context.getContext(), value.getBitWidth(), - value.hasUnknown() ? moore::Domain::FourValued + /// Materialize a Slang integer literal as a constant op. + Value materializeSVInt(const slang::SVInt &svint, Type type) { + auto fvint = convertSVIntToFVInt(svint); + bool typeIsFourValued = false; + if (auto unpackedType = dyn_cast(type)) + typeIsFourValued = unpackedType.getDomain() == moore::Domain::FourValued; + auto intType = moore::IntType::get( + context.getContext(), fvint.getBitWidth(), + fvint.hasUnknown() || typeIsFourValued ? moore::Domain::FourValued : moore::Domain::TwoValued); - Value result = builder.create( - loc, intType, - APInt(value.getBitWidth(), - ArrayRef(value.getRawPtr(), value.getNumWords()))); + Value result = builder.create(loc, intType, fvint); if (result.getType() != type) result = builder.create(loc, type, result); return result; @@ -397,7 +425,7 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); if (!type) return {}; - return convertSVInt(expr.getValue(), type); + return materializeSVInt(expr.getValue(), type); } // Handle integer literals. @@ -405,7 +433,7 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); if (!type) return {}; - return convertSVInt(expr.getValue(), type); + return materializeSVInt(expr.getValue(), type); } // Handle concatenations. @@ -611,31 +639,44 @@ struct RvalueExprVisitor { auto type = context.convertType(*expr.type); // Handle condition. - Value cond = convertToSimpleBitVector( - context.convertRvalueExpression(*expr.conditions.begin()->expr)); - cond = convertToBool(cond); - if (!cond) + if (expr.conditions.size() > 1) { + mlir::emitError(loc) + << "unsupported conditional expression with more than one condition"; + return {}; + } + const auto &cond = expr.conditions[0]; + if (cond.pattern) { + mlir::emitError(loc) << "unsupported conditional expression with pattern"; return {}; - auto conditionalOp = builder.create(loc, type, cond); + } + auto value = + context.convertToBool(context.convertRvalueExpression(*cond.expr)); + if (!value) + return {}; + auto conditionalOp = builder.create(loc, type, value); // Create blocks for true region and false region. - conditionalOp.getTrueRegion().emplaceBlock(); - conditionalOp.getFalseRegion().emplaceBlock(); + auto &trueBlock = conditionalOp.getTrueRegion().emplaceBlock(); + auto &falseBlock = conditionalOp.getFalseRegion().emplaceBlock(); OpBuilder::InsertionGuard g(builder); // Handle left expression. - builder.setInsertionPointToStart(conditionalOp.getBody(0)); + builder.setInsertionPointToStart(&trueBlock); auto trueValue = context.convertRvalueExpression(expr.left()); if (!trueValue) return {}; + if (trueValue.getType() != type) + trueValue = builder.create(loc, type, trueValue); builder.create(loc, trueValue); // Handle right expression. - builder.setInsertionPointToStart(conditionalOp.getBody(1)); + builder.setInsertionPointToStart(&falseBlock); auto falseValue = context.convertRvalueExpression(expr.right()); if (!falseValue) return {}; + if (falseValue.getType() != type) + falseValue = builder.create(loc, type, falseValue); builder.create(loc, falseValue); return conditionalOp.getResult(); @@ -718,6 +759,70 @@ struct RvalueExprVisitor { return builder.create(loc, type, expr.getValue()); } + /// Handle assignment patterns. + Value visitAssignmentPattern( + const slang::ast::AssignmentPatternExpressionBase &expr, + unsigned replCount = 1) { + auto type = context.convertType(*expr.type); + + // Convert the individual elements first. + auto elementCount = expr.elements().size(); + SmallVector elements; + elements.reserve(replCount * elementCount); + for (auto elementExpr : expr.elements()) { + auto value = context.convertRvalueExpression(*elementExpr); + if (!value) + return {}; + elements.push_back(value); + } + for (unsigned replIdx = 1; replIdx < replCount; ++replIdx) + for (unsigned elementIdx = 0; elementIdx < elementCount; ++elementIdx) + elements.push_back(elements[elementIdx]); + + // Handle packed structs. + if (auto structType = dyn_cast(type)) { + assert(structType.getMembers().size() == elements.size()); + return builder.create(loc, structType, elements); + } + + // Handle unpacked structs. + if (auto structType = dyn_cast(type)) { + assert(structType.getMembers().size() == elements.size()); + return builder.create(loc, structType, elements); + } + + // Handle packed arrays. + if (auto arrayType = dyn_cast(type)) { + assert(arrayType.getSize() == elements.size()); + return builder.create(loc, arrayType, elements); + } + + // Handle unpacked arrays. + if (auto arrayType = dyn_cast(type)) { + assert(arrayType.getSize() == elements.size()); + return builder.create(loc, arrayType, elements); + } + + mlir::emitError(loc) << "unsupported assignment pattern with type " << type; + return {}; + } + + Value visit(const slang::ast::SimpleAssignmentPatternExpression &expr) { + return visitAssignmentPattern(expr); + } + + Value visit(const slang::ast::StructuredAssignmentPatternExpression &expr) { + return visitAssignmentPattern(expr); + } + + Value visit(const slang::ast::ReplicatedAssignmentPatternExpression &expr) { + slang::ast::EvalContext evalContext(context.compilation, + slang::ast::EvalFlags::CacheResults); + auto count = expr.count().eval(evalContext).integer().as(); + assert(count && "Slang guarantees constant non-zero replication count"); + return visitAssignmentPattern(expr, *count); + } + /// Emit an error for all other expressions. template Value visit(T &&node) { @@ -904,9 +1009,13 @@ struct LvalueExprVisitor { }; } // namespace -Value Context::convertRvalueExpression(const slang::ast::Expression &expr) { +Value Context::convertRvalueExpression(const slang::ast::Expression &expr, + Type requiredType) { auto loc = convertLocation(expr.sourceRange); - return expr.visit(RvalueExprVisitor(*this, loc)); + auto value = expr.visit(RvalueExprVisitor(*this, loc)); + if (value && requiredType && value.getType() != requiredType) + value = builder.create(loc, requiredType, value); + return value; } Value Context::convertLvalueExpression(const slang::ast::Expression &expr) { @@ -914,3 +1023,29 @@ Value Context::convertLvalueExpression(const slang::ast::Expression &expr) { return expr.visit(LvalueExprVisitor(*this, loc)); } // NOLINTEND(misc-no-recursion) + +/// Helper function to convert a value to its "truthy" boolean value. +Value Context::convertToBool(Value value) { + if (!value) + return {}; + if (auto type = dyn_cast_or_null(value.getType())) + if (type.getBitSize() == 1) + return value; + if (auto type = dyn_cast_or_null(value.getType())) + return builder.create(value.getLoc(), value); + mlir::emitError(value.getLoc(), "expression of type ") + << value.getType() << " cannot be cast to a boolean"; + return {}; +} + +/// Helper function to convert a value to its "truthy" boolean value and +/// convert it to the given domain. +Value Context::convertToBool(Value value, Domain domain) { + value = convertToBool(value); + if (!value) + return {}; + auto type = moore::IntType::get(getContext(), 1, domain); + if (value.getType() == type) + return value; + return builder.create(value.getLoc(), type, value); +} diff --git a/lib/Conversion/ImportVerilog/ImportVerilog.cpp b/lib/Conversion/ImportVerilog/ImportVerilog.cpp index 6d8865ad9fbc..5519f9f35cfe 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilog.cpp +++ b/lib/Conversion/ImportVerilog/ImportVerilog.cpp @@ -258,9 +258,14 @@ LogicalResult ImportDriver::importVerilog(ModuleOp module) { return failure(); compileTimer.stop(); + // If we were only supposed to lint the input, return here. This leaves the + // module empty, but any Slang linting messages got reported as diagnostics. + if (options.mode == ImportVerilogOptions::Mode::OnlyLint) + return success(); + // Traverse the parsed Verilog AST and map it to the equivalent CIRCT ops. - mlirContext->loadDialect(); + mlirContext->loadDialect(); auto conversionTimer = ts.nest("Verilog to dialect mapping"); Context context(*compilation, module, driver.sourceManager, bufferFilePaths); if (failed(context.convertCompilation())) diff --git a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h index 5b9d7fb29398..536b5ea84bb7 100644 --- a/lib/Conversion/ImportVerilog/ImportVerilogInternals.h +++ b/lib/Conversion/ImportVerilog/ImportVerilogInternals.h @@ -13,8 +13,8 @@ #include "circt/Conversion/ImportVerilog.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Moore/MooreOps.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h" -#include "mlir/Dialect/SCF/IR/SCF.h" #include "slang/ast/ASTVisitor.h" #include "llvm/ADT/ScopedHashTable.h" #include "llvm/Support/Debug.h" @@ -26,6 +26,8 @@ namespace circt { namespace ImportVerilog { +using moore::Domain; + /// Port lowering information. struct PortLowering { const slang::ast::PortSymbol * @@ -46,6 +48,15 @@ struct FunctionLowering { mlir::func::FuncOp op; }; +/// Information about a loops continuation and exit blocks relevant while +/// lowering the loop's body statements. +struct LoopFrame { + /// The block to jump to from a `continue` statement. + Block *continueBlock; + /// The block to jump to from a `break` statement. + Block *breakBlock; +}; + /// 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. @@ -88,12 +99,20 @@ struct Context { LogicalResult convertStatement(const slang::ast::Statement &stmt); // Convert an expression AST node to MLIR ops. - Value convertRvalueExpression(const slang::ast::Expression &expr); + Value convertRvalueExpression(const slang::ast::Expression &expr, + Type requiredType = {}); Value convertLvalueExpression(const slang::ast::Expression &expr); // Convert a slang timing control into an MLIR timing control. - LogicalResult - convertTimingControl(const slang::ast::TimingControl &timingControl); + LogicalResult convertTimingControl(const slang::ast::TimingControl &ctrl, + const slang::ast::Statement &stmt); + + /// Helper function to convert a value to its "truthy" boolean value. + Value convertToBool(Value value); + + /// Helper function to convert a value to its "truthy" boolean value and + /// convert it to the given domain. + Value convertToBool(Value value, Domain domain); slang::ast::Compilation &compilation; mlir::ModuleOp intoModuleOp; @@ -135,6 +154,19 @@ struct Context { /// side. This allows expressions to resolve the opaque /// `LValueReferenceExpression`s in the AST. SmallVector lvalueStack; + + /// A stack of loop continuation and exit blocks. Each loop will push the + /// relevant info onto this stack, lower its loop body statements, and pop the + /// info off the stack again. Continue and break statements encountered as + /// part of the loop body statements will use this information to branch to + /// the correct block. + SmallVector loopStack; + + /// A listener called for every variable or net being read. This can be used + /// to collect all variables read as part of an expression or statement, for + /// example to populate the list of observed signals in an implicit event + /// control `@*`. + std::function rvalueReadCallback; }; } // namespace ImportVerilog diff --git a/lib/Conversion/ImportVerilog/Statements.cpp b/lib/Conversion/ImportVerilog/Statements.cpp index d5a6ed443845..6e674fdeaa4f 100644 --- a/lib/Conversion/ImportVerilog/Statements.cpp +++ b/lib/Conversion/ImportVerilog/Statements.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "ImportVerilogInternals.h" +#include "llvm/ADT/ScopeExit.h" using namespace mlir; using namespace circt; @@ -22,6 +23,16 @@ struct StmtVisitor { StmtVisitor(Context &context, Location loc) : context(context), loc(loc), builder(context.builder) {} + bool isTerminated() const { return !builder.getInsertionBlock(); } + void setTerminated() { builder.clearInsertionPoint(); } + + Block &createBlock() { + assert(builder.getInsertionBlock()); + auto block = std::make_unique(); + block->insertAfter(builder.getInsertionBlock()); + return *block.release(); + } + // Skip empty statements (stray semicolons). LogicalResult visit(const slang::ast::EmptyStatement &) { return success(); } @@ -33,9 +44,15 @@ struct StmtVisitor { // which in turn has a single body statement, which then commonly is a list of // statements. LogicalResult visit(const slang::ast::StatementList &stmts) { - for (auto *stmt : stmts.list) + for (auto *stmt : stmts.list) { + if (isTerminated()) { + auto loc = context.convertLocation(stmt->sourceRange); + mlir::emitWarning(loc, "unreachable code"); + break; + } if (failed(context.convertStatement(*stmt))) return failure(); + } return success(); } @@ -104,81 +121,123 @@ struct StmtVisitor { allConds = builder.create(loc, builder.getI1Type(), allConds); - // Generate the if operation. - auto ifOp = - builder.create(loc, allConds, stmt.ifFalse != nullptr); - OpBuilder::InsertionGuard guard(builder); + // Create the blocks for the true and false branches, and the exit block. + Block &exitBlock = createBlock(); + Block *falseBlock = stmt.ifFalse ? &createBlock() : nullptr; + Block &trueBlock = createBlock(); + builder.create(loc, allConds, &trueBlock, + falseBlock ? falseBlock : &exitBlock); - // Generate the "then" body. - builder.setInsertionPoint(ifOp.thenYield()); + // Generate the true branch. + builder.setInsertionPointToEnd(&trueBlock); if (failed(context.convertStatement(stmt.ifTrue))) return failure(); + if (!isTerminated()) + builder.create(loc, &exitBlock); - // Generate the "else" body if present. + // Generate the false branch if present. if (stmt.ifFalse) { - builder.setInsertionPoint(ifOp.elseYield()); + builder.setInsertionPointToEnd(falseBlock); if (failed(context.convertStatement(*stmt.ifFalse))) return failure(); + if (!isTerminated()) + builder.create(loc, &exitBlock); } + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } // Handle case statements. LogicalResult visit(const slang::ast::CaseStatement &caseStmt) { + using slang::ast::CaseStatementCondition; auto caseExpr = context.convertRvalueExpression(caseStmt.expr); if (!caseExpr) return failure(); - auto items = caseStmt.items; - // Used to generate the condition of the default case statement. - SmallVector defaultConds; - // Traverse the case items. - for (auto item : items) { - // One statement will be matched with multi-conditions. - // Like case(cond) 0, 1 : y = x; endcase. - SmallVector allConds; + // Check each case individually. This currently ignores the `unique`, + // `unique0`, and `priority` modifiers which would allow for additional + // optimizations. + auto &exitBlock = createBlock(); + + for (const auto &item : caseStmt.items) { + // Create the block that will contain the main body of the expression. + // This is where any of the comparisons will branch to if they match. + auto &matchBlock = createBlock(); + + // The SV standard requires expressions to be checked in the order + // specified by the user, and for the evaluation to stop as soon as the + // first matching expression is encountered. for (const auto *expr : item.expressions) { - auto itemExpr = context.convertRvalueExpression(*expr); - if (!itemExpr) + auto value = context.convertRvalueExpression(*expr); + if (!value) return failure(); - - auto newEqOp = builder.create(loc, caseExpr, itemExpr); - allConds.push_back(newEqOp); - } - // Bound all conditions of an item into one. - auto cond = allConds.back(); - allConds.pop_back(); - while (!allConds.empty()) { - cond = builder.create(loc, allConds.back(), cond); - allConds.pop_back(); + auto itemLoc = value.getLoc(); + + // Generate the appropriate equality operator. + Value cond; + switch (caseStmt.condition) { + case CaseStatementCondition::Normal: + cond = builder.create(itemLoc, caseExpr, value); + break; + case CaseStatementCondition::WildcardXOrZ: + cond = builder.create(itemLoc, caseExpr, value); + break; + case CaseStatementCondition::WildcardJustZ: + cond = builder.create(itemLoc, caseExpr, value); + break; + case CaseStatementCondition::Inside: + mlir::emitError(loc, "unsupported set membership case statement"); + return failure(); + } + cond = builder.create(itemLoc, builder.getI1Type(), + cond); + + // If the condition matches, branch to the match block. Otherwise + // continue checking the next expression in a new block. + auto &nextBlock = createBlock(); + builder.create(itemLoc, cond, &matchBlock, + &nextBlock); + builder.setInsertionPointToEnd(&nextBlock); } - // Gather all items' conditions. - defaultConds.push_back(cond); - cond = - builder.create(loc, builder.getI1Type(), cond); - auto ifOp = builder.create(loc, cond); + + // The current block is the fall-through after all conditions have been + // checked and nothing matched. Move the match block up before this point + // to make the IR easier to read. + matchBlock.moveBefore(builder.getInsertionBlock()); + + // Generate the code for this item's statement in the match block. OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPoint(ifOp.thenYield()); + builder.setInsertionPointToEnd(&matchBlock); if (failed(context.convertStatement(*item.stmt))) return failure(); - } - // Handle the 'default case' statement if it exists. - if (caseStmt.defaultCase) { - auto cond = defaultConds.back(); - defaultConds.pop_back(); - while (!defaultConds.empty()) { - cond = builder.create(loc, defaultConds.back(), cond); - defaultConds.pop_back(); + if (!isTerminated()) { + auto loc = context.convertLocation(item.stmt->sourceRange); + builder.create(loc, &exitBlock); } - cond = builder.create(loc, cond); - cond = - builder.create(loc, builder.getI1Type(), cond); - auto ifOp = builder.create(loc, cond); - OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPoint(ifOp.thenYield()); + } + + // Generate the default case if present. + if (caseStmt.defaultCase) if (failed(context.convertStatement(*caseStmt.defaultCase))) return failure(); + if (!isTerminated()) + builder.create(loc, &exitBlock); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); } return success(); } @@ -190,148 +249,217 @@ struct StmtVisitor { if (!context.convertRvalueExpression(*initExpr)) return failure(); - // Create the while op. - auto whileOp = builder.create(loc, TypeRange{}, ValueRange{}); - OpBuilder::InsertionGuard guard(builder); + // Create the blocks for the loop condition, body, step, and exit. + auto &exitBlock = createBlock(); + auto &stepBlock = createBlock(); + auto &bodyBlock = createBlock(); + auto &checkBlock = createBlock(); + builder.create(loc, &checkBlock); + + // Push the blocks onto the loop stack such that we can continue and break. + context.loopStack.push_back({&stepBlock, &exitBlock}); + auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); }); - // In the "before" region, check that the condition holds. - builder.createBlock(&whileOp.getBefore()); + // Generate the loop condition check. + builder.setInsertionPointToEnd(&checkBlock); auto cond = context.convertRvalueExpression(*stmt.stopExpr); if (!cond) return failure(); cond = builder.createOrFold(loc, cond); cond = builder.create(loc, builder.getI1Type(), cond); - builder.create(loc, cond, ValueRange{}); + builder.create(loc, cond, &bodyBlock, &exitBlock); - // In the "after" region, generate the loop body and step expressions. - builder.createBlock(&whileOp.getAfter()); + // Generate the loop body. + builder.setInsertionPointToEnd(&bodyBlock); if (failed(context.convertStatement(stmt.body))) return failure(); + if (!isTerminated()) + builder.create(loc, &stepBlock); + + // Generate the step expressions. + builder.setInsertionPointToEnd(&stepBlock); for (auto *stepExpr : stmt.steps) if (!context.convertRvalueExpression(*stepExpr)) return failure(); - builder.create(loc); - + if (!isTerminated()) + builder.create(loc, &checkBlock); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } // Handle `repeat` loops. LogicalResult visit(const slang::ast::RepeatLoopStatement &stmt) { - // Create the while op and feed in the repeat count as the initial counter - // value. auto count = context.convertRvalueExpression(stmt.count); if (!count) return failure(); - auto type = cast(count.getType()); - auto whileOp = builder.create(loc, type, count); - OpBuilder::InsertionGuard guard(builder); - - // In the "before" region, check that the counter is non-zero. - auto *block = builder.createBlock(&whileOp.getBefore(), {}, type, loc); - auto counterArg = block->getArgument(0); - auto cond = builder.createOrFold(loc, counterArg); + + // Create the blocks for the loop condition, body, step, and exit. + auto &exitBlock = createBlock(); + auto &stepBlock = createBlock(); + auto &bodyBlock = createBlock(); + auto &checkBlock = createBlock(); + auto currentCount = checkBlock.addArgument(count.getType(), count.getLoc()); + builder.create(loc, &checkBlock, count); + + // Push the blocks onto the loop stack such that we can continue and break. + context.loopStack.push_back({&stepBlock, &exitBlock}); + auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); }); + + // Generate the loop condition check. + builder.setInsertionPointToEnd(&checkBlock); + auto cond = builder.createOrFold(loc, currentCount); cond = builder.create(loc, builder.getI1Type(), cond); - builder.create(loc, cond, counterArg); + builder.create(loc, cond, &bodyBlock, &exitBlock); - // In the "after" region, generate the loop body and decrement the counter. - block = builder.createBlock(&whileOp.getAfter(), {}, type, loc); + // Generate the loop body. + builder.setInsertionPointToEnd(&bodyBlock); if (failed(context.convertStatement(stmt.body))) return failure(); - counterArg = block->getArgument(0); - auto constOne = builder.create(loc, type, 1); - auto subOp = builder.create(loc, counterArg, constOne); - builder.create(loc, ValueRange{subOp}); - + if (!isTerminated()) + builder.create(loc, &stepBlock); + + // Decrement the current count and branch back to the check block. + builder.setInsertionPointToEnd(&stepBlock); + auto one = builder.create( + count.getLoc(), cast(count.getType()), 1); + Value nextCount = + builder.create(count.getLoc(), currentCount, one); + builder.create(loc, &checkBlock, nextCount); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } - // Handle `while` loops. - LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) { - // Create the while op. - auto whileOp = builder.create(loc, TypeRange{}, ValueRange{}); - OpBuilder::InsertionGuard guard(builder); - - // In the "before" region, check that the condition holds. - builder.createBlock(&whileOp.getBefore()); - auto cond = context.convertRvalueExpression(stmt.cond); + // Handle `while` and `do-while` loops. + LogicalResult createWhileLoop(const slang::ast::Expression &condExpr, + const slang::ast::Statement &bodyStmt, + bool atLeastOnce) { + // Create the blocks for the loop condition, body, and exit. + auto &exitBlock = createBlock(); + auto &bodyBlock = createBlock(); + auto &checkBlock = createBlock(); + builder.create(loc, atLeastOnce ? &bodyBlock : &checkBlock); + if (atLeastOnce) + bodyBlock.moveBefore(&checkBlock); + + // Push the blocks onto the loop stack such that we can continue and break. + context.loopStack.push_back({&checkBlock, &exitBlock}); + auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); }); + + // Generate the loop condition check. + builder.setInsertionPointToEnd(&checkBlock); + auto cond = context.convertRvalueExpression(condExpr); if (!cond) return failure(); cond = builder.createOrFold(loc, cond); cond = builder.create(loc, builder.getI1Type(), cond); - builder.create(loc, cond, ValueRange{}); + builder.create(loc, cond, &bodyBlock, &exitBlock); - // In the "after" region, generate the loop body. - builder.createBlock(&whileOp.getAfter()); - if (failed(context.convertStatement(stmt.body))) + // Generate the loop body. + builder.setInsertionPointToEnd(&bodyBlock); + if (failed(context.convertStatement(bodyStmt))) return failure(); - builder.create(loc); - + if (!isTerminated()) + builder.create(loc, &checkBlock); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } - // Handle `do ... while` loops. - LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) { - // Create the while op. - auto whileOp = builder.create(loc, TypeRange{}, ValueRange{}); - OpBuilder::InsertionGuard guard(builder); - - // In the "before" region, generate the loop body and check that the - // condition holds. - builder.createBlock(&whileOp.getBefore()); - if (failed(context.convertStatement(stmt.body))) - return failure(); - auto cond = context.convertRvalueExpression(stmt.cond); - if (!cond) - return failure(); - cond = builder.createOrFold(loc, cond); - cond = builder.create(loc, builder.getI1Type(), cond); - builder.create(loc, cond, ValueRange{}); - - // Generate an empty "after" region. - builder.createBlock(&whileOp.getAfter()); - builder.create(loc); + LogicalResult visit(const slang::ast::WhileLoopStatement &stmt) { + return createWhileLoop(stmt.cond, stmt.body, false); + } - return success(); + LogicalResult visit(const slang::ast::DoWhileLoopStatement &stmt) { + return createWhileLoop(stmt.cond, stmt.body, true); } // Handle `forever` loops. LogicalResult visit(const slang::ast::ForeverLoopStatement &stmt) { - // Create the while op. - auto whileOp = builder.create(loc, TypeRange{}, ValueRange{}); - OpBuilder::InsertionGuard guard(builder); + // Create the blocks for the loop body and exit. + auto &exitBlock = createBlock(); + auto &bodyBlock = createBlock(); + builder.create(loc, &bodyBlock); - // In the "before" region, return true for the condition. - builder.createBlock(&whileOp.getBefore()); - auto cond = builder.create(loc, builder.getI1Type(), 1); - builder.create(loc, cond, ValueRange{}); + // Push the blocks onto the loop stack such that we can continue and break. + context.loopStack.push_back({&bodyBlock, &exitBlock}); + auto done = llvm::make_scope_exit([&] { context.loopStack.pop_back(); }); - // In the "after" region, generate the loop body. - builder.createBlock(&whileOp.getAfter()); + // Generate the loop body. + builder.setInsertionPointToEnd(&bodyBlock); if (failed(context.convertStatement(stmt.body))) return failure(); - builder.create(loc); - + if (!isTerminated()) + builder.create(loc, &bodyBlock); + + // If control never reaches the exit block, remove it and mark control flow + // as terminated. Otherwise we continue inserting ops in the exit block. + if (exitBlock.hasNoPredecessors()) { + exitBlock.erase(); + setTerminated(); + } else { + builder.setInsertionPointToEnd(&exitBlock); + } return success(); } // Handle timing control. LogicalResult visit(const slang::ast::TimedStatement &stmt) { - if (failed(context.convertTimingControl(stmt.timing))) - return failure(); - if (failed(context.convertStatement(stmt.stmt))) - return failure(); - return success(); + return context.convertTimingControl(stmt.timing, stmt.stmt); } // Handle return statements. LogicalResult visit(const slang::ast::ReturnStatement &stmt) { - Value expr; if (stmt.expr) { - expr = context.convertRvalueExpression(*stmt.expr); + auto expr = context.convertRvalueExpression(*stmt.expr); if (!expr) return failure(); + builder.create(loc, expr); + } else { + builder.create(loc); } - builder.create(loc, expr); + setTerminated(); + return success(); + } + + // Handle continue statements. + LogicalResult visit(const slang::ast::ContinueStatement &stmt) { + if (context.loopStack.empty()) + return mlir::emitError(loc, + "cannot `continue` without a surrounding loop"); + builder.create(loc, context.loopStack.back().continueBlock); + setTerminated(); + return success(); + } + + // Handle break statements. + LogicalResult visit(const slang::ast::BreakStatement &stmt) { + if (context.loopStack.empty()) + return mlir::emitError(loc, "cannot `break` without a surrounding loop"); + builder.create(loc, context.loopStack.back().breakBlock); + setTerminated(); return success(); } @@ -352,6 +480,7 @@ struct StmtVisitor { } // namespace LogicalResult Context::convertStatement(const slang::ast::Statement &stmt) { + assert(builder.getInsertionBlock()); auto loc = convertLocation(stmt.sourceRange); return stmt.visit(StmtVisitor(*this, loc)); } diff --git a/lib/Conversion/ImportVerilog/Structure.cpp b/lib/Conversion/ImportVerilog/Structure.cpp index 6f5b2fac14fe..e1061e96571d 100644 --- a/lib/Conversion/ImportVerilog/Structure.cpp +++ b/lib/Conversion/ImportVerilog/Structure.cpp @@ -97,6 +97,9 @@ struct PackageVisitor : public BaseVisitor { // Ignore parameters. These are materialized on-the-fly as `ConstantOp`s. LogicalResult visit(const slang::ast::ParameterSymbol &) { return success(); } LogicalResult visit(const slang::ast::SpecparamSymbol &) { return success(); } + LogicalResult visit(const slang::ast::TypeParameterSymbol &) { + return success(); + } // Handle functions and tasks. LogicalResult visit(const slang::ast::SubroutineSymbol &subroutine) { @@ -106,7 +109,7 @@ struct PackageVisitor : public BaseVisitor { /// Emit an error for all other members. template LogicalResult visit(T &&node) { - mlir::emitError(loc, "unsupported construct: ") + mlir::emitError(loc, "unsupported package member: ") << slang::ast::toString(node.kind); return failure(); } @@ -192,6 +195,12 @@ struct ModuleVisitor : public BaseVisitor { return success(); } + // Ignore type parameters. These have already been handled by Slang's type + // checking. + LogicalResult visit(const slang::ast::TypeParameterSymbol &) { + return success(); + } + // Handle instances. LogicalResult visit(const slang::ast::InstanceSymbol &instNode) { using slang::ast::ArgumentDirection; @@ -364,7 +373,7 @@ struct ModuleVisitor : public BaseVisitor { Value initial; if (const auto *init = varNode.getInitializer()) { - initial = context.convertRvalueExpression(*init); + initial = context.convertRvalueExpression(*init, loweredType); if (!initial) return failure(); } @@ -384,7 +393,8 @@ struct ModuleVisitor : public BaseVisitor { Value assignment; if (netNode.getInitializer()) { - assignment = context.convertRvalueExpression(*netNode.getInitializer()); + assignment = context.convertRvalueExpression(*netNode.getInitializer(), + loweredType); if (!assignment) return failure(); } @@ -413,21 +423,14 @@ struct ModuleVisitor : public BaseVisitor { const auto &expr = assignNode.getAssignment().as(); - auto lhs = context.convertLvalueExpression(expr.left()); - auto rhs = context.convertRvalueExpression(expr.right()); - if (!lhs || !rhs) + if (!lhs) return failure(); - if (auto refOp = lhs.getDefiningOp()) { - auto input = refOp.getInput(); - if (isa(input.getDefiningOp()->getParentOp())) { - builder.create(loc, input.getType(), input, - refOp.getFieldNameAttr(), rhs); - refOp->erase(); - return success(); - } - } + auto rhs = context.convertRvalueExpression( + expr.right(), cast(lhs.getType()).getNestedType()); + if (!rhs) + return failure(); builder.create(loc, lhs, rhs); return success(); @@ -437,11 +440,14 @@ struct ModuleVisitor : public BaseVisitor { LogicalResult visit(const slang::ast::ProceduralBlockSymbol &procNode) { auto procOp = builder.create( loc, convertProcedureKind(procNode.procedureKind)); - procOp.getBodyRegion().emplaceBlock(); OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToEnd(procOp.getBody()); + builder.setInsertionPointToEnd(&procOp.getBody().emplaceBlock()); Context::ValueSymbolScope scope(context.valueSymbols); - return context.convertStatement(procNode.getBody()); + if (failed(context.convertStatement(procNode.getBody()))) + return failure(); + if (builder.getBlock()) + builder.create(loc); + return success(); } // Handle parameters. @@ -521,7 +527,7 @@ struct ModuleVisitor : public BaseVisitor { /// Emit an error for all other members. template LogicalResult visit(T &&node) { - mlir::emitError(loc, "unsupported construct: ") + mlir::emitError(loc, "unsupported module member: ") << slang::ast::toString(node.kind); return failure(); } @@ -638,7 +644,7 @@ Context::convertModuleHeader(const slang::ast::InstanceBodySymbol *module) { // only minor differences in semantics. if (module->getDefinition().definitionKind != slang::ast::DefinitionKind::Module) { - mlir::emitError(loc) << "unsupported construct: " + mlir::emitError(loc) << "unsupported definition: " << module->getDefinition().getKindString(); return {}; } @@ -910,15 +916,15 @@ Context::convertFunction(const slang::ast::SubroutineSymbol &subroutine) { // If there was no explicit return statement provided by the user, insert a // default one. - if (block.empty() || !block.back().hasTrait()) { + if (builder.getBlock()) { if (returnVar && !subroutine.getReturnType().isVoid()) { - returnVar = builder.create(returnVar.getLoc(), returnVar); - builder.create(lowering->op.getLoc(), returnVar); + Value read = builder.create(returnVar.getLoc(), returnVar); + builder.create(lowering->op.getLoc(), read); } else { builder.create(lowering->op.getLoc(), ValueRange{}); } } - if (returnVar.use_empty()) + if (returnVar && returnVar.use_empty()) returnVar.getDefiningOp()->erase(); return success(); } diff --git a/lib/Conversion/ImportVerilog/TimingControls.cpp b/lib/Conversion/ImportVerilog/TimingControls.cpp index 962f8db5e0ee..cf9535e3b975 100644 --- a/lib/Conversion/ImportVerilog/TimingControls.cpp +++ b/lib/Conversion/ImportVerilog/TimingControls.cpp @@ -8,57 +8,197 @@ #include "ImportVerilogInternals.h" #include "slang/ast/TimingControl.h" +#include "llvm/ADT/ScopeExit.h" + using namespace circt; using namespace ImportVerilog; + +static moore::Edge convertEdgeKind(const slang::ast::EdgeKind edge) { + using slang::ast::EdgeKind; + switch (edge) { + case EdgeKind::None: + return moore::Edge::AnyChange; + case EdgeKind::PosEdge: + return moore::Edge::PosEdge; + case EdgeKind::NegEdge: + return moore::Edge::NegEdge; + case EdgeKind::BothEdges: + return moore::Edge::BothEdges; + } + llvm_unreachable("all edge kinds handled"); +} + +// NOLINTBEGIN(misc-no-recursion) namespace { -struct TimingCtrlVisitor { + +// Handle any of the event control constructs. +struct EventControlVisitor { Context &context; Location loc; OpBuilder &builder; - TimingCtrlVisitor(Context &context, Location loc) - : context(context), loc(loc), builder(context.builder) {} - + // Handle single signal events like `posedge x`, `negedge y iff z`, or `w`. LogicalResult visit(const slang::ast::SignalEventControl &ctrl) { - // TODO: When updating slang to the latest version, we will handle - // "iffCondition". - auto loc = context.convertLocation(ctrl.sourceRange.start()); - auto input = context.convertRvalueExpression(ctrl.expr); - builder.create(loc, static_cast(ctrl.edge), - input); - return success(); - } - - LogicalResult visit(const slang::ast::ImplicitEventControl &ctrl) { + auto edge = convertEdgeKind(ctrl.edge); + auto expr = context.convertRvalueExpression(ctrl.expr); + if (!expr) + return failure(); + Value condition; + if (ctrl.iffCondition) { + condition = context.convertRvalueExpression(*ctrl.iffCondition); + condition = context.convertToBool(condition, Domain::TwoValued); + if (!condition) + return failure(); + } + builder.create(loc, edge, expr, condition); return success(); } + // Handle a list of signal events. LogicalResult visit(const slang::ast::EventListControl &ctrl) { - for (auto *event : ctrl.as().events) { - if (failed(context.convertTimingControl(*event))) + for (const auto *event : ctrl.events) { + auto visitor = *this; + visitor.loc = context.convertLocation(event->sourceRange); + if (failed(event->visit(visitor))) return failure(); } return success(); } - /// Emit an error for all other timing controls. + // Emit an error for all other timing controls. template - LogicalResult visit(T &&node) { - mlir::emitError(loc, "unspported timing control: ") - << slang::ast::toString(node.kind); - return failure(); + LogicalResult visit(T &&ctrl) { + return mlir::emitError(loc) + << "unsupported event control: " << slang::ast::toString(ctrl.kind); } +}; - LogicalResult visitInvalid(const slang::ast::TimingControl &ctrl) { - mlir::emitError(loc, "invalid timing control"); - return failure(); +// Handle any of the delay control constructs. +struct DelayControlVisitor { + Context &context; + Location loc; + OpBuilder &builder; + + // Emit an error for all other timing controls. + template + LogicalResult visit(T &&ctrl) { + return mlir::emitError(loc) + << "unsupported delay control: " << slang::ast::toString(ctrl.kind); } }; + } // namespace +// Entry point to timing control handling. This deals with the layer of repeats +// that a timing control may be wrapped in, and also handles the implicit event +// control which may appear at that point. For any event control a `WaitEventOp` +// will be created and populated by `handleEventControl`. Any delay control will +// be handled by `handleDelayControl`. +static LogicalResult handleRoot(Context &context, + const slang::ast::TimingControl &ctrl, + moore::WaitEventOp &implicitWaitOp) { + auto &builder = context.builder; + auto loc = context.convertLocation(ctrl.sourceRange); + + using slang::ast::TimingControlKind; + switch (ctrl.kind) { + // TODO: Actually implement a lowering for repeated event control. The main + // way to trigger this is through an intra-assignment timing control, which + // is not yet supported: + // + // a = repeat(3) @(posedge b) c; + // + // This will want to recursively call this function at the right insertion + // point to handle the timing control being repeated. + case TimingControlKind::RepeatedEvent: + return mlir::emitError(loc) << "unsupported repeated event control"; + + // Handle implicit events, i.e. `@*` and `@(*)`. This implicitly includes + // all variables read within the statement that follows after the event + // control. Since we haven't converted that statement yet, simply create and + // empty wait op and let `Context::convertTimingControl` populate it once + // the statement has been lowered. + case TimingControlKind::ImplicitEvent: + implicitWaitOp = builder.create(loc); + return success(); + + // Handle event control. + case TimingControlKind::SignalEvent: + case TimingControlKind::EventList: { + auto waitOp = builder.create(loc); + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&waitOp.getBody().emplaceBlock()); + EventControlVisitor visitor{context, loc, builder}; + return ctrl.visit(visitor); + } + + // Handle delay control. + case TimingControlKind::Delay: + case TimingControlKind::Delay3: + case TimingControlKind::OneStepDelay: + case TimingControlKind::CycleDelay: { + DelayControlVisitor visitor{context, loc, builder}; + return ctrl.visit(visitor); + } + + default: + return mlir::emitError(loc, "unsupported timing control: ") + << slang::ast::toString(ctrl.kind); + } +} + LogicalResult -Context::convertTimingControl(const slang::ast::TimingControl &timingControl) { - auto loc = convertLocation(timingControl.sourceRange.start()); - TimingCtrlVisitor visitor{*this, loc}; - return timingControl.visit(visitor); +Context::convertTimingControl(const slang::ast::TimingControl &ctrl, + const slang::ast::Statement &stmt) { + // Convert the timing control. Implicit event control will create a new empty + // `WaitEventOp` and assign it to `implicitWaitOp`. This op will be populated + // further down. + moore::WaitEventOp implicitWaitOp; + { + auto previousCallback = rvalueReadCallback; + auto done = + llvm::make_scope_exit([&] { rvalueReadCallback = previousCallback; }); + // Reads happening as part of the event control should not be added to a + // surrounding implicit event control's list of implicitly observed + // variables. + rvalueReadCallback = nullptr; + if (failed(handleRoot(*this, ctrl, implicitWaitOp))) + return failure(); + } + + // Convert the statement. In case `implicitWaitOp` is set, we register a + // callback to collect all the variables read by the statement into + // `readValues`, such that we can populate the op with implicitly observed + // variables afterwards. + llvm::SmallSetVector readValues; + { + auto previousCallback = rvalueReadCallback; + auto done = + llvm::make_scope_exit([&] { rvalueReadCallback = previousCallback; }); + if (implicitWaitOp) { + rvalueReadCallback = [&](moore::ReadOp readOp) { + readValues.insert(readOp.getInput()); + if (previousCallback) + previousCallback(readOp); + }; + } + if (failed(convertStatement(stmt))) + return failure(); + } + + // Populate the implicit wait op with reads from the variables read by the + // statement. + if (implicitWaitOp) { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(&implicitWaitOp.getBody().emplaceBlock()); + for (auto readValue : readValues) { + auto value = + builder.create(implicitWaitOp.getLoc(), readValue); + builder.create( + implicitWaitOp.getLoc(), moore::Edge::AnyChange, value, Value{}); + } + } + + return success(); } +// NOLINTEND(misc-no-recursion) diff --git a/lib/Conversion/MooreToCore/CMakeLists.txt b/lib/Conversion/MooreToCore/CMakeLists.txt index d97747ce8404..04efccd99682 100644 --- a/lib/Conversion/MooreToCore/CMakeLists.txt +++ b/lib/Conversion/MooreToCore/CMakeLists.txt @@ -8,11 +8,13 @@ add_circt_conversion_library(CIRCTMooreToCore Core LINK_LIBS PUBLIC - CIRCTMoore - CIRCTLLHD - CIRCTHW CIRCTComb + CIRCTHW + CIRCTLLHD + CIRCTMoore MLIRControlFlowDialect MLIRFuncDialect + MLIRSCFDialect + MLIRSideEffectInterfaces MLIRTransforms ) diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 6ea5561eb685..ca56ed58d49b 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -17,9 +17,13 @@ #include "circt/Dialect/Moore/MooreOps.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/IR/Iterators.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Pass/Pass.h" #include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/RegionUtils.h" #include "llvm/ADT/TypeSwitch.h" namespace circt { @@ -32,6 +36,7 @@ using namespace circt; using namespace moore; using comb::ICmpPredicate; +using llvm::SmallDenseSet; namespace { @@ -73,10 +78,19 @@ static hw::ModulePortInfo getModulePortInfo(const TypeConverter &typeConverter, inputs.reserve(moduleTy.getNumInputs()); outputs.reserve(moduleTy.getNumOutputs()); - for (auto port : moduleTy.getPorts()) + for (auto port : moduleTy.getPorts()) { + Type portTy = typeConverter.convertType(port.type); + if (auto ioTy = dyn_cast_or_null(portTy)) { + inputs.push_back(hw::PortInfo( + {{port.name, ioTy.getElementType(), hw::ModulePort::InOut}, + inputNum++, + {}})); + continue; + } + if (port.dir == hw::ModulePort::Direction::Output) { outputs.push_back( - hw::PortInfo({{port.name, port.type, port.dir}, resultNum++, {}})); + hw::PortInfo({{port.name, portTy, port.dir}, resultNum++, {}})); } else { // FIXME: Once we support net<...>, ref<...> type to represent type of // special port like inout or ref port which is not a input or output @@ -84,8 +98,9 @@ static hw::ModulePortInfo getModulePortInfo(const TypeConverter &typeConverter, // port or do specified operation to it. Now inout and ref port is treated // as input port. inputs.push_back( - hw::PortInfo({{port.name, port.type, port.dir}, inputNum++, {}})); + hw::PortInfo({{port.name, portTy, port.dir}, inputNum++, {}})); } + } return hw::ModulePortInfo(inputs, outputs); } @@ -112,6 +127,9 @@ struct SVModuleOpConversion : public OpConversionPattern { SymbolTable::setSymbolVisibility(hwModuleOp, SymbolTable::getSymbolVisibility(op)); rewriter.eraseBlock(hwModuleOp.getBodyBlock()); + if (failed( + rewriter.convertRegionTypes(&op.getBodyRegion(), *typeConverter))) + return failure(); rewriter.inlineRegionBefore(op.getBodyRegion(), hwModuleOp.getBodyRegion(), hwModuleOp.getBodyRegion().end()); @@ -148,6 +166,293 @@ struct InstanceOpConversion : public OpConversionPattern { } }; +static void getValuesToObserve(Region *region, + function_ref setInsertionPoint, + const TypeConverter *typeConverter, + ConversionPatternRewriter &rewriter, + SmallVector &observeValues) { + SmallDenseSet alreadyObserved; + Location loc = region->getLoc(); + + auto probeIfSignal = [&](Value value) -> Value { + if (!isa(value.getType())) + return value; + return rewriter.create(loc, value); + }; + + region->getParentOp()->walk>( + [&](Operation *operation) { + for (auto value : operation->getOperands()) { + if (region->isAncestor(value.getParentRegion())) + continue; + if (auto *defOp = value.getDefiningOp(); + defOp && defOp->hasTrait()) + continue; + if (!alreadyObserved.insert(value).second) + continue; + + OpBuilder::InsertionGuard g(rewriter); + if (auto remapped = rewriter.getRemappedValue(value)) { + setInsertionPoint(remapped); + observeValues.push_back(probeIfSignal(remapped)); + } else { + setInsertionPoint(value); + auto type = typeConverter->convertType(value.getType()); + auto converted = typeConverter->materializeTargetConversion( + rewriter, loc, type, value); + observeValues.push_back(probeIfSignal(converted)); + } + } + }); +} + +struct ProcedureOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ProcedureOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Collect values to observe before we do any modifications to the region. + SmallVector observedValues; + if (op.getKind() == ProcedureKind::AlwaysComb || + op.getKind() == ProcedureKind::AlwaysLatch) { + auto setInsertionPoint = [&](Value value) { + rewriter.setInsertionPoint(op); + }; + getValuesToObserve(&op.getBody(), setInsertionPoint, typeConverter, + rewriter, observedValues); + } + + auto loc = op.getLoc(); + if (failed(rewriter.convertRegionTypes(&op.getBody(), *typeConverter))) + return failure(); + + // Handle initial and final procedures. These lower to a corresponding + // `llhd.process` or `llhd.final` op that executes the body and then halts. + if (op.getKind() == ProcedureKind::Initial || + op.getKind() == ProcedureKind::Final) { + Operation *newOp; + if (op.getKind() == ProcedureKind::Initial) + newOp = rewriter.create(loc); + else + newOp = rewriter.create(loc); + auto &body = newOp->getRegion(0); + rewriter.inlineRegionBefore(op.getBody(), body, body.end()); + for (auto returnOp : + llvm::make_early_inc_range(body.getOps())) { + rewriter.setInsertionPoint(returnOp); + rewriter.replaceOpWithNewOp(returnOp); + } + rewriter.eraseOp(op); + return success(); + } + + // All other procedures lower to a an `llhd.process`. + auto newOp = rewriter.create(loc); + + // We need to add an empty entry block because it is not allowed in MLIR to + // branch back to the entry block. Instead we put the logic in the second + // block and branch to that. + rewriter.createBlock(&newOp.getBody()); + auto *block = &op.getBody().front(); + rewriter.create(loc, block); + rewriter.inlineRegionBefore(op.getBody(), newOp.getBody(), + newOp.getBody().end()); + + // Add special handling for `always_comb` and `always_latch` procedures. + // These run once at simulation startup and then implicitly wait for any of + // the values they access to change before running again. To implement this, + // we create another basic block that contains the implicit wait, and make + // all `moore.return` ops branch to that wait block instead of immediately + // jumping back up to the body. + if (op.getKind() == ProcedureKind::AlwaysComb || + op.getKind() == ProcedureKind::AlwaysLatch) { + Block *waitBlock = rewriter.createBlock(&newOp.getBody()); + rewriter.create(loc, observedValues, Value(), ValueRange{}, + block); + block = waitBlock; + } + + // Make all `moore.return` ops branch back up to the beginning of the + // process, or the wait block created above for `always_comb` and + // `always_latch` procedures. + for (auto returnOp : llvm::make_early_inc_range(newOp.getOps())) { + rewriter.setInsertionPoint(returnOp); + rewriter.create(loc, block); + rewriter.eraseOp(returnOp); + } + + rewriter.eraseOp(op); + return success(); + } +}; + +struct WaitEventOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(WaitEventOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // In order to convert the `wait_event` op we need to create three separate + // blocks at the location of the op: + // + // - A "wait" block that reads the current state of any values used to + // detect events and then waits until any of those values change. When a + // change occurs, control transfers to the "check" block. + // - A "check" block which is executed after any interesting signal has + // changed. This is where any `detect_event` ops read the current state of + // interesting values and compare them against their state before the wait + // in order to detect an event. If any events were detected, control + // transfers to the "resume" block; otherwise control goes back to the + // "wait" block. + // - A "resume" block which holds any ops after the `wait_event` op. This is + // where control is expected to resume after an event has happened. + // + // Block structure before: + // opA + // moore.wait_event { ... } + // opB + // + // Block structure after: + // opA + // cf.br ^wait + // ^wait: + // + // llhd.wait ^check, ... + // ^check: + // + // + // cf.cond_br %event, ^resume, ^wait + // ^resume: + // opB + auto *resumeBlock = + rewriter.splitBlock(op->getBlock(), ++Block::iterator(op)); + auto *waitBlock = rewriter.createBlock(resumeBlock); + auto *checkBlock = rewriter.createBlock(resumeBlock); + + auto loc = op.getLoc(); + rewriter.setInsertionPoint(op); + rewriter.create(loc, waitBlock); + + // We need to inline two copies of the `wait_event`'s body region: one is + // used to determine the values going into `detect_event` ops before the + // `llhd.wait`, and one will do the actual event detection after the + // `llhd.wait`. + // + // Create a copy of the entire `wait_event` op in the wait block, which also + // creates a copy of its region. Take note of all inputs to `detect_event` + // ops and delete the `detect_event` ops in this copy. + SmallVector valuesBefore; + rewriter.setInsertionPointToEnd(waitBlock); + auto clonedOp = cast(rewriter.clone(*op)); + for (auto detectOp : + llvm::make_early_inc_range(clonedOp.getOps())) { + valuesBefore.push_back(detectOp.getInput()); + rewriter.eraseOp(detectOp); + } + + // Determine the values used during event detection that are defined outside + // the `wait_event`'s body region. We want to wait for a change on these + // signals before we check if any interesting event happened. + SmallVector observeValues; + auto setInsertionPointAfterDef = [&](Value value) { + if (auto *op = value.getDefiningOp()) + rewriter.setInsertionPointAfter(op); + if (auto arg = dyn_cast(value)) + rewriter.setInsertionPointToStart(value.getParentBlock()); + }; + + getValuesToObserve(&clonedOp.getBody(), setInsertionPointAfterDef, + typeConverter, rewriter, observeValues); + + // Create the `llhd.wait` op that suspends the current process and waits for + // a change in the interesting values listed in `observeValues`. When a + // change is detected, execution resumes in the "check" block. + auto waitOp = rewriter.create(loc, observeValues, Value(), + ValueRange{}, checkBlock); + rewriter.inlineBlockBefore(&clonedOp.getBody().front(), waitOp); + rewriter.eraseOp(clonedOp); + + // Collect a list of all detect ops and inline the `wait_event` body into + // the check block. + SmallVector detectOps(op.getBody().getOps()); + rewriter.inlineBlockBefore(&op.getBody().front(), checkBlock, + checkBlock->end()); + rewriter.eraseOp(op); + + // Helper function to detect if a certain change occurred between a value + // before the `llhd.wait` and after. + auto computeTrigger = [&](Value before, Value after, Edge edge) -> Value { + before = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), before); + after = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), after); + + if (edge == Edge::AnyChange) + return rewriter.create(loc, ICmpPredicate::ne, before, + after, true); + + SmallVector disjuncts; + Value trueVal = rewriter.create(loc, APInt(1, 1)); + + if (edge == Edge::PosEdge || edge == Edge::BothEdges) { + Value notOldVal = + rewriter.create(loc, before, trueVal, true); + Value posedge = + rewriter.create(loc, notOldVal, after, true); + disjuncts.push_back(posedge); + } + + if (edge == Edge::NegEdge || edge == Edge::BothEdges) { + Value notCurrVal = + rewriter.create(loc, after, trueVal, true); + Value posedge = + rewriter.create(loc, before, notCurrVal, true); + disjuncts.push_back(posedge); + } + + return rewriter.createOrFold(loc, disjuncts, true); + }; + + // Convert all `detect_event` ops into a check for the corresponding event + // between the value before and after the `llhd.wait`. The "before" value + // has been collected into `valuesBefore` in the "wait" block; the "after" + // value corresponds to the detect op's input. + SmallVector triggers; + for (auto [detectOp, before] : llvm::zip(detectOps, valuesBefore)) { + // TODO: Support multi-bit values. Edge detection occurs per-bit. + if (auto intType = dyn_cast(before.getType()); + !intType || intType.getWidth() != 1) + return detectOp->emitError() << "requires single bit operand"; + + rewriter.setInsertionPoint(detectOp); + auto trigger = + computeTrigger(before, detectOp.getInput(), detectOp.getEdge()); + if (detectOp.getCondition()) { + auto condition = typeConverter->materializeTargetConversion( + rewriter, loc, rewriter.getI1Type(), detectOp.getCondition()); + trigger = rewriter.create(loc, trigger, condition, true); + } + triggers.push_back(trigger); + rewriter.eraseOp(detectOp); + } + + // If any `detect_event` op detected an event, branch to the "resume" block + // which contains any code after the `wait_event` op. If no events were + // detected, branch back to the "wait" block to wait for the next change on + // the interesting signals. + rewriter.setInsertionPointToEnd(checkBlock); + if (!triggers.empty()) { + auto triggered = rewriter.createOrFold(loc, triggers, true); + rewriter.create(loc, triggered, resumeBlock, waitBlock); + } else { + rewriter.create(loc, waitBlock); + } + + return success(); + } +}; + //===----------------------------------------------------------------------===// // Declaration Conversion //===----------------------------------------------------------------------===// @@ -158,17 +463,26 @@ struct VariableOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(VariableOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); Type resultType = typeConverter->convertType(op.getResult().getType()); + + // Determine the initial value of the signal. Value init = adaptor.getInitial(); - // TODO: Unsupport x/z, so the initial value is 0. - if (!init && cast(op.getResult().getType()).getDomain() == - Domain::FourValued) - return failure(); + if (!init) { + Type elementType = cast(resultType).getElementType(); + int64_t width = hw::getBitWidth(elementType); + if (width == -1) + return failure(); + + // TODO: Once the core dialects support four-valued integers, this code + // will additionally need to generate an all-X value for four-valued + // variables. + Value constZero = rewriter.create(loc, APInt(width, 0)); + init = rewriter.createOrFold(loc, elementType, constZero); + } - if (!init) - init = rewriter.create(op->getLoc(), resultType, 0); - rewriter.replaceOpWithNewOp(op, hw::InOutType::get(resultType), - op.getNameAttr(), init); + rewriter.replaceOpWithNewOp(op, resultType, + op.getNameAttr(), init); return success(); } }; @@ -183,8 +497,11 @@ struct ConstantOpConv : public OpConversionPattern { LogicalResult matchAndRewrite(ConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - - rewriter.replaceOpWithNewOp(op, op.getValueAttr()); + // FIXME: Discard unknown bits and map them to 0 for now. + auto value = op.getValue().toAPInt(false); + auto type = rewriter.getIntegerType(value.getBitWidth()); + rewriter.replaceOpWithNewOp( + op, type, rewriter.getIntegerAttr(type, value)); return success(); } }; @@ -247,10 +564,78 @@ struct ExtractOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ExtractOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + // TODO: properly handle out-of-bounds accesses Type resultType = typeConverter->convertType(op.getResult().getType()); - rewriter.replaceOpWithNewOp( - op, resultType, adaptor.getInput(), adaptor.getLowBit()); - return success(); + Type inputType = adaptor.getInput().getType(); + + if (isa(inputType)) { + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), adaptor.getLowBit()); + return success(); + } + + if (auto arrTy = dyn_cast(inputType)) { + int64_t width = llvm::Log2_64_Ceil(arrTy.getNumElements()); + Value idx = rewriter.create( + op.getLoc(), rewriter.getIntegerType(width), adaptor.getLowBit()); + if (isa(resultType)) { + rewriter.replaceOpWithNewOp(op, resultType, + adaptor.getInput(), idx); + return success(); + } + + // Otherwise, it has to be the array's element type + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), idx); + return success(); + } + + return failure(); + } +}; + +struct ExtractRefOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ExtractRefOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // TODO: properly handle out-of-bounds accesses + Type resultType = typeConverter->convertType(op.getResult().getType()); + Type inputType = + cast(adaptor.getInput().getType()).getElementType(); + + if (auto intType = dyn_cast(inputType)) { + int64_t width = hw::getBitWidth(inputType); + if (width == -1) + return failure(); + + Value lowBit = rewriter.create( + op.getLoc(), rewriter.getIntegerType(llvm::Log2_64_Ceil(width)), + adaptor.getLowBit()); + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), lowBit); + return success(); + } + + if (auto arrType = dyn_cast(inputType)) { + Value lowBit = rewriter.create( + op.getLoc(), + rewriter.getIntegerType(llvm::Log2_64_Ceil(arrType.getNumElements())), + adaptor.getLowBit()); + + if (isa( + cast(resultType).getElementType())) { + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), lowBit); + return success(); + } + + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), + lowBit); + return success(); + } + + return failure(); } }; @@ -261,14 +646,116 @@ struct DynExtractOpConversion : public OpConversionPattern { matchAndRewrite(DynExtractOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Type resultType = typeConverter->convertType(op.getResult().getType()); - auto width = typeConverter->convertType(op.getInput().getType()) - .getIntOrFloatBitWidth(); - Value amount = - adjustIntegerWidth(rewriter, adaptor.getLowBit(), width, op->getLoc()); - Value value = - rewriter.create(op->getLoc(), adaptor.getInput(), amount); + Type inputType = adaptor.getInput().getType(); + + if (auto intType = dyn_cast(inputType)) { + Value amount = adjustIntegerWidth(rewriter, adaptor.getLowBit(), + intType.getWidth(), op->getLoc()); + Value value = rewriter.create(op->getLoc(), + adaptor.getInput(), amount); + + rewriter.replaceOpWithNewOp(op, resultType, value, 0); + return success(); + } + + if (auto arrType = dyn_cast(inputType)) { + unsigned idxWidth = llvm::Log2_64_Ceil(arrType.getNumElements()); + Value idx = adjustIntegerWidth(rewriter, adaptor.getLowBit(), idxWidth, + op->getLoc()); + + if (isa(resultType)) { + rewriter.replaceOpWithNewOp(op, resultType, + adaptor.getInput(), idx); + return success(); + } + + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), idx); + return success(); + } + + return failure(); + } +}; + +struct DynExtractRefOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(DynExtractRefOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // TODO: properly handle out-of-bounds accesses + Type resultType = typeConverter->convertType(op.getResult().getType()); + Type inputType = + cast(adaptor.getInput().getType()).getElementType(); + + if (auto intType = dyn_cast(inputType)) { + int64_t width = hw::getBitWidth(inputType); + if (width == -1) + return failure(); + + Value amount = + adjustIntegerWidth(rewriter, adaptor.getLowBit(), + llvm::Log2_64_Ceil(width), op->getLoc()); + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), amount); + return success(); + } + + if (auto arrType = dyn_cast(inputType)) { + Value idx = adjustIntegerWidth( + rewriter, adaptor.getLowBit(), + llvm::Log2_64_Ceil(arrType.getNumElements()), op->getLoc()); + + if (isa( + cast(resultType).getElementType())) { + rewriter.replaceOpWithNewOp( + op, resultType, adaptor.getInput(), idx); + return success(); + } + + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), + idx); + return success(); + } + + return failure(); + } +}; + +struct StructCreateOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(StructCreateOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Type resultType = typeConverter->convertType(op.getResult().getType()); + rewriter.replaceOpWithNewOp(op, resultType, + adaptor.getFields()); + return success(); + } +}; + +struct StructExtractOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(StructExtractOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp( + op, adaptor.getInput(), adaptor.getFieldNameAttr()); + return success(); + } +}; - rewriter.replaceOpWithNewOp(op, resultType, value, 0); +struct StructExtractRefOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(StructExtractRefOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp( + op, adaptor.getInput(), adaptor.getFieldNameAttr()); return success(); } }; @@ -376,13 +863,57 @@ struct ICmpOpConversion : public OpConversionPattern { using OpAdaptor = typename SourceOp::Adaptor; LogicalResult - matchAndRewrite(SourceOp op, OpAdaptor adapter, + matchAndRewrite(SourceOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Type resultType = ConversionPattern::typeConverter->convertType(op.getResult().getType()); rewriter.replaceOpWithNewOp( - op, resultType, pred, adapter.getLhs(), adapter.getRhs()); + op, resultType, pred, adaptor.getLhs(), adaptor.getRhs()); + return success(); + } +}; + +template +struct CaseXZEqOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + using OpAdaptor = typename SourceOp::Adaptor; + + LogicalResult + matchAndRewrite(SourceOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // Check each operand if it is a known constant and extract the X and/or Z + // bits to be ignored. + // TODO: Once the core dialects support four-valued integers, we will have + // to create ops that extract X and Z bits from the operands, since we also + // have to do the right casez/casex comparison on non-constant inputs. + unsigned bitWidth = op.getLhs().getType().getWidth(); + auto ignoredBits = APInt::getZero(bitWidth); + auto detectIgnoredBits = [&](Value value) { + auto constOp = value.getDefiningOp(); + if (!constOp) + return; + auto constValue = constOp.getValue(); + if (withoutX) + ignoredBits |= constValue.getZBits(); + else + ignoredBits |= constValue.getUnknownBits(); + }; + detectIgnoredBits(op.getLhs()); + detectIgnoredBits(op.getRhs()); + + // If we have detected any bits to be ignored, mask them in the operands for + // the comparison. + Value lhs = adaptor.getLhs(); + Value rhs = adaptor.getRhs(); + if (!ignoredBits.isZero()) { + ignoredBits.flipAllBits(); + auto maskOp = rewriter.create(op.getLoc(), ignoredBits); + lhs = rewriter.createOrFold(op.getLoc(), lhs, maskOp); + rhs = rewriter.createOrFold(op.getLoc(), rhs, maskOp); + } + + rewriter.replaceOpWithNewOp(op, ICmpPredicate::ceq, lhs, rhs); return success(); } }; @@ -393,12 +924,20 @@ struct ConversionOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ConversionOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { + Location loc = op.getLoc(); Type resultType = typeConverter->convertType(op.getResult().getType()); - Value amount = - adjustIntegerWidth(rewriter, adaptor.getInput(), - resultType.getIntOrFloatBitWidth(), op->getLoc()); + int64_t inputBw = hw::getBitWidth(adaptor.getInput().getType()); + int64_t resultBw = hw::getBitWidth(resultType); + if (inputBw == -1 || resultBw == -1) + return failure(); - rewriter.replaceOpWithNewOp(op, resultType, amount); + Value input = rewriter.createOrFold( + loc, rewriter.getIntegerType(inputBw), adaptor.getInput()); + Value amount = adjustIntegerWidth(rewriter, input, resultBw, loc); + + Value result = + rewriter.createOrFold(loc, resultType, amount); + rewriter.replaceOp(op, result); return success(); } }; @@ -573,14 +1112,25 @@ struct ReadOpConversion : public OpConversionPattern { LogicalResult matchAndRewrite(ReadOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { - Type resultType = typeConverter->convertType(op.getResult().getType()); - rewriter.replaceOpWithNewOp(op, resultType, - adaptor.getInput()); + rewriter.replaceOpWithNewOp(op, adaptor.getInput()); return success(); } }; -template +struct AssignedVariableOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(AssignedVariableOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getInput(), + adaptor.getNameAttr()); + return success(); + } +}; + +template struct AssignOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; using OpAdaptor = typename OpTy::Adaptor; @@ -590,9 +1140,8 @@ struct AssignOpConversion : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { // TODO: When we support delay control in Moore dialect, we need to update // this conversion. - auto timeAttr = - llhd::TimeAttr::get(op->getContext(), unsigned(0), - llvm::StringRef("ns"), unsigned(0), unsigned(0)); + auto timeAttr = llhd::TimeAttr::get( + op->getContext(), 0U, llvm::StringRef("ns"), DeltaTime, EpsilonTime); auto time = rewriter.create(op->getLoc(), timeAttr); rewriter.replaceOpWithNewOp(op, adaptor.getDst(), adaptor.getSrc(), time, Value{}); @@ -600,6 +1149,39 @@ struct AssignOpConversion : public OpConversionPattern { } }; +struct ConditionalOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(ConditionalOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + // TODO: This lowering is only correct if the condition is two-valued. If + // the condition is X or Z, both branches of the conditional must be + // evaluated and merged with the appropriate lookup table. See documentation + // for `ConditionalOp`. + auto type = typeConverter->convertType(op.getType()); + auto ifOp = + rewriter.create(op.getLoc(), type, adaptor.getCondition()); + rewriter.inlineRegionBefore(op.getTrueRegion(), ifOp.getThenRegion(), + ifOp.getThenRegion().end()); + rewriter.inlineRegionBefore(op.getFalseRegion(), ifOp.getElseRegion(), + ifOp.getElseRegion().end()); + rewriter.replaceOp(op, ifOp); + return success(); + } +}; + +struct YieldOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(YieldOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + rewriter.replaceOpWithNewOp(op, adaptor.getResult()); + return success(); + } +}; + } // namespace //===----------------------------------------------------------------------===// @@ -632,6 +1214,8 @@ static void populateLegality(ConversionTarget &target) { addGenericLegality(target); addGenericLegality(target); + addGenericLegality(target); + addGenericLegality(target); addGenericLegality(target); addGenericLegality(target); addGenericLegality(target); @@ -660,25 +1244,44 @@ static void populateLegality(ConversionTarget &target) { static void populateTypeConversion(TypeConverter &typeConverter) { typeConverter.addConversion([&](IntType type) { - return mlir::IntegerType::get(type.getContext(), type.getWidth()); + return IntegerType::get(type.getContext(), type.getWidth()); + }); + + typeConverter.addConversion([&](ArrayType type) { + return hw::ArrayType::get(typeConverter.convertType(type.getElementType()), + type.getSize()); + }); + + typeConverter.addConversion([&](StructType type) { + SmallVector fields; + for (auto field : type.getMembers()) { + hw::StructType::FieldInfo info; + info.type = typeConverter.convertType(field.type); + info.name = field.name; + fields.push_back(info); + } + return hw::StructType::get(type.getContext(), fields); }); typeConverter.addConversion([&](RefType type) -> std::optional { - if (isa(type.getNestedType())) - return mlir::IntegerType::get(type.getContext(), - type.getBitSize().value()); - return std::nullopt; + auto innerType = typeConverter.convertType(type.getNestedType()); + if (innerType) + return hw::InOutType::get(innerType); + return {}; }); // Valid target types. - typeConverter.addConversion([](mlir::IntegerType type) { return type; }); + typeConverter.addConversion([](IntegerType type) { return type; }); + typeConverter.addTargetMaterialization( [&](mlir::OpBuilder &builder, mlir::Type resultType, mlir::ValueRange inputs, mlir::Location loc) -> std::optional { - if (inputs.size() != 1) + if (inputs.size() != 1 || !inputs[0]) return std::nullopt; - return inputs[0]; + return builder + .create(loc, resultType, inputs[0]) + .getResult(0); }); typeConverter.addSourceMaterialization( @@ -701,8 +1304,11 @@ static void populateOpConversion(RewritePatternSet &patterns, // Patterns of miscellaneous operations. ConstantOpConv, ConcatOpConversion, ReplicateOpConversion, - ExtractOpConversion, DynExtractOpConversion, ConversionOpConversion, - ReadOpConversion, NamedConstantOpConv, + ExtractOpConversion, DynExtractOpConversion, DynExtractRefOpConversion, + ConversionOpConversion, ReadOpConversion, NamedConstantOpConv, + StructExtractOpConversion, StructExtractRefOpConversion, + ExtractRefOpConversion, StructCreateOpConversion, ConditionalOpConversion, + YieldOpConversion, // Patterns of unary operations. ReduceAndOpConversion, ReduceOrOpConversion, ReduceXorOpConversion, @@ -735,15 +1341,20 @@ static void populateOpConversion(RewritePatternSet &patterns, ICmpOpConversion, ICmpOpConversion, ICmpOpConversion, + CaseXZEqOpConversion, + CaseXZEqOpConversion, // Patterns of structural operations. - SVModuleOpConversion, InstanceOpConversion, + SVModuleOpConversion, InstanceOpConversion, ProcedureOpConversion, WaitEventOpConversion, // Patterns of shifting operations. ShrOpConversion, ShlOpConversion, AShrOpConversion, // Patterns of assignment operations. - AssignOpConversion, + AssignOpConversion, + AssignOpConversion, + AssignOpConversion, + AssignedVariableOpConversion, // Patterns of branch operations. CondBranchOpConversion, BranchOpConversion, @@ -781,6 +1392,9 @@ void MooreToCorePass::runOnOperation() { MLIRContext &context = getContext(); ModuleOp module = getOperation(); + IRRewriter rewriter(module); + (void)mlir::eraseUnreachableBlocks(rewriter, module->getRegions()); + ConversionTarget target(context); TypeConverter typeConverter; RewritePatternSet patterns(&context); diff --git a/lib/Conversion/SeqToSV/FirMemLowering.cpp b/lib/Conversion/SeqToSV/FirMemLowering.cpp index 8ae7c0a9649e..e8c745fb6f26 100644 --- a/lib/Conversion/SeqToSV/FirMemLowering.cpp +++ b/lib/Conversion/SeqToSV/FirMemLowering.cpp @@ -8,6 +8,7 @@ #include "FirMemLowering.h" #include "mlir/IR/Threading.h" +#include "llvm/ADT/MapVector.h" #include "llvm/Support/Debug.h" using namespace circt; diff --git a/lib/Conversion/SeqToSV/FirMemLowering.h b/lib/Conversion/SeqToSV/FirMemLowering.h index 079535f613c4..edb9a80611ca 100644 --- a/lib/Conversion/SeqToSV/FirMemLowering.h +++ b/lib/Conversion/SeqToSV/FirMemLowering.h @@ -15,6 +15,7 @@ #include "circt/Support/LLVM.h" #include "circt/Support/Namespace.h" #include "circt/Support/SymCache.h" +#include "llvm/ADT/MapVector.h" namespace circt { diff --git a/lib/Conversion/SimToSV/SimToSV.cpp b/lib/Conversion/SimToSV/SimToSV.cpp index a1d0af51ab42..bcdef4e024ec 100644 --- a/lib/Conversion/SimToSV/SimToSV.cpp +++ b/lib/Conversion/SimToSV/SimToSV.cpp @@ -268,8 +268,17 @@ void LowerDPIFunc::lower(sim::DPIFuncOp func) { auto name = builder.getStringAttr(nameSpace.newName( func.getSymNameAttr().getValue(), "dpi_import_fragument")); + // Add include guards to avoid duplicate declarations. See Issue 7458. + auto macroDecl = builder.create(nameSpace.newName( + "__CIRCT_DPI_IMPORT", func.getSymNameAttr().getValue().upper())); builder.create(name, [&]() { - builder.create(func.getSymNameAttr(), StringAttr()); + builder.create( + macroDecl.getSymNameAttr(), []() {}, + [&]() { + builder.create(func.getSymNameAttr(), + StringAttr()); + builder.create(macroDecl.getSymNameAttr(), ""); + }); }); symbolToFragment.insert({func.getSymNameAttr(), name}); diff --git a/lib/Dialect/Arc/ArcCostModel.cpp b/lib/Dialect/Arc/ArcCostModel.cpp new file mode 100644 index 000000000000..32393506df17 --- /dev/null +++ b/lib/Dialect/Arc/ArcCostModel.cpp @@ -0,0 +1,132 @@ +//===- ArcCostModel.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/ArcCostModel.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include + +using namespace llvm; +using namespace circt; +using namespace arc; +using namespace std; + +// FIXME: May be refined and we have more accurate operation costs +enum class OperationCost : size_t { + NOCOST, + NORMALCOST, + PACKCOST = 2, + EXTRACTCOST = 3, + CONCATCOST = 3, + SAMEVECTORNOSHUFFLE = 0, + SAMEVECTORSHUFFLECOST = 2, + DIFFERENTVECTORNOSHUFFLE = 2, + DIFFERENTVECTORSHUFFLECOST = 3 +}; + +OperationCosts ArcCostModel::getCost(Operation *op) { + return computeOperationCost(op); +} + +OperationCosts ArcCostModel::computeOperationCost(Operation *op) { + if (auto it = opCostCache.find(op); it != opCostCache.end()) + return it->second; + + OperationCosts costs; + + if (isa(op)) + costs.normalCost = size_t(OperationCost::CONCATCOST); + else if (isa(op)) + costs.normalCost = size_t(OperationCost::EXTRACTCOST); + else if (auto vecOp = dyn_cast(op)) { + // VectorizeOpCost = packingCost + shufflingCost + bodyCost + OperationCosts inputVecCosts = getInputVectorsCost(vecOp); + costs.packingCost += inputVecCosts.packingCost; + costs.shufflingCost += inputVecCosts.shufflingCost; + + for (auto ®ion : op->getRegions()) { + for (auto &block : region) { + for (auto &innerOp : block) { + OperationCosts innerCosts = computeOperationCost(&innerOp); + costs.vectorizeOpsBodyCost += innerCosts.totalCost(); + } + } + } + } else if (auto callableOp = dyn_cast(op)) { + // Callable Op? then resolve! + if (auto *calledOp = callableOp.resolveCallable()) + return opCostCache[callableOp] = computeOperationCost(calledOp); + } else if (isa(op)) { + // Get the body cost + for (auto ®ion : op->getRegions()) + for (auto &block : region) + for (auto &innerOp : block) + costs += computeOperationCost(&innerOp); + } else + costs.normalCost = size_t(OperationCost::NORMALCOST); + + return opCostCache[op] = costs; +} + +OperationCosts ArcCostModel::getInputVectorsCost(VectorizeOp vecOp) { + OperationCosts costs; + for (auto inputVec : vecOp.getInputs()) { + if (auto otherVecOp = inputVec[0].getDefiningOp(); + all_of(inputVec.begin(), inputVec.end(), [&](auto element) { + return element.template getDefiningOp() == otherVecOp; + })) { + // This means that they came from the same vector or + // VectorizeOp == null so they are all scalars + + // Check if they all scalars we multiply by the PACKCOST (SHL/R + OR) + if (!otherVecOp) + costs.packingCost += inputVec.size() * size_t(OperationCost::PACKCOST); + else + costs.shufflingCost += inputVec == otherVecOp.getResults() + ? size_t(OperationCost::SAMEVECTORNOSHUFFLE) + : getShufflingCost(inputVec, true); + } else + // inputVector consists of elements from different vectotrize ops and + // may have scalars as well. + costs.shufflingCost += getShufflingCost(inputVec); + } + + return costs; +} + +size_t ArcCostModel::getShufflingCost(const ValueRange &inputVec, bool isSame) { + size_t totalCost = 0; + if (isSame) { + auto vecOp = inputVec[0].getDefiningOp(); + for (auto [elem, orig] : llvm::zip(inputVec, vecOp.getResults())) + if (elem != orig) + ++totalCost; + + return totalCost * size_t(OperationCost::SAMEVECTORSHUFFLECOST); + } + + for (size_t i = 0; i < inputVec.size(); ++i) { + auto otherVecOp = inputVec[i].getDefiningOp(); + // If the element is not a result of a vector operation then it's a result + // of a scalar operation, then it just needs to be packed into the vector. + if (!otherVecOp) + totalCost += size_t(OperationCost::PACKCOST); + else { + // If it's a result of a vector operation, then we have two cases: + // (1) Its order in `inputVec` is the same as its order in the result of + // the defining op. + // (2) the order is different. + size_t idx = find(otherVecOp.getResults().begin(), + otherVecOp.getResults().end(), inputVec[i]) - + otherVecOp.getResults().begin(); + totalCost += i == idx ? size_t(OperationCost::DIFFERENTVECTORNOSHUFFLE) + : size_t(OperationCost::DIFFERENTVECTORSHUFFLECOST); + } + } + return totalCost; +} diff --git a/lib/Dialect/Arc/ArcOps.cpp b/lib/Dialect/Arc/ArcOps.cpp index 87070ed8c398..76e457b8d77d 100644 --- a/lib/Dialect/Arc/ArcOps.cpp +++ b/lib/Dialect/Arc/ArcOps.cpp @@ -8,6 +8,7 @@ #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/HW/HWOpInterfaces.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/Builders.h" #include "mlir/IR/OpImplementation.h" #include "mlir/IR/PatternMatch.h" @@ -307,6 +308,30 @@ LogicalResult ModelOp::verify() { return success(); } +LogicalResult ModelOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + if (!getInitialFn().has_value()) + return success(); + + auto referencedOp = + symbolTable.lookupNearestSymbolFrom(*this, getInitialFnAttr()); + if (!referencedOp) + return emitError("Cannot find declaration of initializer function '") + << *getInitialFn() << "'."; + auto funcOp = dyn_cast(referencedOp); + if (!funcOp) { + auto diag = emitError("Referenced initializer must be a 'func.func' op."); + diag.attachNote(referencedOp->getLoc()) << "Initializer declared here:"; + return diag; + } + if (!llvm::equal(funcOp.getArgumentTypes(), getBody().getArgumentTypes())) { + auto diag = emitError("Arguments of initializer function must match " + "arguments of model body."); + diag.attachNote(referencedOp->getLoc()) << "Initializer declared here:"; + return diag; + } + return success(); +} + //===----------------------------------------------------------------------===// // LutOp //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Arc/CMakeLists.txt b/lib/Dialect/Arc/CMakeLists.txt index 00a58eb30d41..c1703987ed40 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 + ArcCostModel.cpp ModelInfo.cpp ) @@ -26,11 +27,14 @@ add_circt_dialect_library(CIRCTArc Support LINK_LIBS PUBLIC + CIRCTComb CIRCTHW CIRCTSeq MLIRIR MLIRInferTypeOpInterface + MLIRFuncDialect MLIRSideEffectInterfaces + MLIRFuncDialect ) add_circt_library(CIRCTArcReductions diff --git a/lib/Dialect/Arc/ModelInfo.cpp b/lib/Dialect/Arc/ModelInfo.cpp index a16dc0b68e24..91e0449df396 100644 --- a/lib/Dialect/Arc/ModelInfo.cpp +++ b/lib/Dialect/Arc/ModelInfo.cpp @@ -105,6 +105,7 @@ LogicalResult circt::arc::collectStates(Value storage, unsigned offset, LogicalResult circt::arc::collectModels(mlir::ModuleOp module, SmallVector &models) { + for (auto modelOp : module.getOps()) { auto storageArg = modelOp.getBody().getArgument(0); auto storageType = cast(storageArg.getType()); @@ -115,7 +116,7 @@ LogicalResult circt::arc::collectModels(mlir::ModuleOp module, llvm::sort(states, [](auto &a, auto &b) { return a.offset < b.offset; }); models.emplace_back(std::string(modelOp.getName()), storageType.getSize(), - std::move(states)); + std::move(states), modelOp.getInitialFnAttr()); } return success(); @@ -130,6 +131,9 @@ void circt::arc::serializeModelInfoToJson(llvm::raw_ostream &outputStream, json.object([&] { json.attribute("name", model.name); json.attribute("numStateBytes", model.numStateBytes); + json.attribute("initialFnSym", !model.initialFnSym + ? "" + : model.initialFnSym.getValue()); json.attributeArray("states", [&] { for (const auto &state : model.states) { json.object([&] { diff --git a/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp b/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp index 5be87596a845..3f64a68bde1f 100644 --- a/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp +++ b/lib/Dialect/Arc/Transforms/ArcCanonicalizer.cpp @@ -619,18 +619,58 @@ MergeVectorizeOps::matchAndRewrite(VectorizeOp vecOp, // Ensure that the input vector matches the output of the `otherVecOp` // Make sure that the results of the otherVecOp have only one use auto otherVecOp = inputVec[0].getDefiningOp(); - if (!otherVecOp || inputVec != otherVecOp.getResults() || - otherVecOp == vecOp || + if (!otherVecOp || otherVecOp == vecOp || !llvm::all_of(otherVecOp.getResults(), - [](auto result) { return result.hasOneUse(); })) { + [](auto result) { return result.hasOneUse(); }) || + !llvm::all_of(inputVec, [&](auto result) { + return result.template getDefiningOp() == otherVecOp; + })) { newOperands.insert(newOperands.end(), inputVec.begin(), inputVec.end()); continue; } + + // Here, all elements are from the same `VectorizeOp`. + // If all elements of the input vector come from the same `VectorizeOp` + // sort the vectors by their indices + DenseMap resultIdxMap; + for (auto [resultIdx, result] : llvm::enumerate(otherVecOp.getResults())) + resultIdxMap[result] = resultIdx; + + SmallVector tempVec(inputVec.begin(), inputVec.end()); + llvm::sort(tempVec, [&](Value a, Value b) { + return resultIdxMap[a] < resultIdxMap[b]; + }); + + // Check if inputVec matches the result after sorting. + if (tempVec != SmallVector(otherVecOp.getResults().begin(), + otherVecOp.getResults().end())) { + newOperands.insert(newOperands.end(), inputVec.begin(), inputVec.end()); + continue; + } + + DenseMap fromRealIdxToSortedIdx; + for (auto [inIdx, in] : llvm::enumerate(inputVec)) + fromRealIdxToSortedIdx[inIdx] = resultIdxMap[in]; + // If this flag is set that means we changed the IR so we cannot return // failure canBeMerged = true; - newOperands.insert(newOperands.end(), otherVecOp.getOperands().begin(), - otherVecOp.getOperands().end()); + + // If the results got shuffled, then shuffle the operands before merging. + if (inputVec != otherVecOp.getResults()) { + for (auto otherVecOpInputVec : otherVecOp.getInputs()) { + // use the tempVec again instead of creating another one. + tempVec = SmallVector(inputVec.size()); + for (auto [realIdx, opernad] : llvm::enumerate(otherVecOpInputVec)) + tempVec[realIdx] = + otherVecOpInputVec[fromRealIdxToSortedIdx[realIdx]]; + + newOperands.insert(newOperands.end(), tempVec.begin(), tempVec.end()); + } + + } else + newOperands.insert(newOperands.end(), otherVecOp.getOperands().begin(), + otherVecOp.getOperands().end()); auto &otherBlock = otherVecOp.getBody().front(); for (auto &otherArg : otherBlock.getArguments()) { @@ -700,29 +740,27 @@ struct DenseMapInfo> { LogicalResult KeepOneVecOp::matchAndRewrite(VectorizeOp vecOp, PatternRewriter &rewriter) const { - BitVector argsToRemove(vecOp.getInputs().size(), false); DenseMap, unsigned> inExists; auto ¤tBlock = vecOp.getBody().front(); SmallVector newOperands; - unsigned shuffledBy = 0; - bool changed = false; - for (auto [argIdx, inputVec] : llvm::enumerate(vecOp.getInputs())) { - auto input = SmallVector(inputVec.begin(), inputVec.end()); + BitVector argsToRemove(vecOp.getInputs().size(), false); + for (size_t argIdx = 0; argIdx < vecOp.getInputs().size(); ++argIdx) { + auto input = SmallVector(vecOp.getInputs()[argIdx].begin(), + vecOp.getInputs()[argIdx].end()); if (auto in = inExists.find(input); in != inExists.end()) { - argsToRemove.set(argIdx); - rewriter.replaceAllUsesWith(currentBlock.getArgument(argIdx - shuffledBy), + rewriter.replaceAllUsesWith(currentBlock.getArgument(argIdx), currentBlock.getArgument(in->second)); - currentBlock.eraseArgument(argIdx - shuffledBy); - ++shuffledBy; - changed = true; + argsToRemove.set(argIdx); continue; } inExists[input] = argIdx; - newOperands.insert(newOperands.end(), inputVec.begin(), inputVec.end()); + newOperands.insert(newOperands.end(), input.begin(), input.end()); } - if (!changed) + if (argsToRemove.none()) return failure(); + + currentBlock.eraseArguments(argsToRemove); return updateInputOperands(vecOp, newOperands); } diff --git a/lib/Dialect/Arc/Transforms/CMakeLists.txt b/lib/Dialect/Arc/Transforms/CMakeLists.txt index fc8296c9dc64..d3690cd408be 100644 --- a/lib/Dialect/Arc/Transforms/CMakeLists.txt +++ b/lib/Dialect/Arc/Transforms/CMakeLists.txt @@ -18,6 +18,7 @@ add_circt_dialect_library(CIRCTArcTransforms LowerVectorizations.cpp MakeTables.cpp MuxToControlFlow.cpp + PrintCostModel.cpp SimplifyVariadicOps.cpp SplitFuncs.cpp SplitLoops.cpp @@ -35,6 +36,7 @@ add_circt_dialect_library(CIRCTArcTransforms CIRCTOM CIRCTSV CIRCTSeq + CIRCTSim CIRCTSupport MLIRFuncDialect MLIRLLVMDialect diff --git a/lib/Dialect/Arc/Transforms/Dedup.cpp b/lib/Dialect/Arc/Transforms/Dedup.cpp index d75601e6ff54..53e904d38c4c 100644 --- a/lib/Dialect/Arc/Transforms/Dedup.cpp +++ b/lib/Dialect/Arc/Transforms/Dedup.cpp @@ -13,6 +13,7 @@ #include "llvm/ADT/SetVector.h" #include "llvm/Support/Debug.h" #include "llvm/Support/SHA256.h" +#include #define DEBUG_TYPE "arc-dedup" diff --git a/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp b/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp index 486235d0bf5b..b68abef62513 100644 --- a/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp +++ b/lib/Dialect/Arc/Transforms/LegalizeStateUpdate.cpp @@ -30,6 +30,8 @@ using namespace arc; /// Check if an operation partakes in state accesses. static bool isOpInteresting(Operation *op) { + if (isa(op)) + return false; if (isa(op)) return true; if (op->getNumRegions() > 0) diff --git a/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp b/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp index 0ef473e428cf..c76467e80f38 100644 --- a/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp +++ b/lib/Dialect/Arc/Transforms/LowerClocksToFuncs.cpp @@ -11,6 +11,7 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Pass/Pass.h" +#include "llvm/ADT/TypeSwitch.h" #include "llvm/Support/Debug.h" #define DEBUG_TYPE "arc-lower-clocks-to-funcs" @@ -50,6 +51,9 @@ struct LowerClocksToFuncsPass Statistic numOpsCopied{this, "ops-copied", "Ops copied into clock trees"}; Statistic numOpsMoved{this, "ops-moved", "Ops moved into clock trees"}; + +private: + bool hasPassthroughOp; }; } // namespace @@ -65,11 +69,38 @@ LogicalResult LowerClocksToFuncsPass::lowerModel(ModelOp modelOp) { << "`\n"); // Find the clocks to extract. + SmallVector initialOps; + SmallVector passthroughOps; SmallVector clocks; modelOp.walk([&](Operation *op) { - if (isa(op)) - clocks.push_back(op); + TypeSwitch(op) + .Case([&](auto) { clocks.push_back(op); }) + .Case([&](auto initOp) { + initialOps.push_back(initOp); + clocks.push_back(initOp); + }) + .Case([&](auto ptOp) { + passthroughOps.push_back(ptOp); + clocks.push_back(ptOp); + }); }); + hasPassthroughOp = !passthroughOps.empty(); + + // Sanity check + if (passthroughOps.size() > 1) { + auto diag = modelOp.emitOpError() + << "containing multiple PassThroughOps cannot be lowered."; + for (auto ptOp : passthroughOps) + diag.attachNote(ptOp.getLoc()) << "Conflicting PassThroughOp:"; + } + if (initialOps.size() > 1) { + auto diag = modelOp.emitOpError() + << "containing multiple InitialOps is currently unsupported."; + for (auto initOp : initialOps) + diag.attachNote(initOp.getLoc()) << "Conflicting InitialOp:"; + } + if (passthroughOps.size() > 1 || initialOps.size() > 1) + return failure(); // Perform the actual extraction. OpBuilder funcBuilder(modelOp); @@ -84,7 +115,7 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, Value modelStorageArg, OpBuilder &funcBuilder) { LLVM_DEBUG(llvm::dbgs() << "- Lowering clock " << clockOp->getName() << "\n"); - assert((isa(clockOp))); + assert((isa(clockOp))); // Add a `StorageType` block argument to the clock's body block which we are // going to use to pass the storage pointer to the clock once it has been @@ -103,8 +134,16 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, // Pick a name for the clock function. SmallString<32> funcName; - funcName.append(clockOp->getParentOfType().getName()); - funcName.append(isa(clockOp) ? "_passthrough" : "_clock"); + auto modelOp = clockOp->getParentOfType(); + funcName.append(modelOp.getName()); + + if (isa(clockOp)) + funcName.append("_passthrough"); + else if (isa(clockOp)) + funcName.append("_initial"); + else + funcName.append("_clock"); + auto funcOp = funcBuilder.create( clockOp->getLoc(), funcName, builder.getFunctionType({modelStorageArg.getType()}, {})); @@ -114,21 +153,41 @@ LogicalResult LowerClocksToFuncsPass::lowerClock(Operation *clockOp, // Create a call to the function within the model. builder.setInsertionPoint(clockOp); - if (auto treeOp = dyn_cast(clockOp)) { - auto ifOp = - builder.create(clockOp->getLoc(), treeOp.getClock(), false); - auto builder = ifOp.getThenBodyBuilder(); - builder.create(clockOp->getLoc(), funcOp, - ValueRange{modelStorageArg}); - } else { - builder.create(clockOp->getLoc(), funcOp, - ValueRange{modelStorageArg}); - } + TypeSwitch(clockOp) + .Case([&](auto treeOp) { + auto ifOp = builder.create(clockOp->getLoc(), + treeOp.getClock(), false); + auto builder = ifOp.getThenBodyBuilder(); + builder.template create(clockOp->getLoc(), funcOp, + ValueRange{modelStorageArg}); + }) + .Case([&](auto) { + builder.template create(clockOp->getLoc(), funcOp, + ValueRange{modelStorageArg}); + }) + .Case([&](auto) { + if (modelOp.getInitialFn().has_value()) + modelOp.emitWarning() << "Existing model initializer '" + << modelOp.getInitialFnAttr().getValue() + << "' will be overridden."; + modelOp.setInitialFnAttr( + FlatSymbolRefAttr::get(funcOp.getSymNameAttr())); + }); // Move the clock's body block to the function and remove the old clock op. funcOp.getBody().takeBody(clockRegion); - clockOp->erase(); + if (isa(clockOp) && hasPassthroughOp) { + // Call PassThroughOp after init + builder.setInsertionPoint(funcOp.getBlocks().front().getTerminator()); + funcName.clear(); + funcName.append(modelOp.getName()); + funcName.append("_passthrough"); + builder.create(clockOp->getLoc(), funcName, TypeRange{}, + ValueRange{funcOp.getBody().getArgument(0)}); + } + + clockOp->erase(); return success(); } diff --git a/lib/Dialect/Arc/Transforms/LowerState.cpp b/lib/Dialect/Arc/Transforms/LowerState.cpp index 0e04ab078419..d659e8870397 100644 --- a/lib/Dialect/Arc/Transforms/LowerState.cpp +++ b/lib/Dialect/Arc/Transforms/LowerState.cpp @@ -8,9 +8,11 @@ #include "circt/Dialect/Arc/ArcOps.h" #include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/Comb/CombOps.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Sim/SimOps.h" #include "circt/Support/BackedgeBuilder.h" #include "mlir/Analysis/TopologicalSortUtils.h" #include "mlir/Dialect/Func/IR/FuncOps.h" @@ -62,7 +64,7 @@ struct Statistics { struct ClockLowering { /// The root clock this lowering is for. Value clock; - /// A `ClockTreeOp` or `PassThroughOp`. + /// A `ClockTreeOp` or `PassThroughOp` or `InitialOp`. Operation *treeOp; /// Pass statistics. Statistics &stats; @@ -75,15 +77,21 @@ struct ClockLowering { /// A cache of OR gates created for aggregating enable conditions. DenseMap, Value> orCache; + // Prevent accidental construction and copying + ClockLowering() = delete; + ClockLowering(const ClockLowering &other) = delete; + ClockLowering(Value clock, Operation *treeOp, Statistics &stats) : clock(clock), treeOp(treeOp), stats(stats), builder(treeOp) { - assert((isa(treeOp))); + assert((isa(treeOp))); builder.setInsertionPointToStart(&treeOp->getRegion(0).front()); } Value materializeValue(Value value); Value getOrCreateAnd(Value lhs, Value rhs, Location loc); Value getOrCreateOr(Value lhs, Value rhs, Location loc); + + bool isInitialTree() const { return isa(treeOp); } }; struct GatedClockLowering { @@ -101,6 +109,7 @@ struct ModuleLowering { MLIRContext *context; DenseMap> clockLowerings; DenseMap gatedClockLowerings; + std::unique_ptr initialLowering; Value storageArg; OpBuilder clockBuilder; OpBuilder stateBuilder; @@ -111,13 +120,20 @@ struct ModuleLowering { GatedClockLowering getOrCreateClockLowering(Value clock); ClockLowering &getOrCreatePassThrough(); + ClockLowering &getInitial(); Value replaceValueWithStateRead(Value value, Value state); void addStorageArg(); LogicalResult lowerPrimaryInputs(); LogicalResult lowerPrimaryOutputs(); LogicalResult lowerStates(); + template + LogicalResult lowerStateLike(Operation *op, Value clock, Value enable, + Value reset, ArrayRef inputs, + FlatSymbolRefAttr callee, + ArrayRef initialValues = {}); LogicalResult lowerState(StateOp stateOp); + LogicalResult lowerState(sim::DPICallOp dpiCallOp); LogicalResult lowerState(MemoryOp memOp); LogicalResult lowerState(MemoryWritePortOp memWriteOp); LogicalResult lowerState(TapOp tapOp); @@ -139,7 +155,7 @@ static bool shouldMaterialize(Operation *op) { return !isa(op); + StateOp, sim::DPICallOp>(op); } static bool shouldMaterialize(Value value) { @@ -153,6 +169,17 @@ static bool shouldMaterialize(Value value) { return shouldMaterialize(op); } +static bool canBeMaterializedInInitializer(Operation *op) { + if (!op) + return false; + if (op->hasTrait()) + return true; + if (isa(op->getDialect())) + return true; + // TODO: There are some other ops we probably want to allow + return false; +} + /// Materialize a value within this clock tree. This clones or moves all /// operations required to produce this value inside the clock tree. Value ClockLowering::materializeValue(Value value) { @@ -200,6 +227,10 @@ Value ClockLowering::materializeValue(Value value) { while (!worklist.empty()) { auto &workItem = worklist.back(); + if (isInitialTree() && !canBeMaterializedInInitializer(workItem.op)) { + workItem.op->emitError("Value cannot be used in initializer."); + return {}; + } if (!workItem.operands.empty()) { auto operand = workItem.operands.pop_back_val(); if (materializedValues.contains(operand) || !shouldMaterialize(operand)) @@ -311,6 +342,11 @@ ClockLowering &ModuleLowering::getOrCreatePassThrough() { return *slot; } +ClockLowering &ModuleLowering::getInitial() { + assert(!!initialLowering && "Initial tree op should have been constructed"); + return *initialLowering; +} + /// Replace all uses of a value with a `StateReadOp` on a state. Value ModuleLowering::replaceValueWithStateRead(Value value, Value state) { OpBuilder builder(state.getContext()); @@ -390,53 +426,49 @@ LogicalResult ModuleLowering::lowerPrimaryOutputs() { LogicalResult ModuleLowering::lowerStates() { SmallVector opsToLower; for (auto &op : *moduleOp.getBodyBlock()) - if (isa(&op)) + if (isa(&op)) opsToLower.push_back(&op); for (auto *op : opsToLower) { LLVM_DEBUG(llvm::dbgs() << "- Lowering " << *op << "\n"); - auto result = TypeSwitch(op) - .Case( - [&](auto op) { return lowerState(op); }) - .Default(success()); + auto result = + TypeSwitch(op) + .Case( + [&](auto op) { return lowerState(op); }) + .Default(success()); if (failed(result)) return failure(); } return success(); } -LogicalResult ModuleLowering::lowerState(StateOp stateOp) { - // We don't support arcs beyond latency 1 yet. These should be easy to add in - // the future though. - if (stateOp.getLatency() > 1) - return stateOp.emitError("state with latency > 1 not supported"); - - // Grab all operands from the state op and make it drop all its references. - // This allows `materializeValue` to move an operation if this state was the - // last user. - auto stateClock = stateOp.getClock(); - auto stateEnable = stateOp.getEnable(); - auto stateReset = stateOp.getReset(); - auto stateInputs = SmallVector(stateOp.getInputs()); +template +LogicalResult ModuleLowering::lowerStateLike( + Operation *stateOp, Value stateClock, Value stateEnable, Value stateReset, + ArrayRef stateInputs, FlatSymbolRefAttr callee, + ArrayRef initialValues) { + // Grab all operands from the state op at the callsite and make it drop all + // its references. This allows `materializeValue` to move an operation if this + // state was the last user. // 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 // the clock gates in the arc's clock tree. auto info = getOrCreateClockLowering(stateClock); info.enable = info.clock.getOrCreateAnd( - info.enable, info.clock.materializeValue(stateEnable), stateOp.getLoc()); + info.enable, info.clock.materializeValue(stateEnable), stateOp->getLoc()); // Allocate the necessary state within the model. SmallVector allocatedStates; - for (unsigned stateIdx = 0; stateIdx < stateOp.getNumResults(); ++stateIdx) { - auto type = stateOp.getResult(stateIdx).getType(); + for (unsigned stateIdx = 0; stateIdx < stateOp->getNumResults(); ++stateIdx) { + auto type = stateOp->getResult(stateIdx).getType(); auto intType = dyn_cast(type); if (!intType) - return stateOp.emitOpError("result ") + return stateOp->emitOpError("result ") << stateIdx << " has non-integer type " << type << "; only integer types are supported"; auto stateType = StateType::get(intType); - auto state = stateBuilder.create(stateOp.getLoc(), stateType, + auto state = stateBuilder.create(stateOp->getLoc(), stateType, storageArg); if (auto names = stateOp->getAttrOfType("names")) state->setAttr("name", names[stateIdx]); @@ -455,44 +487,84 @@ LogicalResult ModuleLowering::lowerState(StateOp stateOp) { OpBuilder nonResetBuilder = info.clock.builder; if (stateReset) { auto materializedReset = info.clock.materializeValue(stateReset); - auto ifOp = info.clock.builder.create(stateOp.getLoc(), + auto ifOp = info.clock.builder.create(stateOp->getLoc(), materializedReset, true); for (auto [alloc, resTy] : - llvm::zip(allocatedStates, stateOp.getResultTypes())) { + llvm::zip(allocatedStates, stateOp->getResultTypes())) { if (!isa(resTy)) stateOp->emitOpError("Non-integer result not supported yet!"); auto thenBuilder = ifOp.getThenBodyBuilder(); Value constZero = - thenBuilder.create(stateOp.getLoc(), resTy, 0); - thenBuilder.create(stateOp.getLoc(), alloc, constZero, + thenBuilder.create(stateOp->getLoc(), resTy, 0); + thenBuilder.create(stateOp->getLoc(), alloc, constZero, Value()); } - nonResetBuilder = ifOp.getElseBodyBuilder(); } + if (!initialValues.empty()) { + assert(initialValues.size() == allocatedStates.size() && + "Unexpected number of initializers"); + auto &initialTree = getInitial(); + for (auto [alloc, init] : llvm::zip(allocatedStates, initialValues)) { + // TODO: Can we get away without materialization? + auto matierializedInit = initialTree.materializeValue(init); + if (!matierializedInit) + return failure(); + initialTree.builder.create(stateOp->getLoc(), alloc, + matierializedInit, Value()); + } + } + stateOp->dropAllReferences(); - auto newStateOp = nonResetBuilder.create( - stateOp.getLoc(), stateOp.getResultTypes(), stateOp.getArcAttr(), + auto newStateOp = nonResetBuilder.create( + stateOp->getLoc(), stateOp->getResultTypes(), callee, materializedOperands); // Create the write ops that write the result of the transfer function to the // allocated state storage. for (auto [alloc, result] : llvm::zip(allocatedStates, newStateOp.getResults())) - nonResetBuilder.create(stateOp.getLoc(), alloc, result, + nonResetBuilder.create(stateOp->getLoc(), alloc, result, info.enable); // Replace all uses of the arc with reads from the allocated state. - for (auto [alloc, result] : llvm::zip(allocatedStates, stateOp.getResults())) + for (auto [alloc, result] : llvm::zip(allocatedStates, stateOp->getResults())) replaceValueWithStateRead(result, alloc); - stateOp.erase(); + stateOp->erase(); return success(); } +LogicalResult ModuleLowering::lowerState(StateOp stateOp) { + // We don't support arcs beyond latency 1 yet. These should be easy to add in + // the future though. + if (stateOp.getLatency() > 1) + return stateOp.emitError("state with latency > 1 not supported"); + + auto stateInputs = SmallVector(stateOp.getInputs()); + auto stateInitializers = SmallVector(stateOp.getInitials()); + + return lowerStateLike( + stateOp, stateOp.getClock(), stateOp.getEnable(), stateOp.getReset(), + stateInputs, stateOp.getArcAttr(), stateInitializers); +} + +LogicalResult ModuleLowering::lowerState(sim::DPICallOp callOp) { + // Clocked call op can be considered as arc state with single latency. + auto stateClock = callOp.getClock(); + if (!stateClock) + return callOp.emitError("unclocked DPI call not implemented yet"); + + auto stateInputs = SmallVector(callOp.getInputs()); + + return lowerStateLike(callOp, stateClock, callOp.getEnable(), + Value(), stateInputs, + callOp.getCalleeAttr()); +} + LogicalResult ModuleLowering::lowerState(MemoryOp memOp) { auto allocMemOp = stateBuilder.create( memOp.getLoc(), memOp.getType(), storageArg, memOp->getAttrs()); @@ -802,6 +874,13 @@ LogicalResult LowerStatePass::runOnModule(HWModuleOp moduleOp, Operation *clockSentinel = lowering.stateBuilder.create(moduleOp.getLoc()); + // Create the 'initial' pseudo clock tree. + auto initialTreeOp = + lowering.stateBuilder.create(moduleOp.getLoc()); + initialTreeOp.getBody().emplaceBlock(); + lowering.initialLowering = + std::make_unique(Value{}, initialTreeOp, stats); + lowering.stateBuilder.setInsertionPoint(stateSentinel); lowering.clockBuilder.setInsertionPoint(clockSentinel); @@ -829,9 +908,9 @@ LogicalResult LowerStatePass::runOnModule(HWModuleOp moduleOp, moduleOp.getBodyBlock()->eraseArguments( [&](auto arg) { return arg != lowering.storageArg; }); ImplicitLocOpBuilder builder(moduleOp.getLoc(), moduleOp); - auto modelOp = - builder.create(moduleOp.getLoc(), moduleOp.getModuleNameAttr(), - TypeAttr::get(moduleOp.getModuleType())); + auto modelOp = builder.create( + moduleOp.getLoc(), moduleOp.getModuleNameAttr(), + TypeAttr::get(moduleOp.getModuleType()), mlir::FlatSymbolRefAttr()); modelOp.getBody().takeBody(moduleOp.getBody()); moduleOp->erase(); sortTopologically(&modelOp.getBodyBlock()); diff --git a/lib/Dialect/Arc/Transforms/PrintCostModel.cpp b/lib/Dialect/Arc/Transforms/PrintCostModel.cpp new file mode 100644 index 000000000000..2f8a03be392e --- /dev/null +++ b/lib/Dialect/Arc/Transforms/PrintCostModel.cpp @@ -0,0 +1,56 @@ +//===- DummyAnalysisTester.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 +// +//===----------------------------------------------------------------------===// +// +// This is a dummy pass to test the cost model results it doesn't do any thing. +// just walks over the ops to compute some statistics. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Arc/ArcCostModel.h" +#include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/HW/HWOps.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/Pass/Pass.h" + +#define DEBUG_TYPE "arc-print-cost-model" + +namespace circt { +namespace arc { +#define GEN_PASS_DEF_PRINTCOSTMODEL +#include "circt/Dialect/Arc/ArcPasses.h.inc" +} // namespace arc +} // namespace circt + +using namespace circt; +using namespace arc; + +namespace { +struct PrintCostModelPass + : public arc::impl::PrintCostModelBase { + void runOnOperation() override; +}; +} // namespace + +void PrintCostModelPass::runOnOperation() { + OperationCosts statVars; + ArcCostModel arcCostModel; + for (auto moduleOp : getOperation().getOps()) { + moduleOp.walk([&](Operation *op) { statVars += arcCostModel.getCost(op); }); + } + + moduleCost = statVars.totalCost(); + packingCost = statVars.packingCost; + shufflingCost = statVars.shufflingCost; + vectoroizeOpsBodyCost = statVars.vectorizeOpsBodyCost; + allVectorizeOpsCost = statVars.packingCost + statVars.shufflingCost + + statVars.vectorizeOpsBodyCost; +} + +std::unique_ptr arc::createPrintCostModelPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/Arc/Transforms/StripSV.cpp b/lib/Dialect/Arc/Transforms/StripSV.cpp index f18602261b40..dbe400e81bfb 100644 --- a/lib/Dialect/Arc/Transforms/StripSV.cpp +++ b/lib/Dialect/Arc/Transforms/StripSV.cpp @@ -151,9 +151,19 @@ void StripSVPass::runOnOperation() { else next = reg.getNext(); + Value presetValue; + // Materialize initial value, assume zero initialization as default. + if (reg.getPreset() && !reg.getPreset()->isZero()) { + assert(hw::type_isa(reg.getType()) && + "cannot lower non integer preset"); + presetValue = builder.createOrFold( + reg.getLoc(), IntegerAttr::get(reg.getType(), *reg.getPreset())); + } + Value compReg = builder.create( reg.getLoc(), next.getType(), next, reg.getClk(), reg.getNameAttr(), - Value{}, Value{}, Value{}, reg.getInnerSymAttr()); + Value{}, Value{}, /*powerOnValue*/ presetValue, + reg.getInnerSymAttr()); reg.replaceAllUsesWith(compReg); opsToDelete.push_back(reg); continue; diff --git a/lib/Dialect/Calyx/CMakeLists.txt b/lib/Dialect/Calyx/CMakeLists.txt index bf327785be47..d0fc81a14898 100644 --- a/lib/Dialect/Calyx/CMakeLists.txt +++ b/lib/Dialect/Calyx/CMakeLists.txt @@ -21,6 +21,7 @@ add_circt_dialect_library(CIRCTCalyx CIRCTComb CIRCTSV CIRCTHW + CIRCTFSM MLIRArithDialect MLIRIR MLIRMemRefDialect diff --git a/lib/Dialect/Calyx/CalyxOps.cpp b/lib/Dialect/Calyx/CalyxOps.cpp index ead67b05b50a..e5b592ab373e 100644 --- a/lib/Dialect/Calyx/CalyxOps.cpp +++ b/lib/Dialect/Calyx/CalyxOps.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/Calyx/CalyxOps.h" #include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/FSM/FSMOps.h" #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWTypes.h" @@ -745,16 +746,25 @@ LogicalResult ComponentOp::verify() { // Verify the component actually does something: has a non-empty Control // region, or continuous assignments. - bool hasNoControlConstructs = - getControlOp().getBodyBlock()->getOperations().empty(); + bool hasNoControlConstructs = true; + getControlOp().walk([&](Operation *op) { + if (isa(op)) { + hasNoControlConstructs = false; + return WalkResult::interrupt(); + } + return WalkResult::advance(); + }); bool hasNoAssignments = getWiresOp().getBodyBlock()->getOps().empty(); if (hasNoControlConstructs && hasNoAssignments) return emitOpError( - "The component currently does nothing. It needs to either have " - "continuous assignments in the Wires region or control constructs in " - "the Control region."); - + "The component currently does nothing. It needs to either have " + "continuous assignments in the Wires region or control " + "constructs in the Control region. The Control region " + "should contain at least one of ") + << "'" << EnableOp::getOperationName() << "' , " + << "'" << InvokeOp::getOperationName() << "' or " + << "'" << fsm::MachineOp::getOperationName() << "'."; return success(); } @@ -843,8 +853,7 @@ LogicalResult CombComponentOp::verify() { if (hasNoAssignments) return emitOpError( "The component currently does nothing. It needs to either have " - "continuous assignments in the Wires region or control constructs in " - "the Control region."); + "continuous assignments in the Wires region."); // Check that all cells are combinational auto cells = getOps(); @@ -2236,6 +2245,8 @@ template static std::optional getLastEnableOp(OpTy parent) { static_assert(IsAny(), "Should be a StaticSeqOp or SeqOp."); + if (parent.getBodyBlock()->empty()) + return std::nullopt; auto &lastOp = parent.getBodyBlock()->back(); if (auto enableOp = dyn_cast(lastOp)) return enableOp; diff --git a/lib/Dialect/Comb/CombFolds.cpp b/lib/Dialect/Comb/CombFolds.cpp index 913be97a76ff..ececa63a30f7 100644 --- a/lib/Dialect/Comb/CombFolds.cpp +++ b/lib/Dialect/Comb/CombFolds.cpp @@ -122,7 +122,7 @@ static inline ComplementMatcher m_Complement(const SubType &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. +/// it has a single user which has a same op type. User must be in same block. static bool shouldBeFlattened(Operation *op) { assert((isa(op) && "must be commutative operations")); @@ -130,7 +130,8 @@ static bool shouldBeFlattened(Operation *op) { auto *user = *op->getUsers().begin(); return user->getName() == op->getName() && op->getAttrOfType("twoState") == - user->getAttrOfType("twoState"); + user->getAttrOfType("twoState") && + op->getBlock() == user->getBlock(); } return false; } @@ -169,8 +170,11 @@ static bool tryFlatteningOperands(Operation *op, PatternRewriter &rewriter) { Value value = *element.current++; auto *flattenOp = value.getDefiningOp(); + // If not defined by a compatible operation of the same kind and + // from the same block, keep this as-is. if (!flattenOp || flattenOp->getName() != op->getName() || - flattenOp == op || binFlag != op->hasAttrOfType("twoState")) { + flattenOp == op || binFlag != op->hasAttrOfType("twoState") || + flattenOp->getBlock() != op->getBlock()) { newOperands.push_back(value); continue; } @@ -689,17 +693,20 @@ LogicalResult ExtractOp::canonicalize(ExtractOp op, PatternRewriter &rewriter) { // `extract(lowBit, shl(1, x))` -> `x == lowBit` when a single bit is // extracted. if (cast(op.getType()).getWidth() == 1 && inputOp) - if (auto shlOp = dyn_cast(inputOp)) - if (auto lhsCst = shlOp.getOperand(0).getDefiningOp()) - if (lhsCst.getValue().isOne()) { - auto newCst = rewriter.create( - shlOp.getLoc(), - APInt(lhsCst.getValue().getBitWidth(), op.getLowBit())); - replaceOpWithNewOpAndCopyName(rewriter, op, ICmpPredicate::eq, - shlOp->getOperand(1), newCst, - false); - return success(); - } + if (auto shlOp = dyn_cast(inputOp)) { + // Don't canonicalize if the shift is multiply used. + if (shlOp->hasOneUse()) + if (auto lhsCst = shlOp.getLhs().getDefiningOp()) + if (lhsCst.getValue().isOne()) { + auto newCst = rewriter.create( + shlOp.getLoc(), + APInt(lhsCst.getValue().getBitWidth(), op.getLowBit())); + replaceOpWithNewOpAndCopyName( + rewriter, op, ICmpPredicate::eq, shlOp->getOperand(1), newCst, + false); + return success(); + } + } return failure(); } @@ -933,34 +940,48 @@ static Value getCommonOperand(Op op) { /// Example: `and(x, y, x, z)` -> `and(x, y, z)` template static bool canonicalizeIdempotentInputs(Op op, PatternRewriter &rewriter) { + // Depth limit to search, in operations. Chosen arbitrarily, keep small. + constexpr unsigned limit = 3; auto inputs = op.getInputs(); llvm::SmallSetVector uniqueInputs(inputs.begin(), inputs.end()); - llvm::SmallDenseSet checked; + llvm::SmallDenseSet checked; checked.insert(op); - llvm::SmallVector worklist; - for (auto input : inputs) { - if (input != op) - worklist.push_back(input); - } + struct OpWithDepth { + Op op; + unsigned depth; + }; + llvm::SmallVector worklist; + + auto enqueue = [&worklist, &checked, &op](Value input, unsigned depth) { + // Add to worklist if within depth limit, is defined in the same block by + // the same kind of operation, has same two-state-ness, and not enqueued + // previously. + if (depth < limit && input.getParentBlock() == op->getBlock()) { + auto inputOp = input.template getDefiningOp(); + if (inputOp && inputOp.getTwoState() == op.getTwoState() && + checked.insert(inputOp).second) + worklist.push_back({inputOp, depth + 1}); + } + }; - while (!worklist.empty()) { - auto element = worklist.pop_back_val(); + for (auto input : uniqueInputs) + enqueue(input, 0); - if (auto idempotentOp = element.getDefiningOp()) { - for (auto input : idempotentOp.getInputs()) { - uniqueInputs.remove(input); + while (!worklist.empty()) { + auto item = worklist.pop_back_val(); - if (checked.insert(input).second) - worklist.push_back(input); - } + for (auto input : item.op.getInputs()) { + uniqueInputs.remove(input); + enqueue(input, item.depth); } } if (uniqueInputs.size() < inputs.size()) { replaceOpWithNewOpAndCopyName(rewriter, op, op.getType(), - uniqueInputs.getArrayRef()); + uniqueInputs.getArrayRef(), + op.getTwoState()); return true; } @@ -968,12 +989,8 @@ static bool canonicalizeIdempotentInputs(Op op, PatternRewriter &rewriter) { } LogicalResult AndOp::canonicalize(AndOp op, PatternRewriter &rewriter) { - if (hasOperandsOutsideOfBlock(&*op)) - return failure(); - auto inputs = op.getInputs(); 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)) @@ -985,6 +1002,10 @@ LogicalResult AndOp::canonicalize(AndOp op, PatternRewriter &rewriter) { if (size > 1 && canonicalizeIdempotentInputs(op, rewriter)) return success(); + if (hasOperandsOutsideOfBlock(&*op)) + return failure(); + assert(size > 1 && "expected 2 or more operands, `fold` should handle this"); + // Patterns for and with a constant on RHS. APInt value; if (matchPattern(inputs.back(), m_ConstantInt(&value))) { @@ -1255,12 +1276,8 @@ static bool canonicalizeOrOfConcatsWithCstOperands(OrOp op, size_t concatIdx1, } LogicalResult OrOp::canonicalize(OrOp op, PatternRewriter &rewriter) { - if (hasOperandsOutsideOfBlock(&*op)) - return failure(); - auto inputs = op.getInputs(); auto size = inputs.size(); - assert(size > 1 && "expected 2 or more operands"); // or(x, or(...)) -> or(x, ...) -- flatten if (tryFlatteningOperands(op, rewriter)) @@ -1272,6 +1289,10 @@ LogicalResult OrOp::canonicalize(OrOp op, PatternRewriter &rewriter) { if (size > 1 && canonicalizeIdempotentInputs(op, rewriter)) return success(); + if (hasOperandsOutsideOfBlock(&*op)) + return failure(); + assert(size > 1 && "expected 2 or more operands"); + // Patterns for and with a constant on RHS. APInt value; if (matchPattern(inputs.back(), m_ConstantInt(&value))) { diff --git a/lib/Dialect/ESI/ESIOps.cpp b/lib/Dialect/ESI/ESIOps.cpp index 893f375f60fc..acf794dc679b 100644 --- a/lib/Dialect/ESI/ESIOps.cpp +++ b/lib/Dialect/ESI/ESIOps.cpp @@ -696,6 +696,12 @@ void ServiceRequestRecordOp::getDetails( StringRef SymbolMetadataOp::getManifestClass() { return "sym_info"; } +StringRef SymbolConstantsOp::getManifestClass() { return "sym_consts"; } +void SymbolConstantsOp::getDetails(SmallVectorImpl &results) { + for (auto &attr : getConstantsAttr()) + results.push_back(attr); +} + #define GET_OP_CLASSES #include "circt/Dialect/ESI/ESI.cpp.inc" diff --git a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp index a50afdf48261..a4022cf75d07 100644 --- a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp +++ b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp @@ -39,10 +39,13 @@ struct ESIBuildManifestPass void gatherFilters(Operation *); void gatherFilters(Attribute); - /// Get a JSON representation of a type. - llvm::json::Value json(Operation *errorOp, Type); - /// Get a JSON representation of a type. - llvm::json::Value json(Operation *errorOp, Attribute); + /// Get a JSON representation of a type. 'useTable' indicates whether to use + /// the type table to determine if the type should be emitted as a reference + /// if it already exists in the type table. + llvm::json::Value json(Operation *errorOp, Type, bool useTable = true); + /// Get a JSON representation of a type. 'elideType' indicates to not print + /// the type if it would have been printed. + llvm::json::Value json(Operation *errorOp, Attribute, bool elideType = false); // Output a node in the appid hierarchy. void emitNode(llvm::json::OStream &, AppIDHierNodeOp nodeOp); @@ -88,6 +91,12 @@ void ESIBuildManifestPass::runOnOperation() { // scraping unnecessary types. appidRoot->walk([&](Operation *op) { gatherFilters(op); }); + // Also gather types from the manifest data. + for (Region ®ion : mod->getRegions()) + for (Block &block : region) + for (auto manifestInfo : block.getOps()) + gatherFilters(manifestInfo); + // JSONify the manifest. std::string jsonManifest = json(); @@ -165,15 +174,34 @@ std::string ESIBuildManifestPass::json() { j.attribute("api_version", esiApiVersion); j.attributeArray("symbols", [&]() { - for (auto symInfo : mod.getBody()->getOps()) { - if (!symbols.contains(symInfo.getSymbolRefAttr())) + // First, gather all of the manifest data for each symbol. + DenseMap> symbolInfoLookup; + for (auto symInfo : mod.getBody()->getOps()) { + FlatSymbolRefAttr sym = symInfo.getSymbolRefAttr(); + if (!sym || !symbols.contains(sym)) continue; + symbolInfoLookup[sym].push_back(symInfo); + } + + // Now, emit a JSON object for each symbol. + for (const auto &symNameInfo : symbolInfoLookup) { j.object([&] { - SmallVector attrs; - symInfo.getDetails(attrs); - for (auto attr : attrs) - j.attribute(attr.getName().getValue(), - json(symInfo, attr.getValue())); + j.attribute("symbol", json(symNameInfo.second.front(), + symNameInfo.first, /*elideType=*/true)); + for (auto symInfo : symNameInfo.second) { + j.attributeBegin(symInfo.getManifestClass()); + j.object([&] { + SmallVector attrs; + symInfo.getDetails(attrs); + for (auto attr : attrs) { + if (attr.getName().getValue() == "symbolRef") + continue; + j.attribute(attr.getName().getValue(), + json(symInfo, attr.getValue())); + } + }); + j.attributeEnd(); + } }); } }); @@ -215,7 +243,7 @@ std::string ESIBuildManifestPass::json() { j.attributeArray("types", [&]() { for (auto type : types) { - j.value(json(mod, type)); + j.value(json(mod, type, /*useTable=*/false)); } }); j.objectEnd(); @@ -245,6 +273,7 @@ void ESIBuildManifestPass::gatherFilters(Attribute attr) { // This is far from complete. Build out as necessary. TypeSwitch(attr) .Case([&](TypeAttr a) { addType(a.getValue()); }) + .Case([&](IntegerAttr a) { addType(a.getType()); }) .Case([&](FlatSymbolRefAttr a) { symbols.insert(a); }) .Case([&](hw::InnerRefAttr a) { symbols.insert(a.getModuleRef()); }) .Case([&](ArrayAttr a) { @@ -259,18 +288,28 @@ void ESIBuildManifestPass::gatherFilters(Attribute attr) { /// Get a JSON representation of a type. // NOLINTNEXTLINE(misc-no-recursion) -llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { +llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type, + bool useTable) { using llvm::json::Array; using llvm::json::Object; using llvm::json::Value; + if (useTable && typeLookup.contains(type)) { + // If the type is in the type table, it'll be present in the types + // section. Just give the circt type name, which is guaranteed to + // uniquely identify the type. + std::string typeName; + llvm::raw_string_ostream(typeName) << type; + return typeName; + } + std::string m; Object o = // This is not complete. Build out as necessary. TypeSwitch(type) .Case([&](ChannelType t) { m = "channel"; - return Object({{"inner", json(errorOp, t.getInner())}}); + return Object({{"inner", json(errorOp, t.getInner(), useTable)}}); }) .Case([&](ChannelBundleType t) { m = "bundle"; @@ -279,7 +318,7 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { channels.push_back(Object( {{"name", field.name.getValue()}, {"direction", stringifyChannelDirection(field.direction)}, - {"type", json(errorOp, field.type)}})); + {"type", json(errorOp, field.type, useTable)}})); return Object({{"channels", Value(std::move(channels))}}); }) .Case([&](AnyType t) { @@ -288,25 +327,29 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { }) .Case([&](ListType t) { m = "list"; - return Object({{"element", json(errorOp, t.getElementType())}}); + return Object( + {{"element", json(errorOp, t.getElementType(), useTable)}}); }) .Case([&](hw::ArrayType t) { m = "array"; - return Object({{"size", t.getNumElements()}, - {"element", json(errorOp, t.getElementType())}}); + return Object( + {{"size", t.getNumElements()}, + {"element", json(errorOp, t.getElementType(), useTable)}}); }) .Case([&](hw::StructType t) { m = "struct"; Array fields; for (auto field : t.getElements()) - fields.push_back(Object({{"name", field.name.getValue()}, - {"type", json(errorOp, field.type)}})); + fields.push_back( + Object({{"name", field.name.getValue()}, + {"type", json(errorOp, field.type, useTable)}})); return Object({{"fields", Value(std::move(fields))}}); }) .Case([&](hw::TypeAliasType t) { m = "alias"; - return Object({{"name", t.getTypeDecl(symCache).getPreferredName()}, - {"inner", json(errorOp, t.getInnerType())}}); + return Object( + {{"name", t.getTypeDecl(symCache).getPreferredName()}, + {"inner", json(errorOp, t.getInnerType(), useTable)}}); }) .Case([&](IntegerType t) { m = "int"; @@ -322,9 +365,9 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { }); // Common metadata. - std::string circtName; - llvm::raw_string_ostream(circtName) << type; - o["circt_name"] = circtName; + std::string typeID; + llvm::raw_string_ostream(typeID) << type; + o["id"] = typeID; int64_t width = hw::getBitWidth(type); if (auto chanType = dyn_cast(type)) @@ -340,59 +383,58 @@ llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Type type) { // Serialize an attribute to a JSON value. // NOLINTNEXTLINE(misc-no-recursion) -llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, - Attribute attr) { +llvm::json::Value ESIBuildManifestPass::json(Operation *errorOp, Attribute attr, + bool elideType) { + // This is far from complete. Build out as necessary. + using llvm::json::Object; using llvm::json::Value; - return TypeSwitch(attr) - .Case([&](StringAttr a) { return a.getValue(); }) - .Case([&](IntegerAttr a) { return a.getValue().getLimitedValue(); }) - .Case([&](TypeAttr a) { - Type t = a.getValue(); - - llvm::json::Object typeMD; - if (typeLookup.contains(t)) { - // If the type is in the type table, it'll be present in the types - // section. Just give the circt type name, which is guaranteed to - // uniquely identify the type. - std::string buff; - llvm::raw_string_ostream(buff) << a; - typeMD["circt_name"] = buff; - return typeMD; - } + Value value = + TypeSwitch(attr) + .Case([&](StringAttr a) { return a.getValue(); }) + .Case([&](IntegerAttr a) { return a.getValue().getLimitedValue(); }) + .Case([&](TypeAttr a) { return json(errorOp, a.getValue()); }) + .Case([&](ArrayAttr a) { + return llvm::json::Array(llvm::map_range( + a, [&](Attribute a) { return json(errorOp, a); })); + }) + .Case([&](DictionaryAttr a) { + llvm::json::Object dict; + for (const auto &entry : a.getValue()) + dict[entry.getName().getValue()] = + json(errorOp, entry.getValue()); + return dict; + }) + .Case([&](hw::InnerRefAttr ref) { + llvm::json::Object dict; + dict["outer_sym"] = ref.getModule().getValue(); + dict["inner"] = ref.getName().getValue(); + return dict; + }) + .Case([&](AppIDAttr appid) { + llvm::json::Object dict; + dict["name"] = appid.getName().getValue(); + auto idx = appid.getIndex(); + if (idx) + dict["index"] = *idx; + return dict; + }) + .Default([&](Attribute a) { + std::string value; + llvm::raw_string_ostream(value) << a; + return value; + }); - typeMD["type"] = json(errorOp, t); - return typeMD; - }) - .Case([&](ArrayAttr a) { - return llvm::json::Array( - llvm::map_range(a, [&](Attribute a) { return json(errorOp, a); })); - }) - .Case([&](DictionaryAttr a) { - llvm::json::Object dict; - for (const auto &entry : a.getValue()) - dict[entry.getName().getValue()] = json(errorOp, entry.getValue()); - return dict; - }) - .Case([&](hw::InnerRefAttr ref) { - llvm::json::Object dict; - dict["outer_sym"] = ref.getModule().getValue(); - dict["inner"] = ref.getName().getValue(); - return dict; - }) - .Case([&](AppIDAttr appid) { - llvm::json::Object dict; - dict["name"] = appid.getName().getValue(); - auto idx = appid.getIndex(); - if (idx) - dict["index"] = *idx; - return dict; - }) - .Default([&](Attribute a) { - std::string buff; - llvm::raw_string_ostream(buff) << a; - return buff; - }); + // Don't print the type if it's None or we're eliding it. + auto typedAttr = llvm::dyn_cast(attr); + if (elideType || !typedAttr || isa(typedAttr.getType())) + return value; + + // Otherwise, return an object with the value and type. + Object dict; + dict["value"] = value; + dict["type"] = json(errorOp, typedAttr.getType()); + return dict; } std::unique_ptr> diff --git a/lib/Dialect/ESI/runtime/CMakeLists.txt b/lib/Dialect/ESI/runtime/CMakeLists.txt index 1bf82fa04c98..b14555cd9153 100644 --- a/lib/Dialect/ESI/runtime/CMakeLists.txt +++ b/lib/Dialect/ESI/runtime/CMakeLists.txt @@ -27,6 +27,11 @@ cmake_minimum_required(VERSION 3.20) project(ESIRuntime LANGUAGES CXX) include(FetchContent) +set(ESI_STATIC_RUNTIME OFF CACHE BOOL "Build the ESI runtime as a static library.") +if(ESI_STATIC_RUNTIME) + message("-- Building ESI runtime as a static library.") +endif() + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib") @@ -44,12 +49,17 @@ if (NOT TARGET nlohmann_json) FetchContent_MakeAvailable(json) endif() +if(ESI_STATIC_RUNTIME) + set(ZLIB_USE_STATIC_LIBS ON) +endif() + # We need zlib to uncompress the manifest. find_package(ZLIB) if(ZLIB_FOUND) set(ZLIB_LIBRARY ZLIB::ZLIB) else() message("-- zlib not found, pulling down zlib from git") + set(ZLIB_BUILD_EXAMPLES OFF) FetchContent_Declare( ZLIB GIT_REPOSITORY https://github.com/madler/zlib.git @@ -57,7 +67,11 @@ else() ) FetchContent_MakeAvailable(ZLIB) set(ZLIB_INCLUDE_DIR ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR}) - set(ZLIB_LIBRARY zlib) + if(ESI_STATIC_RUNTIME) + set(ZLIB_LIBRARY zlibstatic) + else() + set(ZLIB_LIBRARY zlib) + endif() endif() # In a Python wheel build, we need to install libraries to different places. @@ -109,6 +123,7 @@ set(ESICppRuntimeBackendHeaders set(ESIPythonRuntimeSources python/esiaccel/__init__.py python/esiaccel/accelerator.py + python/esiaccel/codegen.py python/esiaccel/types.py python/esiaccel/utils.py ) @@ -118,10 +133,17 @@ IF(MSVC) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS 1) ENDIF(MSVC) -# The core API. For now, compile the backends into it directly. -add_library(ESICppRuntime SHARED - ${ESICppRuntimeSources} -) +if(ESI_STATIC_RUNTIME) + add_library(ESICppRuntime STATIC + ${ESICppRuntimeSources} + ) +else() + add_library(ESICppRuntime SHARED + ${ESICppRuntimeSources} + ) +endif() +add_library(esiaccel::ESICppRuntime ALIAS ESICppRuntime) + target_include_directories(ESICppRuntime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/cpp/include ) @@ -165,10 +187,12 @@ install(TARGETS ESICppRuntime COMPONENT ESIRuntime ) -install(IMPORTED_RUNTIME_ARTIFACTS ESICppRuntime - RUNTIME_DEPENDENCY_SET ESICppRuntime_RUNTIME_DEPS - COMPONENT ESIRuntime -) +if(NOT ESI_STATIC_RUNTIME) + install(IMPORTED_RUNTIME_ARTIFACTS ESICppRuntime + RUNTIME_DEPENDENCY_SET ESICppRuntime_RUNTIME_DEPS + COMPONENT ESIRuntime + ) +endif() install(RUNTIME_DEPENDENCY_SET ESICppRuntime_RUNTIME_DEPS DESTINATION ${LIB_DIR} PRE_EXCLUDE_REGEXES .* diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h index 34567c466f88..365b14f85404 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Accelerator.h @@ -74,11 +74,11 @@ class Accelerator : public HWModule { /// Abstract class representing a connection to an accelerator. Actual /// connections (e.g. to a co-simulation or actual device) are implemented by -/// subclasses. +/// subclasses. No methods in here are thread safe. class AcceleratorConnection { public: AcceleratorConnection(Context &ctxt); - virtual ~AcceleratorConnection() = default; + virtual ~AcceleratorConnection(); Context &getCtxt() const { return ctxt; } /// Disconnect from the accelerator cleanly. @@ -89,7 +89,12 @@ class AcceleratorConnection { virtual std::map requestChannelsFor(AppIDPath, const BundleType *) = 0; - AcceleratorServiceThread *getServiceThread() { return serviceThread.get(); } + /// Return a pointer to the accelerator 'service' thread (or threads). If the + /// thread(s) are not running, they will be started when this method is + /// called. `std::thread` is used. If users don't want the runtime to spin up + /// threads, don't call this method. `AcceleratorServiceThread` is owned by + /// AcceleratorConnection and governed by the lifetime of the this object. + AcceleratorServiceThread *getServiceThread(); using Service = services::Service; /// Get a typed reference to a particular service type. Caller does *not* take @@ -109,6 +114,10 @@ class AcceleratorConnection { ServiceImplDetails details = {}, HWClientDetails clients = {}); + /// Assume ownership of an accelerator object. Ties the lifetime of the + /// accelerator to this connection. Returns a raw pointer to the object. + Accelerator *takeOwnership(std::unique_ptr accel); + protected: /// Called by `getServiceImpl` exclusively. It wraps the pointer returned by /// this in a unique_ptr and caches it. Separate this from the @@ -128,21 +137,26 @@ class AcceleratorConnection { std::map> serviceCache; std::unique_ptr serviceThread; + + /// List of accelerator objects owned by this connection. These are destroyed + /// when the connection dies or is shutdown. + std::vector> ownedAccelerators; }; 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(Context &ctxt, std::string backend, std::string connection); +std::unique_ptr connect(Context &ctxt, + const std::string &backend, + const std::string &connection); namespace internal { /// Backends can register themselves to be connected via a connection string. using BackendCreate = std::function( Context &, std::string)>; -void registerBackend(std::string name, BackendCreate create); +void registerBackend(const std::string &name, BackendCreate create); // Helper struct to template @@ -172,6 +186,9 @@ class AcceleratorServiceThread { addListener(std::initializer_list listenPorts, std::function callback); + /// Poll this module. + void addPoll(HWModule &module); + /// Instruct the service thread to stop running. void stop(); diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h index 7d4e16868336..a47e2c0e76dd 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Common.h @@ -25,6 +25,7 @@ #include namespace esi { +class Type; //===----------------------------------------------------------------------===// // Common accelerator description types. @@ -34,7 +35,6 @@ struct AppID { std::string name; std::optional idx; - AppID(const AppID &) = default; AppID(const std::string &name, std::optional idx = std::nullopt) : name(name), idx(idx) {} @@ -54,13 +54,19 @@ class AppIDPath : public std::vector { }; bool operator<(const AppIDPath &a, const AppIDPath &b); +struct Constant { + std::any value; + std::optional type; +}; + struct ModuleInfo { - const std::optional name; - const std::optional summary; - const std::optional version; - const std::optional repo; - const std::optional commitHash; - const std::map extra; + std::optional name; + std::optional summary; + std::optional version; + std::optional repo; + std::optional commitHash; + std::map constants; + std::map extra; }; /// A description of a service port. Used pretty exclusively in setting up the diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h index 6d1713ce82d4..b95421e0cd01 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Design.h @@ -77,6 +77,11 @@ class HWModule { return portIndex; } + /// Master poll method. Calls the `poll` method on all locally owned ports and + /// the master `poll` method on all of the children. Returns true if any of + /// the `poll` calls returns true. + bool poll(); + protected: const std::optional info; const std::vector> children; diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h index 547ecb9dbe94..fccb6272ba13 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Manifest.h @@ -48,9 +48,10 @@ class Manifest { // Modules which have designer specified metadata. std::vector getModuleInfos() const; - // Build a dynamic design hierarchy from the manifest. - std::unique_ptr - buildAccelerator(AcceleratorConnection &acc) const; + // Build a dynamic design hierarchy from the manifest. The + // AcceleratorConnection owns the returned pointer so its lifetime is + // determined by the connection. + Accelerator *buildAccelerator(AcceleratorConnection &acc) 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 diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h index 2db68e76e5f9..c025cbfa085c 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Ports.h @@ -33,21 +33,38 @@ namespace esi { class ChannelPort { public: ChannelPort(const Type *type) : type(type) {} - virtual ~ChannelPort() { disconnect(); } + virtual ~ChannelPort() {} /// Set up a connection to the accelerator. The buffer size is optional and /// should be considered merely a hint. Individual implementations use it /// however they like. The unit is number of messages of the port type. - virtual void connect(std::optional bufferSize = std::nullopt) { - connectImpl(bufferSize); + virtual void connect(std::optional bufferSize = std::nullopt) = 0; + virtual void disconnect() = 0; + virtual bool isConnected() const = 0; + + /// Poll for incoming data. Returns true if data was read or written into a + /// buffer as a result of the poll. Calling the call back could (will) also + /// happen in that case. Some backends need this to be called periodically. In + /// the usual case, this will be called by a background thread, but the ESI + /// runtime does not want to assume that the host processes use standard + /// threads. If the user wants to provide their own threads, they need to call + /// this on each port occasionally. This is also called from the 'master' poll + /// method in the Accelerator class. + bool poll() { + if (isConnected()) + return pollImpl(); + return false; } - virtual void disconnect() {} const Type *getType() const { return type; } -private: +protected: const Type *type; + /// Method called by poll() to actually poll the channel if the channel is + /// connected. + virtual bool pollImpl() { return false; } + /// Called by all connect methods to let backends initiate the underlying /// connections. virtual void connectImpl(std::optional bufferSize) {} @@ -58,8 +75,19 @@ class WriteChannelPort : public ChannelPort { public: using ChannelPort::ChannelPort; + virtual void + connect(std::optional bufferSize = std::nullopt) override { + connectImpl(bufferSize); + connected = true; + } + virtual void disconnect() override { connected = false; } + virtual bool isConnected() const override { return connected; } + /// A very basic write API. Will likely change for performance reasons. virtual void write(const MessageData &) = 0; + +private: + volatile bool connected = false; }; /// A ChannelPort which reads data from the accelerator. It has two modes: @@ -72,6 +100,9 @@ class ReadChannelPort : public ChannelPort { ReadChannelPort(const Type *type) : ChannelPort(type), mode(Mode::Disconnected) {} virtual void disconnect() override { mode = Mode::Disconnected; } + virtual bool isConnected() const override { + return mode != Mode::Disconnected; + } //===--------------------------------------------------------------------===// // Callback mode: To use a callback, connect with a callback function which @@ -121,7 +152,7 @@ class ReadChannelPort : public ChannelPort { protected: /// Indicates the current mode of the channel. enum Mode { Disconnected, Callback, Polling }; - Mode mode; + volatile Mode mode; /// Backends call this callback when new data is available. std::function callback; @@ -178,6 +209,15 @@ class BundlePort { return const_cast(dynamic_cast(this)); } + /// Calls `poll` on all channels in the bundle and returns true if any of them + /// returned true. + bool poll() { + bool result = false; + for (auto &channel : channels) + result |= channel.second.poll(); + return result; + } + private: AppID id; std::map channels; diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h b/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h index 0101095c84f5..0d11be95473c 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/Utils.h @@ -28,6 +28,11 @@ namespace utils { // Very basic base64 encoding. void encodeBase64(const void *data, size_t size, std::string &out); +/// C++'s stdlib doesn't have a hash_combine function. This is a simple one. +inline size_t hash_combine(size_t h1, size_t h2) { + return h1 + 0x9e3779b9 + (h2 << 6) + (h2 >> 2); +} + /// Thread safe queue. Just wraps std::queue protected with a lock. Long term, /// we need to avoid copying data. It has a lot of data copies currently. template 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 6be52ffe6c85..5cd0406e0050 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Trace.h @@ -43,6 +43,9 @@ class TraceAccelerator : public esi::AcceleratorConnection { // Data read from the accelerator is read from the trace file. // TODO: Full trace mode not yet supported. // Read + + // Discard all data sent to the accelerator. Disable trace file generation. + Discard, }; /// Create a trace-based accelerator backend. @@ -52,6 +55,7 @@ class TraceAccelerator : public esi::AcceleratorConnection { /// is opened for writing. For 'Read' mode, this file is opened for reading. TraceAccelerator(Context &, Mode mode, std::filesystem::path manifestJson, std::filesystem::path traceFile); + ~TraceAccelerator() override; /// Parse the connection string and instantiate the accelerator. Format is: /// ":[:]". diff --git a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h index 04454cb3c8a4..def6b4ebf8bd 100644 --- a/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h +++ b/lib/Dialect/ESI/runtime/cpp/include/esi/backends/Xrt.h @@ -33,6 +33,7 @@ class XrtAccelerator : public esi::AcceleratorConnection { struct Impl; XrtAccelerator(Context &, std::string xclbin, std::string kernelName); + ~XrtAccelerator(); static std::unique_ptr connect(Context &, std::string connectionString); diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp index 019d734b0023..9f94d784489d 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Accelerator.cpp @@ -34,7 +34,14 @@ using namespace esi::services; namespace esi { AcceleratorConnection::AcceleratorConnection(Context &ctxt) - : ctxt(ctxt), serviceThread(std::make_unique()) {} + : ctxt(ctxt), serviceThread(nullptr) {} +AcceleratorConnection::~AcceleratorConnection() { disconnect(); } + +AcceleratorServiceThread *AcceleratorConnection::getServiceThread() { + if (!serviceThread) + serviceThread = std::make_unique(); + return serviceThread.get(); +} services::Service *AcceleratorConnection::getService(Service::Type svcType, AppIDPath id, @@ -54,6 +61,13 @@ services::Service *AcceleratorConnection::getService(Service::Type svcType, return cacheEntry.get(); } +Accelerator * +AcceleratorConnection::takeOwnership(std::unique_ptr acc) { + Accelerator *ret = acc.get(); + ownedAccelerators.push_back(std::move(acc)); + return ret; +} + /// Get the path to the currently running executable. static std::filesystem::path getExePath() { #ifdef __linux__ @@ -175,22 +189,35 @@ static void loadBackend(std::string backend) { namespace registry { namespace internal { -static std::map backendRegistry; -void registerBackend(std::string name, BackendCreate create) { - if (backendRegistry.count(name)) +class BackendRegistry { +public: + static std::map &get() { + static BackendRegistry instance; + return instance.backendRegistry; + } + +private: + std::map backendRegistry; +}; + +void registerBackend(const std::string &name, BackendCreate create) { + auto ®istry = BackendRegistry::get(); + if (registry.count(name)) throw std::runtime_error("Backend already exists in registry"); - backendRegistry[name] = create; + registry[name] = create; } } // namespace internal -std::unique_ptr -connect(Context &ctxt, std::string backend, std::string connection) { - auto f = internal::backendRegistry.find(backend); - if (f == internal::backendRegistry.end()) { +std::unique_ptr connect(Context &ctxt, + const std::string &backend, + const std::string &connection) { + auto ®istry = internal::BackendRegistry::get(); + auto f = registry.find(backend); + if (f == registry.end()) { // If it's not already found in the registry, try to load it dynamically. loadBackend(backend); - f = internal::backendRegistry.find(backend); - if (f == internal::backendRegistry.end()) + f = registry.find(backend); + if (f == registry.end()) throw std::runtime_error("Backend '" + backend + "' not found"); } return f->second(ctxt, connection); @@ -211,18 +238,27 @@ struct AcceleratorServiceThread::Impl { addListener(std::initializer_list listenPorts, std::function callback); + void addTask(std::function task) { + std::lock_guard g(m); + taskList.push_back(task); + } + private: void loop(); volatile bool shutdown = false; std::thread me; - // Protect the listeners std::map. - std::mutex listenerMutex; + // Protect the shared data structures. + std::mutex m; + // Map of read ports to callbacks. std::map, std::future>> listeners; + + /// Tasks which should be called on every loop iteration. + std::vector> taskList; }; void AcceleratorServiceThread::Impl::loop() { @@ -232,6 +268,7 @@ void AcceleratorServiceThread::Impl::loop() { std::function, MessageData>> portUnlockWorkList; + std::vector> taskListCopy; MessageData data; while (!shutdown) { @@ -243,7 +280,7 @@ void AcceleratorServiceThread::Impl::loop() { // Check and gather data from all the read ports we are monitoring. Put the // callbacks to be called later so we can release the lock. { - std::lock_guard g(listenerMutex); + std::lock_guard g(m); for (auto &[channel, cbfPair] : listeners) { assert(channel && "Null channel in listener list"); std::future &f = cbfPair.second; @@ -260,13 +297,22 @@ void AcceleratorServiceThread::Impl::loop() { // Clear the worklist for the next iteration. portUnlockWorkList.clear(); + + // Call any tasks that have been added. Copy it first so we can release the + // lock ASAP. + { + std::lock_guard g(m); + taskListCopy = taskList; + } + for (auto &task : taskListCopy) + task(); } } void AcceleratorServiceThread::Impl::addListener( std::initializer_list listenPorts, std::function callback) { - std::lock_guard g(listenerMutex); + std::lock_guard g(m); for (auto port : listenPorts) { if (listeners.count(port)) throw std::runtime_error("Port already has a listener"); @@ -299,6 +345,11 @@ void AcceleratorServiceThread::addListener( impl->addListener(listenPorts, callback); } +void AcceleratorServiceThread::addPoll(HWModule &module) { + assert(impl && "Service thread not running"); + impl->addTask([&module]() { module.poll(); }); +} + void AcceleratorConnection::disconnect() { if (serviceThread) { serviceThread->stop(); diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp index 8e5bd5e45f0d..9e54e694e449 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Design.cpp @@ -47,4 +47,13 @@ HWModule::HWModule(std::optional info, childIndex(buildIndex(this->children)), services(services), ports(std::move(ports)), portIndex(buildIndex(this->ports)) {} +bool HWModule::poll() { + bool result = false; + for (auto &port : ports) + result |= port->poll(); + for (auto &child : children) + result |= child->poll(); + return result; +} + } // namespace esi diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp index 7e4fd84c5034..56e7986179e4 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Manifest.cpp @@ -19,7 +19,7 @@ #include #include -using namespace esi; +using namespace ::esi; // While building the design, keep around a std::map of active services indexed // by the service name. When a new service is encountered during descent, add it @@ -95,6 +95,10 @@ class Manifest::Impl { const Type *parseType(const nlohmann::json &typeJson); + const std::map &getSymbolInfo() const { + return symbolInfoCache; + } + private: Context &ctxt; std::vector _typeTable; @@ -103,10 +107,14 @@ class Manifest::Impl { return ctxt.getType(id); } + std::any getAny(const nlohmann::json &value) const; + void parseModuleMetadata(ModuleInfo &info, const nlohmann::json &mod) const; + void parseModuleConsts(ModuleInfo &info, const nlohmann::json &mod) const; + // The parsed json. nlohmann::json manifestJson; // Cache the module info for each symbol. - std::map symbolInfoCache; + std::map symbolInfoCache; }; //===----------------------------------------------------------------------===// @@ -134,49 +142,55 @@ static ServicePortDesc parseServicePort(const nlohmann::json &jsonPort) { /// Convert the json value to a 'std::any', which can be exposed outside of this /// file. -static std::any getAny(const nlohmann::json &value) { - auto getObject = [](const nlohmann::json &json) { +std::any Manifest::Impl::getAny(const nlohmann::json &value) const { + auto getObject = [this](const nlohmann::json &json) -> std::any { std::map ret; for (auto &e : json.items()) ret[e.key()] = getAny(e.value()); return ret; }; - auto getArray = [](const nlohmann::json &json) { + auto getArray = [this](const nlohmann::json &json) -> std::any { std::vector ret; for (auto &e : json) ret.push_back(getAny(e)); return ret; }; - if (value.is_string()) - return value.get(); - else if (value.is_number_integer()) - return value.get(); - else if (value.is_number_unsigned()) - return value.get(); - else if (value.is_number_float()) - return value.get(); - else if (value.is_boolean()) - return value.get(); - else if (value.is_null()) - return value.get(); - else if (value.is_object()) - return getObject(value); - else if (value.is_array()) - return getArray(value); - else - throw std::runtime_error("Unknown type in manifest: " + value.dump(2)); -} + auto getValue = [&](const nlohmann::json &innerValue) -> std::any { + if (innerValue.is_string()) + return innerValue.get(); + else if (innerValue.is_number_integer()) + return innerValue.get(); + else if (innerValue.is_number_unsigned()) + return innerValue.get(); + else if (innerValue.is_number_float()) + return innerValue.get(); + else if (innerValue.is_boolean()) + return innerValue.get(); + else if (innerValue.is_null()) + return innerValue.get(); + else if (innerValue.is_object()) + return getObject(innerValue); + else if (innerValue.is_array()) + return getArray(innerValue); + else + throw std::runtime_error("Unknown type in manifest: " + + innerValue.dump(2)); + }; -static ModuleInfo parseModuleInfo(const nlohmann::json &mod) { + if (!value.is_object() || !value.contains("type") || !value.contains("value")) + return getValue(value); + return Constant{getValue(value.at("value")), getType(value.at("type"))}; +} - std::map extras; +void Manifest::Impl::parseModuleMetadata(ModuleInfo &info, + const nlohmann::json &mod) const { for (auto &extra : mod.items()) if (extra.key() != "name" && extra.key() != "summary" && extra.key() != "version" && extra.key() != "repo" && - extra.key() != "commitHash" && extra.key() != "symbolRef") - extras[extra.key()] = getAny(extra.value()); + extra.key() != "commitHash") + info.extra[extra.key()] = getAny(extra.value()); auto value = [&](const std::string &key) -> std::optional { auto f = mod.find(key); @@ -184,8 +198,24 @@ static ModuleInfo parseModuleInfo(const nlohmann::json &mod) { return std::nullopt; return f.value(); }; - return ModuleInfo{value("name"), value("summary"), value("version"), - value("repo"), value("commitHash"), extras}; + info.name = value("name"); + info.summary = value("summary"); + info.version = value("version"); + info.repo = value("repo"); + info.commitHash = value("commitHash"); +} + +void Manifest::Impl::parseModuleConsts(ModuleInfo &info, + const nlohmann::json &mod) const { + for (auto &item : mod.items()) { + std::any value = getAny(item.value()); + auto *c = std::any_cast(&value); + if (c) + info.constants[item.key()] = *c; + else + // If the value isn't a "proper" constant, present it as one with no type. + info.constants[item.key()] = Constant{value, std::nullopt}; + } } //===----------------------------------------------------------------------===// @@ -196,10 +226,25 @@ Manifest::Impl::Impl(Context &ctxt, const std::string &manifestStr) : ctxt(ctxt) { manifestJson = nlohmann::ordered_json::parse(manifestStr); - for (auto &mod : manifestJson.at("symbols")) - symbolInfoCache.insert( - make_pair(mod.at("symbolRef"), parseModuleInfo(mod))); - populateTypes(manifestJson.at("types")); + try { + // Populate the types table first since anything else might need it. + populateTypes(manifestJson.at("types")); + + // Populate the symbol info cache. + for (auto &mod : manifestJson.at("symbols")) { + ModuleInfo info; + if (mod.contains("sym_info")) + parseModuleMetadata(info, mod.at("sym_info")); + if (mod.contains("sym_consts")) + parseModuleConsts(info, mod.at("sym_consts")); + symbolInfoCache.insert(make_pair(mod.at("symbol"), info)); + } + } catch (const std::exception &e) { + std::string msg = "malformed manifest: " + std::string(e.what()); + if (manifestJson.at("api_version") == 0) + msg += " (schema version 0 is not considered stable)"; + throw std::runtime_error(msg); + } } std::unique_ptr @@ -377,7 +422,7 @@ Manifest::Impl::getBundlePorts(AcceleratorConnection &acc, AppIDPath idPath, } services::Service *svc = svcIter->second; - std::string typeName = content.at("bundleType").at("circt_name"); + std::string typeName = content.at("bundleType"); auto type = getType(typeName); if (!type) throw std::runtime_error( @@ -425,12 +470,12 @@ BundleType *parseBundleType(const nlohmann::json &typeJson, Context &cache) { channels.emplace_back(chanJson.at("name"), dir, parseType(chanJson["type"], cache)); } - return new BundleType(typeJson.at("circt_name"), channels); + return new BundleType(typeJson.at("id"), channels); } ChannelType *parseChannelType(const nlohmann::json &typeJson, Context &cache) { assert(typeJson.at("mnemonic") == "channel"); - return new ChannelType(typeJson.at("circt_name"), + return new ChannelType(typeJson.at("id"), parseType(typeJson.at("inner"), cache)); } @@ -438,7 +483,7 @@ 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"); - Type::ID id = typeJson.at("circt_name"); + Type::ID id = typeJson.at("id"); if (sign == "signed") return new SIntType(id, width); @@ -459,13 +504,13 @@ StructType *parseStruct(const nlohmann::json &typeJson, Context &cache) { for (auto &fieldJson : typeJson["fields"]) fields.emplace_back(fieldJson.at("name"), parseType(fieldJson["type"], cache)); - return new StructType(typeJson.at("circt_name"), fields); + return new StructType(typeJson.at("id"), fields); } 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"), + return new ArrayType(typeJson.at("id"), parseType(typeJson.at("element"), cache), size); } @@ -473,10 +518,8 @@ using TypeParser = std::function; const std::map typeParsers = { {"bundle", parseBundleType}, {"channel", parseChannelType}, - {"std::any", - [](const nlohmann::json &typeJson, Context &cache) { - return new AnyType(typeJson.at("circt_name")); - }}, + {"std::any", [](const nlohmann::json &typeJson, + Context &cache) { return new AnyType(typeJson.at("id")); }}, {"int", parseInt}, {"struct", parseStruct}, {"array", parseArray}, @@ -485,19 +528,24 @@ const std::map typeParsers = { // Parse a type if it doesn't already exist in the cache. const Type *parseType(const nlohmann::json &typeJson, Context &cache) { - // We use the circt type string as a unique ID. - std::string circt_name = typeJson.at("circt_name"); - if (std::optional t = cache.getType(circt_name)) + std::string id; + if (typeJson.is_string()) + id = typeJson.get(); + else + id = typeJson.at("id"); + if (std::optional t = cache.getType(id)) return *t; + if (typeJson.is_string()) + throw std::runtime_error("malformed manifest: unknown type '" + id + "'"); - std::string mnemonic = typeJson.at("mnemonic"); Type *t; + std::string mnemonic = typeJson.at("mnemonic"); auto f = typeParsers.find(mnemonic); if (f != typeParsers.end()) t = f->second(typeJson, cache); else // Types we don't know about are opaque. - t = new Type(circt_name); + t = new Type(id); // Insert into the cache. cache.registerType(t); @@ -529,14 +577,20 @@ uint32_t Manifest::getApiVersion() const { std::vector Manifest::getModuleInfos() const { std::vector ret; - for (auto &mod : impl->at("symbols")) - ret.push_back(parseModuleInfo(mod)); + for (auto &[symbol, info] : impl->getSymbolInfo()) + ret.push_back(info); return ret; } -std::unique_ptr -Manifest::buildAccelerator(AcceleratorConnection &acc) const { - return impl->buildAccelerator(acc); +Accelerator *Manifest::buildAccelerator(AcceleratorConnection &acc) const { + try { + return acc.takeOwnership(impl->buildAccelerator(acc)); + } catch (const std::exception &e) { + std::string msg = "malformed manifest: " + std::string(e.what()); + if (getApiVersion() == 0) + msg += " (schema version 0 is not considered stable)"; + throw std::runtime_error(msg); + } } const std::vector &Manifest::getTypeTable() const { @@ -550,6 +604,9 @@ const std::vector &Manifest::getTypeTable() const { // Print a module info, including the extra metadata. std::ostream &operator<<(std::ostream &os, const ModuleInfo &m) { auto printAny = [&os](std::any a) { + if (auto *c = std::any_cast(&a)) + a = std::any_cast(a).value; + const std::type_info &t = a.type(); if (t == typeid(std::string)) os << std::any_cast(a); @@ -583,6 +640,15 @@ std::ostream &operator<<(std::ostream &os, const ModuleInfo &m) { os << ": " << *m.summary; os << "\n"; + if (!m.constants.empty()) { + os << " Constants:\n"; + for (auto &e : m.constants) { + os << " " << e.first << ": "; + printAny(e.second); + os << "\n"; + } + } + if (!m.extra.empty()) { os << " Extra metadata:\n"; for (auto &e : m.extra) { diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp index 29f858defa16..cd5fd6596811 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Ports.cpp @@ -47,7 +47,7 @@ void ReadChannelPort::connect(std::function callback, throw std::runtime_error("Channel already connected"); mode = Mode::Callback; this->callback = callback; - ChannelPort::connect(bufferSize); + connectImpl(bufferSize); } void ReadChannelPort::connect(std::optional bufferSize) { @@ -71,7 +71,7 @@ void ReadChannelPort::connect(std::optional bufferSize) { } return true; }; - ChannelPort::connect(bufferSize); + connectImpl(bufferSize); } std::future ReadChannelPort::readAsync() { diff --git a/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp b/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp index 854fcba4c7d7..fadd3ace8037 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/Services.cpp @@ -145,13 +145,27 @@ CallService::getPort(AppIDPath id, const BundleType *type, return new Callback(acc, id.back(), channels); } +ReadChannelPort &getRead(const std::map &channels, + const std::string &name) { + auto f = channels.find(name); + if (f == channels.end()) + throw std::runtime_error("CallService must have an '" + name + "' channel"); + return dynamic_cast(f->second); +} + +WriteChannelPort &getWrite(const std::map &channels, + const std::string &name) { + auto f = channels.find(name); + if (f == channels.end()) + throw std::runtime_error("CallService must have an '" + name + "' channel"); + return dynamic_cast(f->second); +} + CallService::Callback::Callback( AcceleratorConnection &acc, AppID id, const std::map &channels) - : ServicePort(id, channels), - arg(dynamic_cast(channels.at("arg"))), - result(dynamic_cast(channels.at("result"))), - acc(acc) { + : ServicePort(id, channels), arg(getRead(channels, "arg")), + result(getWrite(channels, "result")), acc(acc) { if (channels.size() != 2) throw std::runtime_error("CallService must have exactly two channels"); } diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp index 86a4685fd58b..42cc8bc25150 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp @@ -126,6 +126,7 @@ CosimAccelerator::CosimAccelerator(Context &ctxt, std::string hostname, rpcClient = new StubContainer(ChannelServer::NewStub(channel)); } CosimAccelerator::~CosimAccelerator() { + disconnect(); if (rpcClient) delete rpcClient; channels.clear(); @@ -418,23 +419,23 @@ class CosimHostMem : public HostMem { this->size = size; } virtual ~CosimHostMemRegion() { free(ptr); } - virtual void *getPtr() const { return ptr; } - virtual std::size_t getSize() const { return size; } + virtual void *getPtr() const override { return ptr; } + virtual std::size_t getSize() const override { return size; } private: void *ptr; std::size_t size; }; - virtual std::unique_ptr allocate(std::size_t size, - HostMem::Options opts) const { + virtual std::unique_ptr + allocate(std::size_t size, HostMem::Options opts) const override { return std::unique_ptr(new CosimHostMemRegion(size)); } virtual bool mapMemory(void *ptr, std::size_t size, - HostMem::Options opts) const { + HostMem::Options opts) const override { return true; } - virtual void unmapMemory(void *ptr) const {} + virtual void unmapMemory(void *ptr) const override {} }; } // namespace diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp index b93df860028d..f6e89826fee8 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Trace.cpp @@ -49,6 +49,8 @@ struct esi::backends::trace::TraceAccelerator::Impl { if (!traceWrite->is_open()) throw std::runtime_error("failed to open trace file '" + traceFile.string() + "'"); + } else if (mode == Discard) { + traceWrite = nullptr; } else { assert(false && "not implemented"); } @@ -76,9 +78,11 @@ struct esi::backends::trace::TraceAccelerator::Impl { void write(const AppIDPath &id, const std::string &portName, const void *data, size_t size); std::ostream &write(std::string service) { + assert(traceWrite && "traceWrite is null"); *traceWrite << "[" << service << "] "; return *traceWrite; } + bool isWriteable() { return traceWrite; } private: std::ofstream *traceWrite; @@ -90,6 +94,8 @@ struct esi::backends::trace::TraceAccelerator::Impl { void TraceAccelerator::Impl::write(const AppIDPath &id, const std::string &portName, const void *data, size_t size) { + if (!isWriteable()) + return; std::string b64data; utils::encodeBase64(data, size, b64data); @@ -105,7 +111,7 @@ TraceAccelerator::connect(Context &ctxt, std::string connectionString) { // Parse the connection std::string. // :[:] - std::regex connPattern("(\\w):([^:]+)(:(\\w+))?"); + std::regex connPattern("([\\w-]):([^:]+)(:(\\w+))?"); std::smatch match; if (regex_search(connectionString, match, connPattern)) { modeStr = match[1]; @@ -121,6 +127,8 @@ TraceAccelerator::connect(Context &ctxt, std::string connectionString) { Mode mode; if (modeStr == "w") mode = Write; + else if (modeStr == "-") + mode = Discard; else throw std::runtime_error("unknown mode '" + modeStr + "'"); @@ -135,6 +143,7 @@ TraceAccelerator::TraceAccelerator(Context &ctxt, Mode mode, : AcceleratorConnection(ctxt) { impl = std::make_unique(mode, manifestJson, traceFile); } +TraceAccelerator::~TraceAccelerator() { disconnect(); } Service *TraceAccelerator::createService(Service::Type svcType, AppIDPath idPath, std::string implName, @@ -197,22 +206,7 @@ class ReadTraceChannelPort : public ReadChannelPort { : ReadChannelPort(type) {} ~ReadTraceChannelPort() { disconnect(); } - void disconnect() override { - ReadChannelPort::disconnect(); - if (!dataPushThread.joinable()) - return; - shutdown = true; - shutdownCV.notify_all(); - dataPushThread.join(); - } - private: - void connectImpl(std::optional bufferSize) override { - assert(!dataPushThread.joinable() && "already connected"); - shutdown = false; - dataPushThread = std::thread(&ReadTraceChannelPort::dataPushLoop, this); - } - MessageData genMessage() { std::ptrdiff_t numBits = getType()->getBitWidth(); if (numBits < 0) @@ -227,19 +221,7 @@ class ReadTraceChannelPort : public ReadChannelPort { return MessageData(bytes); } - void dataPushLoop() { - std::mutex m; - std::unique_lock lock(m); - while (!shutdown) { - shutdownCV.wait_for(lock, std::chrono::milliseconds(100)); - while (this->callback(genMessage())) - shutdownCV.wait_for(lock, std::chrono::milliseconds(10)); - } - } - - std::thread dataPushThread; - std::condition_variable shutdownCV; - std::atomic shutdown; + bool pollImpl() override { return callback(genMessage()); } }; } // namespace @@ -286,11 +268,12 @@ class TraceHostMem : public HostMem { this->size = size; } virtual ~TraceHostMemRegion() { - impl.write("HostMem") << "free " << ptr << std::endl; + if (impl.isWriteable()) + impl.write("HostMem") << "free " << ptr << std::endl; free(ptr); } - virtual void *getPtr() const { return ptr; } - virtual std::size_t getSize() const { return size; } + virtual void *getPtr() const override { return ptr; } + virtual std::size_t getSize() const override { return size; } private: void *ptr; @@ -298,26 +281,30 @@ class TraceHostMem : public HostMem { TraceAccelerator::Impl &impl; }; - virtual std::unique_ptr allocate(std::size_t size, - HostMem::Options opts) const { + virtual std::unique_ptr + allocate(std::size_t size, HostMem::Options opts) const override { auto ret = std::unique_ptr(new TraceHostMemRegion(size, impl)); - impl.write("HostMem 0x") - << ret->getPtr() << " allocate " << size - << " bytes. Writeable: " << opts.writeable - << ", useLargePages: " << opts.useLargePages << std::endl; + if (impl.isWriteable()) + impl.write("HostMem 0x") + << ret->getPtr() << " allocate " << size + << " bytes. Writeable: " << opts.writeable + << ", useLargePages: " << opts.useLargePages << std::endl; return ret; } virtual bool mapMemory(void *ptr, std::size_t size, - HostMem::Options opts) const { - impl.write("HostMem") << "map 0x" << ptr << " size " << size - << " bytes. Writeable: " << opts.writeable - << ", useLargePages: " << opts.useLargePages - << std::endl; + HostMem::Options opts) const override { + + if (impl.isWriteable()) + impl.write("HostMem") + << "map 0x" << ptr << " size " << size + << " bytes. Writeable: " << opts.writeable + << ", useLargePages: " << opts.useLargePages << std::endl; return true; } - virtual void unmapMemory(void *ptr) const { - impl.write("HostMem") << "unmap 0x" << ptr << std::endl; + virtual void unmapMemory(void *ptr) const override { + if (impl.isWriteable()) + impl.write("HostMem") << "unmap 0x" << ptr << std::endl; } private: diff --git a/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp b/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp index 694f069852c5..26b7ada78384 100644 --- a/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp +++ b/lib/Dialect/ESI/runtime/cpp/lib/backends/Xrt.cpp @@ -78,6 +78,7 @@ XrtAccelerator::XrtAccelerator(Context &ctxt, std::string xclbin, : AcceleratorConnection(ctxt) { impl = make_unique(xclbin, device_id); } +XrtAccelerator::~XrtAccelerator() { disconnect(); } namespace { class XrtMMIO : public MMIO { diff --git a/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp b/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp index a093988cf1bd..450cabece943 100644 --- a/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp +++ b/lib/Dialect/ESI/runtime/cpp/tools/esiquery.cpp @@ -121,10 +121,10 @@ void printInstance(std::ostream &os, const HWModule *d, void printHier(std::ostream &os, AcceleratorConnection &acc) { Manifest manifest(acc.getCtxt(), acc.getService()->getJsonManifest()); - std::unique_ptr design = manifest.buildAccelerator(acc); + Accelerator *design = manifest.buildAccelerator(acc); os << "********************************" << std::endl; os << "* Design hierarchy" << std::endl; os << "********************************" << std::endl; os << std::endl; - printInstance(os, design.get()); + printInstance(os, design); } diff --git a/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp b/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp index fa34d4cad4be..d2563616d645 100644 --- a/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp +++ b/lib/Dialect/ESI/runtime/cpp/tools/esitester.cpp @@ -50,9 +50,10 @@ int main(int argc, const char *argv[]) { std::unique_ptr acc = ctxt.connect(backend, conn); const auto &info = *acc->getService(); Manifest manifest(ctxt, info.getJsonManifest()); - std::unique_ptr accel = manifest.buildAccelerator(*acc); + Accelerator *accel = manifest.buildAccelerator(*acc); + acc->getServiceThread()->addPoll(*accel); - registerCallbacks(accel.get()); + registerCallbacks(accel); if (cmd == "loop") { while (true) { diff --git a/lib/Dialect/ESI/runtime/pyproject.toml b/lib/Dialect/ESI/runtime/pyproject.toml index 4099d43a6d60..5ec0f81ae2aa 100644 --- a/lib/Dialect/ESI/runtime/pyproject.toml +++ b/lib/Dialect/ESI/runtime/pyproject.toml @@ -44,3 +44,4 @@ classifiers = [ [project.scripts] esiquery = "esiaccel.utils:run_esiquery" esi-cosim = "esiaccel.utils:run_esi_cosim" +esi-cppgen = "esiaccel.utils:run_cppgen" diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/codegen.py b/lib/Dialect/ESI/runtime/python/esiaccel/codegen.py new file mode 100644 index 000000000000..4fd1eed65c81 --- /dev/null +++ b/lib/Dialect/ESI/runtime/python/esiaccel/codegen.py @@ -0,0 +1,197 @@ +# 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 + +# Code generation from ESI manifests to source code. C++ header support included +# with the runtime, though it is intended to be extensible for other languages. + +from typing import List, TextIO, Type, Optional +from .accelerator import AcceleratorConnection +from .esiCppAccel import ModuleInfo +from . import types + +import argparse +from pathlib import Path +import textwrap +import sys + +_thisdir = Path(__file__).absolute().resolve().parent + + +class Generator: + """Base class for all generators.""" + + language: Optional[str] = None + + def __init__(self, conn: AcceleratorConnection): + self.manifest = conn.manifest() + + def generate(self, output_dir: Path, system_name: str): + raise NotImplementedError("Generator.generate() must be overridden") + + +class CppGenerator(Generator): + """Generate C++ headers from an ESI manifest.""" + + language = "C++" + + # Supported bit widths for lone integer types. + int_width_support = set([8, 16, 32, 64]) + + def get_type_str(self, type: types.ESIType) -> str: + """Get the textual code for the storage class of a type. + + Examples: uint32_t, int64_t, CustomStruct.""" + + if isinstance(type, (types.BitsType, types.IntType)): + if type.bit_width not in self.int_width_support: + raise ValueError(f"Unsupported integer width: {type.bit_width}") + if isinstance(type, (types.BitsType, types.UIntType)): + return f"uint{type.bit_width}_t" + return f"int{type.bit_width}_t" + raise NotImplementedError(f"Type '{type}' not supported for C++ generation") + + def get_consts_str(self, module_info: ModuleInfo) -> str: + """Get the C++ code for a constant in a module.""" + const_strs: List[str] = [ + f"static constexpr {self.get_type_str(const.type)} " + f"{name} = 0x{const.value:x};" + for name, const in module_info.constants.items() + ] + return "\n".join(const_strs) + + def write_modules(self, output_dir: Path, system_name: str): + """Write the C++ header. One for each module in the manifest.""" + + for module_info in self.manifest.module_infos: + s = f""" + /// Generated header for {system_name} module {module_info.name}. + #pragma once + #include "types.h" + + namespace {system_name} {{ + class {module_info.name} {{ + public: + {self.get_consts_str(module_info)} + }}; + }} // namespace {system_name} + """ + + hdr_file = output_dir / f"{module_info.name}.h" + with open(hdr_file, "w") as hdr: + hdr.write(textwrap.dedent(s)) + + def write_type(self, hdr: TextIO, type: types.ESIType): + if isinstance(type, (types.BitsType, types.IntType)): + # Bit vector types use standard C++ types. + return + raise NotImplementedError(f"Type '{type}' not supported for C++ generation") + + def write_types(self, output_dir: Path, system_name: str): + hdr_file = output_dir / "types.h" + with open(hdr_file, "w") as hdr: + hdr.write( + textwrap.dedent(f""" + // Generated header for {system_name} types. + #pragma once + + #include + + namespace {system_name} {{ + """)) + + for type in self.manifest.type_table: + try: + self.write_type(hdr, type) + except NotImplementedError: + sys.stderr.write( + f"Warning: type '{type}' not supported for C++ generation\n") + + hdr.write( + textwrap.dedent(f""" + }} // namespace {system_name} + """)) + + def generate(self, output_dir: Path, system_name: str): + self.write_types(output_dir, system_name) + self.write_modules(output_dir, system_name) + + +def run(generator: Type[Generator] = CppGenerator, + cmdline_args=sys.argv) -> int: + """Create and run a generator reading options from the command line.""" + + argparser = argparse.ArgumentParser( + description=f"Generate {generator.language} headers from an ESI manifest", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=textwrap.dedent(""" + Can read the manifest from either a file OR a running accelerator. + + Usage examples: + # To read the manifest from a file: + esi-cppgen --file /path/to/manifest.json + + # To read the manifest from a running accelerator: + esi-cppgen --platform cosim --connection localhost:1234 + """)) + + argparser.add_argument("--file", + type=str, + default=None, + help="Path to the manifest file.") + argparser.add_argument( + "--platform", + type=str, + help="Name of platform for live accelerator connection.") + argparser.add_argument( + "--connection", + type=str, + help="Connection string for live accelerator connection.") + argparser.add_argument( + "--output-dir", + type=str, + default="esi", + help="Output directory for generated files. Recommend adding either `esi`" + " or the system name to the end of the path so as to avoid header name" + "conflicts. Defaults to `esi`") + argparser.add_argument( + "--system-name", + type=str, + default="esi_system", + help="Name of the ESI system. For C++, this will be the namespace.") + + if (len(cmdline_args) <= 1): + argparser.print_help() + return 1 + args = argparser.parse_args(cmdline_args[1:]) + + if args.file is not None and args.platform is not None: + print("Cannot specify both --file and --platform") + return 1 + + conn: AcceleratorConnection + if args.file is not None: + conn = AcceleratorConnection("trace", f"-:{args.file}") + elif args.platform is not None: + if args.connection is None: + print("Must specify --connection with --platform") + return 1 + conn = AcceleratorConnection(args.platform, args.connection) + else: + print("Must specify either --file or --platform") + return 1 + + output_dir = Path(args.output_dir) + if output_dir.exists() and not output_dir.is_dir(): + print(f"Output directory {output_dir} is not a directory") + return 1 + if not output_dir.exists(): + output_dir.mkdir(parents=True) + + gen = generator(conn) + gen.generate(output_dir, args.system_name) + return 0 + + +if __name__ == '__main__': + sys.exit(run()) diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp index c73df6872ca5..8b7730ae8e87 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp +++ b/lib/Dialect/ESI/runtime/python/esiaccel/esiCppAccel.cpp @@ -42,8 +42,45 @@ struct polymorphic_type_hook { return port; } }; + +namespace detail { +/// Pybind11 doesn't have a built-in type caster for std::any +/// (https://github.com/pybind/pybind11/issues/1590). We must provide one which +/// knows about all of the potential types which the any might be. +template <> +struct type_caster { +public: + PYBIND11_TYPE_CASTER(std::any, const_name("object")); + + static handle cast(std::any src, return_value_policy /* policy */, + handle /* parent */) { + const std::type_info &t = src.type(); + if (t == typeid(std::string)) + return py::str(std::any_cast(src)); + else if (t == typeid(int64_t)) + return py::int_(std::any_cast(src)); + else if (t == typeid(uint64_t)) + return py::int_(std::any_cast(src)); + else if (t == typeid(double)) + return py::float_(std::any_cast(src)); + else if (t == typeid(bool)) + return py::bool_(std::any_cast(src)); + else if (t == typeid(std::nullptr_t)) + return py::none(); + return py::none(); + } +}; +} // namespace detail } // namespace pybind11 +/// Resolve a Type to the Python wrapper object. +py::object getPyType(std::optional t) { + py::object typesModule = py::module_::import("esiaccel.types"); + if (!t) + return py::none(); + return typesModule.attr("_get_esi_type")(*t); +} + // NOLINTNEXTLINE(readability-identifier-naming) PYBIND11_MODULE(esiCppAccel, m) { py::class_(m, "Type") @@ -75,6 +112,12 @@ PYBIND11_MODULE(esiCppAccel, m) { py::return_value_policy::reference) .def_property_readonly("size", &ArrayType::getSize); + py::class_(m, "Constant") + .def_property_readonly("value", [](Constant &c) { return c.value; }) + .def_property_readonly( + "type", [](Constant &c) { return getPyType(*c.type); }, + py::return_value_policy::reference); + py::class_(m, "ModuleInfo") .def_property_readonly("name", [](ModuleInfo &info) { return info.name; }) .def_property_readonly("summary", @@ -84,6 +127,8 @@ PYBIND11_MODULE(esiCppAccel, m) { .def_property_readonly("repo", [](ModuleInfo &info) { return info.repo; }) .def_property_readonly("commit_hash", [](ModuleInfo &info) { return info.commitHash; }) + .def_property_readonly("constants", + [](ModuleInfo &info) { return info.constants; }) // TODO: "extra" field. .def("__repr__", [](ModuleInfo &info) { std::string ret; @@ -161,13 +206,19 @@ PYBIND11_MODULE(esiCppAccel, m) { }) .def("__eq__", [](AppID &a, AppID &b) { return a == b; }) .def("__hash__", [](AppID &id) { - // TODO: This is a bad hash function. Replace it. - return std::hash{}(id.name) ^ - (std::hash{}(id.idx.value_or(-1)) << 1); + return utils::hash_combine(std::hash{}(id.name), + std::hash{}(id.idx.value_or(-1))); }); + // py::class_>(m, "MessageDataFuture"); py::class_>(m, "MessageDataFuture") - .def("valid", &std::future::valid) + .def("valid", + [](std::future &f) { + // For some reason, if we just pass the function pointer, pybind11 + // sees `std::__basic_future` as the type and pybind11_stubgen + // emits an error. + return f.valid(); + }) .def("wait", &std::future::wait) .def("get", [](std::future &f) { MessageData data = f.get(); @@ -270,8 +321,21 @@ PYBIND11_MODULE(esiCppAccel, m) { py::class_(m, "Manifest") .def(py::init()) .def_property_readonly("api_version", &Manifest::getApiVersion) - .def("build_accelerator", &Manifest::buildAccelerator, - py::return_value_policy::take_ownership) - .def_property_readonly("type_table", &Manifest::getTypeTable) + .def( + "build_accelerator", + [&](Manifest &m, AcceleratorConnection &conn) { + auto acc = m.buildAccelerator(conn); + conn.getServiceThread()->addPoll(*acc); + return acc; + }, + py::return_value_policy::reference) + .def_property_readonly("type_table", + [](Manifest &m) { + std::vector ret; + std::ranges::transform(m.getTypeTable(), + std::back_inserter(ret), + getPyType); + return ret; + }) .def_property_readonly("module_infos", &Manifest::getModuleInfos); } diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/types.py b/lib/Dialect/ESI/runtime/python/esiaccel/types.py index d6da5d2a0368..1b1041247522 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/types.py +++ b/lib/Dialect/ESI/runtime/python/esiaccel/types.py @@ -47,6 +47,7 @@ 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.""" + print(f"supports_host: {self.cpp_type} {type(self)}") if self.bit_width % 8 != 0: return (False, "runtime only supports types with multiple of 8 bits") return (True, None) @@ -296,11 +297,12 @@ 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) + + def connect(self, buffer_size: Optional[int] = None): (supports_host, reason) = self.type.supports_host if not supports_host: raise TypeError(f"unsupported type: {reason}") - def connect(self, buffer_size: Optional[int] = None): self.cpp_port.connect(buffer_size) return self diff --git a/lib/Dialect/ESI/runtime/python/esiaccel/utils.py b/lib/Dialect/ESI/runtime/python/esiaccel/utils.py index c0e9d831dd85..29d92b1d79cb 100644 --- a/lib/Dialect/ESI/runtime/python/esiaccel/utils.py +++ b/lib/Dialect/ESI/runtime/python/esiaccel/utils.py @@ -2,6 +2,8 @@ # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +from . import codegen + from pathlib import Path import subprocess import sys @@ -26,5 +28,9 @@ def run_esi_cosim(): return cosim_import.__main__(sys.argv) +def run_cppgen(): + return codegen.run() + + def get_cmake_dir(): return _thisdir / "cmake" diff --git a/lib/Dialect/FIRRTL/Export/FIREmitter.cpp b/lib/Dialect/FIRRTL/Export/FIREmitter.cpp index 76f7fc862288..203ed9df5d7d 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 emitDeclaration(FormalOp op); void emitEnabledLayers(ArrayRef layers); void emitParamAssign(ParamDeclAttr param, Operation *op, @@ -406,6 +407,7 @@ void Emitter::emitCircuit(CircuitOp op) { }) .Case([&](auto op) { emitDeclaration(op); }) .Case([&](auto op) { emitDeclaration(op); }) + .Case([&](auto op) { emitDeclaration(op); }) .Default([&](auto op) { emitOpError(op, "not supported for emission inside circuit"); }); @@ -623,6 +625,21 @@ void Emitter::emitDeclaration(OptionOp op) { ps << PP::newline << PP::newline; } +/// Emit a formal test definition. +void Emitter::emitDeclaration(FormalOp op) { + startStatement(); + ps << "formal " << PPExtString(op.getSymName()) << " of " + << PPExtString(op.getModuleName()) << ", bound = "; + ps.addAsString(op.getBound()); + + if (auto outputFile = op->getAttrOfType("output_file")) { + ps << ", "; + ps.writeQuotedEscaped(outputFile.getFilename().getValue()); + } + + emitLocationAndNewLine(op); +} + /// Check if an operation is inlined into the emission of their users. For /// example, subfields are always inlined. static bool isEmittedInline(Operation *op) { diff --git a/lib/Dialect/FIRRTL/FIRRTLFolds.cpp b/lib/Dialect/FIRRTL/FIRRTLFolds.cpp index 17d013ba4f9f..1ef0ad8857d1 100644 --- a/lib/Dialect/FIRRTL/FIRRTLFolds.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLFolds.cpp @@ -1656,6 +1656,17 @@ LogicalResult MultibitMuxOp::canonicalize(MultibitMuxOp op, return success(); } + // If the index width is narrower than the size of inputs, drop front + // elements. + auto indexWidth = op.getIndex().getType().getBitWidthOrSentinel(); + uint64_t inputSize = op.getInputs().size(); + if (indexWidth >= 0 && indexWidth < 64 && 1ull << indexWidth < inputSize) { + rewriter.modifyOpInPlace(op, [&]() { + op.getInputsMutable().erase(0, inputSize - (1ull << indexWidth)); + }); + return success(); + } + // If the op is a vector indexing (e.g. `multbit_mux idx, a[n-1], a[n-2], ..., // a[0]`), we can fold the op into subaccess op `a[idx]`. if (auto lastSubindex = op.getInputs().back().getDefiningOp()) { diff --git a/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp b/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp index 7403c72b81b0..2e3c72b394e3 100644 --- a/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLInstanceImplementation.cpp @@ -16,7 +16,6 @@ LogicalResult instance_like_impl::verifyReferencedModule(Operation *instanceOp, SymbolTableCollection &symbolTable, mlir::FlatSymbolRefAttr moduleName) { - auto module = instanceOp->getParentOfType(); auto referencedModule = symbolTable.lookupNearestSymbolFrom(instanceOp, moduleName); if (!referencedModule) { @@ -29,15 +28,6 @@ instance_like_impl::verifyReferencedModule(Operation *instanceOp, .attachNote(referencedModule.getLoc()) << "class declared here"; - // Check that this instance doesn't recursively instantiate its wrapping - // module. - if (referencedModule == module) { - auto diag = instanceOp->emitOpError() - << "is a recursive instantiation of its containing module"; - return diag.attachNote(module.getLoc()) - << "containing module declared here"; - } - // Small helper add a note to the original declaration. auto emitNote = [&](InFlightDiagnostic &&diag) -> InFlightDiagnostic && { diag.attachNote(referencedModule->getLoc()) diff --git a/lib/Dialect/FIRRTL/FIRRTLOps.cpp b/lib/Dialect/FIRRTL/FIRRTLOps.cpp index 2304c4700512..8462bd9b102b 100644 --- a/lib/Dialect/FIRRTL/FIRRTLOps.cpp +++ b/lib/Dialect/FIRRTL/FIRRTLOps.cpp @@ -18,9 +18,11 @@ #include "circt/Dialect/FIRRTL/FIRRTLTypes.h" #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/FIRRTLVisitors.h" +#include "circt/Dialect/FIRRTL/FieldRefCache.h" #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWTypes.h" #include "circt/Support/CustomDirectiveImpl.h" +#include "circt/Support/Utils.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Diagnostics.h" #include "mlir/IR/DialectImplementation.h" @@ -519,8 +521,16 @@ LogicalResult CircuitOp::verifyRegions() { mlir::SymbolTable symtbl(getOperation()); - if (!symtbl.lookup(main)) - return emitOpError().append("Module with same name as circuit not found"); + auto *mainModule = symtbl.lookup(main); + if (!mainModule) + return emitOpError().append( + "does not contain module with same name as circuit"); + if (!isa(mainModule)) + return mainModule->emitError( + "entity with name of circuit must be a module"); + if (symtbl.getSymbolVisibility(mainModule) != + mlir::SymbolTable::Visibility::Public) + return mainModule->emitError("main module must be public"); // Store a mapping of defname to either the first external module // that defines it or, preferentially, the first external module @@ -3352,6 +3362,25 @@ void RegResetOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { return forceableAsmResultNames(*this, getName(), setNameFn); } +//===----------------------------------------------------------------------===// +// FormalOp +//===----------------------------------------------------------------------===// + +LogicalResult +FormalOp::verifySymbolUses(::mlir::SymbolTableCollection &symbolTable) { + // The referenced symbol is restricted to FModuleOps + auto referencedModule = symbolTable.lookupNearestSymbolFrom( + *this, getModuleNameAttr()); + if (!referencedModule) + return (*this)->emitOpError("invalid symbol reference"); + + return success(); +} + +//===----------------------------------------------------------------------===// +// WireOp +//===----------------------------------------------------------------------===// + void WireOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { return forceableAsmResultNames(*this, getName(), setNameFn); } @@ -3374,6 +3403,10 @@ LogicalResult WireOp::verifySymbolUses(SymbolTableCollection &symbolTable) { symbolTable, Twine("'") + getOperationName() + "' op is"); } +//===----------------------------------------------------------------------===// +// ObjectOp +//===----------------------------------------------------------------------===// + void ObjectOp::build(OpBuilder &builder, OperationState &state, ClassLike klass, StringRef name) { build(builder, state, klass.getInstanceType(), @@ -6193,10 +6226,30 @@ LogicalResult RWProbeOp::verifyInnerRefs(hw::InnerRefNamespace &ns) { } return success(); }; + + auto checkLayers = [&](Location loc) -> LogicalResult { + auto dstLayers = getAmbientLayersAt(target.getOp()); + auto srcLayers = getLayersFor(getResult()); + SmallVector missingLayers; + if (!isLayerSetCompatibleWith(srcLayers, dstLayers, missingLayers)) { + auto diag = emitOpError("target has insufficient layer requirements"); + auto ¬e = diag.attachNote(loc); + note << "target is missing layer requirements: "; + llvm::interleaveComma(missingLayers, note); + return failure(); + } + return success(); + }; + auto checks = [&](auto type, Location loc) { + if (failed(checkLayers(loc))) + return failure(); + return checkFinalType(type, loc); + }; + if (target.isPort()) { auto mod = cast(target.getOp()); - return checkFinalType(mod.getPortType(target.getPort()), - mod.getPortLocation(target.getPort())); + return checks(mod.getPortType(target.getPort()), + mod.getPortLocation(target.getPort())); } hw::InnerSymbolOpInterface symOp = cast(target.getOp()); @@ -6210,7 +6263,7 @@ LogicalResult RWProbeOp::verifyInnerRefs(hw::InnerRefNamespace &ns) { return emitOpError("is not dominated by target") .attachNote(symOp.getLoc()) .append("target here"); - return checkFinalType(symOp.getTargetResult().getType(), symOp.getLoc()); + return checks(symOp.getTargetResult().getType(), symOp.getLoc()); } //===----------------------------------------------------------------------===// @@ -6256,6 +6309,7 @@ LogicalResult LayerBlockOp::verify() { } // Verify the body of the region. + FieldRefCache fieldRefCache; auto result = getBody(0)->walk( [&](Operation *op) -> WalkResult { // Skip nested layer blocks. Those will be verified separately. @@ -6279,17 +6333,6 @@ LogicalResult LayerBlockOp::verify() { 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. @@ -6298,11 +6341,27 @@ LogicalResult LayerBlockOp::verify() { 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(); + // Verify that connects only drive values declared in the layer block. + // If we see a non-passive connect destination, then verify that the + // source is in the same layer block so that the source is not driven. + auto dest = + fieldRefCache.getFieldRefFromValue(connect.getDest()).getValue(); + bool passive = true; + if (auto type = + type_dyn_cast(connect.getDest().getType())) + passive = type.isPassive(); + // TODO: Improve this verifier. This is intentionally _not_ verifying + // a non-passive ConnectLike because it is hugely annoying to do + // so---it requires a full understanding of if the connect is driving + // destination-to-source, source-to-destination, or bi-directionally + // which requires deep inspection of the type. Eventually, the FIRRTL + // pass pipeline will remove all flips (e.g., canonicalize connect to + // matchingconnect) and this hole won't exist. + if (!passive) + return WalkResult::advance(); + + if (isAncestorOfValueOwner(getOperation(), dest)) + return WalkResult::advance(); auto diag = connect.emitOpError() diff --git a/lib/Dialect/FIRRTL/Import/FIRParser.cpp b/lib/Dialect/FIRRTL/Import/FIRParser.cpp index 1a38326136a1..69dc43049589 100644 --- a/lib/Dialect/FIRRTL/Import/FIRParser.cpp +++ b/lib/Dialect/FIRRTL/Import/FIRParser.cpp @@ -1301,8 +1301,9 @@ struct FIRModuleContext : public FIRParser { llvm::DenseMap, Value> constantCache; /// Get a cached constant. - Value getCachedConstantInt(ImplicitLocOpBuilder &builder, Attribute attr, - IntType type, APInt &value) { + template + Value getCachedConstant(ImplicitLocOpBuilder &builder, Attribute attr, + Type type, Args &&...args) { auto &result = constantCache[{attr, type}]; if (result) return result; @@ -1312,15 +1313,15 @@ struct FIRModuleContext : public FIRParser { OpBuilder::InsertPoint savedIP; auto *parentOp = builder.getInsertionBlock()->getParentOp(); - if (!isa(parentOp)) { + if (!isa(parentOp)) { savedIP = builder.saveInsertionPoint(); - while (!isa(parentOp)) { + while (!isa(parentOp)) { builder.setInsertionPoint(parentOp); parentOp = builder.getInsertionBlock()->getParentOp(); } } - result = builder.create(type, value); + result = builder.create(type, std::forward(args)...); if (savedIP.isSet()) builder.setInsertionPoint(savedIP.getBlock(), savedIP.getPoint()); @@ -1720,6 +1721,7 @@ struct FIRStmtParser : public FIRParser { ParseResult parsePrimExp(Value &result); ParseResult parseIntegerLiteralExp(Value &result); ParseResult parseListExp(Value &result); + ParseResult parseListConcatExp(Value &result); std::optional parseExpWithLeadingKeyword(FIRToken keyword); @@ -1912,8 +1914,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, "expected string literal in String expression") || parseToken(FIRToken::r_paren, "expected ')' in String expression")) return failure(); - result = builder.create( - builder.getStringAttr(FIRToken::getStringValue(spelling))); + auto attr = builder.getStringAttr(FIRToken::getStringValue(spelling)); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), attr); break; } case FIRToken::kw_Integer: { @@ -1926,8 +1929,10 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, parseIntLit(value, "expected integer literal in Integer expression") || parseToken(FIRToken::r_paren, "expected ')' in Integer expression")) return failure(); - result = - builder.create(APSInt(value, /*isUnsigned=*/false)); + APSInt apint(value, /*isUnsigned=*/false); + result = moduleContext.getCachedConstant( + builder, IntegerAttr::get(getContext(), apint), + builder.getType(), apint); break; } case FIRToken::kw_Bool: { @@ -1946,7 +1951,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, return emitError("expected true or false in Bool expression"); if (parseToken(FIRToken::r_paren, "expected ')' in Bool expression")) return failure(); - result = builder.create(value); + auto attr = builder.getBoolAttr(value); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), value); break; } case FIRToken::kw_Double: { @@ -1966,7 +1973,9 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, double d; if (!llvm::to_float(spelling, d)) return emitError("invalid double"); - result = builder.create(builder.getF64FloatAttr(d)); + auto attr = builder.getF64FloatAttr(d); + result = moduleContext.getCachedConstant( + builder, attr, builder.getType(), attr); break; } case FIRToken::kw_List: { @@ -1978,6 +1987,16 @@ ParseResult FIRStmtParser::parseExpImpl(Value &result, const Twine &message, return failure(); break; } + + case FIRToken::lp_list_concat: { + if (isLeadingStmt) + return emitError("unexpected list_create() as start of statement"); + if (requireFeature(nextFIRVersion, "List concat") || + parseListConcatExp(result)) + return failure(); + break; + } + case FIRToken::lp_path: if (isLeadingStmt) return emitError("unexpected path() as start of statement"); @@ -2413,7 +2432,7 @@ ParseResult FIRStmtParser::parseIntegerLiteralExp(Value &result) { } locationProcessor.setLoc(loc); - result = moduleContext.getCachedConstantInt(builder, attr, type, value); + result = moduleContext.getCachedConstant(builder, attr, type, attr); return success(); } @@ -2455,6 +2474,44 @@ ParseResult FIRStmtParser::parseListExp(Value &result) { return success(); } +/// list-concat-exp ::= 'list_concat' '(' exp* ')' +ParseResult FIRStmtParser::parseListConcatExp(Value &result) { + consumeToken(FIRToken::lp_list_concat); + + auto loc = getToken().getLoc(); + ListType type; + SmallVector operands; + if (parseListUntil(FIRToken::r_paren, [&]() -> ParseResult { + Value operand; + locationProcessor.setLoc(loc); + if (parseExp(operand, "expected expression in List concat expression")) + return failure(); + + if (!type_isa(operand.getType())) + return emitError(loc, "unexpected expression of type ") + << operand.getType() << " in List concat expression"; + + if (!type) + type = type_cast(operand.getType()); + + if (operand.getType() != type) + return emitError(loc, "unexpected expression of type ") + << operand.getType() << " in List concat expression of type " + << type; + + operands.push_back(operand); + return success(); + })) + return failure(); + + if (operands.empty()) + return emitError(loc, "need at least one List to concatenate"); + + locationProcessor.setLoc(loc); + result = builder.create(type, operands); + return success(); +} + /// The .fir grammar has the annoying property where: /// 1) some statements start with keywords /// 2) some start with an expression @@ -3700,7 +3757,7 @@ ParseResult FIRStmtParser::parseRefForceInitial() { value.getBitWidth(), IntegerType::Unsigned), value); - auto pred = moduleContext.getCachedConstantInt(builder, attr, type, value); + auto pred = moduleContext.getCachedConstant(builder, attr, type, attr); builder.create(pred, dest, src); return success(); @@ -3763,7 +3820,7 @@ ParseResult FIRStmtParser::parseRefReleaseInitial() { value.getBitWidth(), IntegerType::Unsigned), value); - auto pred = moduleContext.getCachedConstantInt(builder, attr, type, value); + auto pred = moduleContext.getCachedConstant(builder, attr, type, attr); builder.create(pred, dest); return success(); @@ -4585,6 +4642,7 @@ struct FIRCircuitParser : public FIRParser { ParseResult parseExtModule(CircuitOp circuit, unsigned indent); ParseResult parseIntModule(CircuitOp circuit, unsigned indent); ParseResult parseModule(CircuitOp circuit, bool isPublic, unsigned indent); + ParseResult parseFormal(CircuitOp circuit, unsigned indent); ParseResult parseLayerName(SymbolRefAttr &result); ParseResult parseOptionalEnabledLayers(ArrayAttr &result); @@ -4890,6 +4948,7 @@ ParseResult FIRCircuitParser::skipToModuleEnd(unsigned indent) { case FIRToken::kw_extclass: case FIRToken::kw_extmodule: case FIRToken::kw_intmodule: + case FIRToken::kw_formal: case FIRToken::kw_module: case FIRToken::kw_public: case FIRToken::kw_layer: @@ -5153,6 +5212,39 @@ ParseResult FIRCircuitParser::parseModule(CircuitOp circuit, bool isPublic, return success(); } +ParseResult FIRCircuitParser::parseFormal(CircuitOp circuit, unsigned indent) { + consumeToken(FIRToken::kw_formal); + StringRef id, moduleName, boundSpelling; + int64_t bound = -1; + LocWithInfo info(getToken().getLoc(), this); + + // Parse the formal operation + if (parseId(id, "expected a formal test name") || + parseToken(FIRToken::kw_of, + "expected keyword 'of' after formal test name") || + parseId(moduleName, "expected the name of a module") || + parseToken(FIRToken::comma, "expected ','") || + parseGetSpelling(boundSpelling) || + parseToken(FIRToken::identifier, + "expected parameter 'bound' after ','") || + parseToken(FIRToken::equal, "expected '=' after 'bound'") || + parseIntLit(bound, "expected integer in bound specification") || + info.parseOptionalInfo()) + return failure(); + + // Check that the parameter is valid + if (boundSpelling != "bound" || bound <= 0) + return emitError("Invalid parameter given to formal test: ") + << boundSpelling << " = " << bound, + failure(); + + // Build out the firrtl mlir op + auto builder = circuit.getBodyBuilder(); + builder.create(info.getLoc(), id, moduleName, bound); + + return success(); +} + ParseResult FIRCircuitParser::parseToplevelDefinition(CircuitOp circuit, unsigned indent) { switch (getToken().getKind()) { @@ -5167,6 +5259,10 @@ ParseResult FIRCircuitParser::parseToplevelDefinition(CircuitOp circuit, return parseExtClass(circuit, indent); case FIRToken::kw_extmodule: return parseExtModule(circuit, indent); + case FIRToken::kw_formal: + if (requireFeature({4, 0, 0}, "inline formal tests")) + return failure(); + return parseFormal(circuit, indent); case FIRToken::kw_intmodule: if (removedFeature({4, 0, 0}, "intrinsic modules")) return failure(); @@ -5514,6 +5610,7 @@ ParseResult FIRCircuitParser::parseCircuit( case FIRToken::kw_extmodule: case FIRToken::kw_intmodule: case FIRToken::kw_layer: + case FIRToken::kw_formal: case FIRToken::kw_module: case FIRToken::kw_option: case FIRToken::kw_public: @@ -5582,11 +5679,11 @@ ParseResult FIRCircuitParser::parseCircuit( // a SymbolRefAttr. auto parseLayerName = [&](StringRef name) { // Parse the layer name into a SymbolRefAttr. - auto [head, rest] = name.split("::"); + auto [head, rest] = name.split("."); SmallVector nestedRefs; while (!rest.empty()) { StringRef next; - std::tie(next, rest) = rest.split("::"); + std::tie(next, rest) = rest.split("."); nestedRefs.push_back(FlatSymbolRefAttr::get(getContext(), next)); } return SymbolRefAttr::get(getContext(), head, nestedRefs); diff --git a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def index 895779127065..7d25c9191735 100644 --- a/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def +++ b/lib/Dialect/FIRRTL/Import/FIRTokenKinds.def @@ -117,6 +117,7 @@ TOK_KEYWORD(extclass) TOK_KEYWORD(extmodule) TOK_KEYWORD(false) TOK_KEYWORD(flip) +TOK_KEYWORD(formal) TOK_KEYWORD(group) TOK_KEYWORD(infer) TOK_KEYWORD(input) @@ -151,6 +152,7 @@ TOK_KEYWORD(regreset) TOK_KEYWORD(reset) TOK_KEYWORD(skip) TOK_KEYWORD(smem) +TOK_KEYWORD(symbolic) TOK_KEYWORD(true) TOK_KEYWORD(type) TOK_KEYWORD(undefined) @@ -169,6 +171,7 @@ TOK_LPKEYWORD(assume) TOK_LPKEYWORD(cover) TOK_LPKEYWORD(path) +TOK_LPKEYWORD(list_concat) TOK_LPKEYWORD(force) TOK_LPKEYWORD(force_initial) diff --git a/lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp b/lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp index 59ff53164629..72a30fc84bde 100644 --- a/lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp +++ b/lib/Dialect/FIRRTL/Transforms/AssignOutputDirs.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/Passes.h" #include "circt/Dialect/HW/HWAttributes.h" +#include "circt/Support/Debug.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/PostOrderIterator.h" #include "llvm/ADT/SmallPtrSet.h" @@ -98,6 +99,7 @@ struct AssignOutputDirsPass } // namespace void AssignOutputDirsPass::runOnOperation() { + LLVM_DEBUG(debugPassHeader(this) << "\n"); SmallString<64> outputDir(outputDirOption); if (fs::make_absolute(outputDir)) { emitError(mlir::UnknownLoc::get(&getContext()), @@ -112,6 +114,7 @@ void AssignOutputDirsPass::runOnOperation() { bool changed = false; + LLVM_DEBUG(llvm::dbgs() << "Updating modules:\n"); DenseSet visited; for (auto *root : getAnalysis()) { for (auto *node : llvm::inverse_post_order_ext(root, visited)) { @@ -149,12 +152,17 @@ void AssignOutputDirsPass::runOnOperation() { hw::OutputFileAttr::getAsDirectory(&getContext(), moduleOutputDir); module->setAttr("output_file", f); changed = true; + LLVM_DEBUG({ + llvm::dbgs() << " - name: " << module.getName() << "\n" + << " directory: " << f.getFilename() << "\n"; + }); } } } if (!changed) markAllAnalysesPreserved(); + LLVM_DEBUG(debugFooter() << "\n"); } std::unique_ptr diff --git a/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp b/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp index a64deec20c8a..3f9419265ced 100644 --- a/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp +++ b/lib/Dialect/FIRRTL/Transforms/BlackBoxReader.cpp @@ -23,6 +23,7 @@ #include "circt/Dialect/HW/HWAttributes.h" #include "circt/Dialect/HW/HWDialect.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Support/Debug.h" #include "circt/Support/Path.h" #include "mlir/IR/Attributes.h" #include "mlir/Pass/Pass.h" @@ -156,6 +157,7 @@ struct BlackBoxReaderPass /// Emit the annotated source code for black boxes in a circuit. void BlackBoxReaderPass::runOnOperation() { + LLVM_DEBUG(debugPassHeader(this) << "\n"); CircuitOp circuitOp = getOperation(); CircuitNamespace ns(circuitOp); @@ -326,6 +328,7 @@ void BlackBoxReaderPass::runOnOperation() { // Clean up. emittedFileMap.clear(); fileListFiles.clear(); + LLVM_DEBUG(debugFooter() << "\n"); } /// Run on an operation-annotation pair. The annotation need not be a black box diff --git a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt index c885ea49e910..e86980a540a3 100755 --- a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt +++ b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt @@ -3,6 +3,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms AddSeqMemPorts.cpp BlackBoxReader.cpp CheckCombLoops.cpp + CheckRecursiveInstantiation.cpp CreateCompanionAssume.cpp CreateSiFiveMetadata.cpp Dedup.cpp diff --git a/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp b/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp new file mode 100644 index 000000000000..52329f8236db --- /dev/null +++ b/lib/Dialect/FIRRTL/Transforms/CheckRecursiveInstantiation.cpp @@ -0,0 +1,66 @@ +//===- CheckRecursiveInstantiation.cpp - Check recurisve instantiation ----===// +// +// 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/FIRRTL/FIRRTLInstanceGraph.h" +#include "circt/Dialect/FIRRTL/Passes.h" +#include "mlir/Pass/Pass.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SCCIterator.h" + +namespace circt { +namespace firrtl { +#define GEN_PASS_DEF_CHECKRECURSIVEINSTANTIATION +#include "circt/Dialect/FIRRTL/Passes.h.inc" +} // namespace firrtl +} // namespace circt + +using namespace circt; +using namespace firrtl; + +static void printPath(InstanceGraph &instanceGraph, + ArrayRef nodes) { + assert(nodes.size() > 0 && "an scc should have at least one node"); + auto diag = + emitError(nodes.front()->getModule().getLoc(), "recursive instantiation"); + llvm::SmallPtrSet scc(nodes.begin(), nodes.end()); + for (auto *node : nodes) { + for (auto *record : *node) { + auto *target = record->getTarget(); + if (!scc.contains(target)) + continue; + auto ¬e = diag.attachNote(record->getInstance().getLoc()); + note << record->getParent()->getModule().getModuleName(); + note << " instantiates " + << record->getTarget()->getModule().getModuleName() << " here"; + } + } +} + +namespace { +class CheckRecursiveInstantiationPass + : public impl::CheckRecursiveInstantiationBase< + CheckRecursiveInstantiationPass> { +public: + void runOnOperation() override { + auto &instanceGraph = getAnalysis(); + for (auto it = llvm::scc_begin(&instanceGraph), + end = llvm::scc_end(&instanceGraph); + it != end; ++it) { + if (it.hasCycle()) { + printPath(instanceGraph, *it); + signalPassFailure(); + } + } + markAllAnalysesPreserved(); + } +}; +} // namespace + +std::unique_ptr circt::firrtl::createCheckRecursiveInstantiation() { + return std::make_unique(); +} diff --git a/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp b/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp index 30aaee22ee90..d84150e9798f 100644 --- a/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp +++ b/lib/Dialect/FIRRTL/Transforms/CreateSiFiveMetadata.cpp @@ -46,7 +46,7 @@ struct ObjectModelIR { ObjectModelIR( CircuitOp circtOp, FModuleOp dutMod, InstanceGraph &instanceGraph, DenseMap &moduleNamespaces) - : circtOp(circtOp), dutMod(dutMod), + : context(circtOp->getContext()), circtOp(circtOp), dutMod(dutMod), circtNamespace(CircuitNamespace(circtOp)), instancePathCache(InstancePathCache(instanceGraph)), moduleNamespaces(moduleNamespaces) {} @@ -54,20 +54,22 @@ struct ObjectModelIR { // Add the tracker annotation to the op and get a PathOp to the op. PathOp createPathRef(Operation *op, hw::HierPathOp nla, mlir::ImplicitLocOpBuilder &builderOM) { - auto *context = op->getContext(); - NamedAttrList fields; auto id = DistinctAttr::create(UnitAttr::get(context)); - fields.append("id", id); - fields.append("class", StringAttr::get(context, "circt.tracker")); - if (nla) - fields.append("circt.nonlocal", mlir::FlatSymbolRefAttr::get(nla)); - AnnotationSet annos(op); - annos.addAnnotations(DictionaryAttr::get(context, fields)); - annos.applyToOperation(op); TargetKind kind = TargetKind::Reference; - if (isa(op)) - kind = TargetKind::Instance; + // If op is null, then create an empty path. + if (op) { + NamedAttrList fields; + fields.append("id", id); + fields.append("class", StringAttr::get(context, "circt.tracker")); + if (nla) + fields.append("circt.nonlocal", mlir::FlatSymbolRefAttr::get(nla)); + AnnotationSet annos(op); + annos.addAnnotations(DictionaryAttr::get(context, fields)); + annos.applyToOperation(op); + if (isa(op)) + kind = TargetKind::Instance; + } // Create the path operation. return builderOM.create(kind, id); @@ -101,7 +103,6 @@ struct ObjectModelIR { } void createMemorySchema() { - auto *context = circtOp.getContext(); auto unknownLoc = mlir::UnknownLoc::get(context); auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd( @@ -121,7 +122,7 @@ struct ObjectModelIR { buildSimpleClassOp(builderOM, unknownLoc, "ExtraPortsMemorySchema", extraPortFields, extraPortsType); - mlir::Type classFieldTypes[12] = { + mlir::Type classFieldTypes[13] = { StringType::get(context), FIntegerType::get(context), FIntegerType::get(context), @@ -133,9 +134,11 @@ struct ObjectModelIR { FIntegerType::get(context), ListType::get(context, cast(PathType::get(context))), BoolType::get(context), - ListType::get(context, - cast(detail::getInstanceTypeForClassLike( - extraPortsClass)))}; + ListType::get( + context, cast( + detail::getInstanceTypeForClassLike(extraPortsClass))), + ListType::get(context, cast(StringType::get(context))), + }; memorySchemaClass = buildSimpleClassOp(builderOM, unknownLoc, "MemorySchema", @@ -149,7 +152,6 @@ struct ObjectModelIR { } void createRetimeModulesSchema() { - auto *context = circtOp.getContext(); auto unknownLoc = mlir::UnknownLoc::get(context); auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd( unknownLoc, circtOp.getBodyBlock()); @@ -189,7 +191,6 @@ struct ObjectModelIR { } void addBlackBoxModulesSchema() { - auto *context = circtOp.getContext(); auto unknownLoc = mlir::UnknownLoc::get(context); auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd( unknownLoc, circtOp.getBodyBlock()); @@ -232,7 +233,6 @@ struct ObjectModelIR { createMemorySchema(); auto builderOM = mlir::ImplicitLocOpBuilder::atBlockEnd( mem.getLoc(), memoryMetadataClass.getBodyBlock()); - auto *context = builderOM.getContext(); auto createConstField = [&](Attribute constVal) -> Value { if (auto boolConstant = dyn_cast_or_null(constVal)) return builderOM.create(boolConstant); @@ -246,32 +246,52 @@ struct ObjectModelIR { auto memPaths = instancePathCache.getAbsolutePaths(mem); SmallVector memoryHierPaths; - for (auto memPath : memPaths) { - Operation *finalInst = memPath.leaf(); - SmallVector namepath; - bool foundDut = dutMod == nullptr; - for (auto inst : memPath) { - if (!foundDut) - if (inst->getParentOfType() == dutMod) - foundDut = true; - if (!foundDut) - continue; + SmallVector finalInstanceNames; + // Memory hierarchy is relevant only for memories under DUT. + if (inDut) { + for (auto memPath : memPaths) { + { + igraph::InstanceOpInterface finalInst = memPath.leaf(); + finalInstanceNames.emplace_back(builderOM.create( + finalInst.getInstanceNameAttr())); + } + SmallVector namepath; + bool foundDut = dutMod == nullptr; + // The hierpath will be created to the pre-extracted + // instance, thus drop the leaf instance of the path, which can be + // extracted in subsequent passes. + igraph::InstanceOpInterface preExtractedLeafInstance; + for (auto inst : llvm::drop_end(memPath)) { + if (!foundDut) + if (inst->getParentOfType() == dutMod) + foundDut = true; + if (!foundDut) + continue; - namepath.emplace_back(firrtl::getInnerRefTo( - inst, [&](auto mod) -> hw::InnerSymbolNamespace & { - return getModuleNamespace(mod); - })); - } - if (namepath.empty()) - continue; - auto nla = nlaBuilder.create( - mem->getLoc(), - nlaBuilder.getStringAttr(circtNamespace.newName("memNLA")), - nlaBuilder.getArrayAttr(namepath)); + namepath.emplace_back(firrtl::getInnerRefTo( + inst, [&](auto mod) -> hw::InnerSymbolNamespace & { + return getModuleNamespace(mod); + })); + preExtractedLeafInstance = inst; + } + PathOp pathRef; + if (!namepath.empty()) { + auto nla = nlaBuilder.create( + mem->getLoc(), + nlaBuilder.getStringAttr(circtNamespace.newName("memNLA")), + nlaBuilder.getArrayAttr(namepath)); + pathRef = createPathRef(preExtractedLeafInstance, nla, builderOM); + } else { + pathRef = createPathRef({}, {}, builderOM); + } - // Create the path operation. - memoryHierPaths.emplace_back(createPathRef(finalInst, nla, builderOM)); + // Create the path operation. + memoryHierPaths.push_back(pathRef); + } } + auto finalInstNamesList = builderOM.create( + ListType::get(context, cast(StringType::get(context))), + finalInstanceNames); auto hierpaths = builderOM.create( ListType::get(context, cast(PathType::get(context))), memoryHierPaths); @@ -313,10 +333,13 @@ struct ObjectModelIR { .Case("writeLatency", mem.getWriteLatencyAttr()) .Case("hierarchy", {}) .Case("inDut", BoolAttr::get(context, inDut)) - .Case("extraPorts", {})); + .Case("extraPorts", {}) + .Case("preExtInstName", {})); if (!propVal) { if (field.value() == "hierarchy") propVal = hierpaths; + else if (field.value() == "preExtInstName") + propVal = finalInstNamesList; else propVal = extraPorts; } @@ -408,7 +431,6 @@ struct ObjectModelIR { // Create the path ref op and record it. pathOpsToDut.emplace_back(createPathRef(leafInst, nla, builder)); } - auto *context = builder.getContext(); // Create the list of paths op and add it as a field of the class. auto pathList = builder.create( ListType::get(context, cast(PathType::get(context))), @@ -425,6 +447,7 @@ struct ObjectModelIR { hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) { return moduleNamespaces.try_emplace(module, module).first->second; } + MLIRContext *context; CircuitOp circtOp; FModuleOp dutMod; CircuitNamespace circtNamespace; @@ -435,10 +458,11 @@ struct ObjectModelIR { ClassOp memoryMetadataClass; ClassOp retimeModulesMetadataClass, retimeModulesSchemaClass; ClassOp blackBoxModulesSchemaClass, blackBoxMetadataClass; - StringRef memoryParamNames[12] = { - "name", "depth", "width", "maskBits", - "readPorts", "writePorts", "readwritePorts", "writeLatency", - "readLatency", "hierarchy", "inDut", "extraPorts"}; + StringRef memoryParamNames[13] = { + "name", "depth", "width", "maskBits", + "readPorts", "writePorts", "readwritePorts", "writeLatency", + "readLatency", "hierarchy", "inDut", "extraPorts", + "preExtInstName"}; StringRef retimeModulesParamNames[1] = {"moduleName"}; StringRef blackBoxModulesParamNames[1] = {"moduleName"}; llvm::SmallDenseSet blackboxModules; diff --git a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp index 1fb2ad2fe9aa..ca0fecb251a0 100644 --- a/lib/Dialect/FIRRTL/Transforms/Dedup.cpp +++ b/lib/Dialect/FIRRTL/Transforms/Dedup.cpp @@ -46,6 +46,20 @@ using namespace circt; using namespace firrtl; using hw::InnerRefAttr; +//===----------------------------------------------------------------------===// +// Utility function for classifying a Symbol's dedup-ability. +//===----------------------------------------------------------------------===// + +static bool checkVisibility(mlir::SymbolOpInterface symbol) { + // 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. + // Special handling for class-like's and if symbol reports cannot be + // discarded. + return symbol.isPrivate() && + (symbol.canDiscardOnUseEmpty() || isa(*symbol)); +} + //===----------------------------------------------------------------------===// // Hashing //===----------------------------------------------------------------------===// @@ -788,6 +802,20 @@ struct Equivalence { diag.attachNote(b->getLoc()) << "module marked NoDedup"; return; } + auto aSymbol = cast(a); + auto bSymbol = cast(b); + if (!checkVisibility(aSymbol)) { + diag.attachNote(a->getLoc()) + << "module is " + << (aSymbol.isPrivate() ? "private but not discardable" : "public"); + return; + } + if (!checkVisibility(bSymbol)) { + diag.attachNote(b->getLoc()) + << "module is " + << (bSymbol.isPrivate() ? "private but not discardable" : "public"); + return; + } auto aGroup = dyn_cast_or_null(a->getDiscardableAttr(dedupGroupAttrName)); auto bGroup = dyn_cast_or_null( @@ -1674,13 +1702,8 @@ class DedupPass : public circt::firrtl::impl::DedupBase { ext && !ext.getDefname().has_value()) return success(); - // 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() && !isa(*module))) { + if (!checkVisibility(module)) return success(); - } StructuralHasher hasher(hasherConstants); // Calculate the hash of the module and referred module names. diff --git a/lib/Dialect/FIRRTL/Transforms/InferResets.cpp b/lib/Dialect/FIRRTL/Transforms/InferResets.cpp index 5357125f04ea..ca52ee6c470a 100644 --- a/lib/Dialect/FIRRTL/Transforms/InferResets.cpp +++ b/lib/Dialect/FIRRTL/Transforms/InferResets.cpp @@ -1,4 +1,4 @@ -//===- InferResets.cpp - Infer resets and add async reset -------*- C++ -*-===// +//===- InferResets.cpp - Infer resets and add full reset --------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -270,6 +270,15 @@ using ResetNetwork = llvm::iterator_range< /// Whether a reset is sync or async. enum class ResetKind { Async, Sync }; +static StringRef resetKindToStringRef(const ResetKind &kind) { + switch (kind) { + case ResetKind::Async: + return "async"; + case ResetKind::Sync: + return "sync"; + } + llvm_unreachable("unhandled reset kind"); +} } // namespace namespace llvm { @@ -306,12 +315,11 @@ static T &operator<<(T &os, const ResetKind &kind) { //===----------------------------------------------------------------------===// namespace { -/// Infer concrete reset types and insert full async reset. +/// Infer concrete reset types and insert full reset. /// /// This pass replaces `reset` types in the IR with a concrete `asyncreset` or -/// `uint<1>` depending on how the reset is used, and adds async resets to -/// registers in modules marked with the corresponding -/// `FullAsyncResetAnnotation`. +/// `uint<1>` depending on how the reset is used, and adds resets to registers +/// in modules marked with the corresponding `FullResetAnnotation`. /// /// On a high level, the first stage of the pass that deals with reset inference /// operates as follows: @@ -332,28 +340,27 @@ namespace { /// found in step 2. This will replace all `reset` types in the IR with /// a concrete type. /// -/// The second stage that deals with the addition of async resets operates as +/// The second stage that deals with the addition of full resets operates as /// follows: /// /// 4. Visit every module in the design and determine if it has an explicit /// reset annotated. Ports of and wires in the module can have a -/// `FullAsyncResetAnnotation`, which marks that port or wire as the async -/// reset for the module. A module may also carry a -/// `IgnoreFullAsyncResetAnnotation`, which marks it as being explicitly not -/// in a reset domain. These annotations are sparse; it is very much possible -/// that just the top-level module in the design has a full async reset -/// annotation. A module can only ever carry one of these annotations, which -/// puts it into one of three categories from an async reset inference -/// perspective: +/// `FullResetAnnotation`, which marks that port or wire as the reset for +/// the module. A module may also carry a `ExcludeFromFullResetAnnotation`, +/// which marks it as being explicitly not in a reset domain. These +/// annotations are sparse; it is very much possible that just the top-level +/// module in the design has a full reset annotation. A module can only +/// ever carry one of these annotations, which puts it into one of three +/// categories from a full reset inference perspective: /// -/// a. unambiguously marks a port or wire as the module's async reset -/// b. explicitly marks it as not to have any async resets added +/// a. unambiguously marks a port or wire as the module's full reset +/// b. explicitly marks it as not to have any full resets added /// c. inherit reset /// -/// 5. For every module in the design, determine the full async reset domain it +/// 5. For every module in the design, determine the full full reset domain it /// is in. Note that this very narrowly deals with the inference of a -/// "default" async reset, which basically goes through the IR and attaches -/// all non-reset registers to a default async reset signal. If a module +/// "default" full reset, which basically goes through the IR and attaches +/// all non-reset registers to a default full reset signal. If a module /// carries one of the annotations mentioned in (4), the annotated port or /// wire is used as its reset domain. Otherwise, it inherits the reset domain /// from parent modules. This conceptually involves looking at all the places @@ -365,7 +372,7 @@ namespace { /// its local ports or wires, or a port or wire within one of its parent /// modules. /// -/// 6. For every module in the design, determine how async resets shall be +/// 6. For every module in the design, determine how full resets shall be /// implemented. This step handles the following distinct cases: /// /// a. Skip a module because it is marked as having no reset domain. @@ -382,30 +389,30 @@ namespace { /// value to reuse (port or wire), the index of an existing port to reuse, /// and the name of an additional port to insert into its port list. /// -/// 7. For every module in the design, async resets are implemented. This +/// 7. For every module in the design, full resets are implemented. This /// determines the local value to use as the reset signal and updates the /// `reg` and `regreset` operations in the design. If the register already -/// has an async reset, it is left unchanged. If it has a sync reset, the -/// sync reset is moved into a `mux` operation on all `connect`s to the -/// register (which the Scala code base called the `RemoveResets` pass). -/// Finally the register is replaced with a `regreset` operation, with the -/// reset signal determined earlier, and a "zero" value constructed for the -/// register's type. +/// has an async reset, or if the type of the full reset is sync, the +/// register's reset is left unchanged. If it has a sync reset and the full +/// reset is async, the sync reset is moved into a `mux` operation on all +/// `connect`s to the register (which the Scala code base called the +/// `RemoveResets` pass). Finally the register is replaced with a `regreset` +/// operation, with the reset signal determined earlier, and a "zero" value +/// constructed for the register's type. /// /// Determining the local reset value is trivial if step 6 found a module to /// be of case a or b. Case c is the non-trivial one, because it requires /// modifying the port list of the module. This is done by first determining /// the name of the reset signal in the parent module, which is either the /// name of the port or wire declaration. We then look for an existing -/// `asyncreset` port in the port list and reuse that as reset. If no port -/// with that name was found, or the existing port is of the wrong type, a -/// new port is inserted into the port list. +/// port of the same type in the port list and reuse that as reset. If no +/// port with that name was found, or the existing port is of the wrong type, +/// a new port is inserted into the port list. /// /// TODO: This logic is *very* brittle and error-prone. It may make sense to -/// just add an additional port for the inferred async reset in any case, -/// with an optimization to use an existing `asyncreset` port if all of the -/// module's instantiations have that port connected to the desired signal -/// already. +/// just add an additional port for the inferred reset in any case, with an +/// optimization to use an existing port if all of the module's +/// instantiations have that port connected to the desired signal already. /// struct InferResetsPass : public circt::firrtl::impl::InferResetsBase { @@ -432,12 +439,12 @@ struct InferResetsPass bool updateReset(FieldRef field, FIRRTLBaseType resetType); //===--------------------------------------------------------------------===// - // Async reset implementation + // Full reset implementation LogicalResult collectAnnos(CircuitOp circuit); - // Collect async reset annotations in the module and return a reset signal. + // Collect reset annotations in the module and return a reset signal. // Return `failure()` if there was an error in the annotation processing. - // Return `std::nullopt` if there was no async reset annotation. + // Return `std::nullopt` if there was no reset annotation. // Return `nullptr` if there was `ignore` annotation. // Return a non-null Value if the reset was actually provided. FailureOr> collectAnnos(FModuleOp module); @@ -450,9 +457,9 @@ struct InferResetsPass void determineImpl(); void determineImpl(FModuleOp module, ResetDomain &domain); - LogicalResult implementAsyncReset(); - LogicalResult implementAsyncReset(FModuleOp module, ResetDomain &domain); - void implementAsyncReset(Operation *op, FModuleOp module, Value actualReset); + LogicalResult implementFullReset(); + LogicalResult implementFullReset(FModuleOp module, ResetDomain &domain); + void implementFullReset(Operation *op, FModuleOp module, Value actualReset); LogicalResult verifyNoAbstractReset(); @@ -536,8 +543,8 @@ void InferResetsPass::runOnOperationInner() { // Determine how each reset shall be implemented. determineImpl(); - // Implement the async resets. - if (failed(implementAsyncReset())) + // Implement the full resets. + if (failed(implementFullReset())) return signalPassFailure(); // Require that no Abstract Resets exist on ports in the design. @@ -1262,7 +1269,7 @@ bool InferResetsPass::updateReset(FieldRef field, FIRRTLBaseType resetType) { LogicalResult InferResetsPass::collectAnnos(CircuitOp circuit) { LLVM_DEBUG({ llvm::dbgs() << "\n"; - debugHeader("Gather async reset annotations") << "\n\n"; + debugHeader("Gather reset annotations") << "\n\n"; }); SmallVector>> results; for (auto module : circuit.getOps()) @@ -1293,15 +1300,15 @@ InferResetsPass::collectAnnos(FModuleOp module) { // explicitly assigns it no reset domain. bool ignore = false; AnnotationSet::removeAnnotations(module, [&](Annotation anno) { - if (anno.isClass(ignoreFullAsyncResetAnnoClass)) { + if (anno.isClass(excludeFromFullResetAnnoClass)) { ignore = true; conflictingAnnos.insert({anno, module.getLoc()}); return true; } - if (anno.isClass(fullAsyncResetAnnoClass)) { + if (anno.isClass(fullResetAnnoClass)) { anyFailed = true; - module.emitError("'FullAsyncResetAnnotation' cannot target module; " - "must target port or wire/node instead"); + module.emitError("''FullResetAnnotation' cannot target module; must " + "target port or wire/node instead"); return true; } return false; @@ -1311,31 +1318,66 @@ InferResetsPass::collectAnnos(FModuleOp module) { // Consume any reset annotations on module ports. Value reset; - AnnotationSet::removePortAnnotations(module, [&](unsigned argNum, - Annotation anno) { - Value arg = module.getArgument(argNum); - if (anno.isClass(fullAsyncResetAnnoClass)) { - if (!isa(arg.getType())) { - mlir::emitError(arg.getLoc(), "'FullAsyncResetAnnotation' must " - "target async reset, but targets ") + // Helper for checking annotations and determining the reset + auto checkAnnotations = [&](Annotation anno, Value arg) { + if (anno.isClass(fullResetAnnoClass)) { + ResetKind expectedResetKind; + if (auto rt = anno.getMember("resetType")) { + if (rt == "sync") { + expectedResetKind = ResetKind::Sync; + } else if (rt == "async") { + expectedResetKind = ResetKind::Async; + } else { + mlir::emitError(arg.getLoc(), + "'FullResetAnnotation' requires resetType == 'sync' " + "| 'async', but got resetType == ") + << rt; + anyFailed = true; + return true; + } + } else { + mlir::emitError(arg.getLoc(), + "'FullResetAnnotation' requires resetType == " + "'sync' | 'async', but got no resetType"); + anyFailed = true; + return true; + } + // Check that the type is well-formed + bool isAsync = expectedResetKind == ResetKind::Async; + bool validUint = false; + if (auto uintT = dyn_cast(arg.getType())) + validUint = uintT.getWidth() == 1; + if ((isAsync && !isa(arg.getType())) || + (!isAsync && !validUint)) { + auto kind = resetKindToStringRef(expectedResetKind); + mlir::emitError(arg.getLoc(), + "'FullResetAnnotation' with resetType == '") + << kind << "' must target " << kind << " reset, but targets " << arg.getType(); anyFailed = true; return true; } + reset = arg; conflictingAnnos.insert({anno, reset.getLoc()}); return false; } - if (anno.isClass(ignoreFullAsyncResetAnnoClass)) { + if (anno.isClass(excludeFromFullResetAnnoClass)) { anyFailed = true; mlir::emitError(arg.getLoc(), - "'IgnoreFullAsyncResetAnnotation' cannot target port; " - "must target module instead"); + "'ExcludeFromFullResetAnnotation' cannot " + "target port/wire/node; must target module instead"); return true; } return false; - }); + }; + + AnnotationSet::removePortAnnotations(module, + [&](unsigned argNum, Annotation anno) { + Value arg = module.getArgument(argNum); + return checkAnnotations(anno, arg); + }); if (anyFailed) return failure(); @@ -1343,8 +1385,8 @@ InferResetsPass::collectAnnos(FModuleOp module) { module.getBody().walk([&](Operation *op) { // Reset annotations must target wire/node ops. if (!isa(op)) { - if (AnnotationSet::hasAnnotation(op, fullAsyncResetAnnoClass, - ignoreFullAsyncResetAnnoClass)) { + if (AnnotationSet::hasAnnotation(op, fullResetAnnoClass, + excludeFromFullResetAnnoClass)) { anyFailed = true; op->emitError( "reset annotations must target module, port, or wire/node"); @@ -1355,27 +1397,8 @@ InferResetsPass::collectAnnos(FModuleOp module) { // At this point we know that we have a WireOp/NodeOp. Process the reset // annotations. AnnotationSet::removeAnnotations(op, [&](Annotation anno) { - if (anno.isClass(fullAsyncResetAnnoClass)) { - auto resultType = op->getResult(0).getType(); - if (!isa(resultType)) { - mlir::emitError(op->getLoc(), "'FullAsyncResetAnnotation' must " - "target async reset, but targets ") - << resultType; - anyFailed = true; - return true; - } - reset = op->getResult(0); - conflictingAnnos.insert({anno, reset.getLoc()}); - return false; - } - if (anno.isClass(ignoreFullAsyncResetAnnoClass)) { - anyFailed = true; - op->emitError( - "'IgnoreFullAsyncResetAnnotation' cannot target wire/node; must " - "target module instead"); - return true; - } - return false; + auto arg = op->getResult(0); + return checkAnnotations(anno, arg); }); }); if (anyFailed) @@ -1430,7 +1453,7 @@ InferResetsPass::collectAnnos(FModuleOp module) { LogicalResult InferResetsPass::buildDomains(CircuitOp circuit) { LLVM_DEBUG({ llvm::dbgs() << "\n"; - debugHeader("Build async reset domains") << "\n\n"; + debugHeader("Build full reset domains") << "\n\n"; }); // Gather the domains. @@ -1559,7 +1582,7 @@ void InferResetsPass::determineImpl() { /// - If the domain is the place where the reset is defined ("top"), fills in /// the existing port/wire/node as reset. /// - If the module already has a port with the reset's name: -/// - If the type is `asyncreset`, reuses that port. +/// - If the port has the same type as the reset domain, reuses that port. /// - Otherwise appends a `_N` suffix with increasing N to create a /// yet-unused /// port name, and marks that as to be created. @@ -1629,18 +1652,18 @@ void InferResetsPass::determineImpl(FModuleOp module, ResetDomain &domain) { } //===----------------------------------------------------------------------===// -// Async Reset Implementation +// Full Reset Implementation //===----------------------------------------------------------------------===// -/// Implement the async resets gathered in the pass' `domains` map. -LogicalResult InferResetsPass::implementAsyncReset() { +/// Implement the annotated resets gathered in the pass' `domains` map. +LogicalResult InferResetsPass::implementFullReset() { LLVM_DEBUG({ llvm::dbgs() << "\n"; - debugHeader("Implement async resets") << "\n\n"; + debugHeader("Implement full resets") << "\n\n"; }); for (auto &it : domains) - if (failed(implementAsyncReset(cast(it.first), - it.second.back().first))) + if (failed(implementFullReset(cast(it.first), + it.second.back().first))) return failure(); return success(); } @@ -1650,9 +1673,9 @@ LogicalResult InferResetsPass::implementAsyncReset() { /// This will add ports to the module as appropriate, update the register ops /// in the module, and update any instantiated submodules with their /// corresponding reset implementation details. -LogicalResult InferResetsPass::implementAsyncReset(FModuleOp module, - ResetDomain &domain) { - LLVM_DEBUG(llvm::dbgs() << "Implementing async reset for " << module.getName() +LogicalResult InferResetsPass::implementFullReset(FModuleOp module, + ResetDomain &domain) { + LLVM_DEBUG(llvm::dbgs() << "Implementing full reset for " << module.getName() << "\n"); // Nothing to do if the module was marked explicitly with no reset domain. @@ -1666,7 +1689,7 @@ LogicalResult InferResetsPass::implementAsyncReset(FModuleOp module, Value actualReset = domain.existingValue; if (domain.newPortName) { PortInfo portInfo{domain.newPortName, - AsyncResetType::get(&getContext()), + domain.reset.getType(), Direction::In, {}, domain.reset.getLoc()}; @@ -1771,15 +1794,15 @@ LogicalResult InferResetsPass::implementAsyncReset(FModuleOp module, // Update the operations. for (auto *op : opsToUpdate) - implementAsyncReset(op, module, actualReset); + implementFullReset(op, module, actualReset); return success(); } -/// Modify an operation in a module to implement an async reset for that +/// Modify an operation in a module to implement an full reset for that /// module. -void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module, - Value actualReset) { +void InferResetsPass::implementFullReset(Operation *op, FModuleOp module, + Value actualReset) { ImplicitLocOpBuilder builder(op->getLoc(), op); // Handle instances. @@ -1840,7 +1863,7 @@ void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module, if (AnnotationSet::removeAnnotations(regOp, excludeMemToRegAnnoClass)) return; - LLVM_DEBUG(llvm::dbgs() << "- Adding async reset to " << regOp << "\n"); + LLVM_DEBUG(llvm::dbgs() << "- Adding full reset to " << regOp << "\n"); auto zero = createZeroValue(builder, regOp.getResult().getType()); auto newRegOp = builder.create( regOp.getResult().getType(), regOp.getClockVal(), actualReset, zero, @@ -1855,10 +1878,11 @@ void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module, // Handle registers with reset. if (auto regOp = dyn_cast(op)) { - // If the register already has an async reset, leave it untouched. - if (type_isa(regOp.getResetSignal().getType())) { - LLVM_DEBUG(llvm::dbgs() - << "- Skipping (has async reset) " << regOp << "\n"); + // If the register already has an async reset or if the type of the added + // reset is sync, leave it alone. + if (type_isa(regOp.getResetSignal().getType()) || + type_isa(actualReset.getType())) { + LLVM_DEBUG(llvm::dbgs() << "- Skipping (has reset) " << regOp << "\n"); // The following performs the logic of `CheckResets` in the original // Scala source code. if (failed(regOp.verifyInvariants())) @@ -1870,9 +1894,9 @@ void InferResetsPass::implementAsyncReset(Operation *op, FModuleOp module, auto reset = regOp.getResetSignal(); auto value = regOp.getResetValue(); - // If we arrive here, the register has a sync reset. In order to add an - // async reset, we have to move the sync reset into a mux in front of the - // register. + // If we arrive here, the register has a sync reset and the added reset is + // async. In order to add an async reset, we have to move the sync reset + // into a mux in front of the register. insertResetMux(builder, regOp.getResult(), reset, value); builder.setInsertionPointAfterValue(regOp.getResult()); auto mux = builder.create(reset, value, regOp.getResult()); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp b/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp index fe19e2141055..e42d83af73fb 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerAnnotations.cpp @@ -435,6 +435,45 @@ static LogicalResult applyOutputDirAnno(const AnnoPathValue &target, return success(); } +/// Convert from FullAsyncResetAnnotation to FullResetAnnotation +static LogicalResult convertToFullResetAnnotation(const AnnoPathValue &target, + DictionaryAttr anno, + ApplyState &state) { + auto *op = target.ref.getOp(); + auto *context = op->getContext(); + + mlir::emitWarning(op->getLoc()) + << "'" << fullAsyncResetAnnoClass << "' is deprecated, use '" + << fullResetAnnoClass << "' instead"; + + NamedAttrList newAnno(anno.getValue()); + newAnno.set("class", StringAttr::get(context, fullResetAnnoClass)); + newAnno.append("resetType", StringAttr::get(context, "async")); + + DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno); + + return applyWithoutTarget(target, newDictionary, state); +} + +/// Convert from IgnoreFullAsyncResetAnnotation to +/// ExcludeFromFullResetAnnotation +static LogicalResult convertToExcludeFromFullResetAnnotation( + const AnnoPathValue &target, DictionaryAttr anno, ApplyState &state) { + auto *op = target.ref.getOp(); + auto *context = op->getContext(); + + mlir::emitWarning(op->getLoc()) + << "'" << ignoreFullAsyncResetAnnoClass << "' is deprecated, use '" + << excludeFromFullResetAnnoClass << "' instead"; + + NamedAttrList newAnno(anno.getValue()); + newAnno.set("class", StringAttr::get(context, excludeFromFullResetAnnoClass)); + + DictionaryAttr newDictionary = DictionaryAttr::get(op->getContext(), newAnno); + + return applyWithoutTarget(target, newDictionary, state); +} + //===----------------------------------------------------------------------===// // Driving table //===----------------------------------------------------------------------===// @@ -543,9 +582,12 @@ static llvm::StringMap annotationRecords{{ {addSeqMemPortsFileAnnoClass, NoTargetAnnotation}, {extractClockGatesAnnoClass, NoTargetAnnotation}, {extractBlackBoxAnnoClass, {stdResolve, applyWithoutTarget}}, - {fullAsyncResetAnnoClass, {stdResolve, applyWithoutTarget}}, - {ignoreFullAsyncResetAnnoClass, + {fullResetAnnoClass, {stdResolve, applyWithoutTarget}}, + {excludeFromFullResetAnnoClass, {stdResolve, applyWithoutTarget}}, + {fullAsyncResetAnnoClass, {stdResolve, convertToFullResetAnnotation}}, + {ignoreFullAsyncResetAnnoClass, + {stdResolve, convertToExcludeFromFullResetAnnotation}}, {decodeTableAnnotation, {noResolve, drop}}, {blackBoxTargetDirAnnoClass, NoTargetAnnotation}, {traceNameAnnoClass, {stdResolve, applyTraceName}}, diff --git a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp index ea1d49c63234..1cc16de4f08f 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerClasses.cpp @@ -81,17 +81,18 @@ static bool shouldCreateClassImpl(igraph::InstanceGraphNode *node) { /// to the targeted operation. struct PathInfo { PathInfo() = default; - PathInfo(Operation *op, FlatSymbolRefAttr symRef, + PathInfo(Location loc, bool canBeInstanceTarget, FlatSymbolRefAttr symRef, StringAttr altBasePathModule) - : op(op), symRef(symRef), altBasePathModule(altBasePathModule) { - assert(op && "op must not be null"); + : loc(loc), canBeInstanceTarget(canBeInstanceTarget), symRef(symRef), + altBasePathModule(altBasePathModule) { assert(symRef && "symRef must not be null"); } - operator bool() const { return op != nullptr; } + /// The Location of the hardware component targeted by this path. + std::optional loc = std::nullopt; - /// The hardware component targeted by this path. - Operation *op = nullptr; + /// Flag to indicate if the hardware component can be targeted as an instance. + bool canBeInstanceTarget = false; /// A reference to the hierarchical path targeting the op. FlatSymbolRefAttr symRef = nullptr; @@ -584,17 +585,23 @@ LogicalResult PathTracker::updatePathInfoTable(PathInfoTable &pathInfoTable, auto [it, inserted] = pathInfoTable.table.try_emplace(entry.id); auto &pathInfo = it->second; if (!inserted) { - auto diag = - emitError(pathInfo.op->getLoc(), "duplicate identifier found"); + assert(pathInfo.loc.has_value() && "all PathInfo should have a Location"); + auto diag = emitError(pathInfo.loc.value(), "duplicate identifier found"); diag.attachNote(entry.op->getLoc()) << "other identifier here"; return failure(); } - if (entry.pathAttr) - pathInfo = {entry.op, cache.getRefFor(entry.pathAttr), - entry.altBasePathModule}; - else - pathInfo.op = entry.op; + // Check if the op is targetable by an instance target. The op pointer may + // be invalidated later, so this is the last time we want to access it here. + bool canBeInstanceTarget = isa(entry.op); + + if (entry.pathAttr) { + pathInfo = {entry.op->getLoc(), canBeInstanceTarget, + cache.getRefFor(entry.pathAttr), entry.altBasePathModule}; + } else { + pathInfo.loc = entry.op->getLoc(); + pathInfo.canBeInstanceTarget = canBeInstanceTarget; + } } return success(); } @@ -1385,6 +1392,22 @@ struct ListCreateOpConversion } }; +struct ListConcatOpConversion + : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(firrtl::ListConcatOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto listType = getTypeConverter()->convertType(op.getType()); + if (!listType) + return failure(); + rewriter.replaceOpWithNewOp(op, listType, + adaptor.getSubLists()); + return success(); + } +}; + struct IntegerAddOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; @@ -1437,14 +1460,14 @@ struct PathOpConversion : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { auto *context = op->getContext(); auto pathType = om::PathType::get(context); - auto pathInfo = pathInfoTable.table.lookup(op.getTarget()); + auto pathInfoIt = pathInfoTable.table.find(op.getTarget()); // The 0'th argument is the base path by default. auto basePath = op->getBlock()->getArgument(0); // If the target was optimized away, then replace the path operation with // a deleted path. - if (!pathInfo) { + if (pathInfoIt == pathInfoTable.table.end()) { if (op.getTargetKind() == firrtl::TargetKind::DontTouch) return emitError(op.getLoc(), "DontTouch target was deleted"); if (op.getTargetKind() == firrtl::TargetKind::Instance) @@ -1453,6 +1476,7 @@ struct PathOpConversion : public OpConversionPattern { return success(); } + auto pathInfo = pathInfoIt->second; auto symbol = pathInfo.symRef; // Convert the target kind to an OMIR target. Member references are updated @@ -1466,15 +1490,15 @@ struct PathOpConversion : public OpConversionPattern { targetKind = om::TargetKind::Reference; break; case firrtl::TargetKind::Instance: - if (!isa(pathInfo.op)) + if (!pathInfo.canBeInstanceTarget) return emitError(op.getLoc(), "invalid target for instance path") - .attachNote(pathInfo.op->getLoc()) + .attachNote(pathInfo.loc) << "target not instance or module"; targetKind = om::TargetKind::Instance; break; case firrtl::TargetKind::MemberInstance: case firrtl::TargetKind::MemberReference: - if (isa(pathInfo.op)) + if (pathInfo.canBeInstanceTarget) targetKind = om::TargetKind::MemberInstance; else targetKind = om::TargetKind::MemberReference; @@ -1864,6 +1888,7 @@ 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()); patterns.add(converter, patterns.getContext()); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp b/lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp index c0ae5dc03e7c..b8a5b3c760cc 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerDPI.cpp @@ -16,6 +16,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/Namespace.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/Sim/SimOps.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/Threading.h" @@ -91,6 +92,71 @@ void LowerDPI::collectIntrinsics() { funcNameToCallSites[dpi.getFunctionNameAttr()].push_back(dpi); } +// Lower FIRRTL type to core dialect types in which array type is replaced with +// an open array type. +static Type lowerDPIArgumentType(Type type) { + auto loweredType = lowerType(type); + return loweredType.replace([](hw::ArrayType array) { + return sv::UnpackedOpenArrayType::get(array.getElementType()); + }); +} + +static Value convertToUnpackedArray(ImplicitLocOpBuilder &builder, + Value loweredValue) { + if (isa(loweredValue.getType())) + return loweredValue; + + auto array = dyn_cast(loweredValue.getType()); + if (!array) { + // TODO: Support bundle or enum types. + return {}; + } + + SmallVector values; + auto length = array.getNumElements(); + auto width = llvm::Log2_64_Ceil(length); + + // Create an unpacked array from a packed array. + for (int i = length - 1; i >= 0; --i) { + auto index = builder.create(APInt(width, i)); + auto elem = convertToUnpackedArray( + builder, builder.create(loweredValue, index)); + if (!elem) + return {}; + values.push_back(elem); + } + + return builder.create( + hw::UnpackedArrayType::get(lowerType(array.getElementType()), length), + values); +} + +static Value getLowered(ImplicitLocOpBuilder &builder, Value value) { + // Insert an unrealized conversion to cast FIRRTL type to HW type. + if (!value) + return value; + + auto type = lowerType(value.getType()); + Value result = builder.create(type, value) + ->getResult(0); + + // We may need to cast the lowered value to a DPI specific type (e.g. open + // array). Get a DPI type and check if they are same. + auto dpiType = lowerDPIArgumentType(value.getType()); + if (type == dpiType) + return result; + + // Cast into unpacked open array type. + result = convertToUnpackedArray(builder, result); + if (!result) { + mlir::emitError(value.getLoc()) + << "contains a type that currently not supported"; + return {}; + } + + return builder.create(dpiType, result); +} + LogicalResult LowerDPI::lower() { for (auto [name, calls] : funcNameToCallSites) { auto firstDPICallop = calls.front(); @@ -103,25 +169,21 @@ LogicalResult LowerDPI::lower() { ImplicitLocOpBuilder builder(firstDPICallop.getLoc(), circuitOp.getOperation()); auto lowerCall = [&](DPICallIntrinsicOp dpiOp) { - auto getLowered = [&](Value value) -> Value { - // Insert an unrealized conversion to cast FIRRTL type to HW type. - if (!value) - return value; - auto type = lowerType(value.getType()); - return builder.create(type, value) - ->getResult(0); - }; builder.setInsertionPoint(dpiOp); - auto clock = getLowered(dpiOp.getClock()); - auto enable = getLowered(dpiOp.getEnable()); + auto clock = getLowered(builder, dpiOp.getClock()); + auto enable = getLowered(builder, dpiOp.getEnable()); SmallVector inputs; inputs.reserve(dpiOp.getInputs().size()); - for (auto input : dpiOp.getInputs()) - inputs.push_back(getLowered(input)); + for (auto input : dpiOp.getInputs()) { + inputs.push_back(getLowered(builder, input)); + if (!inputs.back()) + return failure(); + } SmallVector outputTypes; if (dpiOp.getResult()) - outputTypes.push_back(lowerType(dpiOp.getResult().getType())); + outputTypes.push_back( + lowerDPIArgumentType(dpiOp.getResult().getType())); auto call = builder.create( outputTypes, firstDPIDecl.getSymNameAttr(), clock, enable, inputs); @@ -188,7 +250,7 @@ sim::DPIFuncOp LowerDPI::getOrCreateDPIFuncDecl(DPICallIntrinsicOp op) { port.dir = hw::ModulePort::Direction::Input; port.name = inputNames ? cast(inputNames[idx]) : builder.getStringAttr(Twine("in_") + Twine(idx)); - port.type = lowerType(inType); + port.type = lowerDPIArgumentType(inType); ports.push_back(port); } @@ -198,7 +260,7 @@ sim::DPIFuncOp LowerDPI::getOrCreateDPIFuncDecl(DPICallIntrinsicOp op) { port.dir = hw::ModulePort::Direction::Output; port.name = outputName ? outputName : builder.getStringAttr(Twine("out_") + Twine(idx)); - port.type = lowerType(outType); + port.type = lowerDPIArgumentType(outType); ports.push_back(port); } diff --git a/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp b/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp index b87786e66252..cf5aff491e1a 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerIntmodules.cpp @@ -181,6 +181,20 @@ void LowerIntmodulesPass::runOnOperation() { }; auto inputs = replaceResults(builder, inst.getResults().drop_back()); + // en and test_en are swapped between extmodule and intrinsic. + if (inputs.size() > 2) { + auto port1 = inst.getPortName(1); + auto port2 = inst.getPortName(2); + if (port1 != "test_en") { + mlir::emitError(op.getPortLocation(1), + "expected port named 'test_en'"); + return signalPassFailure(); + } else if (port2 != "en") { + mlir::emitError(op.getPortLocation(2), "expected port named 'en'"); + return signalPassFailure(); + } else + std::swap(inputs[1], inputs[2]); + } auto intop = builder.create( builder.getType(), "circt_clock_gate", inputs, op.getParameters()); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index e75088749322..96b91a8ec3d2 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -16,6 +16,7 @@ #include "circt/Dialect/FIRRTL/Passes.h" #include "circt/Dialect/HW/InnerSymbolNamespace.h" #include "circt/Dialect/SV/SVOps.h" +#include "circt/Support/Utils.h" #include "mlir/Pass/Pass.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Mutex.h" @@ -33,17 +34,6 @@ namespace firrtl { 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()); -} - namespace { /// Indicates the kind of reference that was captured. @@ -123,7 +113,8 @@ static SmallString<32> fileNameForLayer(StringRef circuitName, } /// For a layerblock `@A::@B::@C`, the verilog macro is `A_B_C`. -static SmallString<32> macroNameForLayer(ArrayRef layerName) { +static SmallString<32> +macroNameForLayer(ArrayRef layerName) { SmallString<32> result; for (auto part : layerName) appendName(part, result); @@ -137,8 +128,7 @@ static SmallString<32> macroNameForLayer(ArrayRef layerName) { class LowerLayersPass : public circt::firrtl::impl::LowerLayersBase { hw::OutputFileAttr getOutputFile(SymbolRefAttr layerName) { - auto layer = - cast(SymbolTable::lookupSymbolIn(getOperation(), layerName)); + auto layer = symbolToLayer.lookup(layerName); if (!layer) return nullptr; return layer->getAttrOfType("output_file"); @@ -162,10 +152,10 @@ class LowerLayersPass SmallVectorImpl &ports); /// Strip layer colors from the module's interface. - InnerRefMap runOnModuleLike(FModuleLike moduleLike); + FailureOr runOnModuleLike(FModuleLike moduleLike); /// Extract layerblocks and strip probe colors from all ops under the module. - void runOnModuleBody(FModuleOp moduleOp, InnerRefMap &innerRefMap); + LogicalResult runOnModuleBody(FModuleOp moduleOp, InnerRefMap &innerRefMap); /// Update the module's port types to remove any explicit layer requirements /// from any probe types. @@ -181,10 +171,11 @@ class LowerLayersPass /// Lower an inline layerblock to an ifdef block. void lowerInlineLayerBlock(LayerOp layer, LayerBlockOp layerBlock); - /// Create macro declarations for a given layer, and its child layers. - void createMacroDecls(CircuitNamespace &ns, OpBuilder &b, LayerOp layer, - SmallVector &stack); - void createMacroDecls(CircuitNamespace &ns, LayerOp layer); + /// Preprocess layers to build macro declarations and cache information about + /// the layers so that this can be quired later. + void preprocessLayers(CircuitNamespace &ns, OpBuilder &b, LayerOp layer, + SmallVector &stack); + void preprocessLayers(CircuitNamespace &ns, LayerOp layer); /// Entry point for the function. void runOnOperation() override; @@ -197,6 +188,9 @@ class LowerLayersPass /// A map from inline layers to their macro names. DenseMap macroNames; + + /// A mapping of symbol name to layer operation. + DenseMap symbolToLayer; }; /// Multi-process safe function to build a module in the circuit and return it. @@ -272,7 +266,8 @@ void LowerLayersPass::removeLayersFromPorts(FModuleLike moduleLike) { } } -InnerRefMap LowerLayersPass::runOnModuleLike(FModuleLike moduleLike) { +FailureOr +LowerLayersPass::runOnModuleLike(FModuleLike moduleLike) { LLVM_DEBUG({ llvm::dbgs() << "Module: " << moduleLike.getModuleName() << "\n"; llvm::dbgs() << " Examining Layer Blocks:\n"; @@ -280,18 +275,24 @@ InnerRefMap LowerLayersPass::runOnModuleLike(FModuleLike moduleLike) { // 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"); }); + auto result = + TypeSwitch(moduleLike.getOperation()) + .Case([&](auto op) { + op.setLayers({}); + removeLayersFromPorts(op); + return runOnModuleBody(op, innerRefMap); + }) + .Case([&](auto op) { + op.setLayers({}); + removeLayersFromPorts(op); + return success(); + }) + .Case([](auto) { return success(); }) + .Default( + [](auto *op) { return op->emitError("unknown module-like op"); }); + + if (failed(result)) + return failure(); return innerRefMap; } @@ -305,8 +306,8 @@ void LowerLayersPass::lowerInlineLayerBlock(LayerOp layer, layerBlock.erase(); } -void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, - InnerRefMap &innerRefMap) { +LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, + InnerRefMap &innerRefMap) { CircuitOp circuitOp = moduleOp->getParentOfType(); StringRef circuitName = circuitOp.getName(); hw::InnerSymbolNamespace ns(moduleOp); @@ -327,7 +328,7 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, // this layer block to the new module. // 4. Instantiate the new module outside the layer block and hook it up. // 5. Erase the layer block. - moduleOp.walk([&](Operation *op) { + auto result = moduleOp.walk([&](Operation *op) { // Strip layer requirements from any op that might represent a probe. if (auto wire = dyn_cast(op)) { removeLayersFromValue(wire.getResult()); @@ -352,8 +353,7 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, if (!layerBlock) return WalkResult::advance(); - auto layer = cast( - SymbolTable::lookupSymbolIn(getOperation(), layerBlock.getLayerName())); + auto layer = symbolToLayer.lookup(layerBlock.getLayerName()); if (layer.getConvention() == LayerConvention::Inline) { lowerInlineLayerBlock(layer, layerBlock); @@ -497,15 +497,55 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, continue; } + // Handle subfields, subindexes, and subaccesses which are indexing into + // non-passive values. If these are kept in the module, then the module + // must create bi-directional ports. This doesn't make sense as the + // FIRRTL spec states that no layerblock may write to values outside it. + // Fix this for subfield and subindex by moving these ops outside the + // layerblock. Try to fix this for subaccess and error if the move can't + // be made because the index is defined inside the layerblock. (This case + // is exceedingly rare given that subaccesses are almost always unexepcted + // when this pass runs.) + if (isa(op)) { + auto input = op.getOperand(0); + if (!firrtl::type_cast(input.getType()).isPassive() && + !isAncestorOfValueOwner(layerBlock, input)) + op.moveBefore(layerBlock); + continue; + } + + if (auto subOp = dyn_cast(op)) { + auto input = subOp.getInput(); + if (firrtl::type_cast(input.getType()).isPassive()) + continue; + + if (!isAncestorOfValueOwner(layerBlock, input) && + !isAncestorOfValueOwner(layerBlock, subOp.getIndex())) { + subOp->moveBefore(layerBlock); + continue; + } + auto diag = op.emitOpError() + << "has a non-passive operand and captures a value defined " + "outside its enclosing bind-convention layerblock. The " + "'LowerLayers' pass cannot lower this as it would " + "create an output port on the resulting module."; + diag.attachNote(layerBlock.getLoc()) + << "the layerblock is defined here"; + return WalkResult::interrupt(); + } + + // Beyond this point, we are handling operations which capture values + // defined outside the layerblock. Whenever we see this, we need to + // create ports for the module that this layerblock will become. if (auto refSend = dyn_cast(op)) { auto src = refSend.getBase(); - if (!isAncestor(layerBlock, src)) + if (!isAncestorOfValueOwner(layerBlock, src)) createInputPort(src, op.getLoc()); continue; } if (auto refCast = dyn_cast(op)) { - if (!isAncestor(layerBlock, refCast)) + if (!isAncestorOfValueOwner(layerBlock, refCast)) createInputPort(refCast.getInput(), op.getLoc()); continue; } @@ -513,8 +553,8 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, if (auto connect = dyn_cast(op)) { auto src = connect.getSrc(); auto dst = connect.getDest(); - auto srcInLayerBlock = isAncestor(layerBlock, src); - auto dstInLayerBlock = isAncestor(layerBlock, dst); + auto srcInLayerBlock = isAncestorOfValueOwner(layerBlock, src); + auto dstInLayerBlock = isAncestorOfValueOwner(layerBlock, dst); if (!srcInLayerBlock && !dstInLayerBlock) { connect->moveBefore(layerBlock); continue; @@ -560,7 +600,7 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, // For any other ops, create input ports for any captured operands. for (auto operand : op.getOperands()) { - if (!isAncestor(layerBlock, operand)) + if (!isAncestorOfValueOwner(layerBlock, operand)) createInputPort(operand, op.getLoc()); } } @@ -656,12 +696,17 @@ void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, return WalkResult::advance(); }); + return success(!result.wasInterrupted()); } -void LowerLayersPass::createMacroDecls(CircuitNamespace &ns, OpBuilder &b, +void LowerLayersPass::preprocessLayers(CircuitNamespace &ns, OpBuilder &b, LayerOp layer, - SmallVector &stack) { - stack.emplace_back(layer.getSymNameAttr()); + SmallVector &stack) { + stack.emplace_back(FlatSymbolRefAttr::get(layer.getSymNameAttr())); + ArrayRef stackRef(stack); + symbolToLayer.insert( + {SymbolRefAttr::get(stackRef.front().getAttr(), stackRef.drop_front()), + layer}); if (layer.getConvention() == LayerConvention::Inline) { auto *ctx = &getContext(); auto macName = macroNameForLayer(stack); @@ -677,14 +722,14 @@ void LowerLayersPass::createMacroDecls(CircuitNamespace &ns, OpBuilder &b, macroNames[layer] = FlatSymbolRefAttr::get(&getContext(), symNameAttr); } for (auto child : layer.getOps()) - createMacroDecls(ns, b, child, stack); + preprocessLayers(ns, b, child, stack); stack.pop_back(); } -void LowerLayersPass::createMacroDecls(CircuitNamespace &ns, LayerOp layer) { +void LowerLayersPass::preprocessLayers(CircuitNamespace &ns, LayerOp layer) { OpBuilder b(layer); - SmallVector stack; - createMacroDecls(ns, b, layer, stack); + SmallVector stack; + preprocessLayers(ns, b, layer, stack); } /// Process a circuit to remove all layer blocks in each module and top-level @@ -712,24 +757,35 @@ void LowerLayersPass::runOnOperation() { continue; } // Build verilog macro declarations for each inline layer declarations. + // Cache layer symbol refs for lookup later. if (auto layerOp = dyn_cast(op)) { - createMacroDecls(ns, layerOp); + preprocessLayers(ns, layerOp); continue; } } auto mergeMaps = [](auto &&a, auto &&b) { - for (auto bb : b) - a.insert(bb); + if (failed(a)) + return std::forward(a); + if (failed(b)) + return std::forward(b); + + for (auto bb : *b) + a->insert(bb); return std::forward(a); }; // Lower the layer blocks of each module. SmallVector modules( circuitOp.getBodyBlock()->getOps()); - auto innerRefMap = - transformReduce(circuitOp.getContext(), modules, InnerRefMap{}, mergeMaps, - [this](FModuleLike mod) { return runOnModuleLike(mod); }); + auto failureOrInnerRefMap = transformReduce( + circuitOp.getContext(), modules, FailureOr(InnerRefMap{}), + mergeMaps, [this](FModuleLike mod) -> FailureOr { + return runOnModuleLike(mod); + }); + if (failed(failureOrInnerRefMap)) + return signalPassFailure(); + auto &innerRefMap = *failureOrInnerRefMap; // Rewrite any hw::HierPathOps which have namepaths that contain rewritting // inner refs. @@ -781,7 +837,7 @@ void LowerLayersPass::runOnOperation() { StringRef circuitName = circuitOp.getName(); circuitOp.walk([&](LayerOp layerOp) { auto parentOp = layerOp->getParentOfType(); - while (parentOp && parentOp != layers.back().first) + while (!layers.empty() && parentOp != layers.back().first) layers.pop_back(); if (layerOp.getConvention() == LayerConvention::Inline) { diff --git a/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp b/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp index fb79491eb650..d59055fb3167 100644 --- a/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp +++ b/lib/Dialect/FIRRTL/Transforms/SFCCompat.cpp @@ -54,21 +54,18 @@ void SFCCompatPass::runOnOperation() { bool madeModifications = false; SmallVector invalidOps; - auto fullAsyncResetAttr = - StringAttr::get(&getContext(), fullAsyncResetAnnoClass); - auto isFullAsyncResetAnno = [fullAsyncResetAttr](Annotation anno) { - return anno.getClassAttr() == fullAsyncResetAttr; + auto fullResetAttr = StringAttr::get(&getContext(), fullResetAnnoClass); + auto isFullResetAnno = [fullResetAttr](Annotation anno) { + auto annoClassAttr = anno.getClassAttr(); + return annoClassAttr == fullResetAttr; }; - bool fullAsyncResetExists = AnnotationSet::removePortAnnotations( - getOperation(), [&](unsigned argNum, Annotation anno) { - return isFullAsyncResetAnno(anno); - }); - getOperation()->walk( - [isFullAsyncResetAnno, &fullAsyncResetExists](Operation *op) { - fullAsyncResetExists |= - AnnotationSet::removeAnnotations(op, isFullAsyncResetAnno); - }); - madeModifications |= fullAsyncResetExists; + bool fullResetExists = AnnotationSet::removePortAnnotations( + getOperation(), + [&](unsigned argNum, Annotation anno) { return isFullResetAnno(anno); }); + getOperation()->walk([isFullResetAnno, &fullResetExists](Operation *op) { + fullResetExists |= AnnotationSet::removeAnnotations(op, isFullResetAnno); + }); + madeModifications |= fullResetExists; auto result = getOperation()->walk([&](Operation *op) { // Populate invalidOps for later handling. @@ -82,11 +79,10 @@ void SFCCompatPass::runOnOperation() { // If the `RegResetOp` has an invalidated initialization and we // are not running FART, then replace it with a `RegOp`. - if (!fullAsyncResetExists && - walkDrivers(reg.getResetValue(), true, true, false, - [](FieldRef dst, FieldRef src) { - return src.isa(); - })) { + if (!fullResetExists && walkDrivers(reg.getResetValue(), true, true, false, + [](FieldRef dst, FieldRef src) { + return src.isa(); + })) { ImplicitLocOpBuilder builder(reg.getLoc(), reg); RegOp newReg = builder.create( reg.getResult().getType(), reg.getClockVal(), reg.getNameAttr(), diff --git a/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp b/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp index 6b629bf968fe..07e2dc0bf821 100644 --- a/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/SpecializeLayers.cpp @@ -605,6 +605,7 @@ struct SpecializeLayers { llvm::make_early_inc_range(block->getOps())) { nestedRefs.push_back(SymbolRefAttr::get(nested)); handleLayer(nested, Block::iterator(nested), ""); + nestedRefs.pop_back(); } return; } @@ -617,6 +618,7 @@ struct SpecializeLayers { nestedRefs.push_back(SymbolRefAttr::get(nested)); handleLayer(nested, insertionPoint, prefix + layer.getSymName() + "_"); + nestedRefs.pop_back(); } // Erase the now empty layer. layer->erase(); diff --git a/lib/Dialect/HW/Transforms/FlattenIO.cpp b/lib/Dialect/HW/Transforms/FlattenIO.cpp index d23ef099d195..63e827c46cad 100644 --- a/lib/Dialect/HW/Transforms/FlattenIO.cpp +++ b/lib/Dialect/HW/Transforms/FlattenIO.cpp @@ -171,7 +171,6 @@ class FlattenIOTypeConverter : public TypeConverter { results.push_back(type); else { for (auto field : structType.getElements()) - results.push_back(field.type); } return success(); @@ -185,9 +184,7 @@ class FlattenIOTypeConverter : public TypeConverter { addTargetMaterialization([](OpBuilder &builder, hw::TypeAliasType type, ValueRange inputs, Location loc) { - auto structType = getStructType(type); - assert(structType && "expected struct type"); - auto result = builder.create(loc, structType, inputs); + auto result = builder.create(loc, type, inputs); return result.getResult(); }); } diff --git a/lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp b/lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp index 3b73257f5c94..0e6c8ac6f7bd 100644 --- a/lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp +++ b/lib/Dialect/Ibis/Transforms/IbisContainersToHW.cpp @@ -369,6 +369,13 @@ void ContainersToHWPass::runOnOperation() { target.addIllegalOp(); target.markUnknownOpDynamicallyLegal([](Operation *) { return true; }); + // Remove the name of the ibis.design's from the namespace - The ibis.design + // op will be removed after this pass, and there may be ibis.component's + // inside the design that have the same name as the design; we want that + // name to persist, and not be falsely considered a duplicate. + for (auto designOp : getOperation().getOps()) + modNamespace.erase(designOp.getSymName()); + // Parts of the conversion patterns will update operations in place, which in // turn requires the updated operations to be legalizeable. These in-place ops // also include ibis ops that eventually will get replaced once all of the diff --git a/lib/Dialect/LLHD/IR/LLHDDialect.cpp b/lib/Dialect/LLHD/IR/LLHDDialect.cpp index 1c60d080e23c..a284de799b5d 100644 --- a/lib/Dialect/LLHD/IR/LLHDDialect.cpp +++ b/lib/Dialect/LLHD/IR/LLHDDialect.cpp @@ -15,11 +15,8 @@ #include "circt/Dialect/LLHD/IR/LLHDOps.h" #include "circt/Dialect/LLHD/IR/LLHDTypes.h" #include "circt/Support/LLVM.h" -#include "mlir/IR/Builders.h" #include "mlir/IR/DialectImplementation.h" #include "mlir/Transforms/InliningUtils.h" -#include "llvm/ADT/ArrayRef.h" -#include "llvm/ADT/StringRef.h" using namespace circt; using namespace circt::llhd; diff --git a/lib/Dialect/LLHD/IR/LLHDOps.cpp b/lib/Dialect/LLHD/IR/LLHDOps.cpp index 7e9517a0bf7b..7abe6541da69 100644 --- a/lib/Dialect/LLHD/IR/LLHDOps.cpp +++ b/lib/Dialect/LLHD/IR/LLHDOps.cpp @@ -6,128 +6,26 @@ // //===----------------------------------------------------------------------===// // -// This file implement the LLHD ops. +// This file implements the LLHD ops. // //===----------------------------------------------------------------------===// #include "circt/Dialect/LLHD/IR/LLHDOps.h" #include "circt/Dialect/HW/HWOps.h" -#include "circt/Dialect/LLHD/IR/LLHDDialect.h" -#include "mlir/Dialect/CommonFolders.h" +#include "circt/Support/CustomDirectiveImpl.h" #include "mlir/IR/Attributes.h" -#include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/Matchers.h" -#include "mlir/IR/OpImplementation.h" #include "mlir/IR/PatternMatch.h" #include "mlir/IR/Region.h" #include "mlir/IR/Types.h" #include "mlir/IR/Value.h" -#include "mlir/Support/LogicalResult.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringSet.h" -#include "llvm/ADT/TypeSwitch.h" using namespace circt; using namespace mlir; - -template > -static Attribute constFoldUnaryOp(ArrayRef operands, - const CalculationT &calculate) { - assert(operands.size() == 1 && "unary op takes one operand"); - if (!operands[0]) - return {}; - - if (auto val = dyn_cast(operands[0])) { - return AttrElementT::get(val.getType(), calculate(val.getValue())); - } else if (auto val = dyn_cast(operands[0])) { - // Operand is a splat so we can avoid expanding the value out and - // just fold based on the splat value. - auto elementResult = calculate(val.getSplatValue()); - return DenseElementsAttr::get(val.getType(), elementResult); - } - if (auto val = dyn_cast(operands[0])) { - // Operand is ElementsAttr-derived; perform an element-wise fold by - // expanding the values. - auto valIt = val.getValues().begin(); - SmallVector elementResults; - elementResults.reserve(val.getNumElements()); - for (size_t i = 0, e = val.getNumElements(); i < e; ++i, ++valIt) - elementResults.push_back(calculate(*valIt)); - return DenseElementsAttr::get(val.getType(), elementResults); - } - return {}; -} - -template > -static Attribute constFoldTernaryOp(ArrayRef operands, - const CalculationT &calculate) { - assert(operands.size() == 3 && "ternary op takes three operands"); - if (!operands[0] || !operands[1] || !operands[2]) - return {}; - - if (isa(operands[0]) && isa(operands[1]) && - isa(operands[2])) { - auto fst = cast(operands[0]); - auto snd = cast(operands[1]); - auto trd = cast(operands[2]); - - return AttrElementT::get( - fst.getType(), - calculate(fst.getValue(), snd.getValue(), trd.getValue())); - } - if (isa(operands[0]) && - isa(operands[1]) && - isa(operands[2])) { - // Operands are splats so we can avoid expanding the values out and - // just fold based on the splat value. - auto fst = cast(operands[0]); - auto snd = cast(operands[1]); - auto trd = cast(operands[2]); - - auto elementResult = calculate(fst.getSplatValue(), - snd.getSplatValue(), - trd.getSplatValue()); - return DenseElementsAttr::get(fst.getType(), elementResult); - } - if (isa(operands[0]) && isa(operands[1]) && - isa(operands[2])) { - // Operands are ElementsAttr-derived; perform an element-wise fold by - // expanding the values. - auto fst = cast(operands[0]); - auto snd = cast(operands[1]); - auto trd = cast(operands[2]); - - auto fstIt = fst.getValues().begin(); - auto sndIt = snd.getValues().begin(); - auto trdIt = trd.getValues().begin(); - SmallVector elementResults; - elementResults.reserve(fst.getNumElements()); - for (size_t i = 0, e = fst.getNumElements(); i < e; - ++i, ++fstIt, ++sndIt, ++trdIt) - elementResults.push_back(calculate(*fstIt, *sndIt, *trdIt)); - return DenseElementsAttr::get(fst.getType(), elementResults); - } - return {}; -} - -namespace { - -struct constant_int_all_ones_matcher { - bool match(Operation *op) { - APInt value; - return mlir::detail::constant_int_value_binder(&value).match(op) && - value.isAllOnes(); - } -}; - -} // anonymous namespace +using namespace llhd; unsigned circt::llhd::getLLHDTypeWidth(Type type) { if (auto sig = dyn_cast(type)) @@ -151,10 +49,6 @@ Type circt::llhd::getLLHDElementType(Type type) { return type; } -//===---------------------------------------------------------------------===// -// LLHD Operations -//===---------------------------------------------------------------------===// - //===----------------------------------------------------------------------===// // ConstantTimeOp //===----------------------------------------------------------------------===// @@ -172,6 +66,15 @@ void llhd::ConstantTimeOp::build(OpBuilder &builder, OperationState &result, return build(builder, result, TimeType::get(ctx), attr); } +//===----------------------------------------------------------------------===// +// SignalOp +//===----------------------------------------------------------------------===// + +void SignalOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { + if (getName() && !getName()->empty()) + setNameFn(getResult(), *getName()); +} + //===----------------------------------------------------------------------===// // SigExtractOp and PtrExtractOp //===----------------------------------------------------------------------===// @@ -351,193 +254,6 @@ LogicalResult llhd::ConnectOp::canonicalize(llhd::ConnectOp op, return success(); } -//===----------------------------------------------------------------------===// -// RegOp -//===----------------------------------------------------------------------===// - -ParseResult llhd::RegOp::parse(OpAsmParser &parser, OperationState &result) { - OpAsmParser::UnresolvedOperand signal; - Type signalType; - SmallVector valueOperands; - SmallVector triggerOperands; - SmallVector delayOperands; - SmallVector gateOperands; - SmallVector valueTypes; - llvm::SmallVector modesArray; - llvm::SmallVector gateMask; - int64_t gateCount = 0; - - if (parser.parseOperand(signal)) - return failure(); - while (succeeded(parser.parseOptionalComma())) { - OpAsmParser::UnresolvedOperand value; - OpAsmParser::UnresolvedOperand trigger; - OpAsmParser::UnresolvedOperand delay; - OpAsmParser::UnresolvedOperand gate; - Type valueType; - StringAttr modeAttr; - NamedAttrList attrStorage; - - if (parser.parseLParen()) - return failure(); - if (parser.parseOperand(value) || parser.parseComma()) - return failure(); - if (parser.parseAttribute(modeAttr, parser.getBuilder().getNoneType(), - "modes", attrStorage)) - return failure(); - auto attrOptional = llhd::symbolizeRegMode(modeAttr.getValue()); - if (!attrOptional) - return parser.emitError(parser.getCurrentLocation(), - "invalid string attribute"); - modesArray.push_back(static_cast(*attrOptional)); - if (parser.parseOperand(trigger)) - return failure(); - if (parser.parseKeyword("after") || parser.parseOperand(delay)) - return failure(); - if (succeeded(parser.parseOptionalKeyword("if"))) { - gateMask.push_back(++gateCount); - if (parser.parseOperand(gate)) - return failure(); - gateOperands.push_back(gate); - } else { - gateMask.push_back(0); - } - if (parser.parseColon() || parser.parseType(valueType) || - parser.parseRParen()) - return failure(); - valueOperands.push_back(value); - triggerOperands.push_back(trigger); - delayOperands.push_back(delay); - valueTypes.push_back(valueType); - } - if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() || - parser.parseType(signalType)) - return failure(); - if (parser.resolveOperand(signal, signalType, result.operands)) - return failure(); - if (parser.resolveOperands(valueOperands, valueTypes, - parser.getCurrentLocation(), result.operands)) - return failure(); - for (auto operand : triggerOperands) - if (parser.resolveOperand(operand, parser.getBuilder().getI1Type(), - result.operands)) - return failure(); - for (auto operand : delayOperands) - if (parser.resolveOperand( - operand, llhd::TimeType::get(parser.getBuilder().getContext()), - result.operands)) - return failure(); - for (auto operand : gateOperands) - if (parser.resolveOperand(operand, parser.getBuilder().getI1Type(), - result.operands)) - return failure(); - result.addAttribute("gateMask", - parser.getBuilder().getI64ArrayAttr(gateMask)); - result.addAttribute("modes", parser.getBuilder().getI64ArrayAttr(modesArray)); - llvm::SmallVector operandSizes; - operandSizes.push_back(1); - operandSizes.push_back(valueOperands.size()); - operandSizes.push_back(triggerOperands.size()); - operandSizes.push_back(delayOperands.size()); - operandSizes.push_back(gateOperands.size()); - result.addAttribute("operandSegmentSizes", - parser.getBuilder().getDenseI32ArrayAttr(operandSizes)); - - return success(); -} - -void llhd::RegOp::print(OpAsmPrinter &printer) { - printer << " " << getSignal(); - for (size_t i = 0, e = getValues().size(); i < e; ++i) { - std::optional mode = llhd::symbolizeRegMode( - cast(getModes().getValue()[i]).getInt()); - if (!mode) { - emitError("invalid RegMode"); - return; - } - printer << ", (" << getValues()[i] << ", \"" - << llhd::stringifyRegMode(*mode) << "\" " << getTriggers()[i] - << " after " << getDelays()[i]; - if (hasGate(i)) - printer << " if " << getGateAt(i); - printer << " : " << getValues()[i].getType() << ")"; - } - printer.printOptionalAttrDict((*this)->getAttrs(), - {"modes", "gateMask", "operandSegmentSizes"}); - printer << " : " << getSignal().getType(); -} - -LogicalResult llhd::RegOp::verify() { - // At least one trigger has to be present - if (getTriggers().size() < 1) - return emitError("At least one trigger quadruple has to be present."); - - // Values variadic operand must have the same size as the triggers variadic - if (getValues().size() != getTriggers().size()) - return emitOpError("Number of 'values' is not equal to the number of " - "'triggers', got ") - << getValues().size() << " modes, but " << getTriggers().size() - << " triggers!"; - - // Delay variadic operand must have the same size as the triggers variadic - if (getDelays().size() != getTriggers().size()) - return emitOpError("Number of 'delays' is not equal to the number of " - "'triggers', got ") - << getDelays().size() << " modes, but " << getTriggers().size() - << " triggers!"; - - // Array Attribute of RegModes must have the same number of elements as the - // variadics - if (getModes().size() != getTriggers().size()) - return emitOpError("Number of 'modes' is not equal to the number of " - "'triggers', got ") - << getModes().size() << " modes, but " << getTriggers().size() - << " triggers!"; - - // Array Attribute 'gateMask' must have the same number of elements as the - // triggers and values variadics - if (getGateMask().size() != getTriggers().size()) - return emitOpError("Size of 'gateMask' is not equal to the size of " - "'triggers', got ") - << getGateMask().size() << " modes, but " << getTriggers().size() - << " triggers!"; - - // Number of non-zero elements in 'gateMask' has to be the same as the size - // of the gates variadic, also each number from 1 to size-1 has to occur - // only once and in increasing order - unsigned counter = 0; - unsigned prevElement = 0; - for (Attribute maskElem : getGateMask().getValue()) { - int64_t val = cast(maskElem).getInt(); - if (val < 0) - return emitError("Element in 'gateMask' must not be negative!"); - if (val == 0) - continue; - if (val != ++prevElement) - return emitError( - "'gateMask' has to contain every number from 1 to the " - "number of gates minus one exactly once in increasing order " - "(may have zeros in-between)."); - counter++; - } - if (getGates().size() != counter) - return emitError("The number of non-zero elements in 'gateMask' and the " - "size of the 'gates' variadic have to match."); - - // Each value must be either the same type as the 'signal' or the underlying - // type of the 'signal' - for (auto val : getValues()) { - if (val.getType() != getSignal().getType() && - val.getType() != - cast(getSignal().getType()).getElementType()) { - return emitOpError( - "type of each 'value' has to be either the same as the " - "type of 'signal' or the underlying type of 'signal'"); - } - } - return success(); -} - #include "circt/Dialect/LLHD/IR/LLHDEnums.cpp.inc" #define GET_OP_CLASSES diff --git a/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp b/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp index bab5cb513ffd..27b7a6afe3fe 100644 --- a/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp +++ b/lib/Dialect/LLHD/Transforms/EarlyCodeMotionPass.cpp @@ -70,7 +70,7 @@ void EarlyCodeMotionPass::runOnProcess(llhd::ProcessOp proc) { for (auto iter = block->getOperations().begin(); iter != block->getOperations().end(); ++iter) { Operation &op = *iter; - if (!isa(op) && !isa(op) && + if (!isa(op) && !isa(op) && (!mlir::isMemoryEffectFree(&op) || op.hasTrait())) continue; diff --git a/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp b/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp index d20a5f437e88..2ec645d6ae9c 100644 --- a/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp +++ b/lib/Dialect/LLHD/Transforms/ProcessLoweringPass.cpp @@ -34,40 +34,6 @@ struct ProcessLoweringPass void runOnOperation() override; }; -/// Backtrack a signal value and make sure that every part of it is in the -/// observer list at some point. Assumes that there is no operation that adds -/// parts to a signal that it does not take as input (e.g. something like -/// llhd.sig.zext %sig : !hw.inout -> !hw.inout). -static LogicalResult checkSignalsAreObserved(OperandRange obs, Value value) { - // If the value in the observer list, we don't need to backtrack further. - if (llvm::is_contained(obs, value)) - return success(); - - if (Operation *op = value.getDefiningOp()) { - // If no input is a signal, this operation creates one and thus this is the - // last point where it could have been observed. As we've already checked - // that, we can fail here. This includes for example llhd.sig - if (llvm::none_of(op->getOperands(), [](Value arg) { - return isa(arg.getType()); - })) - return failure(); - - // Only recusively backtrack signal values. Other values cannot be changed - // from outside or with a delay. If they come from probes at some point, - // they are covered by that probe. As soon as we find a signal that is not - // observed no matter how far we backtrack, we fail. - return success(llvm::all_of(op->getOperands(), [&](Value arg) { - return !isa(arg.getType()) || - succeeded(checkSignalsAreObserved(obs, arg)); - })); - } - - // If the value is a module argument (no block arguments except for the entry - // block are allowed here) and was not observed, we cannot backtrack further - // and thus fail. - return failure(); -} - static LogicalResult isProcValidToLower(llhd::ProcessOp op) { size_t numBlocks = op.getBody().getBlocks().size(); @@ -98,13 +64,24 @@ static LogicalResult isProcValidToLower(llhd::ProcessOp op) { "during process-lowering: llhd.wait terminators with optional time " "argument cannot be lowered to structural LLHD"); + SmallVector observedSignals; + for (Value obs : wait.getObserved()) + if (auto prb = obs.getDefiningOp()) + if (!op.getBody().isAncestor(prb->getParentRegion())) + observedSignals.push_back(prb.getSignal()); + // Every probed signal has to occur in the observed signals list in // the wait instruction - WalkResult result = op.walk([&wait](llhd::PrbOp prbOp) -> WalkResult { - if (failed(checkSignalsAreObserved(wait.getObs(), prbOp.getSignal()))) - return wait.emitOpError( - "during process-lowering: the wait terminator is required to " - "have all probed signals as arguments"); + WalkResult result = op.walk([&](Operation *operation) -> WalkResult { + // TODO: value does not need to be observed if all values this value is + // a combinatorial result of are observed. + for (Value operand : operation->getOperands()) + if (!op.getBody().isAncestor(operand.getParentRegion()) && + !llvm::is_contained(wait.getObserved(), operand) && + !llvm::is_contained(observedSignals, operand)) + return wait.emitOpError( + "during process-lowering: the wait terminator is required to " + "have values used in the process as arguments"); return WalkResult::advance(); }); diff --git a/lib/Dialect/Moore/CMakeLists.txt b/lib/Dialect/Moore/CMakeLists.txt index 4cb49c6d18a2..941d420ec110 100644 --- a/lib/Dialect/Moore/CMakeLists.txt +++ b/lib/Dialect/Moore/CMakeLists.txt @@ -1,4 +1,5 @@ add_circt_dialect_library(CIRCTMoore + MooreAttributes.cpp MooreDialect.cpp MooreOps.cpp MooreTypes.cpp diff --git a/lib/Dialect/Moore/MooreAttributes.cpp b/lib/Dialect/Moore/MooreAttributes.cpp new file mode 100644 index 000000000000..4cb8dd159b01 --- /dev/null +++ b/lib/Dialect/Moore/MooreAttributes.cpp @@ -0,0 +1,68 @@ +//===- MooreAttributes.cpp - Implement the Moore attributes ---------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the Moore dialect attributes. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Moore/MooreAttributes.h" +#include "circt/Dialect/Moore/MooreDialect.h" +#include "circt/Dialect/Moore/MooreTypes.h" +#include "mlir/IR/DialectImplementation.h" +#include "llvm/ADT/TypeSwitch.h" + +using namespace circt; +using namespace circt::moore; +using mlir::AsmParser; +using mlir::AsmPrinter; + +//===----------------------------------------------------------------------===// +// FVIntegerAttr +//===----------------------------------------------------------------------===// + +Attribute FVIntegerAttr::parse(AsmParser &p, Type) { + // Parse the value and width specifier. + FVInt value; + unsigned width; + llvm::SMLoc widthLoc; + if (p.parseLess() || parseFVInt(p, value) || p.parseColon() || + p.getCurrentLocation(&widthLoc) || p.parseInteger(width) || + p.parseGreater()) + return {}; + + // Make sure the integer fits into the requested number of bits. + unsigned neededBits = + value.isNegative() ? value.getSignificantBits() : value.getActiveBits(); + if (width < neededBits) { + p.emitError(widthLoc) << "integer literal requires at least " << neededBits + << " bits, but attribute specifies only " << width; + return {}; + } + + return FVIntegerAttr::get(p.getContext(), value.sextOrTrunc(width)); +} + +void FVIntegerAttr::print(AsmPrinter &p) const { + p << "<"; + printFVInt(p, getValue()); + p << " : " << getValue().getBitWidth() << ">"; +} + +//===----------------------------------------------------------------------===// +// Generated logic +//===----------------------------------------------------------------------===// + +#define GET_ATTRDEF_CLASSES +#include "circt/Dialect/Moore/MooreAttributes.cpp.inc" + +void MooreDialect::registerAttributes() { + addAttributes< +#define GET_ATTRDEF_LIST +#include "circt/Dialect/Moore/MooreAttributes.cpp.inc" + >(); +} diff --git a/lib/Dialect/Moore/MooreDialect.cpp b/lib/Dialect/Moore/MooreDialect.cpp index 3aa3a6e37bfd..72f42265929e 100644 --- a/lib/Dialect/Moore/MooreDialect.cpp +++ b/lib/Dialect/Moore/MooreDialect.cpp @@ -22,8 +22,9 @@ using namespace circt::moore; //===----------------------------------------------------------------------===// void MooreDialect::initialize() { - // Register types. + // Register types and attributes. registerTypes(); + registerAttributes(); // Register operations. addOperations< @@ -32,4 +33,13 @@ void MooreDialect::initialize() { >(); } +Operation *MooreDialect::materializeConstant(OpBuilder &builder, + Attribute value, Type type, + Location loc) { + if (auto intType = dyn_cast(type)) + if (auto intValue = dyn_cast(value)) + return builder.create(loc, intType, intValue); + return nullptr; +} + #include "circt/Dialect/Moore/MooreDialect.cpp.inc" diff --git a/lib/Dialect/Moore/MooreOps.cpp b/lib/Dialect/Moore/MooreOps.cpp index 4897e3ff2916..b04d13d3d517 100644 --- a/lib/Dialect/Moore/MooreOps.cpp +++ b/lib/Dialect/Moore/MooreOps.cpp @@ -13,6 +13,7 @@ #include "circt/Dialect/Moore/MooreOps.h" #include "circt/Dialect/HW/CustomDirectiveImpl.h" #include "circt/Dialect/HW/ModuleImplementation.h" +#include "circt/Dialect/Moore/MooreAttributes.h" #include "circt/Support/CustomDirectiveImpl.h" #include "mlir/IR/Builders.h" #include "llvm/ADT/SmallString.h" @@ -259,30 +260,104 @@ void VariableOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { setNameFn(getResult(), *getName()); } -llvm::SmallVector VariableOp::getPromotableSlots() { - if (isa(getOperation()->getParentOp())) +LogicalResult VariableOp::canonicalize(VariableOp op, + PatternRewriter &rewriter) { + // If the variable is embedded in an SSACFG region, move the initial value + // into an assignment immediately after the variable op. This allows the + // mem2reg pass which cannot handle variables with initial values. + auto initial = op.getInitial(); + if (initial && mlir::mayHaveSSADominance(*op->getParentRegion())) { + rewriter.modifyOpInPlace(op, [&] { op.getInitialMutable().clear(); }); + rewriter.setInsertionPointAfter(op); + rewriter.create(initial.getLoc(), op, initial); + return success(); + } + + // Check if the variable has one unique continuous assignment to it, all other + // uses are reads, and that all uses are in the same block as the variable + // itself. + auto *block = op->getBlock(); + ContinuousAssignOp uniqueAssignOp; + for (auto *user : op->getUsers()) { + // Ensure that all users of the variable are in the same block. + if (user->getBlock() != block) + return failure(); + + // Ensure there is at most one unique continuous assignment to the variable. + if (auto assignOp = dyn_cast(user)) { + if (uniqueAssignOp) + return failure(); + uniqueAssignOp = assignOp; + continue; + } + + // Ensure all other users are reads. + if (!isa(user)) + return failure(); + } + if (!uniqueAssignOp) + return failure(); + + // If the original variable had a name, create an `AssignedVariableOp` as a + // replacement. Otherwise substitute the assigned value directly. + Value assignedValue = uniqueAssignOp.getSrc(); + if (auto name = op.getNameAttr(); name && !name.empty()) + assignedValue = rewriter.create( + op.getLoc(), name, uniqueAssignOp.getSrc()); + + // Remove the assign op and replace all reads with the new assigned var op. + rewriter.eraseOp(uniqueAssignOp); + for (auto *user : llvm::make_early_inc_range(op->getUsers())) { + auto readOp = cast(user); + rewriter.replaceOp(readOp, assignedValue); + } + + // Remove the original variable. + rewriter.eraseOp(op); + return success(); +} + +SmallVector VariableOp::getPromotableSlots() { + // We cannot promote variables with an initial value, since that value may not + // dominate the location where the default value needs to be constructed. + if (mlir::mayBeGraphRegion(*getOperation()->getParentRegion()) || + getInitial()) return {}; - if (getInitial()) + + // Ensure that `getDefaultValue` can conjure up a default value for the + // variable's type. + if (!isa(getType().getNestedType())) return {}; - return {MemorySlot{getResult(), getType()}}; + + return {MemorySlot{getResult(), getType().getNestedType()}}; } Value VariableOp::getDefaultValue(const MemorySlot &slot, OpBuilder &builder) { - if (auto value = getInitial()) - return value; - return builder.create( - getLoc(), - cast(cast(slot.elemType).getNestedType()), 0); + auto packedType = dyn_cast(slot.elemType); + if (!packedType) + return {}; + auto bitWidth = packedType.getBitSize(); + if (!bitWidth) + return {}; + auto fvint = packedType.getDomain() == Domain::FourValued + ? FVInt::getAllX(*bitWidth) + : FVInt::getZero(*bitWidth); + Value value = builder.create( + getLoc(), IntType::get(getContext(), *bitWidth, packedType.getDomain()), + fvint); + if (value.getType() != packedType) + builder.create(getLoc(), packedType, value); + return value; } void VariableOp::handleBlockArgument(const MemorySlot &slot, BlockArgument argument, OpBuilder &builder) {} -::std::optional<::mlir::PromotableAllocationOpInterface> +std::optional VariableOp::handlePromotionComplete(const MemorySlot &slot, Value defaultValue, OpBuilder &builder) { - if (defaultValue.use_empty()) + if (defaultValue && defaultValue.use_empty()) defaultValue.getDefiningOp()->erase(); this->erase(); return std::nullopt; @@ -402,34 +477,139 @@ void NetOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { setNameFn(getResult(), *getName()); } +LogicalResult NetOp::canonicalize(NetOp op, PatternRewriter &rewriter) { + bool modified = false; + + // Check if the net has one unique continuous assignment to it, and + // additionally if all other users are reads. + auto *block = op->getBlock(); + ContinuousAssignOp uniqueAssignOp; + bool allUsesAreReads = true; + for (auto *user : op->getUsers()) { + // Ensure that all users of the net are in the same block. + if (user->getBlock() != block) + return failure(); + + // Ensure there is at most one unique continuous assignment to the net. + if (auto assignOp = dyn_cast(user)) { + if (uniqueAssignOp) + return failure(); + uniqueAssignOp = assignOp; + continue; + } + + // Ensure all other users are reads. + if (!isa(user)) + allUsesAreReads = false; + } + + // If there was one unique assignment, and the `NetOp` does not yet have an + // assigned value set, fold the assignment into the net. + if (uniqueAssignOp && !op.getAssignment()) { + rewriter.modifyOpInPlace( + op, [&] { op.getAssignmentMutable().assign(uniqueAssignOp.getSrc()); }); + rewriter.eraseOp(uniqueAssignOp); + modified = true; + uniqueAssignOp = {}; + } + + // If all users of the net op are reads, and any potential unique assignment + // has been folded into the net op itself, directly replace the reads with the + // net's assigned value. + if (!uniqueAssignOp && allUsesAreReads && op.getAssignment()) { + // If the original net had a name, create an `AssignedVariableOp` as a + // replacement. Otherwise substitute the assigned value directly. + auto assignedValue = op.getAssignment(); + if (auto name = op.getNameAttr(); name && !name.empty()) + assignedValue = + rewriter.create(op.getLoc(), name, assignedValue); + + // Replace all reads with the new assigned var op and remove the original + // net op. + for (auto *user : llvm::make_early_inc_range(op->getUsers())) { + auto readOp = cast(user); + rewriter.replaceOp(readOp, assignedValue); + } + rewriter.eraseOp(op); + modified = true; + } + + return success(modified); +} + //===----------------------------------------------------------------------===// -// AssignedVarOp +// AssignedVariableOp //===----------------------------------------------------------------------===// -void AssignedVarOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { +void AssignedVariableOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) { if (getName() && !getName()->empty()) setNameFn(getResult(), *getName()); } +LogicalResult AssignedVariableOp::canonicalize(AssignedVariableOp op, + PatternRewriter &rewriter) { + // Eliminate chained variables with the same name. + // var(name, var(name, x)) -> var(name, x) + if (auto otherOp = op.getInput().getDefiningOp()) { + if (otherOp.getNameAttr() == op.getNameAttr()) { + rewriter.replaceOp(op, otherOp); + return success(); + } + } + + // Eliminate variables that alias an input port of the same name. + if (auto blockArg = dyn_cast(op.getInput())) { + if (auto moduleOp = + dyn_cast(blockArg.getOwner()->getParentOp())) { + auto moduleType = moduleOp.getModuleType(); + auto portName = moduleType.getInputNameAttr(blockArg.getArgNumber()); + if (portName == op.getNameAttr()) { + rewriter.replaceOp(op, blockArg); + return success(); + } + } + } + + // Eliminate variables that feed an output port of the same name. + for (auto &use : op->getUses()) { + auto outputOp = dyn_cast(use.getOwner()); + if (!outputOp) + continue; + auto moduleOp = dyn_cast(outputOp.getParentOp()); + if (!moduleOp) + break; + auto moduleType = moduleOp.getModuleType(); + auto portName = moduleType.getOutputNameAttr(use.getOperandNumber()); + if (portName == op.getNameAttr()) { + rewriter.replaceOp(op, op.getInput()); + return success(); + } + } + + return failure(); +} + //===----------------------------------------------------------------------===// // ConstantOp //===----------------------------------------------------------------------===// void ConstantOp::print(OpAsmPrinter &p) { p << " "; - p.printAttributeWithoutType(getValueAttr()); + printFVInt(p, getValue()); p.printOptionalAttrDict((*this)->getAttrs(), /*elidedAttrs=*/{"value"}); p << " : "; p.printStrippedAttrOrType(getType()); } ParseResult ConstantOp::parse(OpAsmParser &parser, OperationState &result) { - // Parse the constant value without bit width. - APInt value; + // Parse the constant value. + FVInt value; auto valueLoc = parser.getCurrentLocation(); + if (parseFVInt(parser, value)) + return failure(); - if (parser.parseInteger(value) || - parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()) + // Parse any optional attributes and the `:`. + if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon()) return failure(); // Parse the result type. @@ -449,16 +629,21 @@ ParseResult ConstantOp::parse(OpAsmParser &parser, OperationState &result) { unsigned neededBits = value.isNegative() ? value.getSignificantBits() : value.getActiveBits(); if (type.getWidth() < neededBits) - return parser.emitError(valueLoc, - "constant out of range for result type ") - << type; + return parser.emitError(valueLoc) + << "value requires " << neededBits + << " bits, but result type only has " << type.getWidth(); value = value.trunc(type.getWidth()); } - // Build the attribute and op. - auto attrType = IntegerType::get(parser.getContext(), type.getWidth()); - auto attrValue = IntegerAttr::get(attrType, value); + // If the constant contains any X or Z bits, the result type must be + // four-valued. + if (value.hasUnknown() && type.getDomain() != Domain::FourValued) + return parser.emitError(valueLoc) + << "value contains X or Z bits, but result type " << type + << " only allows two-valued bits"; + // Build the attribute and op. + auto attrValue = FVIntegerAttr::get(parser.getContext(), value); result.addAttribute("value", attrValue); result.addTypes(type); return success(); @@ -473,12 +658,18 @@ LogicalResult ConstantOp::verify() { return success(); } +void ConstantOp::build(OpBuilder &builder, OperationState &result, IntType type, + const FVInt &value) { + assert(type.getWidth() == value.getBitWidth() && + "FVInt width must match type width"); + build(builder, result, type, FVIntegerAttr::get(builder.getContext(), value)); +} + void ConstantOp::build(OpBuilder &builder, OperationState &result, IntType type, const APInt &value) { assert(type.getWidth() == value.getBitWidth() && "APInt width must match type width"); - build(builder, result, type, - builder.getIntegerAttr(builder.getIntegerType(type.getWidth()), value)); + build(builder, result, type, FVInt(value)); } /// This builder allows construction of small signed integers like 0, 1, -1 @@ -544,6 +735,39 @@ LogicalResult ConcatRefOp::inferReturnTypes( return success(); } +//===----------------------------------------------------------------------===// +// ArrayCreateOp +//===----------------------------------------------------------------------===// + +static std::pair getArrayElements(Type type) { + if (auto arrayType = dyn_cast(type)) + return {arrayType.getSize(), arrayType.getElementType()}; + if (auto arrayType = dyn_cast(type)) + return {arrayType.getSize(), arrayType.getElementType()}; + assert(0 && "expected ArrayType or UnpackedArrayType"); + return {}; +} + +LogicalResult ArrayCreateOp::verify() { + auto [size, elementType] = getArrayElements(getType()); + + // Check that the number of operands matches the array size. + if (getElements().size() != size) + return emitOpError() << "has " << getElements().size() + << " operands, but result type requires " << size; + + // Check that the operand types match the array element type. We only need to + // check one of the operands, since the `SameTypeOperands` trait ensures all + // operands have the same type. + if (size > 0) { + auto value = getElements()[0]; + if (value.getType() != elementType) + return emitOpError() << "operands have type " << value.getType() + << ", but array requires " << elementType; + } + return success(); +} + //===----------------------------------------------------------------------===// // StructCreateOp //===----------------------------------------------------------------------===// @@ -849,6 +1073,23 @@ OpFoldResult ConversionOp::fold(FoldAdaptor adaptor) { // Fold away no-op casts. if (getInput().getType() == getResult().getType()) return getInput(); + + // Convert domains of constant integer inputs. + auto intInput = dyn_cast_or_null(adaptor.getInput()); + auto fromIntType = dyn_cast(getInput().getType()); + auto toIntType = dyn_cast(getResult().getType()); + if (intInput && fromIntType && toIntType && + fromIntType.getWidth() == toIntType.getWidth()) { + // If we are going *to* a four-valued type, simply pass through the + // constant. + if (toIntType.getDomain() == Domain::FourValued) + return intInput; + + // Otherwise map all unknown bits to zero (the default in SystemVerilog) and + // return a new constant. + return FVIntegerAttr::get(getContext(), intInput.getValue().toAPInt(false)); + } + return {}; } @@ -888,8 +1129,7 @@ bool BlockingAssignOp::canUsesBeRemoved( return false; Value blockingUse = (*blockingUses.begin())->get(); return blockingUse == slot.ptr && getDst() == slot.ptr && - getSrc() != slot.ptr && - getSrc().getType() == cast(slot.elemType).getNestedType(); + getSrc() != slot.ptr && getSrc().getType() == slot.elemType; } DeletionKind BlockingAssignOp::removeBlockingUses( @@ -933,7 +1173,7 @@ LogicalResult BlockingAssignOp::canonicalize(BlockingAssignOp op, //===----------------------------------------------------------------------===// bool ReadOp::loadsFrom(const MemorySlot &slot) { - return getOperand() == slot.ptr; + return getInput() == slot.ptr; } bool ReadOp::storesTo(const MemorySlot &slot) { return false; } @@ -952,7 +1192,7 @@ bool ReadOp::canUsesBeRemoved(const MemorySlot &slot, return false; Value blockingUse = (*blockingUses.begin())->get(); return blockingUse == slot.ptr && getOperand() == slot.ptr && - getResult().getType() == cast(slot.elemType).getNestedType(); + getResult().getType() == slot.elemType; } DeletionKind @@ -977,6 +1217,74 @@ LogicalResult ReadOp::canonicalize(ReadOp op, PatternRewriter &rewriter) { return failure(); } +//===----------------------------------------------------------------------===// +// PowSOp +//===----------------------------------------------------------------------===// + +static OpFoldResult powCommonFolding(MLIRContext *ctxt, Attribute lhs, + Attribute rhs) { + auto lhsValue = dyn_cast_or_null(lhs); + if (lhsValue && lhsValue.getValue() == 1) + return lhs; + + auto rhsValue = dyn_cast_or_null(rhs); + if (rhsValue && rhsValue.getValue().isZero()) + return FVIntegerAttr::get(ctxt, + FVInt(rhsValue.getValue().getBitWidth(), 1)); + + return {}; +} + +OpFoldResult PowSOp::fold(FoldAdaptor adaptor) { + return powCommonFolding(getContext(), adaptor.getLhs(), adaptor.getRhs()); +} + +LogicalResult PowSOp::canonicalize(PowSOp op, PatternRewriter &rewriter) { + Location loc = op.getLoc(); + auto intType = cast(op.getRhs().getType()); + if (auto baseOp = op.getLhs().getDefiningOp()) { + if (baseOp.getValue() == 2) { + Value constOne = rewriter.create(loc, intType, 1); + Value constZero = rewriter.create(loc, intType, 0); + Value shift = rewriter.create(loc, constOne, op.getRhs()); + Value isNegative = rewriter.create(loc, op.getRhs(), constZero); + auto condOp = rewriter.replaceOpWithNewOp( + op, op.getLhs().getType(), isNegative); + Block *thenBlock = rewriter.createBlock(&condOp.getTrueRegion()); + rewriter.setInsertionPointToStart(thenBlock); + rewriter.create(loc, constZero); + Block *elseBlock = rewriter.createBlock(&condOp.getFalseRegion()); + rewriter.setInsertionPointToStart(elseBlock); + rewriter.create(loc, shift); + return success(); + } + } + + return failure(); +} + +//===----------------------------------------------------------------------===// +// PowUOp +//===----------------------------------------------------------------------===// + +OpFoldResult PowUOp::fold(FoldAdaptor adaptor) { + return powCommonFolding(getContext(), adaptor.getLhs(), adaptor.getRhs()); +} + +LogicalResult PowUOp::canonicalize(PowUOp op, PatternRewriter &rewriter) { + Location loc = op.getLoc(); + auto intType = cast(op.getRhs().getType()); + if (auto baseOp = op.getLhs().getDefiningOp()) { + if (baseOp.getValue() == 2) { + Value constOne = rewriter.create(loc, intType, 1); + rewriter.replaceOpWithNewOp(op, constOne, op.getRhs()); + return success(); + } + } + + return failure(); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp b/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp index 1054a3f8e899..c80f3e731a39 100644 --- a/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp +++ b/lib/Dialect/Moore/Transforms/SimplifyProcedures.cpp @@ -43,8 +43,9 @@ void SimplifyProceduresPass::runOnOperation() { // Use to collect blocking assignments that have been replaced by a "shadow" // variable. - DenseSet assignOps; - for (auto &nestedOp : procedureOp) { + procedureOp.walk([&](Operation *op) { + SmallVector> assignOps; + auto &nestedOp = *op; // Only create a "shadow" varaible for the global variable used by other // operations in the procedure body. if (isa(nestedOp) && @@ -54,27 +55,28 @@ void SimplifyProceduresPass::runOnOperation() { DenseSet users; for (auto *user : nestedOp.getOperand(0).getUsers()) // Ensuring don't handle the users existing in another procedure body. - if (user->getBlock() == procedureOp.getBody()) + if (procedureOp->isAncestor(user)) users.insert(user); // Because the operand of moore.event_wait is net. if (auto varOp = llvm::dyn_cast_or_null( nestedOp.getOperand(0).getDefiningOp())) { auto resultType = varOp.getResult().getType(); - builder.setInsertionPointToStart(procedureOp.getBody()); + builder.setInsertionPointToStart(&procedureOp.getBody().front()); auto readOp = builder.create( nestedOp.getLoc(), cast(resultType).getNestedType(), varOp.getResult()); auto newVarOp = builder.create( - nestedOp.getLoc(), resultType, StringAttr{}, readOp); + nestedOp.getLoc(), resultType, StringAttr{}, Value{}); + builder.create(nestedOp.getLoc(), newVarOp, readOp); builder.clearInsertionPoint(); // Replace the users of the global variable with a corresponding // "shadow" variable. for (auto *user : users) { user->replaceUsesOfWith(user->getOperand(0), newVarOp); - if (isa(user)) - assignOps.insert(user); + if (auto assignOp = dyn_cast(user)) + assignOps.push_back({assignOp, newVarOp, varOp}); } } } @@ -82,20 +84,12 @@ void SimplifyProceduresPass::runOnOperation() { // Ensure the global variable has the correct value. So needing to create // a blocking assign for the global variable when the "shadow" variable // has a new value. - for (auto *assignOp : assignOps) - if (auto localVarOp = llvm::dyn_cast_or_null( - assignOp->getOperand(0).getDefiningOp())) { - auto resultType = localVarOp.getResult().getType(); - builder.setInsertionPointAfter(assignOp); - auto readOp = builder.create( - localVarOp.getLoc(), cast(resultType).getNestedType(), - localVarOp.getResult()); - builder.create( - nestedOp.getLoc(), - localVarOp.getInitial().getDefiningOp()->getOperand(0), readOp); - builder.clearInsertionPoint(); - assignOps.erase(assignOp); - } - } + for (auto [assignOp, localVar, var] : assignOps) { + builder.setInsertionPointAfter(assignOp); + auto readOp = builder.create(assignOp.getLoc(), localVar); + builder.create(assignOp.getLoc(), var, readOp); + builder.clearInsertionPoint(); + } + }); }); } diff --git a/lib/Dialect/OM/Evaluator/Evaluator.cpp b/lib/Dialect/OM/Evaluator/Evaluator.cpp index d029cfc9df55..e100a0e8d086 100644 --- a/lib/Dialect/OM/Evaluator/Evaluator.cpp +++ b/lib/Dialect/OM/Evaluator/Evaluator.cpp @@ -167,10 +167,10 @@ FailureOr circt::om::Evaluator::getOrCreateValue( evaluator::PathValue::getEmptyPath(loc)); return success(result); }) - .Case( - [&](auto op) { - return getPartiallyEvaluatedValue(op.getType(), loc); - }) + .Case([&](auto op) { + return getPartiallyEvaluatedValue(op.getType(), loc); + }) .Case([&](auto op) { return getPartiallyEvaluatedValue(op.getType(), op.getLoc()); }) @@ -360,6 +360,9 @@ circt::om::Evaluator::evaluateValue(Value value, ActualParameters actualParams, .Case([&](ListCreateOp op) { return evaluateListCreate(op, actualParams, loc); }) + .Case([&](ListConcatOp op) { + return evaluateListConcat(op, actualParams, loc); + }) .Case([&](TupleCreateOp op) { return evaluateTupleCreate(op, actualParams, loc); }) @@ -583,6 +586,45 @@ circt::om::Evaluator::evaluateListCreate(ListCreateOp op, return list; } +/// Evaluator dispatch function for List concatenation. +FailureOr +circt::om::Evaluator::evaluateListConcat(ListConcatOp op, + ActualParameters actualParams, + Location loc) { + // Evaluate the List concat op itself, in case it hasn't been evaluated yet. + SmallVector values; + auto list = getOrCreateValue(op, actualParams, loc); + + // Extract the ListValue, either directly or through an object reference. + auto extractList = [](evaluator::EvaluatorValue *value) { + return std::move( + llvm::TypeSwitch( + value) + .Case([](evaluator::ListValue *val) { return val; }) + .Case([](evaluator::ReferenceValue *val) { + return cast(val->getStrippedValue()->get()); + })); + }; + + for (auto operand : op.getOperands()) { + auto result = evaluateValue(operand, actualParams, loc); + if (failed(result)) + return result; + if (!result.value()->isFullyEvaluated()) + return list; + + // Append each EvaluatorValue from the sublist. + evaluator::ListValue *subList = extractList(result.value().get()); + for (auto subValue : subList->getElements()) + values.push_back(subValue); + } + + // Return the concatenated list. + llvm::cast(list.value().get()) + ->setElements(std::move(values)); + return list; +} + /// Evaluator dispatch function for Tuple creation. FailureOr circt::om::Evaluator::evaluateTupleCreate(TupleCreateOp op, diff --git a/lib/Dialect/Sim/CMakeLists.txt b/lib/Dialect/Sim/CMakeLists.txt index db239ddd2652..395e7216dfc8 100644 --- a/lib/Dialect/Sim/CMakeLists.txt +++ b/lib/Dialect/Sim/CMakeLists.txt @@ -29,6 +29,7 @@ add_circt_dialect_library(CIRCTSim CIRCTHW CIRCTSeq CIRCTSV + MLIRFuncDialect MLIRIR MLIRPass MLIRTransforms diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp index 8d7c3c96c805..7ae834a12931 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -13,8 +13,10 @@ #include "circt/Dialect/Sim/SimOps.h" #include "circt/Dialect/HW/ModuleImplementation.h" #include "circt/Dialect/SV/SVOps.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Interfaces/FunctionImplementation.h" +#include "llvm/ADT/MapVector.h" using namespace mlir; using namespace circt; @@ -69,12 +71,15 @@ ParseResult DPIFuncOp::parse(OpAsmParser &parser, OperationState &result) { LogicalResult sim::DPICallOp::verifySymbolUses(SymbolTableCollection &symbolTable) { - auto referencedOp = dyn_cast_or_null( - symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr())); + auto referencedOp = + symbolTable.lookupNearestSymbolFrom(*this, getCalleeAttr()); if (!referencedOp) return emitError("cannot find function declaration '") << getCallee() << "'"; - return success(); + if (isa(referencedOp)) + return success(); + return emitError("callee must be 'sim.dpi.func' or 'func.func' but got '") + << referencedOp->getName() << "'"; } void DPIFuncOp::print(OpAsmPrinter &p) { diff --git a/lib/Dialect/Sim/Transforms/CMakeLists.txt b/lib/Dialect/Sim/Transforms/CMakeLists.txt index 6802452cca59..d6caf2ddc26f 100644 --- a/lib/Dialect/Sim/Transforms/CMakeLists.txt +++ b/lib/Dialect/Sim/Transforms/CMakeLists.txt @@ -1,4 +1,5 @@ add_circt_dialect_library(CIRCTSimTransforms + LowerDPIFunc.cpp ProceduralizeSim.cpp @@ -12,8 +13,10 @@ add_circt_dialect_library(CIRCTSimTransforms CIRCTSV CIRCTComb CIRCTSupport + MLIRFuncDialect MLIRIR MLIRPass + MLIRLLVMDialect MLIRSCFDialect MLIRTransformUtils ) diff --git a/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp b/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp new file mode 100644 index 000000000000..dd65b2bcaa8a --- /dev/null +++ b/lib/Dialect/Sim/Transforms/LowerDPIFunc.cpp @@ -0,0 +1,177 @@ +//===- LowerDPIFunc.cpp - Lower sim.dpi.func to func.func ----*- 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 pass lowers Sim DPI func ops to MLIR func and call. +// +// sim.dpi.func @foo(input %a: i32, output %b: i64) +// hw.module @top (..) { +// %result = sim.dpi.call @foo(%a) clock %clock +// } +// +// -> +// +// func.func @foo(%a: i32, %b: !llvm.ptr) // Output is passed by a reference. +// func.func @foo_wrapper(%a: i32) -> (i64) { +// %0 = llvm.alloca: !llvm.ptr +// %v = func.call @foo (%a, %0) +// func.return %v +// } +// hw.module @mod(..) { +// %result = sim.dpi.call @foo_wrapper(%a) clock %clock +// } +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Sim/SimOps.h" +#include "circt/Dialect/Sim/SimPasses.h" +#include "circt/Support/Namespace.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/LLVMIR/LLVMTypes.h" +#include "mlir/Transforms/DialectConversion.h" +#include "llvm/Support/Debug.h" + +#define DEBUG_TYPE "sim-lower-dpi-func" + +namespace circt { +namespace sim { +#define GEN_PASS_DEF_LOWERDPIFUNC +#include "circt/Dialect/Sim/SimPasses.h.inc" +} // namespace sim +} // namespace circt + +using namespace mlir; +using namespace circt; + +//===----------------------------------------------------------------------===// +// Pass Implementation +//===----------------------------------------------------------------------===// + +namespace { + +struct LoweringState { + DenseMap dpiFuncDeclMapping; + circt::Namespace nameSpace; +}; + +struct LowerDPIFuncPass : public sim::impl::LowerDPIFuncBase { + + LogicalResult lowerDPI(); + LogicalResult lowerDPIFuncOp(sim::DPIFuncOp simFunc, + LoweringState &loweringState, + SymbolTable &symbolTable); + void runOnOperation() override; +}; + +} // namespace + +LogicalResult LowerDPIFuncPass::lowerDPIFuncOp(sim::DPIFuncOp simFunc, + LoweringState &loweringState, + SymbolTable &symbolTable) { + ImplicitLocOpBuilder builder(simFunc.getLoc(), simFunc); + auto moduleType = simFunc.getModuleType(); + + llvm::SmallVector dpiFunctionArgumentTypes; + for (auto arg : moduleType.getPorts()) { + // TODO: Support a non-integer type. + if (!arg.type.isInteger()) + return simFunc->emitError() + << "non-integer type argument is unsupported now"; + + if (arg.dir == hw::ModulePort::Input) + dpiFunctionArgumentTypes.push_back(arg.type); + else + // Output must be passed by a reference. + dpiFunctionArgumentTypes.push_back( + LLVM::LLVMPointerType::get(arg.type.getContext())); + } + + auto funcType = builder.getFunctionType(dpiFunctionArgumentTypes, {}); + func::FuncOp func; + + // Look up func.func by verilog name since the function name is equal to the + // symbol name in MLIR + if (auto verilogName = simFunc.getVerilogName()) { + func = symbolTable.lookup(*verilogName); + // TODO: Check if function type matches. + } + + // If a referred function is not in the same module, create an external + // function declaration. + if (!func) { + func = builder.create(simFunc.getVerilogName() + ? *simFunc.getVerilogName() + : simFunc.getSymName(), + funcType); + // External function needs to be private. + func.setPrivate(); + } + + // Create a wrapper module that calls a DPI function. + auto funcOp = builder.create( + loweringState.nameSpace.newName(simFunc.getSymName() + "_wrapper"), + moduleType.getFuncType()); + + // Map old symbol to a new func op. + loweringState.dpiFuncDeclMapping[simFunc.getSymNameAttr()] = funcOp; + + builder.setInsertionPointToStart(funcOp.addEntryBlock()); + SmallVector functionInputs; + SmallVector functionOutputAllocas; + + size_t inputIndex = 0; + for (auto arg : moduleType.getPorts()) { + if (arg.dir == hw::ModulePort::InOut) + return funcOp->emitError() << "inout is currently not supported"; + + if (arg.dir == hw::ModulePort::Input) { + functionInputs.push_back(funcOp.getArgument(inputIndex)); + ++inputIndex; + } else { + // Allocate an output placeholder. + auto one = builder.create(builder.getI64IntegerAttr(1)); + auto alloca = builder.create( + builder.getType(), arg.type, one); + functionInputs.push_back(alloca); + functionOutputAllocas.push_back(alloca); + } + } + + builder.create(func, functionInputs); + + SmallVector results; + for (auto functionOutputAlloca : functionOutputAllocas) + results.push_back(builder.create( + functionOutputAlloca.getElemType(), functionOutputAlloca)); + + builder.create(results); + + simFunc.erase(); + return success(); +} + +LogicalResult LowerDPIFuncPass::lowerDPI() { + LLVM_DEBUG(llvm::dbgs() << "Lowering sim DPI func to func.func\n"); + auto op = getOperation(); + LoweringState state; + state.nameSpace.add(op); + auto &symbolTable = getAnalysis(); + for (auto simFunc : llvm::make_early_inc_range(op.getOps())) + if (failed(lowerDPIFuncOp(simFunc, state, symbolTable))) + return failure(); + + op.walk([&](sim::DPICallOp op) { + auto func = state.dpiFuncDeclMapping.at(op.getCalleeAttr().getAttr()); + op.setCallee(func.getSymNameAttr()); + }); + return success(); +} + +void LowerDPIFuncPass::runOnOperation() { + if (failed(lowerDPI())) + return signalPassFailure(); +} diff --git a/lib/Dialect/Verif/VerifOps.cpp b/lib/Dialect/Verif/VerifOps.cpp index 21d4e1d1af47..62a56f80de61 100644 --- a/lib/Dialect/Verif/VerifOps.cpp +++ b/lib/Dialect/Verif/VerifOps.cpp @@ -104,50 +104,26 @@ LogicalResult ContractOp::verifyRegions() { if (!parent) return emitOpError() << "parent of contract must be an hw.module!"; - auto nInputsInModule = parent.getNumInputPorts(); - auto nOutputsInModule = parent.getNumOutputPorts(); - auto nOps = (*this)->getNumOperands(); - - // Check that the region block arguments match the op's inputs - if (nInputsInModule != nOps) - return emitOpError() << "contract must have the same number of arguments " - << "as the number of inputs in the parent module!"; - - // Check that the region block arguments match the op's inputs - if (getNumRegionArgs() != (nOps + nOutputsInModule)) - return emitOpError() << "region must have the same number of arguments " - << "as the number of arguments in the parent module!"; - - // Check that the region block arguments share the same types as the inputs - if (getBody().front().getArgumentTypes() != parent.getPortTypes()) + auto nRes = (*this)->getNumResults(); + auto resTypes = (*this)->getResultTypes(); + auto *yield = getBody().front().getTerminator(); + + // Check that the region terminator yields the same number of ops as the + // number of results + if (yield->getNumOperands() != nRes) + return emitOpError() << "region terminator must yield the same number of " + << "operands as there are results!"; + + // Check that the region terminator yields the same types of ops as the + // types of results + if (yield->getOperandTypes() != resTypes) + return emitOpError() << "region terminator must yield the same types of " + << "operands as the result types!"; + + // Check that the region block arguments share the same types as the results + if (getBody().front().getArgumentTypes() != resTypes) return emitOpError() << "region must have the same type of arguments " - << "as the type of inputs!"; - - return success(); -} - -LogicalResult InstanceOp::verifyRegions() { - // Check that the region block arguments match the op's inputs - if (getNumRegionArgs() != (*this)->getNumOperands()) - return emitOpError() << "region must have the same number of arguments " - << "as the number of inputs!"; - - // Check that the region block arguments share the same types as the inputs - if (getBody().front().getArgumentTypes() != (*this)->getOperandTypes()) - return emitOpError() << "region must have the same type of arguments " - << "as the type of inputs!"; - - // Check that verif.yield yielded the expected number of operations - if ((*this)->getNumResults() != - getBody().front().getTerminator()->getNumOperands()) - return emitOpError() << "region terminator must yield the same number" - << "of operations as there are results!"; - - // Check that the yielded types match the result types - if ((*this)->getResultTypes() != - getBody().front().getTerminator()->getOperandTypes()) - return emitOpError() << "region terminator must yield the same types" - << "as the result types!"; + << "as the type of results!"; return success(); } diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index 0bfbfc13fb75..fe93765ce7e3 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -26,6 +26,8 @@ using namespace circt; LogicalResult firtool::populatePreprocessTransforms(mlir::PassManager &pm, const FirtoolOptions &opt) { + pm.nest().addPass( + firrtl::createCheckRecursiveInstantiation()); // Legalize away "open" aggregates to hw-only versions. pm.nest().addPass(firrtl::createLowerOpenAggsPass()); @@ -78,7 +80,7 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, pm.nest().addPass(firrtl::createInferWidthsPass()); pm.nest().addPass( - firrtl::createMemToRegOfVecPass(opt.shouldReplicateSequentialMemories(), + firrtl::createMemToRegOfVecPass(opt.shouldReplaceSequentialMemories(), opt.shouldIgnoreReadEnableMemories())); pm.nest().addPass(firrtl::createInferResetsPass()); @@ -161,7 +163,7 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, pm.nest().nest().addPass( firrtl::createInferReadWritePass()); - if (opt.shouldReplicateSequentialMemories()) + if (opt.shouldReplaceSequentialMemories()) pm.nest().addPass(firrtl::createLowerMemoryPass()); pm.nest().addPass(firrtl::createPrefixModulesPass()); @@ -176,7 +178,7 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, pm.addNestedPass(firrtl::createAddSeqMemPortsPass()); pm.addPass(firrtl::createCreateSiFiveMetadataPass( - opt.shouldReplicateSequentialMemories(), + opt.shouldReplaceSequentialMemories(), opt.getReplaceSequentialMemoriesFile())); pm.addNestedPass(firrtl::createExtractInstancesPass()); @@ -303,7 +305,7 @@ LogicalResult firtool::populateHWToSV(mlir::PassManager &pm, FirtoolOptions::RandomKind::Mem), /*disableRegRandomization=*/ !opt.isRandomEnabled(FirtoolOptions::RandomKind::Reg), - /*replSeqMem=*/opt.shouldReplicateSequentialMemories(), + /*replSeqMem=*/opt.shouldReplaceSequentialMemories(), /*readEnableMode=*/opt.shouldIgnoreReadEnableMemories() ? seq::ReadEnableMode::Ignore : seq::ReadEnableMode::Undefined, diff --git a/lib/Support/CMakeLists.txt b/lib/Support/CMakeLists.txt index 08b9d250ad35..1a9a9b79bd82 100644 --- a/lib/Support/CMakeLists.txt +++ b/lib/Support/CMakeLists.txt @@ -10,18 +10,19 @@ add_circt_library(CIRCTSupport BackedgeBuilder.cpp CustomDirectiveImpl.cpp Debug.cpp + FVInt.cpp FieldRef.cpp + InstanceGraph.cpp JSON.cpp LoweringOptions.cpp Naming.cpp + ParsingUtils.cpp Passes.cpp Path.cpp PrettyPrinter.cpp PrettyPrinterHelpers.cpp - ParsingUtils.cpp SymCache.cpp ValueMapper.cpp - InstanceGraph.cpp "${VERSION_CPP}" LINK_LIBS PUBLIC @@ -33,13 +34,15 @@ add_circt_library(CIRCTSupport #------------------------------------------------------------------------------- # Generate Version.cpp #------------------------------------------------------------------------------- + find_first_existing_vc_file("${CIRCT_SOURCE_DIR}" CIRCT_GIT_LOGS_HEAD) set(GEN_VERSION_SCRIPT "${CIRCT_SOURCE_DIR}/cmake/modules/GenVersionFile.cmake") if (CIRCT_RELEASE_TAG_ENABLED) add_custom_command(OUTPUT "${VERSION_CPP}" DEPENDS "${CIRCT_GIT_LOGS_HEAD}" "${GEN_VERSION_SCRIPT}" - COMMAND ${CMAKE_COMMAND} -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" -DOUT_FILE="${VERSION_CPP}" -DRELEASE_PATTERN=${CIRCT_RELEASE_TAG}* -DDRY_RUN=OFF -DSOURCE_ROOT="${CIRCT_SOURCE_DIR}" -P "${GEN_VERSION_SCRIPT}") @@ -48,7 +51,8 @@ else () # cmake configuration. add_custom_command(OUTPUT "${VERSION_CPP}" DEPENDS "${GEN_VERSION_SCRIPT}" - COMMAND ${CMAKE_COMMAND} -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" + COMMAND ${CMAKE_COMMAND} + -DIN_FILE="${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" -DOUT_FILE="${VERSION_CPP}" -DDRY_RUN=ON -DSOURCE_ROOT="${CIRCT_SOURCE_DIR}" -P "${GEN_VERSION_SCRIPT}") endif() diff --git a/lib/Support/FVInt.cpp b/lib/Support/FVInt.cpp new file mode 100644 index 000000000000..e895923d1a1c --- /dev/null +++ b/lib/Support/FVInt.cpp @@ -0,0 +1,195 @@ +//===- FVInt.cpp - Four-valued integer --------------------------*- 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/FVInt.h" +#include "mlir/IR/OpImplementation.h" +#include "llvm/ADT/StringExtras.h" + +#define DEBUG_TYPE "fvint" + +using namespace circt; + +std::optional FVInt::tryFromString(StringRef str, unsigned radix) { + assert(radix == 2 || radix == 8 || radix == 10 || radix == 16); + if (str.empty()) + return {}; + + // Overestimate the number of bits that will be needed to hold all digits. + unsigned radixLog2 = 0; + for (unsigned r = radix - 1; r > 0; r >>= 1) + ++radixLog2; + bool radixIsPow2 = (radix == (1U << radixLog2)); + + // Parse the string. + auto result = FVInt::getZero(str.size() * radixLog2); + while (!str.empty()) { + unsigned digit = llvm::toLower(str[0]); + str = str.drop_front(); + + // Handle X and Z digits. + if (digit == 'x' || digit == 'z') { + if (!radixIsPow2) + return {}; + result <<= radixLog2; + result.unknown.setLowBits(radixLog2); + if (digit == 'z') + result.value.setLowBits(radixLog2); + continue; + } + + // Determine the value of the current digit. + if (digit >= '0' && digit <= '9') + digit = digit - '0'; + else if (digit >= 'a' && digit <= 'z') + digit = digit - 'a' + 10; + else + return {}; + if (digit >= radix) + return {}; + + // Add the digit to the result. + if (radixIsPow2) { + result <<= radixLog2; + result.value |= digit; + } else { + result.value *= radix; + result.value += digit; + } + } + + return result; +} + +bool FVInt::tryToString(SmallVectorImpl &str, unsigned radix, + bool uppercase) const { + size_t strBaseLen = str.size(); + assert(radix == 2 || radix == 8 || radix == 10 || radix == 16); + + // Determine if the radix is a power of two. + unsigned radixLog2 = 0; + for (unsigned r = radix - 1; r > 0; r >>= 1) + ++radixLog2; + bool radixIsPow2 = (radix == (1U << radixLog2)); + unsigned radixMask = (1U << radixLog2) - 1; + + // If the number has no X or Z bits, take the easy route and print the `APInt` + // directly. + if (!hasUnknown()) { + value.toString(str, radix, /*Signed=*/false, /*formatAsCLiteral=*/false, + uppercase); + return true; + } + + // We can only print with non-power-of-two radices if there are no X or Z + // bits. So at this point we require radix be a power of two. + if (!radixIsPow2) + return false; + + // Otherwise chop off digits at the bottom and print them to the string. This + // prints the digits in reverse order, with the least significant digit as the + // first character. + APInt value = this->value; + APInt unknown = this->unknown; + + char chrA = uppercase ? 'A' : 'a'; + char chrX = uppercase ? 'X' : 'x'; + char chrZ = uppercase ? 'Z' : 'z'; + + while (!value.isZero() || !unknown.isZero()) { + unsigned digitValue = value.getRawData()[0] & radixMask; + unsigned digitUnknown = unknown.getRawData()[0] & radixMask; + unsigned shiftAmount = std::min(radixLog2, getBitWidth()); + value.lshrInPlace(shiftAmount); + unknown.lshrInPlace(shiftAmount); + + // Handle unknown bits. Since we only get to print a single X or Z character + // to the string, either all bits in the digit have to be X, or all have to + // be Z. But we cannot represent the case where X, Z and 0/1 bits are mixed. + if (digitUnknown != 0) { + if (digitUnknown != radixMask || + (digitValue != 0 && digitValue != radixMask)) { + str.resize(strBaseLen); + return false; + } + str.push_back(digitValue == 0 ? chrX : chrZ); + continue; + } + + // Handle known bits. + if (digitValue < 10) + str.push_back(digitValue + '0'); + else + str.push_back(digitValue - 10 + chrA); + } + + // Reverse the digits. + std::reverse(str.begin() + strBaseLen, str.end()); + return true; +} + +void FVInt::print(raw_ostream &os) const { + SmallString<32> buffer; + if (!tryToString(buffer)) + if (!tryToString(buffer, 16)) + tryToString(buffer, 2); + os << buffer; +} + +llvm::hash_code circt::hash_value(const FVInt &a) { + return llvm::hash_combine(a.getRawValue(), a.getRawUnknown()); +} + +void circt::printFVInt(AsmPrinter &p, const FVInt &value) { + SmallString<32> buffer; + if (value.getBitWidth() > 1 && value.isNegative() && + (-value).tryToString(buffer)) { + p << "-" << buffer; + } else if (value.tryToString(buffer)) { + p << buffer; + } else if (value.tryToString(buffer, 16)) { + p << "h" << buffer; + } else { + value.tryToString(buffer, 2); + p << "b" << buffer; + } +} + +ParseResult circt::parseFVInt(AsmParser &p, FVInt &result) { + // Parse the value as either a keyword (`b[01XZ]+` for binary or + // `h[0-9A-FXZ]+` for hexadecimal), or an integer value (for decimal). + FVInt value; + StringRef strValue; + auto valueLoc = p.getCurrentLocation(); + if (succeeded(p.parseOptionalKeyword(&strValue))) { + // Determine the radix based on the `b` or `h` prefix. + unsigned base = 0; + if (strValue.consume_front("b")) { + base = 2; + } else if (strValue.consume_front("h")) { + base = 16; + } else { + return p.emitError(valueLoc) << "expected `b` or `h` prefix"; + } + + // Parse the value. + auto parsedValue = FVInt::tryFromString(strValue, base); + if (!parsedValue) { + return p.emitError(valueLoc) + << "expected base-" << base << " four-valued integer"; + } + + // Add a zero bit at the top to ensure the value reads as positive. + result = parsedValue->zext(parsedValue->getBitWidth() + 1); + } else { + APInt intValue; + if (p.parseInteger(intValue)) + return failure(); + result = std::move(intValue); + } + return success(); +} diff --git a/lib/Support/FieldRef.cpp b/lib/Support/FieldRef.cpp index 41eaf8aac483..2db716c486a6 100644 --- a/lib/Support/FieldRef.cpp +++ b/lib/Support/FieldRef.cpp @@ -21,4 +21,3 @@ Operation *FieldRef::getDefiningOp() const { return op; return cast(value).getOwner()->getParentOp(); } - diff --git a/lib/Support/LoweringOptions.cpp b/lib/Support/LoweringOptions.cpp index d6dfff5ee97f..f424494f18f1 100644 --- a/lib/Support/LoweringOptions.cpp +++ b/lib/Support/LoweringOptions.cpp @@ -121,6 +121,8 @@ void LoweringOptions::parse(StringRef text, ErrorHandlerT errorHandler) { caseInsensitiveKeywords = true; } else if (option == "emitVerilogLocations") { emitVerilogLocations = true; + } else if (option == "fixUpEmptyModules") { + fixUpEmptyModules = true; } else { errorHandler(llvm::Twine("unknown style option \'") + option + "\'"); // We continue parsing options after a failure. @@ -180,6 +182,8 @@ std::string LoweringOptions::toString() const { options += "caseInsensitiveKeywords,"; if (emitVerilogLocations) options += "emitVerilogLocations,"; + if (fixUpEmptyModules) + options += "fixUpEmptyModules,"; // Remove a trailing comma if present. if (!options.empty()) { diff --git a/llvm b/llvm index 0870afaaaccd..c69b8c445a6b 160000 --- a/llvm +++ b/llvm @@ -1 +1 @@ -Subproject commit 0870afaaaccde5b4bae37abfc982207ffafb8332 +Subproject commit c69b8c445a6b7efd29e67b665adaf04575f3ed92 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 11738c2363bf..07f262ca563c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,6 +24,7 @@ set(CIRCT_TEST_DEPENDS circt-capi-firtool-test circt-as circt-dis + circt-lec circt-opt circt-translate circt-reduce diff --git a/test/Conversion/CalyxToFSM/materialize-errors.mlir b/test/Conversion/CalyxToFSM/materialize-errors.mlir index 84aa9cd99940..87976620d925 100644 --- a/test/Conversion/CalyxToFSM/materialize-errors.mlir +++ b/test/Conversion/CalyxToFSM/materialize-errors.mlir @@ -1,7 +1,8 @@ // RUN: circt-opt -pass-pipeline='builtin.module(calyx.component(materialize-calyx-to-fsm))' -split-input-file -verify-diagnostics %s - calyx.component @main(%go: i1 {go}, %reset: i1 {reset}, %clk: i1 {clk}) -> (%done: i1 {done}) { + %r.in, %r.write_en, %r.clk, %r.reset, %r.out, %r.done = calyx.register @r : i32, i1, i1, i1, i32, i1 calyx.wires { + calyx.assign %r.clk = %clk : i1 } // expected-error @+1 {{'calyx.control' op expected an 'fsm.machine' operation as the top-level operation within the control region of this component.}} calyx.control { @@ -9,10 +10,7 @@ calyx.component @main(%go: i1 {go}, %reset: i1 {reset}, %clk: i1 {clk}) -> (%don } } - - // ----- - calyx.component @main(%go: i1 {go}, %reset: i1 {reset}, %clk: i1 {clk}) -> (%done: i1 {done}) { calyx.wires { } diff --git a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir index 21fac9ecc0b5..047a82f59eb3 100644 --- a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir +++ b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir @@ -110,6 +110,36 @@ hw.module @Reshuffling(in %clockA: !seq.clock, in %clockB: !seq.clock, out z0: i hw.module.extern private @Reshuffling2(out z0: i4, out z1: i4, out z2: i4, out z3: i4) +// CHECK-LABEL: arc.define @ReshufflingInit_arc(%arg0: i4, %arg1: i4) +// CHECK-NEXT: arc.output %arg0, %arg1 +// CHECK-NEXT: } + +// CHECK-LABEL: arc.define @ReshufflingInit_arc_0(%arg0: i4, %arg1: i4) +// CHECK-NEXT: arc.output %arg0, %arg1 +// CHECK-NEXT: } + +// CHECK-LABEL: hw.module @ReshufflingInit +hw.module @ReshufflingInit(in %clockA: !seq.clock, in %clockB: !seq.clock, out z0: i4, out z1: i4, out z2: i4, out z3: i4) { + // CHECK-NEXT: [[C1:%.+]] = hw.constant 1 : i4 + // CHECK-NEXT: [[C2:%.+]] = hw.constant 2 : i4 + // CHECK-NEXT: [[C3:%.+]] = hw.constant 3 : i4 + // CHECK-NEXT: hw.instance "x" @Reshuffling2() + // CHECK-NEXT: [[C0:%.+]] = hw.constant 0 : i4 + // CHECK-NEXT: arc.state @ReshufflingInit_arc(%x.z0, %x.z1) clock %clockA initial ([[C0]], [[C1]] : i4, i4) latency 1 + // CHECK-NEXT: arc.state @ReshufflingInit_arc_0(%x.z2, %x.z3) clock %clockB initial ([[C2]], [[C3]] : i4, i4) latency 1 + // CHECK-NEXT: hw.output + %cst1 = hw.constant 1 : i4 + %cst2 = hw.constant 2 : i4 + %cst3 = hw.constant 3 : i4 + %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 + %5 = seq.compreg %x.z1, %clockA powerOn %cst1 : i4 + %6 = seq.compreg %x.z2, %clockB powerOn %cst2 : i4 + %7 = seq.compreg %x.z3, %clockB powerOn %cst3 : i4 + hw.output %4, %5, %6, %7 : i4, i4, i4, i4 +} +// CHECK-NEXT: } + // CHECK-LABEL: arc.define @FactorOutCommonOps_arc( // CHECK-NEXT: comb.xor @@ -196,6 +226,22 @@ hw.module @Trivial(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out out: i4 } // CHECK-NEXT: } +// CHECK: arc.define @[[TRIVIALINIT_ARC:.+]]([[ARG0:%.+]]: i4) +// CHECK-NEXT: arc.output [[ARG0]] +// CHECK-NEXT: } + +// CHECK-LABEL: hw.module @TrivialWithInit( +hw.module @TrivialWithInit(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out out: i4) { + // CHECK: [[CST2:%.+]] = hw.constant 2 : i4 + // CHECK: [[RES0:%.+]] = arc.state @[[TRIVIALINIT_ARC]](%i0) clock %clock reset %reset initial ([[CST2]] : i4) latency 1 {names = ["foo"] + // CHECK-NEXT: hw.output [[RES0:%.+]] + %0 = hw.constant 0 : i4 + %cst2 = hw.constant 2 : i4 + %foo = seq.compreg %i0, %clock reset %reset, %0 powerOn %cst2: i4 + hw.output %foo : i4 +} +// CHECK-NEXT: } + // CHECK-NEXT: arc.define @[[NONTRIVIAL_ARC_0:.+]]([[ARG0_1:%.+]]: i4) // CHECK-NEXT: arc.output [[ARG0_1]] // CHECK-NEXT: } diff --git a/test/Conversion/ExportVerilog/bugs.mlir b/test/Conversion/ExportVerilog/bugs.mlir index 9d6792b4f705..cb31e847ac5f 100644 --- a/test/Conversion/ExportVerilog/bugs.mlir +++ b/test/Conversion/ExportVerilog/bugs.mlir @@ -10,4 +10,4 @@ module attributes {circt.loweringOptions = "disallowExpressionInliningInPorts"} // CHECK: .a (a), %bar.b = hw.instance "bar" @Bar(a: %a: !hw.inout) -> (b: i1) } -} \ No newline at end of file +} diff --git a/test/Conversion/ExportVerilog/fixup-empty-modules.mlir b/test/Conversion/ExportVerilog/fixup-empty-modules.mlir new file mode 100644 index 000000000000..e0304327fdfa --- /dev/null +++ b/test/Conversion/ExportVerilog/fixup-empty-modules.mlir @@ -0,0 +1,19 @@ +// RUN: circt-opt --test-apply-lowering-options='options=fixUpEmptyModules' --export-verilog %s | FileCheck %s --check-prefixes=CHECK + +// CHECK-LABEL: module empty1 +hw.module @empty1() { + // CHECK-NEXT: /* This wire is added to avoid emitting empty modules. See `fixUpEmptyModules` lowering option in CIRCT. */ + // CHECK-NEXT: wire _GEN = 1'h1; +} + +// CHECK-LABEL: module empty2 +hw.module @empty2(in %in: i1, in %in2: i32) { + // CHECK: /* This wire is added to avoid emitting empty modules. See `fixUpEmptyModules` lowering option in CIRCT. */ + // CHECK-NEXT: wire _GEN = 1'h1; +} + +// CHECK-LABEL: module not_empty +hw.module @not_empty(in %in: i1, out out: i1) { + // CHECK: assign out = in; + hw.output %in : i1 +} diff --git a/test/Conversion/ExportVerilog/prepare-for-emission.mlir b/test/Conversion/ExportVerilog/prepare-for-emission.mlir index 1992b2d8a33e..7bfa026fa110 100644 --- a/test/Conversion/ExportVerilog/prepare-for-emission.mlir +++ b/test/Conversion/ExportVerilog/prepare-for-emission.mlir @@ -1,5 +1,5 @@ // RUN: circt-opt %s --pass-pipeline='builtin.module(any(prepare-for-emission))' --split-input-file -verify-diagnostics | FileCheck %s -// RUN: circt-opt %s -export-verilog -split-input-file +// RUN: circt-opt %s --lower-verif-to-sv -export-verilog -split-input-file // CHECK: @namehint_variadic hw.module @namehint_variadic(in %a: i3, out b: i3) { diff --git a/test/Conversion/ExportVerilog/sv-dialect.mlir b/test/Conversion/ExportVerilog/sv-dialect.mlir index 3d75fd441e62..8146d9dfc231 100644 --- a/test/Conversion/ExportVerilog/sv-dialect.mlir +++ b/test/Conversion/ExportVerilog/sv-dialect.mlir @@ -521,7 +521,8 @@ hw.module @reg_0(in %in4: i4, in %in8: i8, in %in8_2: i8, out a: i8, out b: i8) %unpacked_array = sv.unpacked_array_create %in8, %in8_2 : (i8, i8) -> !hw.uarray<2xi8> %unpacked_wire = sv.wire : !hw.inout> - // CHECK: wire [7:0] unpacked_wire[0:1] = '{in8_2, in8}; + // CHECK: wire [7:0] unpacked_wire[0:1]; + // CHECK-NEXT: assign unpacked_wire = '{in8_2, in8}; sv.assign %unpacked_wire, %unpacked_array: !hw.uarray<2xi8> // CHECK-NEXT: assign a = myReg; @@ -1111,6 +1112,23 @@ hw.module @DontDuplicateSideEffectingVerbatim() { } } +// Issue 6363 +// CHECK-LABEL: module DontInlineAssignmentForUnpackedArrays( +hw.module @DontInlineAssignmentForUnpackedArrays(in %a: !hw.uarray<2xi1>) { +// CHECK: wire w[0:1]; +// CHECK-NEXT: assign w = a; + %w = sv.wire : !hw.inout> + sv.assign %w, %a : !hw.uarray<2xi1> + // CHECK: logic u[0:1]; + // CHECK-NEXT: u = a; + sv.initial { + %u = sv.logic : !hw.inout> + sv.bpassign %u, %a : !hw.uarray<2xi1> + } + + hw.output +} + hw.generator.schema @verbatim_schema, "Simple", ["ports", "write_latency", "read_latency"] hw.module.extern @verbatim_inout_2 () // CHECK-LABEL: module verbatim_M1( @@ -1789,7 +1807,8 @@ sv.func private @open_array(in %array : !sv.open_uarray) sv.func.dpi.import @open_array // CHECK-LABEL: test_open_array -// CHECK: wire [7:0] _GEN[0:1] = '{in_0, in_1}; +// CHECK: wire [7:0] _GEN[0:1]; +// CHECK-NEXT: assign _GEN = '{in_0, in_1}; // CHECK-NEXT: always @(posedge clock) // CHECK-NEXT: open_array(_GEN); hw.module @test_open_array(in %clock : i1, in %in_0 : i8, in %in_1 : i8) { diff --git a/test/Conversion/ExportVerilog/verilog-basic.mlir b/test/Conversion/ExportVerilog/verilog-basic.mlir index 2f28b0e88c04..55586662ac4f 100644 --- a/test/Conversion/ExportVerilog/verilog-basic.mlir +++ b/test/Conversion/ExportVerilog/verilog-basic.mlir @@ -489,7 +489,8 @@ hw.module @ArrayParamsInst() { uarr: %uarr : !hw.uarray<2 x i8>) -> () } // CHECK: wire [1:0][7:0] [[G0:_.*]] = '{8'h1, 8'h2}; -// CHECK: wire [7:0] [[G1:_.*]][0:1] = '{8'h1, 8'h2}; +// CHECK: wire [7:0] [[G1:_.*]][0:1]; +// CHECK: assign [[G1]] = '{8'h1, 8'h2}; // CHECK: ArrayParams #( // CHECK: .param(2) // CHECK: ) arrays ( diff --git a/test/Conversion/HWArithToHW/test_basic.mlir b/test/Conversion/HWArithToHW/test_basic.mlir index b44fa44c387c..11d4c5282ac6 100644 --- a/test/Conversion/HWArithToHW/test_basic.mlir +++ b/test/Conversion/HWArithToHW/test_basic.mlir @@ -243,13 +243,8 @@ hw.module @icmp(in %op0: i32, in %op1: i32, out sisi: i1, out siui: i1, out uisi // CHECK: %[[UIUI_OUT:.*]] = comb.icmp ult %op0, %op1 : i32 %uiui = hwarith.icmp lt %op0Unsigned, %op1Unsigned : ui32, ui32 - %sisiOut = hwarith.cast %sisi : (ui1) -> i1 - %siuiOut = hwarith.cast %siui : (ui1) -> i1 - %uisiOut = hwarith.cast %uisi : (ui1) -> i1 - %uiuiOut = hwarith.cast %uiui : (ui1) -> i1 - // CHECK: hw.output %[[SISI_OUT]], %[[SIUI_OUT]], %[[UISI_OUT]], %[[UIUI_OUT]] : i1, i1, i1, i1 - hw.output %sisiOut, %siuiOut, %uisiOut, %uiuiOut : i1, i1, i1, i1 + hw.output %sisi, %siui, %uisi, %uiui: i1, i1, i1, i1 } // ----- @@ -285,13 +280,8 @@ hw.module @icmp_mixed_width(in %op0: i5, in %op1: i7, out sisi: i1, out siui: i1 // CHECK: %[[UIUI_OUT:.*]] = comb.icmp ult %[[OP0_PADDED]], %op1 : i7 %uiui = hwarith.icmp lt %op0Unsigned, %op1Unsigned : ui5, ui7 - %sisiOut = hwarith.cast %sisi : (ui1) -> i1 - %siuiOut = hwarith.cast %siui : (ui1) -> i1 - %uisiOut = hwarith.cast %uisi : (ui1) -> i1 - %uiuiOut = hwarith.cast %uiui : (ui1) -> i1 - // CHECK: hw.output %[[SISI_OUT]], %[[SIUI_OUT]], %[[UISI_OUT]], %[[UIUI_OUT]] : i1, i1, i1, i1 - hw.output %sisiOut, %siuiOut, %uisiOut, %uiuiOut : i1, i1, i1, i1 + hw.output %sisi, %siui, %uisi, %uiui: i1, i1, i1, i1 } // ----- diff --git a/test/Conversion/ImportVerilog/basic.sv b/test/Conversion/ImportVerilog/basic.sv index a5c9257d1170..b5b0085ef92d 100644 --- a/test/Conversion/ImportVerilog/basic.sv +++ b/test/Conversion/ImportVerilog/basic.sv @@ -1,4 +1,5 @@ // RUN: circt-translate --import-verilog %s | FileCheck %s +// RUN: circt-verilog --ir-moore %s // REQUIRES: slang // Internal issue in Slang v3 about jump depending on uninitialised value. @@ -32,7 +33,7 @@ endmodule // CHECK: moore.module private @DedupB(in %a : !moore.i32) // CHECK: moore.module private @DedupB_0(in %a : !moore.i16) // CHECK-NOT: @DedupB -module DedupB #(parameter int W)(input bit [W-1:0] a); +module DedupB #(parameter int W, parameter type T = bit [W-1:0])(input T a); endmodule // CHECK-LABEL: moore.module @Dedup() @@ -214,221 +215,343 @@ module Basic; string s1; assign s1 = "Hello World"; + typedef struct packed { bit x; bit y; } MyStruct; + // CHECK: [[VAR_S2:%.+]] = moore.variable : > + MyStruct s2; + // CHECK: [[TMP1:%.+]] = moore.read [[VAR_S2]] + // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.struct<{x: i1, y: i1}> -> !moore.i2 + // CHECK: [[TMP3:%.+]] = moore.not [[TMP2]] : i2 + // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i2 -> !moore.struct<{x: i1, y: i1}> + // CHECK: moore.assign [[VAR_S2]], [[TMP4]] + assign s2 = ~s2; + // CHECK: [[TMP1:%.+]] = moore.read [[VAR_S2]] + // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.struct<{x: i1, y: i1}> -> !moore.i2 + // CHECK: [[TMP3:%.+]] = moore.not [[TMP2]] : i2 + // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i2 -> !moore.struct<{x: i1, y: i1}> + // CHECK: [[VAR_S3:%.+]] = moore.variable [[TMP4]] : > + MyStruct s3 = ~s2; endmodule -// CHECK-LABEL: moore.module @Statements -module Statements; - bit x, y, z; - int i; - initial begin - // CHECK: %a = moore.variable : - automatic int a; - // CHECK: [[TMP1:%.+]] = moore.read %a - // CHECK moore.blocking_assign %i, [[TMP1]] : i32 - i = a; +// CHECK-LABEL: func.func private @dummyA( +// CHECK-LABEL: func.func private @dummyB( +// CHECK-LABEL: func.func private @dummyC( +// CHECK-LABEL: func.func private @dummyD( +function void dummyA(); endfunction +function void dummyB(); endfunction +function void dummyC(); endfunction +function void dummyD(int a); endfunction +function bit dummyE(bit a); return a; endfunction + +// CHECK-LABEL: func.func private @ConditionalStatements( +// CHECK-SAME: %arg0: !moore.i1 +// CHECK-SAME: %arg1: !moore.i1 +function void ConditionalStatements(bit x, bit y); + // CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB2]] + // CHECK: ^[[BB2]]: + if (x) dummyA(); + + // CHECK: [[COND1:%.+]] = moore.and %arg0, %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB2]] + // CHECK: ^[[BB2]]: + if (x &&& y) dummyA(); + + // CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB3:.+]] + // CHECK: ^[[BB2]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB3]] + // CHECK: ^[[BB3]]: + if (x) + dummyA(); + else + dummyB(); + + // CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB6:.+]] + // CHECK: ^[[BB2]]: + // CHECK: [[COND:%.+]] = moore.conversion %arg1 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB3:.+]], ^[[BB4:.+]] + // CHECK: ^[[BB3]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB5:.+]] + // CHECK: ^[[BB4]]: + // CHECK: call @dummyC() + // CHECK: cf.br ^[[BB5]] + // CHECK: ^[[BB5]]: + // CHECK: cf.br ^[[BB6]] + // CHECK: ^[[BB6]]: + if (x) + dummyA(); + else if (y) + dummyB(); + else + dummyC(); + + // CHECK: [[COND:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: return + // CHECK: ^[[BB2]]: + if (x) return; +endfunction - //===------------------------------------------------------------------===// - // Conditional statements +// CHECK-LABEL: func.func private @CaseStatements( +// CHECK-SAME: %arg0: !moore.i32 +// CHECK-SAME: %arg1: !moore.i32 +// CHECK-SAME: %arg2: !moore.i32 +// CHECK-SAME: %arg3: !moore.i32 +function void CaseStatements(int x, int a, int b, int c); + // CHECK: [[FLAG:%.+]] = moore.add %arg0, %arg0 + case (x + x) + // CHECK: [[COND1:%.+]] = moore.case_eq [[FLAG]], %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB3:.+]] + a: dummyA(); + // CHECK: ^[[BB2]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB3]] + default: dummyB(); + // CHECK: ^[[BB3]]: + endcase + + // CHECK: [[COND1:%.+]] = moore.case_eq %arg0, %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB_MATCH:.+]], ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: [[TMP:%.+]] = moore.add %arg2, %arg3 + // CHECK: [[COND1:%.+]] = moore.case_eq %arg0, [[TMP]] + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB_MATCH:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB_MATCH]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_EXIT:.+]] + // CHECK: ^[[BB2]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_EXIT]] + // CHECK: ^[[BB_EXIT]]: + case (x) + a, (b+c): dummyA(); + default: dummyB(); + endcase + + // CHECK: [[COND1:%.+]] = moore.casez_eq %arg0, %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB3:.+]] + // CHECK: ^[[BB2]]: + // CHECK: cf.br ^[[BB3]] + // CHECK: ^[[BB3]]: + casez (x) + a: dummyA(); + endcase + + // CHECK: [[COND1:%.+]] = moore.casexz_eq %arg0, %arg1 + // CHECK: [[COND2:%.+]] = moore.conversion [[COND1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[COND2]], ^[[BB1:.+]], ^[[BB2:.+]] + // CHECK: ^[[BB1]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB3:.+]] + // CHECK: ^[[BB2]]: + // CHECK: cf.br ^[[BB3]] + // CHECK: ^[[BB3]]: + casex (x) + a: dummyA(); + endcase +endfunction - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND]] { - // CHECK: [[TMP2:%.+]] = moore.read %y : - // CHECK: moore.blocking_assign %x, [[TMP2]] : i1 - // CHECK: } - if (x) x = y; - - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[TMP2:%.+]] = moore.read %y - // CHECK: [[COND0:%.+]] = moore.and [[TMP1]], [[TMP2]] - // CHECK: [[COND1:%.+]] = moore.conversion [[COND0]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND1]] { - // CHECK: [[TMP3:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP3]] - // CHECK: } - if (x &&& y) x = y; - - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND]] { - // CHECK: [[TMP2:%.+]] = moore.read %z - // CHECK: moore.blocking_assign %x, [[TMP2]] - // CHECK: } else { - // CHECK: [[TMP3:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP3]] - // CHECK: } - if (x) x = z; else x = y; - - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND]] { - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %x, [[TMP2]] - // CHECK: } else { - // CHECK: [[TMP3:%.+]] = moore.read %y - // CHECK: [[COND:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1 - // CHECK: scf.if [[COND]] { - // CHECK: [[TMP4:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP4]] - // CHECK: } else { - // CHECK: [[TMP5:%.+]] = moore.read %z - // CHECK: moore.blocking_assign %x, [[TMP5]] - // CHECK: } - // CHECK: } +// CHECK-LABEL: func.func private @ForLoopStatements( +// CHECK-SAME: %arg0: !moore.i32 +// CHECK-SAME: %arg1: !moore.i32 +// CHECK-SAME: %arg2: !moore.i1 +function void ForLoopStatements(int a, int b, bit c); + int x; + + // CHECK: moore.blocking_assign %x, %arg0 + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP1:%.+]] = moore.read %x + // CHECK: [[TMP2:%.+]] = moore.slt [[TMP1]], %arg1 + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP3]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_STEP:.+]] + // CHECK: ^[[BB_STEP]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_CHECK]] + // CHECK: ^[[BB_EXIT]]: + for (x = a; x < b; dummyB()) dummyA(); + + // CHECK: %y = moore.variable %arg0 : + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP1:%.+]] = moore.read %y + // CHECK: [[TMP2:%.+]] = moore.slt [[TMP1]], %arg1 + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP3]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_STEP:.+]] + // CHECK: ^[[BB_STEP]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_CHECK]] + // CHECK: ^[[BB_EXIT]]: + for (int y = a; y < b; dummyB()) dummyA(); + + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg2 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg2 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_TRUE:.+]], ^[[BB_FALSE:.+]] + // CHECK: ^[[BB_TRUE]]: + // CHECK: cf.br ^[[BB_STEP:.+]] + // CHECK: ^[[BB_FALSE]]: + // CHECK: cf.br ^[[BB_EXIT]] + // CHECK: ^[[BB_STEP]]: + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_CHECK]] + // CHECK: ^[[BB_EXIT]]: + for (; c; dummyB()) + if (c) + continue; + else + break; +endfunction + +// CHECK-LABEL: func.func private @ForeverLoopStatements( +// CHECK-SAME: %arg0: !moore.i1 +// CHECK-SAME: %arg1: !moore.i1 +function void ForeverLoopStatements(bit x, bit y); + // CHECK: cf.br ^[[BB_BODY:.+]] + // CHECK: ^[[BB_BODY]]: + forever begin if (x) begin - x = x; - end else if (y) begin - x = y; + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_EXIT:.+]] + dummyA(); + break; end else begin - x = z; + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_BODY]] + dummyB(); + continue; end + end + // CHECK: ^[[BB_EXIT]]: - //===------------------------------------------------------------------===// - // Case statements - - - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: [[TMP3:%.+]] = moore.eq [[TMP1]], [[TMP2]] : i1 -> i1 - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP4]] { - // CHECK: [[TMP5:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %x, [[TMP5]] : i1 - // CHECK: } - // CHECK: [[TMP6:%.+]] = moore.read %x - // CHECK: [[TMP7:%.+]] = moore.eq [[TMP1]], [[TMP6]] : i1 -> i1 - // CHECK: [[TMP8:%.+]] = moore.read %y - // CHECK: [[TMP9:%.+]] = moore.eq [[TMP1]], [[TMP8]] : i1 -> i1 - // CHECK: [[TMP10:%.+]] = moore.or [[TMP7]], [[TMP9]] : i1 - // CHECK: [[TMP11:%.+]] = moore.conversion [[TMP10]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP11]] { - // CHECK: [[TMP12:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP12]] : i1 - // CHECK: } - case (x) - x: x = x; - x, y: x = y; - endcase + // CHECK: cf.br ^[[BB_BODY:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_BODY]] + forever dummyA(); +endfunction - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: [[TMP3:%.+]] = moore.eq [[TMP1]], [[TMP2]] : i1 -> i1 - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP4]] { - // CHECK: [[TMP5:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %x, [[TMP5]] : i1 - // CHECK: } - // CHECK: [[TMP6:%.+]] = moore.read %x - // CHECK: [[TMP7:%.+]] = moore.eq [[TMP1]], [[TMP6]] : i1 -> i1 - // CHECK: [[TMP8:%.+]] = moore.read %y - // CHECK: [[TMP9:%.+]] = moore.eq [[TMP1]], [[TMP8]] : i1 -> i1 - // CHECK: [[TMP10:%.+]] = moore.or [[TMP7]], [[TMP9]] : i1 - // CHECK: [[TMP11:%.+]] = moore.conversion [[TMP10]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP11]] { - // CHECK: [[TMP12:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP12]] : i1 - // CHECK: } - // CHECK: [[TMP13:%.+]] = moore.read %z - // CHECK: [[TMP14:%.+]] = moore.eq [[TMP1]], [[TMP13]] : i1 -> i1 - // CHECK: [[TMP15:%.+]] = moore.conversion [[TMP14]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP15]] { - // CHECK: [[TMP16:%.+]] = moore.read %z - // CHECK: moore.blocking_assign %x, [[TMP16]] : i1 - // CHECK: } - // CHECK: [[TMP17:%.+]] = moore.or [[TMP10]], [[TMP14]] : i1 - // CHECK: [[TMP18:%.+]] = moore.or [[TMP3]], [[TMP17]] : i1 - // CHECK: [[TMP19:%.+]] = moore.not [[TMP18]] : i1 - // CHECK: [[TMP20:%.+]] = moore.conversion [[TMP19]] : !moore.i1 -> i1 - // CHECK: scf.if [[TMP20]] { - // CHECK: [[TMP21:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %x, [[TMP21]] : i1 - // CHECK: } - case (x) - x: x = x; - x, y: x = y; - z: x = z; - default x = x; - endcase +// CHECK-LABEL: func.func private @WhileLoopStatements( +// CHECK-SAME: %arg0: !moore.i1 +// CHECK-SAME: %arg1: !moore.i1 +function void WhileLoopStatements(bit x, bit y); + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_CHECK]] + // CHECK: ^[[BB_EXIT]]: + while (x) dummyA(); + + // CHECK: cf.br ^[[BB_BODY:.+]] + // CHECK: ^[[BB_BODY]]: + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_BODY]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_EXIT]]: + do dummyA(); while (x); + + // CHECK: cf.br ^[[BB_CHECK:.+]] + // CHECK: ^[[BB_CHECK]]: + // CHECK: [[TMP:%.+]] = moore.conversion %arg0 : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + while (x) begin + if (y) begin + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_EXIT]] + dummyA(); + break; + end else begin + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_CHECK]] + dummyB(); + continue; + end + end + // CHECK: ^[[BB_EXIT]]: +endfunction - //===------------------------------------------------------------------===// - // Loop statements +// CHECK-LABEL: func.func private @RepeatLoopStatements( +// CHECK-SAME: %arg0: !moore.i32 +// CHECK-SAME: %arg1: !moore.i1 +function void RepeatLoopStatements(int x, bit y); + // CHECK: cf.br ^[[BB_CHECK:.+]](%arg0 : !moore.i32) + repeat (x) begin + // CHECK: ^[[BB_CHECK]]([[COUNT:%.+]]: !moore.i32): + // CHECK: [[TMP1:%.+]] = moore.bool_cast [[COUNT]] : i32 -> i1 + // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 + // CHECK: cf.cond_br [[TMP2]], ^[[BB_BODY:.+]], ^[[BB_EXIT:.+]] + // CHECK: ^[[BB_BODY]]: + if (y) begin + // CHECK: call @dummyA() + // CHECK: cf.br ^[[BB_EXIT]] + dummyA(); + break; + end else begin + // CHECK: call @dummyB() + // CHECK: cf.br ^[[BB_STEP:.+]] + dummyB(); + continue; + end + // CHECK: ^[[BB_STEP]]: + // CHECK: [[TMP1:%.+]] = moore.constant 1 : i32 + // CHECK: [[TMP2:%.+]] = moore.sub [[COUNT]], [[TMP1]] : i32 + // CHECK: cf.br ^[[BB_CHECK]]([[TMP2]] : !moore.i32) + end + // CHECK: ^[[BB_EXIT]]: +endfunction - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: moore.blocking_assign %y, [[TMP1]] : i1 - // CHECK: scf.while : () -> () { - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 - // CHECK: scf.condition([[COND]]) - // CHECK: } do { - // CHECK: [[TMP3:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP3]] : i1 - // CHECK: [[TMP4:%.+]] = moore.read %z - // CHECK: moore.blocking_assign %x, [[TMP4]] : i1 - // CHECK: scf.yield - // CHECK: } - for (y = x; x; x = z) x = y; - - // CHECK: [[TMP:%.+]] = moore.constant true : i1 - // CHECK: %iv = moore.variable [[TMP]] : - // CHECK: scf.while : () -> () { - // CHECK: [[TMP:%.+]] = moore.read %iv - // CHECK: [[COND:%.+]] = moore.conversion [[TMP]] : !moore.i1 -> i1 - // CHECK: scf.condition([[COND]]) - // CHECK: } do { - // CHECK: [[TMP:%.+]] = moore.read %iv - // CHECK: moore.blocking_assign %y, [[TMP]] : i1 - // CHECK: [[TMP:%.+]] = moore.read %iv - // CHECK: moore.blocking_assign %x, [[TMP]] : i1 - // CHECK: scf.yield - // CHECK: } - for (bit iv = '1; iv; x = iv) y = iv; - - // CHECK: [[TMP1:%.+]] = moore.read %i - // CHECK: scf.while (%arg0 = [[TMP1]]) : (!moore.i32) -> !moore.i32 { - // CHECK: [[TMP2:%.+]] = moore.bool_cast %arg0 : i32 -> i1 - // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 - // CHECK: scf.condition([[TMP3]]) %arg0 : !moore.i32 - // CHECK: } do { - // CHECK: ^bb0(%arg0: !moore.i32): - // CHECK: [[TMP4:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP4]] : i1 - // CHECK: [[TMP5:%.+]] = moore.constant 1 : i32 - // CHECK: [[TMP6:%.+]] = moore.sub %arg0, [[TMP5]] : i32 - // CHECK: scf.yield [[TMP6]] : !moore.i32 - // CHECK: } - repeat (i) x = y; - - // CHECK: scf.while : () -> () { - // CHECK: [[TMP1:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP1]] : !moore.i1 -> i1 - // CHECK: scf.condition([[COND]]) - // CHECK: } do { - // CHECK: [[TMP2:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP2]] : i1 - // CHECK: scf.yield - // CHECK: } - while (x) x = y; - - // CHECK: scf.while : () -> () { - // CHECK: [[TMP1:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP1]] : i1 - // CHECK: [[TMP2:%.+]] = moore.read %x - // CHECK: [[COND:%.+]] = moore.conversion [[TMP2]] : !moore.i1 -> i1 - // CHECK: scf.condition([[COND]]) - // CHECK: } do { - // CHECK: scf.yield - // CHECK: } - do x = y; while (x); - - // CHECK: scf.while : () -> () { - // CHECK: %true = hw.constant true - // CHECK: scf.condition(%true) - // CHECK: } do { - // CHECK: [[TMP1:%.+]] = moore.read %y - // CHECK: moore.blocking_assign %x, [[TMP1]] : i1 - // CHECK: scf.yield - // CHECK: } - forever x = y; +// CHECK-LABEL: moore.module @Statements +module Statements; + bit x, y, z; + int i; + initial begin + // CHECK: %a = moore.variable : + automatic int a; + // CHECK: [[TMP1:%.+]] = moore.read %a + // CHECK moore.blocking_assign %i, [[TMP1]] : i32 + i = a; //===------------------------------------------------------------------===// // Assignments @@ -478,6 +601,10 @@ module Expressions; struct packed { int a, b; } struct0; + // CHECK: %ustruct0 = moore.variable : > + struct { + int a, b; + } ustruct0; // CHECK: %struct1 = moore.variable : , d: struct<{a: i32, b: i32}>}>> struct packed { struct packed { @@ -497,12 +624,20 @@ module Expressions; // CHECK: %r1 = moore.variable : // CHECK: %r2 = moore.variable : real r1,r2; + // CHECK: %arrayInt = moore.variable : > + bit [1:0][31:0] arrayInt; + // CHECK: %uarrayInt = moore.variable : > + bit [31:0] uarrayInt [2]; initial begin // CHECK: moore.constant 0 : i32 c = '0; // CHECK: moore.constant -1 : i32 c = '1; + // CHECK: moore.constant hXXXXXXXX : l32 + f = 'X; + // CHECK: moore.constant hZZZZZZZZ : l32 + f = 'Z; // CHECK: moore.constant 42 : i32 c = 42; // CHECK: moore.constant 42 : i19 @@ -511,6 +646,8 @@ module Expressions; c = 19'sd42; // CHECK: moore.constant 123456789123456789123456789123456789 : i128 c = 128'd123456789123456789123456789123456789; + // CHECK: moore.constant h123XZ : l19 + f = 19'h123XZ; // CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: [[TMP2:%.+]] = moore.read %b // CHECK: [[TMP3:%.+]] = moore.read %c @@ -524,7 +661,7 @@ module Expressions; {a, b, c} = a; // CHECK: moore.concat_ref %d, %e : (!moore.ref, !moore.ref) -> {d, e} = d; - // CHECK: [[TMP1:%.+]] = moore.constant false : i1 + // CHECK: [[TMP1:%.+]] = moore.constant 0 : i1 // CHECK: [[TMP2:%.+]] = moore.concat [[TMP1]] : (!moore.i1) -> i1 // CHECK: moore.replicate [[TMP2]] : i1 -> i32 a = {32{1'b0}}; @@ -594,8 +731,7 @@ module Expressions; c = -a; // CHECK: [[TMP1:%.+]] = moore.read %v // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.array<2 x i4> -> !moore.i32 - // CHECK: [[TMP3:%.+]] = moore.neg [[TMP2]] : i32 - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i32 -> !moore.i32 + // CHECK: moore.neg [[TMP2]] : i32 c = -v; // CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: moore.not [[TMP1]] : i32 @@ -665,10 +801,9 @@ module Expressions; // CHECK: moore.add [[TMP1]], [[TMP2]] : i32 c = a + b; // CHECK: [[TMP1:%.+]] = moore.read %a - // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.i32 -> !moore.i32 - // CHECK: [[TMP3:%.+]] = moore.read %v - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.array<2 x i4> -> !moore.i32 - // CHECK: moore.add [[TMP2]], [[TMP4]] : i32 + // CHECK: [[TMP2:%.+]] = moore.read %v + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.array<2 x i4> -> !moore.i32 + // CHECK: moore.add [[TMP1]], [[TMP3]] : i32 c = a + v; // CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: [[TMP2:%.+]] = moore.read %b @@ -837,6 +972,16 @@ module Expressions; // CHECK: [[NOT_BOTH:%.+]] = moore.and [[NOT_A]], [[NOT_B]] : i1 // CHECK: moore.or [[BOTH]], [[NOT_BOTH]] : i1 c = a <-> b; + // CHECK: [[TMP:%.+]] = moore.read %x : + // CHECK: [[Y:%.+]] = moore.read %y : + // CHECK: [[X:%.+]] = moore.conversion [[TMP]] : !moore.i1 -> !moore.l1 + // CHECK: moore.and [[X]], [[Y]] : l1 + y = x && y; + // CHECK: [[Y:%.+]] = moore.read %y : + // CHECK: [[TMP:%.+]] = moore.read %x : + // CHECK: [[X:%.+]] = moore.conversion [[TMP]] : !moore.i1 -> !moore.l1 + // CHECK: moore.and [[Y]], [[X]] : l1 + y = y && x; // CHECK: [[TMP1:%.+]] = moore.read %a // CHECK: [[TMP2:%.+]] = moore.read %b @@ -1046,6 +1191,64 @@ module Expressions; // CHECK: moore.blocking_assign %b, [[TMP2]] b = struct0.b; + //===------------------------------------------------------------------===// + // Assignment Patterns + + // CHECK: [[TMP0:%.+]] = moore.constant 17 + // CHECK: [[TMP1:%.+]] = moore.constant 17 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{default: 17}; + + // CHECK: [[TMP0:%.+]] = moore.constant 1337 + // CHECK: [[TMP1:%.+]] = moore.constant 1337 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{int: 1337}; + + // CHECK: [[TMP0:%.+]] = moore.constant 420 + // CHECK: moore.struct_create [[TMP0]], [[TMP0]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{2{420}}; + + // CHECK: [[TMP0:%.+]] = moore.constant 42 + // CHECK: [[TMP1:%.+]] = moore.constant 9001 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{a: 42, b: 9001}; + + // CHECK: [[TMP0:%.+]] = moore.constant 43 + // CHECK: [[TMP1:%.+]] = moore.constant 9002 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + struct0 = '{43, 9002}; + + // CHECK: [[TMP0:%.+]] = moore.constant 44 + // CHECK: [[TMP1:%.+]] = moore.constant 9003 + // CHECK: moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> ustruct<{a: i32, b: i32}> + ustruct0 = '{44, 9003}; + + // CHECK: [[TMP0:%.+]] = moore.constant 1 + // CHECK: [[TMP1:%.+]] = moore.constant 2 + // CHECK: [[TMP2:%.+]] = moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + // CHECK: [[TMP0:%.+]] = moore.constant 3 + // CHECK: [[TMP1:%.+]] = moore.constant 4 + // CHECK: [[TMP3:%.+]] = moore.struct_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + // CHECK: moore.struct_create [[TMP2]], [[TMP3]] : !moore.struct<{a: i32, b: i32}>, !moore.struct<{a: i32, b: i32}> -> struct<{c: struct<{a: i32, b: i32}>, d: struct<{a: i32, b: i32}>}> + struct1 = '{c: '{a: 1, b: 2}, d: '{a: 3, b: 4}}; + + // CHECK: [[TMP0:%.+]] = moore.constant 42 + // CHECK: [[TMP1:%.+]] = moore.constant 9001 + // CHECK: moore.array_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> array<2 x i32> + arrayInt = '{42, 9001}; + + // CHECK: [[TMP0:%.+]] = moore.constant 43 + // CHECK: [[TMP1:%.+]] = moore.constant 9002 + // CHECK: moore.array_create [[TMP0]], [[TMP1]] : !moore.i32, !moore.i32 -> uarray<2 x i32> + uarrayInt = '{43, 9002}; + + // CHECK: [[TMP0:%.+]] = moore.constant 1 + // CHECK: [[TMP1:%.+]] = moore.constant 2 + // CHECK: [[TMP2:%.+]] = moore.constant 3 + // CHECK: [[TMP3:%.+]] = moore.array_create [[TMP0]], [[TMP1]], [[TMP2]], [[TMP0]], [[TMP1]], [[TMP2]] : !moore.i4, !moore.i4, !moore.i4, !moore.i4, !moore.i4, !moore.i4 -> uarray<6 x i4> + // CHECK: moore.array_create [[TMP3]], [[TMP3]], [[TMP3]] : !moore.uarray<6 x i4>, !moore.uarray<6 x i4>, !moore.uarray<6 x i4> -> uarray<3 x uarray<6 x i4>> + arr = '{3{'{2{4'd1, 4'd2, 4'd3}}}}; + //===------------------------------------------------------------------===// // Builtin Functions @@ -1080,11 +1283,9 @@ module Conversion; // Sign conversion. // CHECK: [[TMP1:%.+]] = moore.read %b - // CHECK: [[TMP2:%.+]] = moore.conversion [[TMP1]] : !moore.i32 -> !moore.i32 - // CHECK: %d1 = moore.variable [[TMP2]] - // CHECK: [[TMP3:%.+]] = moore.read %b - // CHECK: [[TMP4:%.+]] = moore.conversion [[TMP3]] : !moore.i32 -> !moore.i32 - // CHECK: %d2 = moore.variable [[TMP4]] + // CHECK: %d1 = moore.variable [[TMP1]] + // CHECK: [[TMP2:%.+]] = moore.read %b + // CHECK: %d2 = moore.variable [[TMP2]] bit signed [31:0] d1 = signed'(b); bit [31:0] d2 = unsigned'(b); @@ -1325,53 +1526,6 @@ module PortsUnconnected( // CHECK: moore.output [[D_READ]], [[E_READ]] : !moore.l1, !moore.l1 endmodule -// CHECK-LABEL: moore.module @EventControl(in %clk : !moore.l1) -module EventControl(input clk); - // CHECK: %clk_0 = moore.net name "clk" wire : - - int a1, a2, b, c; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event posedge [[CLK_READ]] : l1 - always @(posedge clk) begin end; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event negedge [[CLK_READ]] : l1 - always @(negedge clk) begin end; - - // CHECK: moore.procedure always - // CHECK: [[CLK_READ:%.+]] = moore.read %clk_0 - // CHECK: moore.wait_event edge [[CLK_READ]] : l1 - always @(edge clk) begin end; - - // CHECK: moore.procedure always { - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: moore.wait_event none [[B_READ]] : i32 - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: moore.wait_event none [[C_READ]] : i32 - always @(b, c) begin - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: [[ADD:%.+]] = moore.add [[B_READ]], [[C_READ]] : i32 - // CHECK: moore.blocking_assign %a1, [[ADD]] : i32 - a1 = b + c; - end; - - // CHECK: moore.procedure always - always @(*) begin - // CHECK: [[B_READ:%.+]] = moore.read %b - // CHECK: [[C_READ:%.+]] = moore.read %c - // CHECK: [[ADD:%.+]] = moore.add [[B_READ]], [[C_READ]] : i32 - // CHECK: moore.blocking_assign %a2, [[ADD]] : i32 - a2 = b + c; - end - - // CHECK: moore.assign %clk_0, %clk : l1 - // CHECK: moore.output -endmodule - // CHECK-LABEL: moore.module @GenerateConstructs() module GenerateConstructs; genvar i; @@ -1514,3 +1668,239 @@ function void funcArgs2(); funcArgs1(42, x, y, z, w); // CHECK: return endfunction + +// CHECK-LABEL: func.func private @ConvertConditionalExprsToResultType( +function void ConvertConditionalExprsToResultType(bit [15:0] x, struct packed { bit [15:0] a; } y, bit z); + bit [15:0] r; + // CHECK: moore.conditional %arg2 : i1 -> i16 { + // CHECK: moore.yield %arg0 + // CHECK: } { + // CHECK: [[TMP:%.+]] = moore.conversion %arg1 + // CHECK: moore.yield [[TMP]] + // CHECK: } + r = z ? x : y; + // CHECK: moore.conditional %arg2 : i1 -> i16 { + // CHECK: [[TMP:%.+]] = moore.conversion %arg1 + // CHECK: moore.yield [[TMP]] + // CHECK: } { + // CHECK: moore.yield %arg0 + // CHECK: } + r = z ? y : x; +endfunction + +// CHECK-LABEL: func.func private @ImplicitEventControl( +// CHECK-SAME: [[X:%[^:]+]]: !moore.ref +// CHECK-SAME: [[Y:%[^:]+]]: !moore.ref +task automatic ImplicitEventControl(ref int x, ref int y); + // CHECK: moore.wait_event { + // CHECK: } + // CHECK: call @dummyA() + @* dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: call @dummyD([[TMP]]) + @* dummyD(x); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[Y]] + // CHECK: [[TMP3:%.+]] = moore.add [[TMP1]], [[TMP2]] + // CHECK: call @dummyD([[TMP3]]) + @* dummyD(x + y); +endtask + +// CHECK-LABEL: func.func private @SignalEventControl( +// CHECK-SAME: [[X:%[^:]+]]: !moore.ref +// CHECK-SAME: [[Y:%[^:]+]]: !moore.ref +// CHECK-SAME: [[U:%[^:]+]]: !moore.ref +// CHECK-SAME: [[V:%[^:]+]]: !moore.ref +task automatic SignalEventControl(ref int x, ref int y, ref bit u, ref logic v); + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @x dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event posedge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event negedge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(negedge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event edge [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(edge x) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[U]] + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP2]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff u) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[V]] : + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.l1 -> !moore.i1 + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff v) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[Y]] + // CHECK: [[TMP3:%.+]] = moore.bool_cast [[TMP2]] : i32 -> i1 + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x or y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read [[X]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read [[Y]] + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: call @dummyA() + @(x, y) dummyA(); + + // CHECK: moore.wait_event { + // CHECK: [[TMP1:%.+]] = moore.read [[X]] + // CHECK: [[TMP2:%.+]] = moore.read [[U]] + // CHECK: moore.detect_event posedge [[TMP1]] if [[TMP2]] + // CHECK: [[TMP1:%.+]] = moore.read [[Y]] + // CHECK: [[TMP2:%.+]] = moore.read [[V]] + // CHECK: [[TMP3:%.+]] = moore.conversion [[TMP2]] : !moore.l1 -> !moore.i1 + // CHECK: moore.detect_event negedge [[TMP1]] if [[TMP3]] + // CHECK: } + // CHECK: call @dummyA() + @(posedge x iff u, negedge y iff v) dummyA(); +endtask + +// CHECK-LABEL: func.func private @ImplicitEventControlExamples( +task automatic ImplicitEventControlExamples(); + // Taken from IEEE 1800-2017 section 9.4.2.2 "Implicit event_expression list". + bit a, b, c, d, f, y, tmp1, tmp2; + int x; + + // Example 1 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %f + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @(*) y = (a & b) | (c & d) | dummyE(f); // equivalent to @(a, b, c, d, f) + + // Example 2 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %tmp1 + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %tmp2 + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b, c, d, tmp1, tmp2) + tmp1 = a & b; + tmp2 = c & d; + y = tmp1 | tmp2; + end + + // Example 3 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(b) + @(a) y = b; // a inside @(a) is not added to outer @* + end + + // Example 4 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %d + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b, c, d) + y = a ^ b; + @* y = c ^ d; // equivalent to @(c, d) + end + + // Example 5 + // CHECK: moore.wait_event { + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: moore.detect_event any [[TMP]] + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: moore.detect_event any [[TMP]] + // CHECK: } + @* begin // equivalent to @(a, b) + x[a] = !b; + end +endtask diff --git a/test/Conversion/ImportVerilog/errors.sv b/test/Conversion/ImportVerilog/errors.sv index 0d161459b5b3..554c36028861 100644 --- a/test/Conversion/ImportVerilog/errors.sv +++ b/test/Conversion/ImportVerilog/errors.sv @@ -28,13 +28,13 @@ endmodule // ----- module Foo; parameter a = 1; - // expected-error @below {{unsupported construct}} + // expected-error @below {{unsupported module member}} defparam a = 2; endmodule // ----- module Foo; - // expected-error @below {{unsupported construct}} + // expected-error @below {{unsupported module member}} nettype real x; endmodule @@ -73,43 +73,9 @@ module Foo; initial if (x matches 42) x = y; endmodule -// ----- -module Foo; - // expected-error @below {{literals with X or Z bits not supported}} - logic a = 'x; -endmodule - -// ----- -module Foo; - // expected-error @below {{literals with X or Z bits not supported}} - logic a = 'z; -endmodule - // ----- module Foo; int a, b[3]; // expected-error @below {{unpacked arrays in 'inside' expressions not supported}} int c = a inside { b }; endmodule - -// ----- -module Foo; - logic a, b; - initial begin - casez (a) - // expected-error @below {{literals with X or Z bits not supported}} - 1'bz : b = 1'b1; - endcase - end -endmodule - -// ----- -module Foo; - logic a; - initial begin - // expected-error @below {{literals with X or Z bits not supported}} - casez (1'bz) - 1'bz : a = 1'b1; - endcase - end -endmodule diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index a516525be4a0..fd2a1fd7b112 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -46,7 +46,7 @@ func.func @UnrealizedConversionCast(%arg0: !moore.i8) -> !moore.i16 { } // CHECK-LABEL: func @Expressions -func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %arg3: !moore.i5, %arg4: !moore.i1) { +func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %arg3: !moore.i5, %arg4: !moore.i1, %arg5: !moore.array<5 x i32>, %arg6: !moore.ref, %arg7: !moore.ref>) { // CHECK-NEXT: %0 = comb.concat %arg0, %arg0 : i1, i1 // CHECK-NEXT: %1 = comb.concat %arg1, %arg1 : i1, i1 moore.concat %arg0, %arg0 : (!moore.i1, !moore.i1) -> !moore.i2 @@ -57,13 +57,25 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar moore.replicate %arg0 : i1 -> i2 moore.replicate %arg1 : l1 -> l2 + // CHECK-NEXT: %name = hw.wire %arg0 : i1 + %name = moore.assigned_variable %arg0 : !moore.i1 + // CHECK-NEXT: %c12_i32 = hw.constant 12 : i32 // CHECK-NEXT: %c3_i6 = hw.constant 3 : i6 moore.constant 12 : !moore.i32 moore.constant 3 : !moore.i6 - // CHECK-NEXT: hw.bitcast %arg0 : (i1) -> i1 moore.conversion %arg0 : !moore.i1 -> !moore.l1 + // CHECK-NEXT: [[V0:%.+]] = hw.constant 0 : i2 + // CHECK-NEXT: comb.concat [[V0]], %arg2 : i2, i6 + moore.conversion %arg2 : !moore.i6 -> !moore.l8 + // CHECK-NEXT: [[V0:%.+]] = comb.extract %arg2 from 4 : (i6) -> i2 + // CHECK-NEXT: [[V1:%.+]] = hw.constant 0 : i2 + // CHECK-NEXT: [[V2:%.+]] = comb.icmp eq [[V0]], [[V1]] : i2 + // CHECK-NEXT: [[V3:%.+]] = comb.extract %arg2 from 0 : (i6) -> i4 + // CHECK-NEXT: [[V4:%.+]] = hw.constant -1 : i4 + // CHECK-NEXT: comb.mux [[V2]], [[V3]], [[V4]] : i4 + moore.conversion %arg2 : !moore.i6 -> !moore.l4 // CHECK-NEXT: [[V0:%.+]] = hw.constant 0 : i5 // CHECK-NEXT: [[V1:%.+]] = comb.concat [[V0]], %arg0 : i5, i1 @@ -98,9 +110,27 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar // CHECK-NEXT: %c2_i32 = hw.constant 2 : i32 %2 = moore.constant 2 : !moore.i32 + // CHECK-NEXT: %c0_i32 = hw.constant 0 : i32 + %c0 = moore.constant 0 : !moore.i32 // CHECK-NEXT: comb.extract %arg2 from 2 : (i6) -> i2 moore.extract %arg2 from 2 : !moore.i6 -> !moore.i2 + // CHECK-NEXT: [[V0:%.+]] = hw.constant 2 : i3 + // CHECK-NEXT: hw.array_slice %arg5[[[V0]]] : (!hw.array<5xi32>) -> !hw.array<2xi32> + moore.extract %arg5 from 2 : !moore.array<5 x i32> -> !moore.array<2 x i32> + // CHECK-NEXT: [[V0:%.+]] = hw.constant 2 : i3 + // CHECK-NEXT: hw.array_get %arg5[[[V0]]] : !hw.array<5xi32> + moore.extract %arg5 from 2 : !moore.array<5 x i32> -> i32 + + // CHECK-NEXT: [[V0:%.+]] = hw.constant 0 : i0 + // CHECK-NEXT: llhd.sig.extract %arg6 from [[V0]] : (!hw.inout) -> !hw.inout + moore.extract_ref %arg6 from 0 : !moore.ref -> !moore.ref + // CHECK-NEXT: [[V0:%.+]] = hw.constant 2 : i3 + // CHECK-NEXT: llhd.sig.array_slice %arg7 at [[V0]] : (!hw.inout>) -> !hw.inout> + moore.extract_ref %arg7 from 2 : !moore.ref> -> !moore.ref> + // CHECK-NEXT: [[V0:%.+]] = hw.constant 2 : i3 + // CHECK-NEXT: llhd.sig.array_get %arg7[[[V0]]] : !hw.inout> + moore.extract_ref %arg7 from 2 : !moore.ref> -> !moore.ref // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 6 : (i32) -> i26 // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i26 @@ -111,6 +141,47 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar // CHECK-NEXT: [[V25:%.+]] = comb.shru %arg2, [[V24]] : i6 // CHECK-NEXT: comb.extract [[V25]] from 0 : (i6) -> i1 moore.dyn_extract %arg2 from %2 : !moore.i6, !moore.i32 -> !moore.i1 + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 3 : (i32) -> i29 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i29 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i29 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c2_i32 from 0 : (i32) -> i3 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant -1 : i3 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i3 + // CHECK-NEXT: hw.array_slice %arg5[[[V24]]] : (!hw.array<5xi32>) -> !hw.array<2xi32> + moore.dyn_extract %arg5 from %2 : !moore.array<5 x i32>, !moore.i32 -> !moore.array<2 x i32> + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 3 : (i32) -> i29 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i29 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i29 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c2_i32 from 0 : (i32) -> i3 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant -1 : i3 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i3 + // CHECK-NEXT: hw.array_get %arg5[[[V24]]] : !hw.array<5xi32> + moore.dyn_extract %arg5 from %2 : !moore.array<5 x i32>, !moore.i32 -> !moore.i32 + + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c0_i32 from 0 : (i32) -> i32 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i32 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i32 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c0_i32 from 0 : (i32) -> i0 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant 0 : i0 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i0 + // CHECK-NEXT: llhd.sig.extract %arg6 from [[V24]] : (!hw.inout) -> !hw.inout + moore.dyn_extract_ref %arg6 from %c0 : !moore.ref, !moore.i32 -> !moore.ref + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 3 : (i32) -> i29 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i29 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i29 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c2_i32 from 0 : (i32) -> i3 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant -1 : i3 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i3 + // CHECK-NEXT: llhd.sig.array_slice %arg7 at [[V24]] : (!hw.inout>) -> !hw.inout> + moore.dyn_extract_ref %arg7 from %2 : !moore.ref>, !moore.i32 -> !moore.ref> + // CHECK-NEXT: [[V21:%.+]] = comb.extract %c2_i32 from 3 : (i32) -> i29 + // CHECK-NEXT: [[CONST_0:%.+]] = hw.constant 0 : i29 + // CHECK-NEXT: [[V22:%.+]] = comb.icmp eq [[V21]], [[CONST_0]] : i29 + // CHECK-NEXT: [[V23:%.+]] = comb.extract %c2_i32 from 0 : (i32) -> i3 + // CHECK-NEXT: [[MAX:%.+]] = hw.constant -1 : i3 + // CHECK-NEXT: [[V24:%.+]] = comb.mux [[V22]], [[V23]], [[MAX]] : i3 + // CHECK-NEXT: llhd.sig.array_get %arg7[[[V24]]] : !hw.inout> + moore.dyn_extract_ref %arg7 from %2 : !moore.ref>, !moore.i32 -> !moore.ref // CHECK-NEXT: [[V26:%.+]] = hw.constant -1 : i6 // CHECK-NEXT: comb.icmp eq %arg2, [[V26]] : i6 @@ -187,6 +258,64 @@ func.func @Expressions(%arg0: !moore.i1, %arg1: !moore.l1, %arg2: !moore.i6, %ar moore.wildcard_eq %arg0, %arg0 : !moore.i1 -> !moore.i1 moore.wildcard_ne %arg0, %arg0 : !moore.i1 -> !moore.i1 + // CHECK-NEXT: [[RES:%.+]] = scf.if %arg0 -> (i6) { + // CHECK-NEXT: scf.yield %arg2 : i6 + // CHECK-NEXT: } else { + // CHECK-NEXT: [[TMP:%.+]] = hw.constant 19 : i6 + // CHECK-NEXT: scf.yield [[TMP]] : i6 + // CHECK-NEXT: } + // CHECK-NEXT: comb.parity [[RES]] : i6 + %k0 = moore.conditional %arg0 : i1 -> i6 { + moore.yield %arg2 : i6 + } { + %0 = moore.constant 19 : i6 + moore.yield %0 : i6 + } + moore.reduce_xor %k0 : i6 -> i1 + + // CHECK-NEXT: [[RES:%.+]] = scf.if %arg1 -> (i6) { + // CHECK-NEXT: [[TMP:%.+]] = hw.constant 0 : i6 + // CHECK-NEXT: scf.yield [[TMP]] : i6 + // CHECK-NEXT: } else { + // CHECK-NEXT: [[TMP:%.+]] = hw.constant 19 : i6 + // CHECK-NEXT: scf.yield [[TMP]] : i6 + // CHECK-NEXT: } + // CHECK-NEXT: comb.parity [[RES]] : i6 + %k1 = moore.conditional %arg1 : l1 -> l6 { + %0 = moore.constant bXXXXXX : l6 + moore.yield %0 : l6 + } { + %0 = moore.constant 19 : l6 + moore.yield %0 : l6 + } + moore.reduce_xor %k1 : l6 -> l1 + + // CHECK-NEXT: return + return +} + +// CHECK-LABEL: func @AdvancedConversion +func.func @AdvancedConversion(%arg0: !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>) -> (!moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i320) { + // CHECK: [[V0:%.+]] = hw.constant 3978585893941511189997889893581765703992223160870725712510875979948892565035285336817671 : i320 + %0 = moore.constant 3978585893941511189997889893581765703992223160870725712510875979948892565035285336817671 : i320 + // CHECK: [[V1:%.+]] = hw.bitcast [[V0]] : (i320) -> !hw.array<5xstruct> + %1 = moore.conversion %0 : !moore.i320 -> !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>> + // CHECK: [[V2:%.+]] = hw.bitcast %arg0 : (!hw.array<5xstruct>) -> i320 + %2 = moore.conversion %arg0 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>> -> !moore.i320 + // CHECK: return [[V1]], [[V2]] + return %1, %2 : !moore.array<5 x struct<{exp_bits: i32, man_bits: i32}>>, !moore.i320 +} + +// CHECK-LABEL: func @Statements +func.func @Statements(%arg0: !moore.i42) { + // CHECK: %x = llhd.sig + %x = moore.variable : + // CHECK: [[TMP:%.+]] = llhd.constant_time <0ns, 0d, 1e> + // CHECK: llhd.drv %x, %arg0 after [[TMP]] : !hw.inout + moore.blocking_assign %x, %arg0 : i42 + // CHECK: [[TMP:%.+]] = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv %x, %arg0 after [[TMP]] : !hw.inout + moore.nonblocking_assign %x, %arg0 : i42 // CHECK-NEXT: return return } @@ -212,13 +341,13 @@ moore.module private @Null() { // CHECK-SAME: %[[V0:.*]] : i1, in // CHECK-SAME: %[[V1:.*]] : i1, out out0 : i1) { moore.module @Top(in %arg0 : !moore.l1, in %arg1 : !moore.l1, out out0 : !moore.l1) { -// CHECK-NEXT: %[[V2:.*]] = hw.instance "inst_0" @SubModule_0(a: %[[V0]]: i1, b: %[[V1]]: i1) -> (c: i1) + // CHECK-NEXT: %[[V2:.*]] = hw.instance "inst_0" @SubModule_0(a: %[[V0]]: i1, b: %[[V1]]: i1) -> (c: i1) %inst_0.c = moore.instance "inst_0" @SubModule_0(a: %arg0 : !moore.l1, b: %arg1 : !moore.l1) -> (c: !moore.l1) -// CHECK-NEXT: %[[V3:.*]] = hw.instance "inst_1" @SubModule_0(a: %[[V2]]: i1, b: %[[V1]]: i1) -> (c: i1) + // CHECK-NEXT: %[[V3:.*]] = hw.instance "inst_1" @SubModule_0(a: %[[V2]]: i1, b: %[[V1]]: i1) -> (c: i1) %inst_1.c = moore.instance "inst_1" @SubModule_0(a: %inst_0.c : !moore.l1, b: %arg1 : !moore.l1) -> (c: !moore.l1) -// CHECK-NEXT: hw.output %[[V3]] : i1 + // CHECK-NEXT: hw.output %[[V3]] : i1 moore.output %inst_1.c : !moore.l1 } @@ -254,32 +383,401 @@ moore.module @ParamTest(){ moore.module @Variable() { // CHECK: [[TMP0:%.+]] = hw.constant 0 : i32 - // CHECK: [[A:%.+]] = llhd.sig "a" [[TMP0]] : i32 + // CHECK: %a = llhd.sig [[TMP0]] : i32 %a = moore.variable : // CHECK: [[TMP1:%.+]] = hw.constant 0 : i8 - // CHECK: [[B:%.+]] = llhd.sig "b1" [[TMP1]] : i8 + // CHECK: %b1 = llhd.sig [[TMP1]] : i8 %b1 = moore.variable : - // CHECK: [[PRB:%.+]] = llhd.prb [[B]] : !hw.inout + // CHECK: [[PRB:%.+]] = llhd.prb %b1 : !hw.inout %0 = moore.read %b1 : - // CHECK: llhd.sig "b2" [[PRB]] : i8 + // CHECK: %b2 = llhd.sig [[PRB]] : i8 %b2 = moore.variable %0 : // CHECK: %true = hw.constant true - %1 = moore.constant true : i1 - // CHECK: [[CAST:%.+]] = hw.bitcast %true : (i1) -> i1 - %2 = moore.conversion %1 : !moore.i1 -> !moore.l1 - // CHECK: llhd.sig "l" [[CAST]] : i1 - %l = moore.variable %2 : + %1 = moore.constant 1 : l1 + // CHECK: %l = llhd.sig %true : i1 + %l = moore.variable %1 : + // CHECK: [[TMP:%.+]] = hw.constant 0 : i19 + // CHECK: %m = llhd.sig [[TMP]] : i19 + %m = moore.variable : // CHECK: [[TMP2:%.+]] = hw.constant 10 : i32 %3 = moore.constant 10 : i32 - // CHECK: [[TIME:%.+]] = llhd.constant_time <0ns, 0d, 0e> - // CHECK: llhd.drv [[A]], [[TMP2]] after [[TIME]] : !hw.inout + // CHECK: [[TIME:%.+]] = llhd.constant_time <0ns, 0d, 1e> + // CHECK: llhd.drv %a, [[TMP2]] after [[TIME]] : !hw.inout moore.assign %a, %3 : i32 // CHECK: hw.output moore.output } + +// CHECK-LABEL: hw.module @Struct +moore.module @Struct(in %a : !moore.i32, in %b : !moore.i32, in %arg0 : !moore.struct<{exp_bits: i32, man_bits: i32}>, in %arg1 : !moore.ref>, out a : !moore.i32, out b : !moore.struct<{exp_bits: i32, man_bits: i32}>, out c : !moore.struct<{exp_bits: i32, man_bits: i32}>) { + // CHECK: hw.struct_extract %arg0["exp_bits"] : !hw.struct + %0 = moore.struct_extract %arg0, "exp_bits" : !moore.struct<{exp_bits: i32, man_bits: i32}> -> i32 + + // CHECK: llhd.sig.struct_extract %arg1["exp_bits"] : !hw.inout> + %ref = moore.struct_extract_ref %arg1, "exp_bits" : > -> + moore.assign %ref, %0 : !moore.i32 + + // CHECK: [[C0:%.+]] = hw.constant 0 : i64 + // CHECK: [[INIT:%.+]] = hw.bitcast [[C0]] : (i64) -> !hw.struct + // CHECK: llhd.sig [[INIT]] : !hw.struct + // CHECK: llhd.sig %arg0 : !hw.struct + %1 = moore.variable : > + %2 = moore.variable %arg0 : > + + %3 = moore.read %1 : > + %4 = moore.read %2 : > + + // CHECK; hw.struct_create %a, %b : !hw.struct + moore.struct_create %a, %b : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> + + moore.output %0, %3, %4 : !moore.i32, !moore.struct<{exp_bits: i32, man_bits: i32}>, !moore.struct<{exp_bits: i32, man_bits: i32}> +} + +// CHECK-LABEL: func.func @CaseXZ( +func.func @CaseXZ(%arg0: !moore.l8, %arg1: !moore.l8) { + // CHECK: hw.constant -124 : i8 + // CHECK: hw.constant -120 : i8 + %0 = moore.constant b10XX01ZZ : l8 + %1 = moore.constant b1XX01ZZ0 : l8 + + // CHECK: comb.icmp ceq %arg0, %arg1 : i8 + moore.casez_eq %arg0, %arg1 : l8 + // CHECK: [[MASK:%.+]] = hw.constant -7 : i8 + // CHECK: [[TMP1:%.+]] = comb.and %arg0, [[MASK]] + // CHECK: [[TMP2:%.+]] = hw.constant -120 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casez_eq %arg0, %1 : l8 + // CHECK: [[MASK:%.+]] = hw.constant -4 : i8 + // CHECK: [[TMP1:%.+]] = comb.and %arg1, [[MASK]] + // CHECK: [[TMP2:%.+]] = hw.constant -124 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casez_eq %0, %arg1 : l8 + // CHECK: [[TMP1:%.+]] = hw.constant -128 : i8 + // CHECK: [[TMP2:%.+]] = hw.constant -120 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casez_eq %0, %1 : l8 + + // CHECK: comb.icmp ceq %arg0, %arg1 : i8 + moore.casexz_eq %arg0, %arg1 : l8 + // CHECK: [[MASK:%.+]] = hw.constant -103 : i8 + // CHECK: [[TMP1:%.+]] = comb.and %arg0, [[MASK]] + // CHECK: [[TMP2:%.+]] = hw.constant -120 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casexz_eq %arg0, %1 : l8 + // CHECK: [[MASK:%.+]] = hw.constant -52 : i8 + // CHECK: [[TMP1:%.+]] = comb.and %arg1, [[MASK]] + // CHECK: [[TMP2:%.+]] = hw.constant -124 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casexz_eq %0, %arg1 : l8 + // CHECK: [[TMP1:%.+]] = hw.constant -128 : i8 + // CHECK: [[TMP2:%.+]] = hw.constant -120 : i8 + // CHECK: comb.icmp ceq [[TMP1]], [[TMP2]] : i8 + moore.casexz_eq %0, %1 : l8 + + return +} + +// CHECK-LABEL: hw.module @Procedures +moore.module @Procedures() { + // CHECK: llhd.process { + // CHECK: func.call @dummyA() + // CHECK: llhd.halt + // CHECK: } + moore.procedure initial { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.final { + // CHECK: func.call @dummyA() + // CHECK: llhd.halt + // CHECK: } + moore.procedure final { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB:.+]] + // CHECK: ^[[BB]]: + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[BB]] + // CHECK: } + moore.procedure always { + func.call @dummyA() : () -> () + moore.return + } + + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB:.+]] + // CHECK: ^[[BB]]: + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[BB]] + // CHECK: } + moore.procedure always_ff { + func.call @dummyA() : () -> () + moore.return + } + + // TODO: moore.procedure always_comb + // TODO: moore.procedure always_latch +} + +func.func private @dummyA() -> () +func.func private @dummyB() -> () +func.func private @dummyC() -> () + +// CHECK-LABEL: hw.module @WaitEvent +moore.module @WaitEvent() { + // CHECK: %a = llhd.sig + // CHECK: [[PRB_A6:%.+]] = llhd.prb %a + // CHECK: [[PRB_A5:%.+]] = llhd.prb %a + // CHECK: [[PRB_A4:%.+]] = llhd.prb %a + // CHECK: [[PRB_A3:%.+]] = llhd.prb %a + // CHECK: [[PRB_A2:%.+]] = llhd.prb %a + // CHECK: [[PRB_A1:%.+]] = llhd.prb %a + // CHECK: [[PRB_A0:%.+]] = llhd.prb %a + // CHECK: %b = llhd.sig + // CHECK: [[PRB_B2:%.+]] = llhd.prb %b + // CHECK: [[PRB_B1:%.+]] = llhd.prb %b + // CHECK: [[PRB_B0:%.+]] = llhd.prb %b + // CHECK: %c = llhd.sig + // CHECK: [[PRB_C:%.+]] = llhd.prb %c + %a = moore.variable : + %b = moore.variable : + %c = moore.variable : + + // CHECK: llhd.process { + // CHECK: func.call @dummyA() + // CHECK: cf.br ^[[WAIT:.+]] + // CHECK: ^[[WAIT]]: + // CHECK: func.call @dummyB() + // CHECK: llhd.wait ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: func.call @dummyB() + // CHECK: cf.br ^[[WAIT:.+]] + // CHECK: ^[[RESUME:.+]]: + // CHECK: func.call @dummyC() + // CHECK: llhd.prb %a + // CHECK: llhd.halt + // CHECK: } + moore.procedure initial { + func.call @dummyA() : () -> () + moore.wait_event { + func.call @dummyB() : () -> () + } + func.call @dummyC() : () -> () + moore.read %a : + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait ([[PRB_A0]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TMP:%.+]] = comb.icmp bin ne [[BEFORE]], [[AFTER]] + // CHECK: cf.cond_br [[TMP]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event any %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE_A:%.+]] = llhd.prb %a + // CHECK: [[BEFORE_B:%.+]] = llhd.prb %b + // CHECK: llhd.wait ([[PRB_A1]], [[PRB_B0]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER_A:%.+]] = llhd.prb %a + // CHECK: [[AFTER_B:%.+]] = llhd.prb %b + // CHECK: [[TMP1:%.+]] = comb.icmp bin ne [[BEFORE_A]], [[AFTER_A]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER_B]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + %1 = moore.read %b : + moore.detect_event any %0 if %1 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE_A:%.+]] = llhd.prb %a + // CHECK: [[BEFORE_B:%.+]] = llhd.prb %b + // CHECK: [[BEFORE_C:%.+]] = llhd.prb %c + // CHECK: llhd.wait ([[PRB_A2]], [[PRB_B1]], [[PRB_C]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER_A:%.+]] = llhd.prb %a + // CHECK: [[AFTER_B:%.+]] = llhd.prb %b + // CHECK: [[AFTER_C:%.+]] = llhd.prb %c + // CHECK: [[TMP1:%.+]] = comb.icmp bin ne [[BEFORE_A]], [[AFTER_A]] + // CHECK: [[TMP2:%.+]] = comb.icmp bin ne [[BEFORE_B]], [[AFTER_B]] + // CHECK: [[TMP3:%.+]] = comb.icmp bin ne [[BEFORE_C]], [[AFTER_C]] + // CHECK: [[TMP4:%.+]] = comb.or bin [[TMP1]], [[TMP2]], [[TMP3]] + // CHECK: cf.cond_br [[TMP4]] + moore.wait_event { + %0 = moore.read %a : + %1 = moore.read %b : + %2 = moore.read %c : + moore.detect_event any %0 : i1 + moore.detect_event any %1 : i1 + moore.detect_event any %2 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait ([[PRB_A3]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[BEFORE]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event posedge %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait ([[PRB_A4]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[AFTER]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[BEFORE]], [[TMP1]] + // CHECK: cf.cond_br [[TMP2]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event negedge %0 : i1 + } + moore.return + } + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: [[BEFORE:%.+]] = llhd.prb %a + // CHECK: llhd.wait ([[PRB_A5]] : {{.+}}), ^[[CHECK:.+]] + // CHECK: ^[[CHECK]]: + // CHECK: [[AFTER:%.+]] = llhd.prb %a + // CHECK: [[TRUE:%.+]] = hw.constant true + // CHECK: [[TMP1:%.+]] = comb.xor bin [[BEFORE]], [[TRUE]] + // CHECK: [[TMP2:%.+]] = comb.and bin [[TMP1]], [[AFTER]] + // CHECK: [[TMP3:%.+]] = comb.xor bin [[AFTER]], [[TRUE]] + // CHECK: [[TMP4:%.+]] = comb.and bin [[BEFORE]], [[TMP3]] + // CHECK: [[TMP5:%.+]] = comb.or bin [[TMP2]], [[TMP4]] + // CHECK: cf.cond_br [[TMP5]] + moore.wait_event { + %0 = moore.read %a : + moore.detect_event edge %0 : i1 + } + moore.return + } + + // CHECK: [[PRB_A:%.+]] = llhd.prb %a + // CHECK: [[PRB_B:%.+]] = llhd.prb %b + // CHECK: llhd.process { + %cond = moore.constant 0 : i1 + moore.procedure always_comb { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: llhd.prb %a + // CHECK: llhd.prb %b + // CHECK: cf.br ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: llhd.wait ([[PRB_A]], [[PRB_B]] : {{.*}}), ^[[BB1]] + %1 = moore.conditional %cond : i1 -> i1 { + %2 = moore.read %a : + moore.yield %2 : !moore.i1 + } { + %3 = moore.read %b : + moore.yield %3 : !moore.i1 + } + moore.return + } + + // CHECK: [[PRB_D:%.+]] = llhd.prb %d + // CHECK: llhd.process { + // CHECK: cf.br ^[[BB1:.+]] + // CHECK: ^[[BB1]]: + // CHECK: llhd.prb %d + // CHECK: cf.br ^[[BB2:.+]] + // CHECK: ^[[BB2]]: + // CHECK: llhd.wait ([[PRB_D]] : {{.*}}), ^[[BB1]] + moore.procedure always_latch { + %3 = moore.read %d : + moore.return + } + + // CHECK: %d = llhd.sig %false + %d = moore.variable : + + // CHECK: llhd.process { + moore.procedure initial { + // CHECK: llhd.wait ([[PRB_A6]], [[PRB_B2]] : + moore.wait_event { + %0 = moore.constant 0 : i1 + %1 = moore.conditional %0 : i1 -> i1 { + %2 = moore.read %a : + moore.yield %2 : !moore.i1 + } { + %3 = moore.read %b : + moore.yield %3 : !moore.i1 + } + moore.detect_event any %1 : i1 + } + moore.return + } +} + +// Just check that block without predecessors are handled without crashing +// CHECK-LABEL: @NoPredecessorBlockErasure +moore.module @NoPredecessorBlockErasure(in %clk_i : !moore.l1, in %raddr_i : !moore.array<2 x l5>, out rdata_o : !moore.array<2 x l32>, in %waddr_i : !moore.array<1 x l5>, in %wdata_i : !moore.array<1 x l32>, in %we_i : !moore.l1) { + %0 = moore.constant 0 : l32 + %1 = moore.constant 1 : i32 + %2 = moore.constant 0 : i32 + %rdata_o = moore.variable : > + %mem = moore.variable : > + moore.procedure always_ff { + cf.br ^bb1(%2 : !moore.i32) + ^bb1(%4: !moore.i32): // 2 preds: ^bb0, ^bb8 + moore.return + ^bb2: // no predecessors + cf.br ^bb3(%2 : !moore.i32) + ^bb3(%5: !moore.i32): // 2 preds: ^bb2, ^bb6 + cf.br ^bb8 + ^bb4: // no predecessors + cf.br ^bb6 + ^bb5: // no predecessors + cf.br ^bb6 + ^bb6: // 2 preds: ^bb4, ^bb5 + %6 = moore.add %5, %1 : i32 + cf.br ^bb3(%6 : !moore.i32) + ^bb7: // no predecessors + %7 = moore.extract_ref %mem from 0 : > -> + moore.nonblocking_assign %7, %0 : l32 + cf.br ^bb8 + ^bb8: // 2 preds: ^bb3, ^bb7 + %8 = moore.add %4, %1 : i32 + cf.br ^bb1(%8 : !moore.i32) + } + %3 = moore.read %rdata_o : > + moore.output %3 : !moore.array<2 x l32> +} diff --git a/test/Conversion/SimToSV/dpi.mlir b/test/Conversion/SimToSV/dpi.mlir index c352d5907ade..959e5152e2ef 100644 --- a/test/Conversion/SimToSV/dpi.mlir +++ b/test/Conversion/SimToSV/dpi.mlir @@ -3,8 +3,13 @@ sim.func.dpi @dpi(out arg0: i1, in %arg1: i1, out arg2: i1) // CHECK: sv.func private @dpi(out arg0 : i1, in %arg1 : i1, out arg2 : i1) +// CHECK-NEXT: sv.macro.decl @__CIRCT_DPI_IMPORT_DPI // CHECK-NEXT: emit.fragment @dpi_dpi_import_fragument { -// CHECK-NEXT: sv.func.dpi.import @dpi +// CHECK-NEXT: sv.ifdef @__CIRCT_DPI_IMPORT_DPI { +// CHECK-NEXT: } else { +// CHECK-NEXT: sv.func.dpi.import @dpi +// CHECK-NEXT: sv.macro.def @__CIRCT_DPI_IMPORT_DPI "" +// CHECK-NEXT: } // CHECK-NEXT: } // VERILOG: import "DPI-C" context function void dpi( diff --git a/test/Conversion/VerifToSMT/verif-to-smt.mlir b/test/Conversion/VerifToSMT/verif-to-smt.mlir index b73dd35520f2..92b828b23c9e 100644 --- a/test/Conversion/VerifToSMT/verif-to-smt.mlir +++ b/test/Conversion/VerifToSMT/verif-to-smt.mlir @@ -11,15 +11,15 @@ func.func @test(%arg0: !smt.bv<1>) -> (i1, i1, i1) { // CHECK: [[EQ:%.+]] = smt.solver() : () -> i1 // CHECK: [[IN0:%.+]] = smt.declare_fun : !smt.bv<32> - // CHECK: [[V0:%.+]] = builtin.unrealized_conversion_cast [[IN0]] : !smt.bv<32> to i32 // CHECK: [[IN1:%.+]] = smt.declare_fun : !smt.bv<32> - // CHECK: [[V1:%.+]] = builtin.unrealized_conversion_cast [[IN1]] : !smt.bv<32> to i32 - // CHECK: [[V2:%.+]]:2 = "some_op"([[V0]], [[V1]]) : (i32, i32) -> (i32, i32) - // CHECK: [[V3:%.+]] = builtin.unrealized_conversion_cast [[V2]]#0 : i32 to !smt.bv<32> - // CHECK: [[V4:%.+]] = smt.distinct [[IN0]], [[V3]] : !smt.bv<32> - // CHECK: [[V5:%.+]] = builtin.unrealized_conversion_cast [[V2]]#1 : i32 to !smt.bv<32> - // CHECK: [[V6:%.+]] = smt.distinct [[IN1]], [[V5]] : !smt.bv<32> - // CHECK: [[V7:%.+]] = smt.or [[V4]], [[V6]] + // CHECK-DAG: [[V0:%.+]] = builtin.unrealized_conversion_cast [[IN0]] : !smt.bv<32> to i32 + // CHECK-DAG: [[V1:%.+]] = builtin.unrealized_conversion_cast [[IN1]] : !smt.bv<32> to i32 + // CHECK-DAG: [[V2:%.+]]:2 = "some_op"([[V0]], [[V1]]) : (i32, i32) -> (i32, i32) + // CHECK-DAG: [[V3:%.+]] = builtin.unrealized_conversion_cast [[V2]]#0 : i32 to !smt.bv<32> + // CHECK-DAG: [[V4:%.+]] = smt.distinct [[IN0]], [[V3]] : !smt.bv<32> + // CHECK-DAG: [[V5:%.+]] = builtin.unrealized_conversion_cast [[V2]]#1 : i32 to !smt.bv<32> + // CHECK-DAG: [[V6:%.+]] = smt.distinct [[IN1]], [[V5]] : !smt.bv<32> + // CHECK-DAG: [[V7:%.+]] = smt.or [[V4]], [[V6]] // CHECK: smt.assert [[V7]] // CHECK: [[FALSE:%.+]] = arith.constant false // CHECK: [[TRUE:%.+]] = arith.constant true diff --git a/test/Dialect/Arc/arc-canonicalizer.mlir b/test/Dialect/Arc/arc-canonicalizer.mlir index 29c793458805..eae501a4e17d 100644 --- a/test/Dialect/Arc/arc-canonicalizer.mlir +++ b/test/Dialect/Arc/arc-canonicalizer.mlir @@ -453,22 +453,47 @@ in %clock: !seq.clock, in %o: i8, in %v: i8, in %q: i8, in %s: i8) { } // CHECK-LABEL: hw.module @Needs_Shuffle(in %b : i8, in %e : i8, in %h : i8, in %k : i8, in %c : i8, in %f : i8, in %i : i8, in %l : i8, in %n : i8, in %p : i8, in %r : i8, in %t : i8, in %en : i1, in %clock : !seq.clock, in %o : i8, in %v : i8, in %q : i8, in %s : i8) { -// CHECK-NEXT: [[VEC0:%.+]]:4 = arc.vectorize (%b, %e, %h, %k), (%c, %f, %i, %l) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { -// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8): -// CHECK-NEXT: [[OUT:%.+]] = comb.add %arg0, %arg1 : i8 -// CHECK-NEXT: arc.vectorize.return [[OUT]] : i8 -// CHECK-NEXT: } -// CHECK-NEXT: [[VEC1:%.+]]:4 = arc.vectorize ([[VEC0]]#1, [[VEC0]]#0, [[VEC0]]#2, [[VEC0]]#3), (%n, %p, %r, %t) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { -// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8): -// CHECK-NEXT: [[OUT:%.+]] = comb.and %arg0, %arg1 : i8 -// CHECK-NEXT: arc.vectorize.return [[OUT]] : i8 +// CHECK-NEXT: [[VEC:%.+]]:4 = arc.vectorize (%b, %e, %h, %k), (%c, %f, %i, %l), (%p, %n, %r, %t), (%o, %v, %q, %s) : (i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { +// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8, %arg2: i8, %arg3: i8): +// CHECK-NEXT: [[ADD:%.+]] = comb.add %arg0, %arg1 : i8 +// CHECK-NEXT: [[AND:%.+]] = comb.and [[ADD]], %arg2 : i8 +// CHECK-NEXT: [[CALL:%.+]] = arc.call @Just_A_Dummy_Func([[AND]], %arg3) : (i8, i8) -> i8 +// CHECK-NEXT: arc.vectorize.return [[CALL]] : i8 // CHECK-NEXT: } -// CHECK-NEXT: [[VEC2:%.+]]:4 = arc.vectorize ([[VEC1]]#1, [[VEC1]]#0, [[VEC1]]#2, [[VEC1]]#3), (%o, %v, %q, %s) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { -// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8): -// CHECK-NEXT: [[OUT:%.+]] = arc.call @Just_A_Dummy_Func(%arg0, %arg1) : (i8, i8) -> i8 -// CHECK-NEXT: arc.vectorize.return [[OUT]] : i8 +// CHECK-NEXT: [[STATE:%.+]] = arc.state @FooMux(%en, [[VEC]]#0, [[STATE]]) clock %clock latency 1 : (i1, i8, i8) -> i8 +// CHECK-NEXT: hw.output +// CHECK-NEXT: } + +hw.module @Needs_Shuffle_2(in %b: i8, in %e: i8, in %h: i8, in %k: i8, in %c: i8, in %f: i8, +in %i: i8, in %l: i8, in %n: i8, in %p: i8, in %r: i8, in %t: i8, in %en: i1, +in %clock: !seq.clock, in %o: i8, in %v: i8, in %q: i8, in %s: i8) { + %R:4 = arc.vectorize(%b, %e, %h, %k), (%c, %f, %i, %l) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { + ^bb0(%arg0: i8, %arg1: i8): + %ret = comb.add %arg0, %arg1: i8 + arc.vectorize.return %ret: i8 + } + %L:4 = arc.vectorize(%R#3, %R#2, %R#1, %R#0), (%n, %p, %r, %t): (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { + ^bb0(%arg0: i8, %arg1: i8): + %ret = comb.and %arg0, %arg1: i8 + arc.vectorize.return %ret: i8 + } + %C:4 = arc.vectorize(%L#1, %L#0, %L#2, %L#3), (%o, %v, %q, %s) : (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { + ^bb0(%arg0 : i8, %arg1: i8): + %1692 = arc.call @Just_A_Dummy_Func(%arg0, %arg1) : (i8, i8) -> i8 + arc.vectorize.return %1692 : i8 + } + %4 = arc.state @FooMux(%en, %C#0, %4) clock %clock latency 1 : (i1, i8, i8) -> i8 +} + +// CHECK-LABEL: hw.module @Needs_Shuffle_2(in %b : i8, in %e : i8, in %h : i8, in %k : i8, in %c : i8, in %f : i8, in %i : i8, in %l : i8, in %n : i8, in %p : i8, in %r : i8, in %t : i8, in %en : i1, in %clock : !seq.clock, in %o : i8, in %v : i8, in %q : i8, in %s : i8) { +// CHECK-NEXT: [[VEC:%.+]]:4 = arc.vectorize (%h, %k, %e, %b), (%i, %l, %f, %c), (%p, %n, %r, %t), (%o, %v, %q, %s) : (i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { +// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i8, %arg1: i8, %arg2: i8, %arg3: i8): +// CHECK-NEXT: [[ADD:%.+]] = comb.add %arg0, %arg1 : i8 +// CHECK-NEXT: [[AND:%.+]] = comb.and [[ADD]], %arg2 : i8 +// CHECK-NEXT: [[CALL:%.+]] = arc.call @Just_A_Dummy_Func([[AND]], %arg3) : (i8, i8) -> i8 +// CHECK-NEXT: arc.vectorize.return [[CALL]] : i8 // CHECK-NEXT: } -// CHECK-NEXT: [[STATE:%.+]] = arc.state @FooMux(%en, [[VEC2]]#0, [[STATE:%.+]]) clock %clock latency 1 : (i1, i8, i8) -> i8 +// CHECK-NEXT: [[STATE:%.+]] = arc.state @FooMux(%en, [[VEC]]#0, [[STATE]]) clock %clock latency 1 : (i1, i8, i8) -> i8 // CHECK-NEXT: hw.output // CHECK-NEXT: } @@ -491,6 +516,25 @@ hw.module @Repeated_input(in %b: i8, in %e: i8, in %h: i8, in %k: i8, in %en: i1 // CHECK-NEXT: hw.output // CHECK-NEXT: } + +hw.module @Repeated_again(in %clock : !seq.clock, in %0 : i1, in %in1: i1, in %in2: i1, out oh: i1) { + %2:2 = arc.vectorize (%in1, %in2), (%in1, %in2), (%0, %0), (%0, %0) : (i1, i1, i1, i1, i1, i1, i1, i1) -> (i1, i1) { + ^bb0(%arg0: i1, %arg1: i1, %arg2: i1, %arg3: i1): + %4 = comb.or %arg0, %arg1, %arg2, %arg3 : i1 + arc.vectorize.return %4 : i1 + } + hw.output %2: i1 +} + +// CHECK-LABEL: hw.module @Repeated_again(in %clock : !seq.clock, in %0 "" : i1, in %in1 : i1, in %in2 : i1, out oh : i1) { +// CHECK-NEXT: [[VEC:%.+]]:2 = arc.vectorize (%in1, %in2), (%0, %0) : (i1, i1, i1, i1) -> (i1, i1) { +// CHECK-NEXT: ^[[BLOCK:[[:alnum:]]+]](%arg0: i1, %arg1: i1): +// CHECK-NEXT: [[OR:%.+]] = comb.or %arg0, %arg1 : i1 +// CHECK-NEXT: arc.vectorize.return [[OR]] : i1 +// CHECK-NEXT: } +// CHECK-NEXT: hw.output [[VEC]]#0 : i1 +// CHECK-NEXT: } + hw.module @Repeated_input_1(in %b: i8, in %e: i8, in %h: i8, in %k: i8, in %en: i1, in %clock: !seq.clock) { %R:4 = arc.vectorize(%b, %e, %h, %k), (%b, %e, %h, %k): (i8, i8, i8, i8, i8, i8, i8, i8) -> (i8, i8, i8, i8) { ^bb0(%arg0: i8, %arg1: i8): diff --git a/test/Dialect/Arc/basic-errors.mlir b/test/Dialect/Arc/basic-errors.mlir index 6fe538beef05..723a29ebd5ee 100644 --- a/test/Dialect/Arc/basic-errors.mlir +++ b/test/Dialect/Arc/basic-errors.mlir @@ -524,3 +524,42 @@ hw.module @vectorize(in %in0: i4, in %in1: i4, out out0: i4) { // expected-error @below {{state type must have a known bit width}} func.func @InvalidStateType(%arg0: !arc.state) + +// ----- + +// expected-error @below {{Cannot find declaration of initializer function 'MissingInitilaizer_initial'.}} +arc.model @MissingInitilaizer io !hw.modty<> initializer @MissingInitilaizer_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +// expected-note @below {{Initializer declared here:}} +hw.module @NonFuncInitilaizer_initial() { +} + +// expected-error @below {{Referenced initializer must be a 'func.func' op.}} +arc.model @NonFuncInitilaizer io !hw.modty<> initializer @NonFuncInitilaizer_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +// expected-note @below {{Initializer declared here:}} +func.func @IncorrectArg_initial(!arc.storage<24>) { + ^bb0(%arg0: !arc.storage<24>): + return +} + +// expected-error @below {{Arguments of initializer function must match arguments of model body.}} +arc.model @IncorrectArg io !hw.modty<> initializer @IncorrectArg_initial { + ^bb0(%arg0: !arc.storage<42>): +} + +// ----- + +hw.module @InvalidInitType(in %clock: !seq.clock, in %input: i7) { + %cst = hw.constant 0 : i8 + // expected-error @below {{failed to verify that types of initial arguments match result types}} + %res = arc.state @Bar(%input) clock %clock initial (%cst: i8) latency 1 : (i7) -> i7 +} diff --git a/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir b/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir index a80487779825..a9100f6b9430 100644 --- a/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir +++ b/test/Dialect/Arc/lower-clocks-to-funcs-errors.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt %s --arc-lower-clocks-to-funcs --verify-diagnostics +// RUN: circt-opt %s --arc-lower-clocks-to-funcs --split-input-file --verify-diagnostics arc.model @NonConstExternalValue io !hw.modty<> { ^bb0(%arg0: !arc.storage<42>): @@ -12,3 +12,31 @@ arc.model @NonConstExternalValue io !hw.modty<> { %1 = comb.sub %0, %0 : i9001 } } + +// ----- + +func.func @VictimInit(%arg0: !arc.storage<42>) { + return +} + +// expected-warning @below {{Existing model initializer 'VictimInit' will be overridden.}} +arc.model @ExistingInit io !hw.modty<> initializer @VictimInit { +^bb0(%arg0: !arc.storage<42>): + arc.initial {} +} + +// ----- + +// expected-error @below {{op containing multiple PassThroughOps cannot be lowered.}} +// expected-error @below {{op containing multiple InitialOps is currently unsupported.}} +arc.model @MultiInitAndPassThrough io !hw.modty<> { +^bb0(%arg0: !arc.storage<1>): + // expected-note @below {{Conflicting PassThroughOp:}} + arc.passthrough {} + // expected-note @below {{Conflicting InitialOp:}} + arc.initial {} + // expected-note @below {{Conflicting PassThroughOp:}} + arc.passthrough {} + // expected-note @below {{Conflicting InitialOp:}} + arc.initial {} +} diff --git a/test/Dialect/Arc/lower-clocks-to-funcs.mlir b/test/Dialect/Arc/lower-clocks-to-funcs.mlir index 4226706910ee..9dd7d8899965 100644 --- a/test/Dialect/Arc/lower-clocks-to-funcs.mlir +++ b/test/Dialect/Arc/lower-clocks-to-funcs.mlir @@ -14,7 +14,15 @@ // CHECK-NEXT: return // CHECK-NEXT: } -// CHECK-LABEL: arc.model @Trivial io !hw.modty<> { +// CHECK-LABEL: func.func @Trivial_initial(%arg0: !arc.storage<42>) { +// CHECK-NEXT: %true = hw.constant true +// CHECK-NEXT: %c1_i9002 = hw.constant 1 : i9002 +// CHECK-NEXT: %0 = comb.mux %true, %c1_i9002, %c1_i9002 : i9002 +// CHECK-NEXT: call @Trivial_passthrough(%arg0) : (!arc.storage<42>) -> () +// CHECK-NEXT: return +// CHECK-NEXT: } + +// CHECK-LABEL: arc.model @Trivial io !hw.modty<> initializer @Trivial_initial { // CHECK-NEXT: ^bb0(%arg0: !arc.storage<42>): // CHECK-NEXT: %true = hw.constant true // CHECK-NEXT: %false = hw.constant false @@ -36,6 +44,10 @@ arc.model @Trivial io !hw.modty<> { %c1_i9001 = hw.constant 1 : i9001 %0 = comb.mux %true, %c1_i9001, %c1_i9001 : i9001 } + arc.initial { + %c1_i9002 = hw.constant 1 : i9002 + %0 = comb.mux %true, %c1_i9002, %c1_i9002 : i9002 + } } //===----------------------------------------------------------------------===// diff --git a/test/Dialect/Arc/lower-state-errors.mlir b/test/Dialect/Arc/lower-state-errors.mlir new file mode 100644 index 000000000000..cd6135a3e072 --- /dev/null +++ b/test/Dialect/Arc/lower-state-errors.mlir @@ -0,0 +1,24 @@ +// RUN: circt-opt %s --arc-lower-state --split-input-file --verify-diagnostics + +arc.define @DummyArc(%arg0: i42) -> i42 { + arc.output %arg0 : i42 +} + +// expected-error @+1 {{Value cannot be used in initializer.}} +hw.module @argInit(in %clk: !seq.clock, in %input: i42) { + %0 = arc.state @DummyArc(%0) clock %clk initial (%input : i42) latency 1 : (i42) -> i42 +} + + +// ----- + + +arc.define @DummyArc(%arg0: i42) -> i42 { + arc.output %arg0 : i42 +} + +hw.module @argInit(in %clk: !seq.clock, in %input: i42) { + // expected-error @+1 {{Value cannot be used in initializer.}} + %0 = arc.state @DummyArc(%0) clock %clk latency 1 : (i42) -> i42 + %1 = arc.state @DummyArc(%1) clock %clk initial (%0 : i42) latency 1 : (i42) -> i42 +} diff --git a/test/Dialect/Arc/lower-state.mlir b/test/Dialect/Arc/lower-state.mlir index 0538d1e4aae4..87f3e19cd6c6 100644 --- a/test/Dialect/Arc/lower-state.mlir +++ b/test/Dialect/Arc/lower-state.mlir @@ -353,3 +353,54 @@ hw.module @BlackBox(in %clk: !seq.clock) { } // CHECK-NOT: hw.module.extern private @BlackBoxExt hw.module.extern private @BlackBoxExt(in %a: i42, in %b: i42, out c: i42, out d: i42) + + +func.func private @func(%arg0: i32, %arg1: i32) -> i32 +// CHECK-LABEL: arc.model @adder +hw.module @adder(in %clock : i1, in %a : i32, in %b : i32, out c : i32) { + %0 = seq.to_clock %clock + %1 = sim.func.dpi.call @func(%a, %b) clock %0 : (i32, i32) -> i32 + // CHECK: arc.clock_tree + // CHECK-NEXT: %[[A:.+]] = arc.state_read %in_a : + // CHECK-NEXT: %[[B:.+]] = arc.state_read %in_b : + // CHECK-NEXT: %[[RESULT:.+]] = func.call @func(%6, %7) : (i32, i32) -> i32 + hw.output %1 : i32 +} + +// CHECK-LABEL: arc.model @InitializedStates +hw.module @InitializedStates(in %clk: !seq.clock, in %reset: i1, in %input: i42) { + +// CHECK: [[ST1:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST2:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST3:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST4:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state +// CHECK-NEXT: [[ST5:%.+]] = arc.alloc_state %arg0 : (!arc.storage) -> !arc.state + +// CHECK: arc.initial { + + %csta = hw.constant 1 : i42 + %cstb = hw.constant 10 : i42 + %cstc = hw.constant 100 : i42 + %cstd = hw.constant 1000 : i42 + %add = comb.add bin %cstb, %cstc, %csta : i42 + %mul = comb.mul bin %add, %csta : i42 + + // CHECK-NEXT: [[CSTD:%.+]] = hw.constant 1000 : i42 + // CHECK-NEXT: arc.state_write [[ST1]] = [[CSTD]] : + %0 = arc.state @DummyArc(%input) clock %clk initial (%cstd : i42) latency 1 : (i42) -> i42 + + // CHECK-DAG: [[CSTA:%.+]] = hw.constant 1 : i42 + // CHECK-DAG: [[CSTB:%.+]] = hw.constant 10 : i42 + // CHECK-DAG: [[CSTC:%.+]] = hw.constant 100 : i42 + // CHECK-DAG: [[ADD:%.+]] = comb.add bin [[CSTB]], [[CSTC]], [[CSTA]] : i42 + // CHECK-DAG: [[MUL:%.+]] = comb.mul bin [[ADD]], [[CSTA]] : i42 + + // CHECK: arc.state_write [[ST2]] = [[MUL]] : + %1 = arc.state @DummyArc(%0) clock %clk initial (%mul : i42) latency 1 : (i42) -> i42 + // CHECK-NEXT: arc.state_write [[ST3]] = [[CSTB]] : + %2 = arc.state @DummyArc(%1) clock %clk reset %reset initial (%cstb : i42) latency 1 : (i42) -> i42 + // CHECK-DAG: arc.state_write [[ST4]] = [[CSTB]] : + // CHECK-DAG: arc.state_write [[ST5]] = [[ADD]] : + %3, %4 = arc.state @DummyArc2(%2) clock %clk initial (%cstb, %add : i42, i42) latency 1 : (i42) -> (i42, i42) +// CHECK: } +} diff --git a/test/Dialect/Calyx/clk-insertion.mlir b/test/Dialect/Calyx/clk-insertion.mlir index 4b32b7498ac1..8f7f924d1d41 100644 --- a/test/Dialect/Calyx/clk-insertion.mlir +++ b/test/Dialect/Calyx/clk-insertion.mlir @@ -6,7 +6,7 @@ module attributes {calyx.entrypoint = "main"} { calyx.wires { calyx.assign %done = %c1_1 : i1 } calyx.control {} } - calyx.component @main(%go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%done: i1 {done}) { + calyx.component @main(%in: i8, %go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%out: i8, %done: i1 {done}) { %c0.in, %c0.go, %c0.clk, %c0.reset, %c0.out, %c0.done = calyx.instance @c0 of @A : i8, i1, i1, i1, i8, i1 %r.in, %r.write_en, %r.clk, %r.reset, %r.out, %r.done = calyx.register @r : i1, i1, i1, i1, i1, i1 // CHECK: calyx.wires { @@ -16,6 +16,8 @@ module attributes {calyx.entrypoint = "main"} { // CHECK: calyx.assign %r.clk = %clk : i1 // CHECK: } calyx.wires { + calyx.assign %c0.in = %in : i8 + calyx.assign %out = %c0.out : i8 } calyx.control { calyx.seq { } diff --git a/test/Dialect/Calyx/errors.mlir b/test/Dialect/Calyx/errors.mlir index 78129580ab18..68a765023090 100644 --- a/test/Dialect/Calyx/errors.mlir +++ b/test/Dialect/Calyx/errors.mlir @@ -886,7 +886,7 @@ module attributes {calyx.entrypoint = "main"} { // ----- module attributes {calyx.entrypoint = "main"} { - // expected-error @+1 {{'calyx.component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region.}} + // expected-error @+1 {{'calyx.component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region. The Control region should contain at least one of 'calyx.enable' , 'calyx.invoke' or 'fsm.machine'.}} calyx.component @main(%go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%done: i1 {done}) { %std_lt_0.left, %std_lt_0.right, %std_lt_0.out = calyx.std_lt @std_lt_0 : i32, i32, i1 %c64_i32 = hw.constant 64 : i32 @@ -899,6 +899,54 @@ module attributes {calyx.entrypoint = "main"} { } } +// ----- + +module attributes {calyx.entrypoint = "main"} { + // expected-error @+1 {{'calyx.component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region. The Control region should contain at least one of 'calyx.enable' , 'calyx.invoke' or 'fsm.machine'.}} + calyx.component @main(%go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%done: i1 {done}) { + %r.in, %r.write_en, %r.clk, %r.reset, %r.out, %r.done = calyx.register @r : i1, i1, i1, i1, i1, i1 + %eq.left, %eq.right, %eq.out = calyx.std_eq @eq : i1, i1, i1 + %c1_1 = hw.constant 1 : i1 + calyx.wires { + } + calyx.control { + calyx.seq { + calyx.if %eq.out { + calyx.seq { + } + } else { + calyx.seq { + } + } + } + } + } +} + +// ----- + +module attributes {calyx.entrypoint = "main"} { + // expected-error @+1 {{'calyx.component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region. The Control region should contain at least one of 'calyx.enable' , 'calyx.invoke' or 'fsm.machine'.}} + calyx.component @main(%go: i1 {go}, %clk: i1 {clk}, %reset: i1 {reset}) -> (%done: i1 {done}) { + %r.in, %r.write_en, %r.clk, %r.reset, %r.out, %r.done = calyx.register @r : i1, i1, i1, i1, i1, i1 + %eq.left, %eq.right, %eq.out = calyx.std_eq @eq : i1, i1, i1 + %c1_1 = hw.constant 1 : i1 + calyx.wires { + } + calyx.control { + calyx.par { + calyx.if %eq.out { + calyx.par { + } + } else { + calyx.par { + } + } + } + } + } +} + // ----- module attributes {calyx.entrypoint = "A"} { hw.module.extern @params(in %in: !hw.int<#hw.param.decl.ref<"WIDTH">>, in %clk: i1 {calyx.clk}, in %go: i1 {calyx.go = 1}, out out: !hw.int<#hw.param.decl.ref<"WIDTH">>, out done: i1 {calyx.done}) attributes {filename = "test.v"} @@ -988,7 +1036,7 @@ module attributes {calyx.entrypoint = "main"} { // ----- module attributes {calyx.entrypoint = "main"} { - // expected-error @+1 {{'calyx.comb_component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region or control constructs in the Control region.}} + // expected-error @+1 {{'calyx.comb_component' op The component currently does nothing. It needs to either have continuous assignments in the Wires region.}} calyx.comb_component @main() -> () { %std_lt_0.left, %std_lt_0.right, %std_lt_0.out = calyx.std_lt @std_lt_0 : i32, i32, i1 %c64_i32 = hw.constant 64 : i32 diff --git a/test/Dialect/Comb/canonicalization.mlir b/test/Dialect/Comb/canonicalization.mlir index ea4f6af92f9b..77d0eadbc9ab 100644 --- a/test/Dialect/Comb/canonicalization.mlir +++ b/test/Dialect/Comb/canonicalization.mlir @@ -1221,17 +1221,26 @@ hw.module @test1560(in %value: i38, out a: i1) { } // CHECK-LABEL: hw.module @extractShift -hw.module @extractShift(in %arg0 : i4, out o1 : i1, out o2: i1) { +hw.module @extractShift(in %arg0 : i4, out o1 : i1, out o2: i1, out o3: i1, out o4: i1) { %c1 = hw.constant 1: i4 %0 = comb.shl %c1, %arg0 : i4 + %1 = comb.shl %c1, %arg0 : i4 + %2 = comb.shl %c1, %arg0 : i4 - // CHECK: %0 = comb.icmp eq %arg0, %c0_i4 : i4 - %1 = comb.extract %0 from 0 : (i4) -> i1 + // CHECK: %[[O1:.+]] = comb.icmp eq %arg0, %c0_i4 : i4 + %3 = comb.extract %0 from 0 : (i4) -> i1 - // CHECK: %1 = comb.icmp eq %arg0, %c2_i4 : i4 - %2 = comb.extract %0 from 2 : (i4) -> i1 - // CHECK: hw.output %0, %1 - hw.output %1, %2: i1, i1 + // CHECK: %[[O2:.+]] = comb.icmp eq %arg0, %c2_i4 : i4 + %4 = comb.extract %1 from 2 : (i4) -> i1 + + // CHECK: %[[O3:.+]] = comb.extract + %5 = comb.extract %2 from 2 : (i4) -> i1 + + // CHECK: %[[O4:.+]] = comb.extract + %6 = comb.extract %2 from 2 : (i4) -> i1 + + // CHECK: hw.output %[[O1]], %[[O2]], %[[O3]], %[[O4]] + hw.output %3, %4, %5, %6: i1, i1, i1, i1 } // CHECK-LABEL: hw.module @moduloZeroDividend @@ -1553,6 +1562,59 @@ hw.module @OrMuxSameTrueValueAndZero(in %tag_0: i1, in %tag_1: i1, in %tag_2: i1 "terminator"(%add2) : (i32) -> () }) : () -> () +// CHECK-LABEL: "test.acrossBlockCanonicalizationBarrierFlattenAndIdem" +// CHECK: ^bb1: +// CHECK-NEXT: %[[OUT:.+]] = comb.or %0, %1, %2 : i32 +// CHECK-NEXT: "terminator"(%[[OUT]]) : (i32) -> () +"test.acrossBlockCanonicalizationBarrierFlattenAndIdem"() ({ +^bb0(%arg0: i32, %arg1: i32, %arg2: i32): + %0 = comb.or %arg0, %arg1 : i32 + %1 = comb.or %arg1, %arg2 : i32 + %2 = comb.or %arg0, %arg2 : i32 + "terminator"() : () -> () +^bb1: // no predecessors + // Flatten and unique, but not across blocks. + %3 = comb.or %0, %1 : i32 + %4 = comb.or %1, %2 : i32 + %5 = comb.or %3, %4, %0, %1, %1, %2 : i32 + + "terminator"(%5) : (i32) -> () +}) : () -> () + +// CHECK-LABEL: "test.acrossBlockCanonicalizationBarrierIdem" +// CHECK: ^bb1: +// CHECK-NEXT: %[[OUT1:.+]] = comb.or %0, %1 : i32 +// CHECK-NEXT: %[[OUT2:.+]] = comb.or %[[OUT1]], %arg0 : i32 +// CHECK-NEXT: "terminator"(%[[OUT1]], %[[OUT2]]) : (i32, i32) -> () +"test.acrossBlockCanonicalizationBarrierIdem"() ({ +^bb0(%arg0: i32, %arg1: i32, %arg2: i32): + %0 = comb.or %arg0, %arg1 : i32 + %1 = comb.or %arg1, %arg2 : i32 + "terminator"() : () -> () +^bb1: // no predecessors + %2 = comb.or %0, %1, %1 : i32 + %3 = comb.or %2, %0, %1, %arg0 : i32 + + "terminator"(%2, %3) : (i32, i32) -> () +}) : () -> () + +// Check multi-operation idempotent operand deduplication. +// CHECK-LABEL: @IdemTwoState +// CHECK-NEXT: %[[ZERO:.+]] = comb.or bin %cond, %val1 +// CHECK-NEXT: %[[ONE:.+]] = comb.or bin %val1, %val2 +// Don't allow dropping these (%0/%1) due to two-state differences. +// CHECK-NEXT: %[[TWO:.+]] = comb.or %[[ZERO]], %[[ONE]] +// New operation should preserve two-state-ness. +// CHECK-NEXT: %[[THREE:.+]] = comb.or bin %[[TWO]], %[[ZERO]], %[[ONE]] +// CHECK-NEXT: hw.output %[[ZERO]], %[[ONE]], %[[TWO]], %[[THREE]] +hw.module @IdemTwoState(in %cond: i32, in %val1: i32, in %val2: i32, out o1: i32, out o2: i32, out o3: i32, out o4: i32) { + %0 = comb.or bin %cond, %val1 : i32 + %1 = comb.or bin %val1, %val2: i32 + %2 = comb.or %0, %1 : i32 + %3 = comb.or bin %cond, %val1, %val2, %2, %0, %1 : i32 + hw.output %0, %1, %2, %3: i32, i32, i32, i32 +} + // CHECK-LABEL: hw.module @combineOppositeBinCmpIntoConstant // CHECK: %[[TRUE:.+]] = hw.constant true // CHECK: %[[FALSE:.+]] = hw.constant false diff --git a/test/Dialect/DC/materialize-forks-sinks.mlir b/test/Dialect/DC/materialize-forks-sinks.mlir index 79590c654549..9dfa3e4df040 100644 --- a/test/Dialect/DC/materialize-forks-sinks.mlir +++ b/test/Dialect/DC/materialize-forks-sinks.mlir @@ -54,4 +54,4 @@ func.func @testUnusedArg(%t: !dc.token, %v : !dc.value) -> () { // CHECK: } func.func @testForkOfValue(%v : !dc.value) -> (!dc.value, !dc.value) { return %v, %v : !dc.value, !dc.value -} \ No newline at end of file +} diff --git a/test/Dialect/ESI/manifest.mlir b/test/Dialect/ESI/manifest.mlir index b32af1474641..4f4b193ed9a7 100644 --- a/test/Dialect/ESI/manifest.mlir +++ b/test/Dialect/ESI/manifest.mlir @@ -33,6 +33,7 @@ hw.module @Loopback (in %clk: !seq.clock) { } esi.manifest.sym @Loopback name "LoopbackIP" version "v0.0" summary "IP which simply echos bytes" {foo=1} +esi.manifest.constants @Loopback {depth=5:ui32} esi.service.std.func @funcs @@ -95,13 +96,25 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK: { // CHECK-LABEL: "api_version": 0, + // CHECK-LABEL: "symbols": [ // CHECK-NEXT: { -// CHECK-NEXT: "foo": 1, -// CHECK-NEXT: "name": "LoopbackIP", -// CHECK-NEXT: "summary": "IP which simply echos bytes", -// CHECK-NEXT: "symbolRef": "@Loopback", -// CHECK-NEXT: "version": "v0.0" +// CHECK-NEXT: "symbol": "@Loopback", +// CHECK-NEXT: "sym_info": { +// CHECK-NEXT: "foo": { +// CHECK-NEXT: "type": "i64", +// CHECK-NEXT: "value": 1 +// CHECK-NEXT: }, +// CHECK-NEXT: "name": "LoopbackIP", +// CHECK-NEXT: "summary": "IP which simply echos bytes", +// CHECK-NEXT: "version": "v0.0" +// CHECK-NEXT: }, +// CHECK-NEXT: "sym_consts": { +// CHECK-NEXT: "depth": { +// CHECK-NEXT: "type": "ui32", +// CHECK-NEXT: "value": 5 +// CHECK-NEXT: } +// CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], @@ -231,9 +244,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "func1" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "call", // CHECK-NEXT: "outer_sym": "funcs" @@ -254,9 +265,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_tohw" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Recv", // CHECK-NEXT: "outer_sym": "HostComms" @@ -267,9 +276,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Send", // CHECK-NEXT: "outer_sym": "HostComms" @@ -280,9 +287,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw_i0" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "SendI0", // CHECK-NEXT: "outer_sym": "HostComms" @@ -303,9 +308,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_tohw" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Recv", // CHECK-NEXT: "outer_sym": "HostComms" @@ -316,9 +319,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "Send", // CHECK-NEXT: "outer_sym": "HostComms" @@ -329,9 +330,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "appID": { // CHECK-NEXT: "name": "loopback_fromhw_i0" // CHECK-NEXT: }, -// CHECK-NEXT: "bundleType": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: }, +// CHECK-NEXT: "bundleType": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: "servicePort": { // CHECK-NEXT: "inner": "SendI0", // CHECK-NEXT: "outer_sym": "HostComms" @@ -349,21 +348,15 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "ports": [ // CHECK-NEXT: { // CHECK-NEXT: "name": "Send", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: } +// CHECK-NEXT: "type": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: }, // CHECK-NEXT: { // CHECK-NEXT: "name": "Recv", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"recv\"]>" -// CHECK-NEXT: } +// CHECK-NEXT: "type": "!esi.bundle<[!esi.channel to \"recv\"]>" // CHECK-NEXT: }, // CHECK-NEXT: { // CHECK-NEXT: "name": "SendI0", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>" -// CHECK-NEXT: } +// CHECK-NEXT: "type": "!esi.bundle<[!esi.channel from \"send\"]>" // CHECK-NEXT: } // CHECK-NEXT: ] // CHECK-NEXT: }, @@ -374,41 +367,39 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: { // CHECK-NEXT: "name": "call", // CHECK-NEXT: "type": { -// CHECK-NEXT: "type": { -// CHECK-NEXT: "channels": [ -// CHECK-NEXT: { -// CHECK-NEXT: "direction": "to", -// CHECK-NEXT: "name": "arg", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", +// CHECK-NEXT: "channels": [ +// CHECK-NEXT: { +// CHECK-NEXT: "direction": "to", +// CHECK-NEXT: "name": "arg", +// CHECK-NEXT: "type": { +// CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.channel", +// CHECK-NEXT: "inner": { // CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "!esi.any", -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "mnemonic": "any" -// CHECK-NEXT: }, -// CHECK-NEXT: "mnemonic": "channel" -// CHECK-NEXT: } -// CHECK-NEXT: }, -// CHECK-NEXT: { -// CHECK-NEXT: "direction": "from", -// CHECK-NEXT: "name": "result", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", +// CHECK-NEXT: "id": "!esi.any", +// CHECK-NEXT: "mnemonic": "any" +// CHECK-NEXT: }, +// CHECK-NEXT: "mnemonic": "channel" +// CHECK-NEXT: } +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "direction": "from", +// CHECK-NEXT: "name": "result", +// CHECK-NEXT: "type": { +// CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.channel", +// CHECK-NEXT: "inner": { // CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "!esi.any", -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "mnemonic": "any" -// CHECK-NEXT: }, -// CHECK-NEXT: "mnemonic": "channel" -// CHECK-NEXT: } +// CHECK-NEXT: "id": "!esi.any", +// CHECK-NEXT: "mnemonic": "any" +// CHECK-NEXT: }, +// CHECK-NEXT: "mnemonic": "channel" // CHECK-NEXT: } -// CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "mnemonic": "bundle" -// CHECK-NEXT: } +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", +// CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ] @@ -422,13 +413,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "to", // CHECK-NEXT: "name": "recv", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 8, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i8", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 8, +// CHECK-NEXT: "id": "i8", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -436,8 +427,8 @@ 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 \"recv\"]>", // CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel to \"recv\"]>", // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, // CHECK-NEXT: { @@ -446,13 +437,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "from", // CHECK-NEXT: "name": "send", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 8, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i8", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 8, +// CHECK-NEXT: "id": "i8", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -460,8 +451,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, // CHECK-NEXT: { @@ -470,13 +461,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "from", // CHECK-NEXT: "name": "send", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 0, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i0", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 0, +// CHECK-NEXT: "id": "i0", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -484,8 +475,8 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: } // CHECK-NEXT: } // CHECK-NEXT: ], -// CHECK-NEXT: "circt_name": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel from \"send\"]>", // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, // CHECK-NEXT: { @@ -494,13 +485,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "to", // CHECK-NEXT: "name": "arg", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 16, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i16", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 16, +// CHECK-NEXT: "id": "i16", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -511,13 +502,13 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "direction": "from", // CHECK-NEXT: "name": "result", // CHECK-NEXT: "type": { -// CHECK-NEXT: "circt_name": "!esi.channel", // CHECK-NEXT: "dialect": "esi", // CHECK-NEXT: "hw_bitwidth": 16, +// CHECK-NEXT: "id": "!esi.channel", // CHECK-NEXT: "inner": { -// CHECK-NEXT: "circt_name": "i16", // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hw_bitwidth": 16, +// CHECK-NEXT: "id": "i16", // CHECK-NEXT: "mnemonic": "int", // CHECK-NEXT: "signedness": "signless" // CHECK-NEXT: }, @@ -525,9 +516,23 @@ 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 \"arg\", !esi.channel from \"result\"]>", // CHECK-NEXT: "dialect": "esi", +// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", // CHECK-NEXT: "mnemonic": "bundle" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "dialect": "builtin", +// CHECK-NEXT: "hw_bitwidth": 64, +// CHECK-NEXT: "id": "i64", +// CHECK-NEXT: "mnemonic": "int", +// CHECK-NEXT: "signedness": "signless" +// CHECK-NEXT: }, +// CHECK-NEXT: { +// CHECK-NEXT: "dialect": "builtin", +// CHECK-NEXT: "hw_bitwidth": 32, +// CHECK-NEXT: "id": "ui32", +// CHECK-NEXT: "mnemonic": "int", +// CHECK-NEXT: "signedness": "unsigned" // CHECK-NEXT: } // CHECK-NEXT: ] // CHECK-NEXT: } diff --git a/test/Dialect/FIRRTL/SFCTests/directories.fir b/test/Dialect/FIRRTL/SFCTests/directories.fir index c1d576c99ec4..1c4c13fcd426 100644 --- a/test/Dialect/FIRRTL/SFCTests/directories.fir +++ b/test/Dialect/FIRRTL/SFCTests/directories.fir @@ -151,7 +151,7 @@ circuit TestHarness: ; MLIR_OUT: om.class.field @[[V3:.+]], %5 : !om.class.type<@SitestBlackBoxModulesSchema> ; MLIR_OUT: } -; MLIR_OUT: om.class @MemorySchema(%basepath: !om.basepath, %name_in: !om.string, %depth_in: !om.integer, %width_in: !om.integer, %maskBits_in: !om.integer, %readPorts_in: !om.integer, %writePorts_in: !om.integer, %readwritePorts_in: !om.integer, %writeLatency_in: !om.integer, %readLatency_in: !om.integer, %hierarchy_in: !om.list, %inDut_in: i1, %extraPorts_in: !om.list>) +; MLIR_OUT: om.class @MemorySchema(%basepath: !om.basepath, %name_in: !om.string, %depth_in: !om.integer, %width_in: !om.integer, %maskBits_in: !om.integer, %readPorts_in: !om.integer, %writePorts_in: !om.integer, %readwritePorts_in: !om.integer, %writeLatency_in: !om.integer, %readLatency_in: !om.integer, %hierarchy_in: !om.list, %inDut_in: i1, %extraPorts_in: !om.list> ; MLIR_OUT: om.class.field @name, %name_in : !om.string ; MLIR_OUT: om.class.field @depth, %depth_in : !om.integer ; MLIR_OUT: om.class.field @width, %width_in : !om.integer @@ -163,7 +163,6 @@ circuit TestHarness: ; MLIR_OUT: om.class.field @readLatency, %readLatency_in : !om.integer ; MLIR_OUT: om.class.field @hierarchy, %hierarchy_in : !om.list ; MLIR_OUT: om.class.field @extraPorts, %extraPorts_in : !om.list> -; MLIR_OUT: } ; MLIR_OUT: om.class @MemoryMetadata(%basepath: !om.basepath) ; MLIR_OUT: om.path_create instance %basepath @memNLA ; MLIR_OUT: om.list_create diff --git a/test/Dialect/FIRRTL/annotations-errors.mlir b/test/Dialect/FIRRTL/annotations-errors.mlir index 96208374f957..4b229779a383 100644 --- a/test/Dialect/FIRRTL/annotations-errors.mlir +++ b/test/Dialect/FIRRTL/annotations-errors.mlir @@ -438,23 +438,19 @@ firrtl.circuit "Top" attributes { // ----- // OutputDirAnnotation targeting a non-public module should fail. -// expected-error @below {{Unable to apply annotation: {class = "circt.OutputDirAnnotation", dirname = "foo", target = "~Top|Top"}}} +// expected-error @below {{Unable to apply annotation: {class = "circt.OutputDirAnnotation", dirname = "foo", target = "~Top|NotTop"}}} firrtl.circuit "Top" attributes { rawAnnotations = [ { class = "circt.OutputDirAnnotation", dirname = "foo", - target = "~Top|Top" - }, - { - class = "circt.OutputDirAnnotation", - dirname = "foo", - target = "~Top|Top" + target = "~Top|NotTop" } ] } { + firrtl.module @Top() {} // expected-error @below {{circt.OutputDirAnnotation must target a public module}} - firrtl.module private @Top() {} + firrtl.module private @NotTop() {} } // ----- diff --git a/test/Dialect/FIRRTL/annotations.mlir b/test/Dialect/FIRRTL/annotations.mlir index a43617ce1ee9..d753d1905bf0 100644 --- a/test/Dialect/FIRRTL/annotations.mlir +++ b/test/Dialect/FIRRTL/annotations.mlir @@ -1,4 +1,4 @@ -// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-annotations{allow-adding-ports-on-public-modules=true}))' --split-input-file %s | FileCheck %s +// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl-lower-annotations{allow-adding-ports-on-public-modules=true}))' --verify-diagnostics --split-input-file %s | FileCheck %s // circt.test copies the annotation to the target // circt.testNT puts the targetless annotation on the circuit @@ -1953,3 +1953,35 @@ firrtl.circuit "Top" attributes { // CHECK-SAME: attributes {output_file = #hw.output_file<"foobarbaz{{/|\\\\}}qux{{/|\\\\}}">} firrtl.module @Top() {} } + +// ----- +// Check that FullAsyncResetAnnotation is lowered to FullResetAnnotation (and prints a warning) +// CHECK-LABEL: firrtl.circuit "Top" +firrtl.circuit "Top" attributes { + rawAnnotations = [ + { + class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation", + target = "~Top|Top>reset" + }] + } { + // CHECK-LABEL: firrtl.module @Top + // CHECK-SAME: (in %reset: !firrtl.asyncreset [{class = "circt.FullResetAnnotation", resetType = "async"}]) + // expected-warning @+1 {{'sifive.enterprise.firrtl.FullAsyncResetAnnotation' is deprecated, use 'circt.FullResetAnnotation' instead}} + firrtl.module @Top(in %reset: !firrtl.asyncreset) {} +} + +// ----- +// Check that IgnoreFullAsyncResetAnnotation is lowered to ExcludeFromFullResetAnnotation (and prints a warning) +// CHECK-LABEL: firrtl.circuit "Top" +firrtl.circuit "Top" attributes { + rawAnnotations = [ + { + class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation", + target = "~Top|Top" + }] + } { + // CHECK-LABEL: firrtl.module @Top + // CHECK-SAME: (in %reset: !firrtl.asyncreset) attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} + // expected-warning @+1 {{'sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation' is deprecated, use 'circt.ExcludeFromFullResetAnnotation' instead}} + firrtl.module @Top(in %reset: !firrtl.asyncreset) {} +} diff --git a/test/Dialect/FIRRTL/canonicalization.mlir b/test/Dialect/FIRRTL/canonicalization.mlir index cc7754073a6a..022922b59422 100644 --- a/test/Dialect/FIRRTL/canonicalization.mlir +++ b/test/Dialect/FIRRTL/canonicalization.mlir @@ -3619,5 +3619,20 @@ firrtl.module @sizeof(in %clock: !firrtl.clock, %n_bundle = firrtl.node interesting_name %s_bundle : !firrtl.uint<32> } +// CHECK-LABEL: @multibit_mux_drop_front +firrtl.module @multibit_mux_drop_front(in %vec_0: !firrtl.uint<8>, in %vec_1: !firrtl.uint<8>, + in %vec_2: !firrtl.uint<8>, in %vec_3: !firrtl.uint<8>, + in %vec_4: !firrtl.uint<8>, in %vec_5: !firrtl.uint<8>, + in %vec_6: !firrtl.uint<8>, in %vec_7: !firrtl.uint<8>, + in %index: !firrtl.uint<2>, in %index_unknown_width: !firrtl.uint, + out %b: !firrtl.uint<8>, out %c: !firrtl.uint<8>) { + // CHECK-NEXT: %0 = firrtl.multibit_mux %index, %vec_3, %vec_2, %vec_1, %vec_0 + %0 = firrtl.multibit_mux %index, %vec_7, %vec_6, %vec_5, %vec_4, %vec_3, %vec_2, %vec_1, %vec_0 : !firrtl.uint<2>, !firrtl.uint<8> + firrtl.matchingconnect %b, %0 : !firrtl.uint<8> + + // CHECK: %1 = firrtl.multibit_mux %index_unknown_width, %vec_7 + %1 = firrtl.multibit_mux %index_unknown_width, %vec_7, %vec_6, %vec_5, %vec_4, %vec_3, %vec_2, %vec_1, %vec_0 : !firrtl.uint, !firrtl.uint<8> + firrtl.matchingconnect %c, %1 : !firrtl.uint<8> +} } diff --git a/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir b/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir new file mode 100644 index 000000000000..608b1f2cc8ab --- /dev/null +++ b/test/Dialect/FIRRTL/check-recursive-instantiation-errors.mlir @@ -0,0 +1,89 @@ +// RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-check-recursive-instantiation))' %s --verify-diagnostics --split-input-file + +firrtl.circuit "SelfLoop0" { + // expected-error @below {{recursive instantiation}} + firrtl.module @SelfLoop0() { + // expected-note @below {{SelfLoop0 instantiates SelfLoop0 here}} + firrtl.instance inst @SelfLoop0() + } +} + +// ----- + +firrtl.circuit "SelfLoop1" { + // expected-error @below {{recursive instantiation}} + firrtl.module @SelfLoop1() { + // expected-note @below {{SelfLoop1 instantiates SelfLoop1 here}} + firrtl.instance inst @SelfLoop1() + // expected-note @below {{SelfLoop1 instantiates SelfLoop1 here}} + firrtl.instance inst @SelfLoop1() + } +} + +// ----- + +firrtl.circuit "TwoLoops" { + // expected-error @below {{recursive instantiation}} + firrtl.module @TwoLoops() { + // expected-note @below {{TwoLoops instantiates TwoLoops here}} + firrtl.instance inst @TwoLoops() + firrtl.instance inst @OtherModule() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @OtherModule() { + // expected-note @below {{OtherModule instantiates OtherModule here}} + firrtl.instance inst @OtherModule() + } +} + +// ----- + +firrtl.circuit "MutualLoop" { + firrtl.module @MutualLoop() { + firrtl.instance a @A() + } + firrtl.module @A() { + // expected-note @below {{A instantiates B here}} + firrtl.instance b @B() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @B() { + // expected-note @below {{B instantiates A here}} + firrtl.instance a @A() + } +} + +// ----- + +// Should disallow recursive class instantiation. +firrtl.circuit "Classes" { + firrtl.module @Classes() { + firrtl.object @RecursiveClass() + } + + // expected-error @below {{recursive instantiation}} + firrtl.class @RecursiveClass() { + // expected-note @below {{RecursiveClass instantiates RecursiveClass here}} + %0 = firrtl.object @RecursiveClass() + } +} + +// ----- + +firrtl.circuit "A" { + firrtl.module @A() { + // expected-note @below {{A instantiates B here}} + firrtl.instance b @B() + } + firrtl.module @B() { + // expected-note @below {{B instantiates C here}} + firrtl.instance c @C() + // expected-note @below {{B instantiates A here}} + firrtl.instance a @A() + } + // expected-error @below {{recursive instantiation}} + firrtl.module @C() { + // expected-note @below {{C instantiates B here}} + firrtl.instance b @B() + } +} diff --git a/test/Dialect/FIRRTL/check-recursive-instantiation.mlir b/test/Dialect/FIRRTL/check-recursive-instantiation.mlir new file mode 100644 index 000000000000..c9ede2b247bb --- /dev/null +++ b/test/Dialect/FIRRTL/check-recursive-instantiation.mlir @@ -0,0 +1,6 @@ +// RUN: circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-check-recursive-instantiation))' %s | FileCheck %s + +// CHECK-LABEL: firrtl.circuit "NoLoop" +firrtl.circuit "NoLoop" { + firrtl.module @NoLoop() { } +} diff --git a/test/Dialect/FIRRTL/dedup-errors.mlir b/test/Dialect/FIRRTL/dedup-errors.mlir index 19355c0e4a38..1e13dc4518fb 100644 --- a/test/Dialect/FIRRTL/dedup-errors.mlir +++ b/test/Dialect/FIRRTL/dedup-errors.mlir @@ -630,3 +630,56 @@ firrtl.circuit "MustDedup" attributes {annotations = [{ firrtl.instance test1 @Test1(in in : !firrtl.vector, 2>) } } + +// ----- + +// Modules instantiating equivalent public modules do not dedup. +// Check diagnostic. +// expected-error @below {{module "Test1Parent" not deduplicated with "Test0Parent"}} +// expected-note @below {{in instance "test0" of "Test0", and instance "test1" of "Test1"}} +firrtl.circuit "InstOfEquivPublic" attributes {annotations = [{ + class = "firrtl.transforms.MustDeduplicateAnnotation", + modules = ["~InstOfEquivPublic|Test0Parent", "~InstOfEquivPublic|Test1Parent"] + }]} { + // expected-note @below {{module is public}} + firrtl.module public @Test0() { } + firrtl.module public @Test1() { } + firrtl.module private @Test0Parent() { + firrtl.instance test0 @Test0() + } + firrtl.module private @Test1Parent() { + firrtl.instance test1 @Test1() + } + + firrtl.module @InstOfEquivPublic() { + firrtl.instance test0p @Test0Parent() + firrtl.instance test1p @Test1Parent() + } +} + +// ----- + +// Modules instantiating mixed public/private do not dedup. +// Check diagnostic. +// expected-error @below {{module "Test1Parent" not deduplicated with "Test0Parent"}} +// expected-note @below {{in instance "test0" of "Test0", and instance "test1" of "Test1"}} +firrtl.circuit "InstOfPublicPrivate" attributes {annotations = [{ + class = "firrtl.transforms.MustDeduplicateAnnotation", + modules = ["~InstOfPublicPrivate|Test0Parent", "~InstOfPublicPrivate|Test1Parent"] + }]} { + firrtl.module private @Test0() { } + // expected-note @below {{module is public}} + firrtl.module public @Test1() { } + + firrtl.module private @Test0Parent() { + firrtl.instance test0 @Test0() + } + firrtl.module private @Test1Parent() { + firrtl.instance test1 @Test1() + } + + firrtl.module @InstOfPublicPrivate() { + firrtl.instance test0p @Test0Parent() + firrtl.instance test1p @Test1Parent() + } +} diff --git a/test/Dialect/FIRRTL/eliminate-wires.mlir b/test/Dialect/FIRRTL/eliminate-wires.mlir index ee4f8eddc085..c6a2e55e3ece 100644 --- a/test/Dialect/FIRRTL/eliminate-wires.mlir +++ b/test/Dialect/FIRRTL/eliminate-wires.mlir @@ -26,4 +26,4 @@ firrtl.circuit "TopLevel" { // CHECK-NEXT: %b = firrtl.node %[[inv]] : !firrtl.uint<3> // CHECK-NEXT: %a = firrtl.node %b : !firrtl.uint<3> } -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/emit-metadata.mlir b/test/Dialect/FIRRTL/emit-metadata.mlir index 5f4dec917531..3759433e4f08 100644 --- a/test/Dialect/FIRRTL/emit-metadata.mlir +++ b/test/Dialect/FIRRTL/emit-metadata.mlir @@ -187,7 +187,7 @@ firrtl.circuit "top" // CHECK-LABEL: firrtl.circuit "OneMemory" firrtl.circuit "OneMemory" { firrtl.module @OneMemory() { - %0:5= firrtl.instance MWrite_ext sym @MWrite_ext_0 @MWrite_ext(in W0_addr: !firrtl.uint<4>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<42>, in user_input: !firrtl.uint<5>) + %0:5= firrtl.instance MWrite_ext_inst sym @MWrite_ext_0 @MWrite_ext(in W0_addr: !firrtl.uint<4>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<42>, in user_input: !firrtl.uint<5>) } firrtl.memmodule @MWrite_ext(in W0_addr: !firrtl.uint<4>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<42>, in user_input: !firrtl.uint<5>) attributes {dataWidth = 42 : ui32, depth = 12 : ui64, extraPorts = [{direction = "input", name = "user_input", width = 5 : ui32}], maskBits = 1 : ui32, numReadPorts = 0 : ui32, numReadWritePorts = 0 : ui32, numWritePorts = 1 : ui32, readLatency = 1 : ui32, writeLatency = 1 : ui32} @@ -203,9 +203,11 @@ firrtl.circuit "OneMemory" { // CHECK: firrtl.propassign %writeLatency, %writeLatency_in : !firrtl.integer // CHECK: firrtl.propassign %readLatency, %readLatency_in : !firrtl.integer // CHECK: firrtl.propassign %hierarchy, %hierarchy_in : !firrtl.list - + // CHECK: firrtl.propassign %preExtInstName, %preExtInstName_in : !firrtl.list // CHECK: firrtl.class @MemoryMetadata - // CHECK: firrtl.path instance distinct[0]<> + // CHECK: %[[V0:.+]] = firrtl.string "MWrite_ext_inst" + // CHECK: %[[V1:.+]] = firrtl.path reference distinct[0]<> + // CHECK: %[[V2:.+]] = firrtl.list.create %[[V0]] : !firrtl.list // CHECK: %MWrite_ext = firrtl.object @MemorySchema // CHECK: firrtl.string "MWrite_ext" // CHECK: firrtl.object.subfield %MWrite_ext[name_in] @@ -226,11 +228,13 @@ firrtl.circuit "OneMemory" { // CHECK: firrtl.integer 1 // CHECK: firrtl.object.subfield %MWrite_ext[readLatency_in] // CHECK: firrtl.object.subfield %MWrite_ext[hierarchy_in] + // CHECK: %[[V33:.+]] = firrtl.object.subfield %MWrite_ext[preExtInstName_in] + // CHECK: firrtl.propassign %[[V33]], %[[V2]] : !firrtl.list // CHECK: firrtl.propassign %MWrite_ext_field, %MWrite_ext // CHECK: } // CHECK: emit.file "metadata{{/|\\\\}}seq_mems.json" { - // CHECK-NEXT{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: 0,\0A \22write\22: 1,\0A \22readwrite\22: 0,\0A \22extra_ports\22: [\0A {\0A \22name\22: \22user_input\22,\0A \22direction\22: \22input\22,\0A \22width\22: 5\0A }\0A ],\0A \22hierarchy\22: [\0A \22{{1}}.MWrite_ext\22\0A ]\0A }\0A]" + // CHECK-NEXT{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: 0,\0A \22write\22: 1,\0A \22readwrite\22: 0,\0A \22extra_ports\22: [\0A {\0A \22name\22: \22user_input\22,\0A \22direction\22: \22input\22,\0A \22width\22: 5\0A }\0A ],\0A \22hierarchy\22: [\0A \22{{1}}.MWrite_ext_inst\22\0A ]\0A }\0A]" // CHECK-SAME: {symbols = [@MWrite_ext, @OneMemory]} // CHECK-NEXT: } @@ -272,8 +276,9 @@ firrtl.circuit "ReadOnlyMemory" { // CHECK-LABEL: firrtl.circuit "top" firrtl.circuit "top" { // CHECK: hw.hierpath @[[DUTNLA:.+]] [@top::@sym] + // CHECK-LABEL: firrtl.module @top firrtl.module @top() { - // CHECK: firrtl.instance dut sym @[[DUT_SYM:.+]] {annotations = [{circt.nonlocal = @dutNLA, class = "circt.tracker", id = distinct[0]<>}]} @DUT() + // CHECK: firrtl.instance dut sym @[[DUT_SYM:.+]] {annotations = [{circt.nonlocal = @dutNLA, class = "circt.tracker", id = distinct[0]<>}]} @DUT() firrtl.instance dut @DUT() firrtl.instance mem1 @Mem1() firrtl.instance mem2 @Mem2() @@ -284,9 +289,11 @@ firrtl.circuit "top" { firrtl.module private @Mem2() { %0:4 = firrtl.instance head_0_ext @head_0_ext(in W0_addr: !firrtl.uint<5>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<5>) } + // CHECK-LABEL: firrtl.module private @DUT( firrtl.module private @DUT() attributes {annotations = [ {class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} { - // CHECK: firrtl.instance mem1 sym @[[MEM1_SYM:.+]] @Mem( + // CHECK: firrtl.instance mem1 sym @[[MEM1_SYM:.+]] {annotations = [{ + // CHECK-SAME: circt.nonlocal = @memNLA, class = "circt.tracker", id = distinct firrtl.instance mem1 @Mem() } firrtl.module private @Mem() { @@ -298,6 +305,12 @@ firrtl.circuit "top" { firrtl.memmodule private @memory_ext(in R0_addr: !firrtl.uint<4>, in R0_en: !firrtl.uint<1>, in R0_clk: !firrtl.clock, out R0_data: !firrtl.uint<8>, in RW0_addr: !firrtl.uint<4>, in RW0_en: !firrtl.uint<1>, in RW0_clk: !firrtl.clock, in RW0_wmode: !firrtl.uint<1>, in RW0_wdata: !firrtl.uint<8>, out RW0_rdata: !firrtl.uint<8>) attributes {dataWidth = 8 : ui32, depth = 16 : ui64, extraPorts = [], maskBits = 1 : ui32, numReadPorts = 1 : ui32, numReadWritePorts = 1 : ui32, numWritePorts = 0 : ui32, readLatency = 1 : ui32, writeLatency = 1 : ui32} firrtl.memmodule private @dumm_ext(in R0_addr: !firrtl.uint<5>, in R0_en: !firrtl.uint<1>, in R0_clk: !firrtl.clock, out R0_data: !firrtl.uint<5>, in W0_addr: !firrtl.uint<5>, in W0_en: !firrtl.uint<1>, in W0_clk: !firrtl.clock, in W0_data: !firrtl.uint<5>) attributes {dataWidth = 5 : ui32, depth = 20 : ui64, extraPorts = [], maskBits = 1 : ui32, numReadPorts = 1 : ui32, numReadWritePorts = 0 : ui32, numWritePorts = 1 : ui32, readLatency = 1 : ui32, writeLatency = 1 : ui32} + // CHECK-LABEL: firrtl.class @MemoryMetadata + // CHECK: %[[V2:.+]] = firrtl.string "memory_ext" + // CHECK: %[[V3:.+]] = firrtl.path instance distinct[1]<> + // CHECK: firrtl.list.create %[[V2]] : !firrtl.list + // CHECK: firrtl.list.create %[[V3]] : !firrtl.list + // CHECK: emit.file "metadata{{/|\\\\}}seq_mems.json" { // CHECK-NEXT{LITERAL}: sv.verbatim "[\0A {\0A \22module_name\22: \22{{0}}\22,\0A \22depth\22: 16,\0A \22width\22: 8,\0A \22masked\22: false,\0A \22read\22: 1,\0A \22write\22: 0,\0A \22readwrite\22: 1,\0A \22extra_ports\22: [],\0A \22hierarchy\22: [\0A \22{{3}}.{{4}}.memory_ext\22\0A ]\0A },\0A {\0A \22module_name\22: \22{{5}}\22,\0A \22depth\22: 20,\0A \22width\22: 5,\0A \22masked\22: false,\0A \22read\22: 1,\0A \22write\22: 1,\0A \22readwrite\22: 0,\0A \22extra_ports\22: [],\0A \22hierarchy\22: [\0A \22{{3}}.{{4}}.dumm_ext\22\0A ]\0A }\0A]" // CHECK-SAME: {symbols = [@memory_ext, @top, #hw.innerNameRef<@top::@[[DUT_SYM]]>, @DUT, #hw.innerNameRef<@DUT::@[[MEM1_SYM]]>, @dumm_ext]} @@ -308,7 +321,7 @@ firrtl.circuit "top" { // CHECK-SAME: {symbols = [@head_ext, @head_0_ext, @memory_ext, @dumm_ext]} // CHECK-NEXT: } - // CHECK: firrtl.class @SiFive_Metadata + // CHECK-LABEL: firrtl.class @SiFive_Metadata // CHECK: %[[V0:.+]] = firrtl.path instance distinct[0]<> // CHECK-NEXT: %[[V1:.+]] = firrtl.list.create %[[V0]] : !firrtl.list // CHECK-NEXT: firrtl.propassign %dutModulePath_field_1, %[[V1]] : !firrtl.list diff --git a/test/Dialect/FIRRTL/errors.mlir b/test/Dialect/FIRRTL/errors.mlir index 25fb6a262e8a..02905986d31b 100644 --- a/test/Dialect/FIRRTL/errors.mlir +++ b/test/Dialect/FIRRTL/errors.mlir @@ -285,16 +285,6 @@ firrtl.circuit "Foo" { // ----- -firrtl.circuit "Foo" { - // expected-note @+1 {{containing module declared here}} - firrtl.module @Foo() { - // expected-error @+1 {{'firrtl.instance' op is a recursive instantiation of its containing module}} - firrtl.instance "" @Foo() - } -} - -// ----- - firrtl.circuit "Foo" { // expected-note @+1 {{original module declared here}} firrtl.module @Callee(in %arg0: !firrtl.uint<1>) { } @@ -1894,23 +1884,6 @@ firrtl.circuit "WrongLayerBlockNesting" { // ----- -// A layer block captures a non-passive type. -firrtl.circuit "NonPassiveCapture" { - firrtl.layer @A bind {} - firrtl.module @NonPassiveCapture() { - // expected-note @below {{operand is defined here}} - %a = firrtl.wire : !firrtl.bundle> - // expected-error @below {{'firrtl.layerblock' op captures an operand which is not a passive type}} - firrtl.layerblock @A { - %b = firrtl.wire : !firrtl.bundle> - // expected-note @below {{operand is used here}} - firrtl.connect %b, %a : !firrtl.bundle>, !firrtl.bundle> - } - } -} - -// ----- - // A layer block may not drive sinks outside the layer block. firrtl.circuit "LayerBlockDrivesSinksOutside" { firrtl.layer @A bind {} @@ -2072,6 +2045,20 @@ firrtl.circuit "RWProbeUseDef" { // ----- +firrtl.circuit "RWProbeLayerRequirements" { + firrtl.layer @A bind { } + firrtl.module @RWProbeLayerRequirements(in %cond : !firrtl.uint<1>) { + // expected-note @below {{target is missing layer requirements: @A}} + %w = firrtl.wire sym @x : !firrtl.uint<1> + firrtl.layerblock @A { + // expected-error @below {{target has insufficient layer requirements}} + %rw = firrtl.ref.rwprobe <@RWProbeLayerRequirements::@x> : !firrtl.rwprobe> + } + } +} + +// ----- + firrtl.circuit "MissingClassForObjectPortInModule" { // expected-error @below {{'firrtl.module' op references unknown class @Missing}} firrtl.module @MissingClassForObjectPortInModule(out %o: !firrtl.class<@Missing()>) {} @@ -2554,3 +2541,23 @@ firrtl.module @LHSTypes() { } } +// ----- + +// expected-error @below {{op does not contain module with same name as circuit}} +firrtl.circuit "NoMain" { } + +// ----- + +firrtl.circuit "PrivateMain" { + // expected-error @below {{main module must be public}} + firrtl.module private @PrivateMain() {} +} + +// ----- + +firrtl.circuit "MainNotModule" { + // expected-error @below {{entity with name of circuit must be a module}} + func.func @MainNotModule() { + return + } +} diff --git a/test/Dialect/FIRRTL/infer-resets-errors.mlir b/test/Dialect/FIRRTL/infer-resets-errors.mlir index c0c6814756b5..b43520ccb901 100644 --- a/test/Dialect/FIRRTL/infer-resets-errors.mlir +++ b/test/Dialect/FIRRTL/infer-resets-errors.mlir @@ -6,7 +6,6 @@ // - github.com/sifive/$internal: // - test/scala/firrtl/FullAsyncResetTransform.scala - //===----------------------------------------------------------------------===// // Reset Inference //===----------------------------------------------------------------------===// @@ -152,34 +151,43 @@ firrtl.circuit "top" { } //===----------------------------------------------------------------------===// -// Full Async Reset +// Full Reset //===----------------------------------------------------------------------===// // ----- // Reset annotation cannot target module firrtl.circuit "top" { - // expected-error @+1 {{FullAsyncResetAnnotation' cannot target module; must target port or wire/node instead}} - firrtl.module @top() attributes {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} { + // expected-error @+1 {{'FullResetAnnotation' cannot target module; must target port or wire/node instead}} + firrtl.module @top() attributes {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} { } } // ----- -// Reset annotation cannot target synchronous reset signals +// Reset annotation resetType must match type of signal firrtl.circuit "top" { firrtl.module @top() { - // expected-error @below {{'FullAsyncResetAnnotation' must target async reset, but targets '!firrtl.uint<1>'}} - %innerReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.uint<1> + // expected-error @below {{'FullResetAnnotation' with resetType == 'async' must target async reset, but targets '!firrtl.uint<1>'}} + %innerReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.uint<1> + // expected-error @below {{'FullResetAnnotation' with resetType == 'sync' must target sync reset, but targets '!firrtl.asyncreset'}} + %innerReset2 = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "sync"}]} : !firrtl.asyncreset + // expected-error @below {{'FullResetAnnotation' with resetType == 'sync' must target sync reset, but targets '!firrtl.uint<2>'}} + %innerReset3 = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "sync"}]} : !firrtl.uint<2> } } // ----- -// Reset annotation cannot target reset signals which are inferred to be synchronous +// Reset annotation cannot target reset signals which are inferred to the wrong type firrtl.circuit "top" { firrtl.module @top() { - // expected-error @below {{'FullAsyncResetAnnotation' must target async reset, but targets '!firrtl.uint<1>'}} - %innerReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.reset + // expected-error @below {{'FullResetAnnotation' with resetType == 'async' must target async reset, but targets '!firrtl.uint<1>'}} + %innerReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.reset %invalid = firrtl.invalidvalue : !firrtl.reset firrtl.matchingconnect %innerReset, %invalid : !firrtl.reset + + // expected-error @below {{'FullResetAnnotation' with resetType == 'sync' must target sync reset, but targets '!firrtl.asyncreset'}} + %innerReset2 = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "sync"}]} : !firrtl.reset + %asyncWire = firrtl.wire : !firrtl.asyncreset + firrtl.connect %innerReset2, %asyncWire : !firrtl.reset, !firrtl.asyncreset } } @@ -187,8 +195,8 @@ firrtl.circuit "top" { // ----- // Ignore reset annotation cannot target port firrtl.circuit "top" { - // expected-error @+1 {{IgnoreFullAsyncResetAnnotation' cannot target port; must target module instead}} - firrtl.module @top(in %reset: !firrtl.asyncreset) attributes {portAnnotations =[[{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]]} { + // expected-error @+1 {{ExcludeFromFullResetAnnotation' cannot target port/wire/node; must target module instead}} + firrtl.module @top(in %reset: !firrtl.asyncreset) attributes {portAnnotations =[[{class = "circt.ExcludeFromFullResetAnnotation"}]]} { } } @@ -196,14 +204,14 @@ firrtl.circuit "top" { // Ignore reset annotation cannot target wire/node firrtl.circuit "top" { firrtl.module @top() { - // expected-error @+1 {{IgnoreFullAsyncResetAnnotation' cannot target wire/node; must target module instead}} - %0 = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} : !firrtl.asyncreset - // expected-error @+1 {{IgnoreFullAsyncResetAnnotation' cannot target wire/node; must target module instead}} - %1 = firrtl.node %0 {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} : !firrtl.asyncreset + // expected-error @+1 {{ExcludeFromFullResetAnnotation' cannot target port/wire/node; must target module instead}} + %0 = firrtl.wire {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} : !firrtl.asyncreset + // expected-error @+1 {{ExcludeFromFullResetAnnotation' cannot target port/wire/node; must target module instead}} + %1 = firrtl.node %0 {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} : !firrtl.asyncreset // expected-error @+1 {{reset annotations must target module, port, or wire/node}} - %2 = firrtl.asUInt %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : (!firrtl.asyncreset) -> !firrtl.uint<1> + %2 = firrtl.asUInt %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : (!firrtl.asyncreset) -> !firrtl.uint<1> // expected-error @+1 {{reset annotations must target module, port, or wire/node}} - %3 = firrtl.asUInt %0 {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} : (!firrtl.asyncreset) -> !firrtl.uint<1> + %3 = firrtl.asUInt %0 {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} : (!firrtl.asyncreset) -> !firrtl.uint<1> } } @@ -211,12 +219,12 @@ firrtl.circuit "top" { // Cannot have multiple reset annotations on a module firrtl.circuit "top" { // expected-error @+2 {{multiple reset annotations on module 'top'}} - // expected-note @+1 {{conflicting "sifive.enterprise.firrtl.FullAsyncResetAnnotation":}} - firrtl.module @top(in %outerReset: !firrtl.asyncreset) attributes {portAnnotations = [[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { - // expected-note @+1 {{conflicting "sifive.enterprise.firrtl.FullAsyncResetAnnotation":}} - %innerReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset - // expected-note @+1 {{conflicting "sifive.enterprise.firrtl.FullAsyncResetAnnotation":}} - %anotherReset = firrtl.node %innerReset {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + // expected-note @+1 {{conflicting "circt.FullResetAnnotation":}} + firrtl.module @top(in %outerReset: !firrtl.asyncreset) attributes {portAnnotations = [[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { + // expected-note @+1 {{conflicting "circt.FullResetAnnotation":}} + %innerReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset + // expected-note @+1 {{conflicting "circt.FullResetAnnotation":}} + %anotherReset = firrtl.node %innerReset {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset } } @@ -228,18 +236,18 @@ firrtl.circuit "Top" { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> } // expected-note @+1 {{reset domain 'otherReset' of module 'Child' declared here:}} - firrtl.module @Child(in %clock: !firrtl.clock, in %otherReset: !firrtl.asyncreset) attributes {portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + firrtl.module @Child(in %clock: !firrtl.clock, in %otherReset: !firrtl.asyncreset) attributes {portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { // expected-note @+1 {{instance 'child/inst' is in reset domain rooted at 'otherReset' of module 'Child'}} %inst_clock = firrtl.instance inst @Foo(in clock: !firrtl.clock) firrtl.connect %inst_clock, %clock : !firrtl.clock, !firrtl.clock } - firrtl.module @Other(in %clock: !firrtl.clock) attributes {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} { + firrtl.module @Other(in %clock: !firrtl.clock) attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} { // expected-note @+1 {{instance 'other/inst' is in no reset domain}} %inst_clock = firrtl.instance inst @Foo(in clock: !firrtl.clock) firrtl.connect %inst_clock, %clock : !firrtl.clock, !firrtl.clock } // expected-note @+1 {{reset domain 'reset' of module 'Top' declared here:}} - firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes {portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes {portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { %child_clock, %child_otherReset = firrtl.instance child @Child(in clock: !firrtl.clock, in otherReset: !firrtl.asyncreset) %other_clock = firrtl.instance other @Other(in clock: !firrtl.clock) // expected-note @+1 {{instance 'foo' is in reset domain rooted at 'reset' of module 'Top'}} @@ -266,3 +274,10 @@ firrtl.circuit "UninferredRefReset" { // expected-note @+1 {{the module with this uninferred reset port was defined here}} firrtl.module private @UninferredRefResetPriv(out %reset: !firrtl.probe) {} } + +// ----- +// Invalid FullResetAnnotation resetType +firrtl.circuit "Top" { + // expected-error @+1 {{'FullResetAnnotation' requires resetType == 'sync' | 'async', but got resetType == "potato"}} + firrtl.module @Top(in %reset: !firrtl.asyncreset) attributes {portAnnotations = [[{class = "circt.FullResetAnnotation", resetType = "potato"}]]} {} +} diff --git a/test/Dialect/FIRRTL/infer-resets.mlir b/test/Dialect/FIRRTL/infer-resets.mlir index 7f13717d7792..1eb7d0e0a77d 100644 --- a/test/Dialect/FIRRTL/infer-resets.mlir +++ b/test/Dialect/FIRRTL/infer-resets.mlir @@ -358,18 +358,18 @@ firrtl.module @ForeignTypes(out %out: !firrtl.reset) { // CHECK-LABEL: firrtl.module @ConsumeIgnoreAnno -// CHECK-NOT: IgnoreFullAsyncResetAnnotation -firrtl.module @ConsumeIgnoreAnno() attributes {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} { +// CHECK-NOT: ExcludeFromFullResetAnnotation +firrtl.module @ConsumeIgnoreAnno() attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} { } } // firrtl.circuit // ----- -// Reset-less registers should inherit the annotated async reset signal. +// AsyncReset-less registers should inherit the annotated async reset signal. firrtl.circuit "Top" { // CHECK-LABEL: firrtl.module @Top firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %init: !firrtl.uint<1>, in %in: !firrtl.uint<8>, in %extraReset: !firrtl.asyncreset ) attributes { - portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "async"}]]} { %c1_ui8 = firrtl.constant 1 : !firrtl.uint<8> // CHECK: %reg1 = firrtl.regreset sym @reg1 %clock, %extraReset, %c0_ui8 %reg1 = firrtl.reg sym @reg1 %clock : !firrtl.clock, !firrtl.uint<8> @@ -434,13 +434,36 @@ firrtl.circuit "Top" { } } +// ----- +// Reset-less registers should inherit the annotated sync reset signal. +firrtl.circuit "Top" { + // CHECK-LABEL: firrtl.module @Top + firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %init: !firrtl.uint<1>, in %in: !firrtl.uint<8>, in %extraReset: !firrtl.uint<1> ) attributes { + portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "sync"}]]} { + %c1_ui8 = firrtl.constant 1 : !firrtl.uint<8> + // CHECK: %reg1 = firrtl.regreset sym @reg1 %clock, %extraReset, %c0_ui8 + %reg1 = firrtl.reg sym @reg1 %clock : !firrtl.clock, !firrtl.uint<8> + firrtl.matchingconnect %reg1, %in : !firrtl.uint<8> + + // Existing async reset remains untouched. + // CHECK: %reg2 = firrtl.regreset %clock, %reset, %c1_ui8 + %reg2 = firrtl.regreset %clock, %reset, %c1_ui8 : !firrtl.clock, !firrtl.asyncreset, !firrtl.uint<8>, !firrtl.uint<8> + firrtl.matchingconnect %reg2, %in : !firrtl.uint<8> + + // Existing sync reset remains untouched. + // CHECK: %reg3 = firrtl.regreset %clock, %init, %c1_ui8 + %reg3 = firrtl.regreset %clock, %init, %c1_ui8 : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>, !firrtl.uint<8> + firrtl.matchingconnect %reg3, %in : !firrtl.uint<8> + } +} + // ----- // Async reset inference should be able to construct reset values for aggregate // types. firrtl.circuit "Top" { // CHECK-LABEL: firrtl.module @Top firrtl.module @Top(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes { - portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { // CHECK: %c0_ui = firrtl.constant 0 : !firrtl.const.uint // CHECK: %reg_uint = firrtl.regreset %clock, %reset, %c0_ui %reg_uint = firrtl.reg %clock : !firrtl.clock, !firrtl.uint @@ -476,8 +499,8 @@ firrtl.circuit "Top" { } // ----- -// Reset should reuse ports if name and type matches. -firrtl.circuit "ReusePorts" { +// Reset should reuse ports if name and type matches for async wiring. +firrtl.circuit "ReusePortsAsync" { // CHECK-LABEL: firrtl.module @Child // CHECK-SAME: in %clock: !firrtl.clock // CHECK-SAME: in %reset: !firrtl.asyncreset @@ -502,8 +525,8 @@ firrtl.circuit "ReusePorts" { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> } // CHECK-LABEL: firrtl.module @ReusePorts - firrtl.module @ReusePorts(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes { - portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + firrtl.module @ReusePortsAsync(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes { + portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { // CHECK: %child_clock, %child_reset = firrtl.instance child // CHECK: firrtl.matchingconnect %child_reset, %reset // CHECK: %badName_reset, %badName_clock, %badName_existingReset = firrtl.instance badName @@ -516,6 +539,47 @@ firrtl.circuit "ReusePorts" { } } +// ----- +// Reset should reuse ports if name and type matches for sync wiring. +firrtl.circuit "ReusePortsSync" { + // CHECK-LABEL: firrtl.module @Child + // CHECK-SAME: in %clock: !firrtl.clock + // CHECK-SAME: in %reset: !firrtl.uint<1> + // CHECK: %reg = firrtl.regreset %clock, %reset, %c0_ui8 + firrtl.module @Child(in %clock: !firrtl.clock, in %reset: !firrtl.uint<1>) { + %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> + } + // CHECK-LABEL: firrtl.module @BadName + // CHECK-SAME: in %reset: !firrtl.uint<1>, + // CHECK-SAME: in %clock: !firrtl.clock + // CHECK-SAME: in %existingReset: !firrtl.uint<1> + // CHECK: %reg = firrtl.regreset %clock, %reset, %c0_ui8 + firrtl.module @BadName(in %clock: !firrtl.clock, in %existingReset: !firrtl.uint<1>) { + %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> + } + // CHECK-LABEL: firrtl.module @BadType + // CHECK-SAME: in %reset_0: !firrtl.uint<1> + // CHECK-SAME: in %clock: !firrtl.clock + // CHECK-SAME: in %reset: !firrtl.asyncreset + // CHECK: %reg = firrtl.regreset %clock, %reset_0, %c0_ui8 + firrtl.module @BadType(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) { + %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> + } + // CHECK-LABEL: firrtl.module @ReusePorts + firrtl.module @ReusePortsSync(in %clock: !firrtl.clock, in %reset: !firrtl.uint<1>) attributes { + portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "sync"}]]} { + // CHECK: %child_clock, %child_reset = firrtl.instance child + // CHECK: firrtl.matchingconnect %child_reset, %reset + // CHECK: %badName_reset, %badName_clock, %badName_existingReset = firrtl.instance badName + // CHECK: firrtl.matchingconnect %badName_reset, %reset + // CHECK: %badType_reset_0, %badType_clock, %badType_reset = firrtl.instance badType + // CHECK: firrtl.matchingconnect %badType_reset_0, %reset + %child_clock, %child_reset = firrtl.instance child @Child(in clock: !firrtl.clock, in reset: !firrtl.uint<1>) + %badName_clock, %badName_existingReset = firrtl.instance badName @BadName(in clock: !firrtl.clock, in existingReset: !firrtl.uint<1>) + %badType_clock, %badType_reset = firrtl.instance badType @BadType(in clock: !firrtl.clock, in reset: !firrtl.asyncreset) + } +} + // ----- // Infer async reset: nested firrtl.circuit "FullAsyncNested" { @@ -544,7 +608,7 @@ firrtl.circuit "FullAsyncNested" { } // CHECK-LABEL: firrtl.module @FullAsyncNested firrtl.module @FullAsyncNested(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>) attributes { - portAnnotations=[[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}], [], []] } { + portAnnotations=[[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "async"}], [], []] } { %inst_clock, %inst_reset, %inst_io_in, %inst_io_out = firrtl.instance inst @FullAsyncNestedChild(in clock: !firrtl.clock, in reset: !firrtl.asyncreset, in io_in: !firrtl.uint<8>, out io_out: !firrtl.uint<8>) firrtl.matchingconnect %inst_clock, %clock : !firrtl.clock firrtl.matchingconnect %inst_reset, %reset : !firrtl.asyncreset @@ -560,7 +624,7 @@ firrtl.circuit "FullAsyncNested" { firrtl.circuit "FullAsyncExcluded" { // CHECK-LABEL: firrtl.module @FullAsyncExcludedChild // CHECK-SAME: (in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>) - firrtl.module @FullAsyncExcludedChild(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>) attributes {annotations = [{class = "sifive.enterprise.firrtl.IgnoreFullAsyncResetAnnotation"}]} { + firrtl.module @FullAsyncExcludedChild(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>) attributes {annotations = [{class = "circt.ExcludeFromFullResetAnnotation"}]} { // CHECK: %io_out_REG = firrtl.reg %clock %io_out_REG = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> firrtl.matchingconnect %io_out_REG, %io_in : !firrtl.uint<8> @@ -568,7 +632,7 @@ firrtl.circuit "FullAsyncExcluded" { } // CHECK-LABEL: firrtl.module @FullAsyncExcluded firrtl.module @FullAsyncExcluded(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %io_in: !firrtl.uint<8>, out %io_out: !firrtl.uint<8>, in %extraReset: !firrtl.asyncreset) attributes { - portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "async"}]]} { // CHECK: %inst_clock, %inst_reset, %inst_io_in, %inst_io_out = firrtl.instance inst @FullAsyncExcludedChild %inst_clock, %inst_reset, %inst_io_in, %inst_io_out = firrtl.instance inst @FullAsyncExcludedChild(in clock: !firrtl.clock, in reset: !firrtl.asyncreset, in io_in: !firrtl.uint<8>, out io_out: !firrtl.uint<8>) firrtl.matchingconnect %inst_clock, %clock : !firrtl.clock @@ -586,7 +650,7 @@ firrtl.circuit "WireShouldDominate" { // CHECK-LABEL: firrtl.module @WireShouldDominate firrtl.module @WireShouldDominate(in %clock: !firrtl.clock) { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset - %localReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %localReset = firrtl.wire // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 // CHECK-NEXT: %reg = firrtl.regreset %clock, %localReset, [[RV]] @@ -602,7 +666,7 @@ firrtl.circuit "MovableNodeShouldDominate" { firrtl.module @MovableNodeShouldDominate(in %clock: !firrtl.clock, in %ui1: !firrtl.uint<1>) { %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // does not block move of node %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %0 = firrtl.asAsyncReset %ui1 // CHECK-NEXT: %localReset = firrtl.node sym @theReset %0 // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 @@ -620,7 +684,7 @@ firrtl.circuit "UnmovableNodeShouldDominate" { firrtl.module @UnmovableNodeShouldDominate(in %clock: !firrtl.clock, in %ui1: !firrtl.uint<1>) { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %localReset = firrtl.wire sym @theReset // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 // CHECK-NEXT: %reg = firrtl.regreset %clock, %localReset, [[RV]] @@ -638,7 +702,7 @@ firrtl.circuit "UnmovableForceableNodeShouldDominate" { firrtl.module @UnmovableForceableNodeShouldDominate(in %clock: !firrtl.clock, in %ui1: !firrtl.uint<1>) { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset, %ref = firrtl.node sym @theReset %0 forceable {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset, %ref = firrtl.node sym @theReset %0 forceable {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %localReset, %{{.+}} = firrtl.wire sym @theReset // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 // CHECK-NEXT: %reg = firrtl.regreset %clock, %localReset, [[RV]] @@ -660,7 +724,7 @@ firrtl.circuit "MoveAcrossBlocks1" { } firrtl.when %ui1 : !firrtl.uint<1> { %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset } // CHECK-NEXT: %localReset = firrtl.wire // CHECK-NEXT: firrtl.when %ui1 : !firrtl.uint<1> { @@ -683,7 +747,7 @@ firrtl.circuit "MoveAcrossBlocks2" { // <-- should move reset here firrtl.when %ui1 : !firrtl.uint<1> { %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset } firrtl.when %ui1 : !firrtl.uint<1> { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset @@ -710,7 +774,7 @@ firrtl.circuit "MoveAcrossBlocks3" { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset firrtl.when %ui1 : !firrtl.uint<1> { %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset } // CHECK-NEXT: %localReset = firrtl.wire // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 @@ -733,7 +797,7 @@ firrtl.circuit "MoveAcrossBlocks4" { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<8> // gets wired to localReset } %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset // blocks move of node - %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node sym @theReset %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK-NEXT: %localReset = firrtl.wire // CHECK-NEXT: firrtl.when %ui1 : !firrtl.uint<1> { // CHECK-NEXT: [[RV:%.+]] = firrtl.constant 0 @@ -750,7 +814,7 @@ firrtl.circuit "MoveAcrossBlocks4" { firrtl.circuit "SubAccess" { firrtl.module @SubAccess(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset, in %init: !firrtl.uint<1>, in %in: !firrtl.uint<8>, in %extraReset: !firrtl.asyncreset ) attributes { // CHECK-LABEL: firrtl.module @SubAccess - portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[],[],[],[{class = "firrtl.transforms.DontTouchAnnotation"}, {class = "circt.FullResetAnnotation", resetType = "async"}]]} { %c1_ui8 = firrtl.constant 1 : !firrtl.uint<2> %arr = firrtl.wire : !firrtl.vector, 1> %reg6 = firrtl.regreset %clock, %init, %c1_ui8 : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<2>, !firrtl.uint<2> @@ -772,7 +836,7 @@ firrtl.circuit "SubAccess" { // CHECK-LABEL: firrtl.module @ZeroWidthRegister firrtl.circuit "ZeroWidthRegister" { firrtl.module @ZeroWidthRegister(in %clock: !firrtl.clock, in %reset: !firrtl.asyncreset) attributes { - portAnnotations = [[],[{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]]} { + portAnnotations = [[],[{class = "circt.FullResetAnnotation", resetType = "async"}]]} { %reg = firrtl.reg %clock : !firrtl.clock, !firrtl.uint<0> // CHECK-NEXT: [[TMP:%.+]] = firrtl.constant 0 : !firrtl.const.uint<0> // CHECK-NEXT: %reg = firrtl.regreset %clock, %reset, [[TMP]] @@ -1143,8 +1207,8 @@ firrtl.circuit "MovableNodeShouldDominateInstance" { firrtl.connect %child_clock, %clock : !firrtl.clock %ui1 = firrtl.constant 1 : !firrtl.uint<1> %0 = firrtl.asAsyncReset %ui1 : (!firrtl.uint<1>) -> !firrtl.asyncreset - %localReset = firrtl.node %0 {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset - // CHECK: %localReset = firrtl.wire {annotations = [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}]} : !firrtl.asyncreset + %localReset = firrtl.node %0 {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset + // CHECK: %localReset = firrtl.wire {annotations = [{class = "circt.FullResetAnnotation", resetType = "async"}]} : !firrtl.asyncreset // CHECK: %child_localReset, %child_clock = firrtl.instance child @Child(in localReset: !firrtl.asyncreset, in clock: !firrtl.clock } firrtl.module @Child(in %clock: !firrtl.clock) { diff --git a/test/Dialect/FIRRTL/layers.mlir b/test/Dialect/FIRRTL/layers.mlir index 472b171a9a43..2155d0cc7910 100644 --- a/test/Dialect/FIRRTL/layers.mlir +++ b/test/Dialect/FIRRTL/layers.mlir @@ -25,6 +25,47 @@ firrtl.circuit "Test" { firrtl.ref.define %o, %s : !firrtl.probe, @B> } + //===--------------------------------------------------------------------===// + // Capture Tests + //===--------------------------------------------------------------------===// + + firrtl.module @CaptureTests() { + %a = firrtl.wire : !firrtl.vector, b flip: uint<1>>, 2> + %b = firrtl.wire : !firrtl.bundle, b flip: uint<2>> + firrtl.layerblock @A { + %0 = firrtl.subindex %a[0] : !firrtl.vector, b flip: uint<1>>, 2> + %1 = firrtl.constant 0 : !firrtl.uint<1> + %2 = firrtl.subaccess %a[%1] : !firrtl.vector, b flip: uint<1>>, 2>, !firrtl.uint<1> + %3 = firrtl.subfield %b[a] : !firrtl.bundle, b flip: uint<2>> + %4 = firrtl.ref.send %b : !firrtl.bundle, b flip: uint<2>> + } + } + + //===--------------------------------------------------------------------===// + // Connect Tests + //===--------------------------------------------------------------------===// + + firrtl.module @ConnectTests() { + %a = firrtl.wire : !firrtl.uint<1> + %b = firrtl.wire : !firrtl.bundle> + %c = firrtl.wire : !firrtl.bundle> + %d = firrtl.wire : !firrtl.bundle>> + %e = firrtl.wire : !firrtl.bundle>> + firrtl.layerblock @A { + %_a = firrtl.wire : !firrtl.uint<1> + %_b = firrtl.wire : !firrtl.bundle> + %_c = firrtl.wire : !firrtl.bundle> + %_d = firrtl.wire : !firrtl.bundle>> + %_e = firrtl.wire : !firrtl.bundle>> + + firrtl.connect %_a, %a : !firrtl.uint<1> + firrtl.connect %_b, %b : !firrtl.bundle> + firrtl.connect %c, %_c : !firrtl.bundle> + firrtl.connect %d, %_d : !firrtl.bundle>> + firrtl.connect %_e, %e : !firrtl.bundle>> + } + } + //===--------------------------------------------------------------------===// // Basic Casting Tests //===--------------------------------------------------------------------===// @@ -201,4 +242,16 @@ firrtl.circuit "Test" { firrtl.propassign %foo_in, %str : !firrtl.string } } + + //===--------------------------------------------------------------------===// + // RWProbe under Layer + //===--------------------------------------------------------------------===// + + firrtl.module @RWProbeInLayer() { + firrtl.layerblock @A { + %w = firrtl.wire sym @sym : !firrtl.uint<1> + %rwp = firrtl.ref.rwprobe <@RWProbeInLayer::@sym> : !firrtl.rwprobe> + } + } + } diff --git a/test/Dialect/FIRRTL/lower-classes.mlir b/test/Dialect/FIRRTL/lower-classes.mlir index 3cc0a551e2b6..5ab1befacc50 100644 --- a/test/Dialect/FIRRTL/lower-classes.mlir +++ b/test/Dialect/FIRRTL/lower-classes.mlir @@ -224,6 +224,13 @@ firrtl.circuit "PathModule" { // CHECK: %[[c1:.+]] = om.list_create %propIn, %[[c0]] : !om.integer // CHECK: om.class.field @propOut, %[[c1]] : !om.list } + + firrtl.module @ListConcat(in %propIn0: !firrtl.list, in %propIn1: !firrtl.list, out %propOut: !firrtl.list) { + // CHECK: [[CONCAT:%.+]] = om.list_concat %propIn0, %propIn1 + %1 = firrtl.list.concat %propIn0, %propIn1 : !firrtl.list + // CHECK: om.class.field @propOut, [[CONCAT]] + firrtl.propassign %propOut, %1 : !firrtl.list + } } // CHECK-LABEL: firrtl.circuit "WireProp" @@ -335,7 +342,7 @@ firrtl.circuit "AnyCast" { // CHECK-LABEL: firrtl.circuit "ModuleWithPropertySubmodule" firrtl.circuit "ModuleWithPropertySubmodule" { // CHECK: om.class @ModuleWithPropertySubmodule_Class - firrtl.module private @ModuleWithPropertySubmodule() { + firrtl.module @ModuleWithPropertySubmodule() { %c0 = firrtl.integer 0 // CHECK: om.object @SubmoduleWithProperty_Class %inst.prop = firrtl.instance inst @SubmoduleWithProperty(in prop: !firrtl.integer) @@ -451,3 +458,16 @@ firrtl.circuit "OwningModulePrefix" { firrtl.path reference distinct[0]<> } } + +// CHECK-LABEL: firrtl.circuit "PathTargetReplaced" +firrtl.circuit "PathTargetReplaced" { + // CHECK: hw.hierpath private [[NLA:@.+]] [@PathTargetReplaced::[[SYM:@.+]]] + firrtl.module @PathTargetReplaced() { + // CHECK: firrtl.instance replaced sym [[SYM]] + firrtl.instance replaced {annotations = [{class = "circt.tracker", id = distinct[0]<>}]} @WillBeReplaced(out output: !firrtl.integer) + // CHECK: om.path_create instance %basepath [[NLA]] + %path = firrtl.path instance distinct[0]<> + } + firrtl.module private @WillBeReplaced(out %output: !firrtl.integer) { + } +} diff --git a/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir b/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir index 9e91432796ce..cb7ffd83e63b 100644 --- a/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir +++ b/test/Dialect/FIRRTL/lower-intmodules-eicg.mlir @@ -11,13 +11,37 @@ firrtl.circuit "FixupEICGWrapper" { annotations = [{class = "firrtl.transforms.DedupGroupAnnotation", group = "foo"}]} // CHECK: FixupEICGWrapper - firrtl.module @FixupEICGWrapper(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) { + firrtl.module @FixupEICGWrapper(in %clock: !firrtl.clock, in %test_en: !firrtl.uint<1>, in %en: !firrtl.uint<1>) { // CHECK-NOEICG: firrtl.instance // CHECK-EICG-NOT: firrtl.instance - // CHECK-EICG: firrtl.int.generic "circt_clock_gate" + // CHECK-EICG-DAG: firrtl.matchingconnect %[[CLK:.+]], %clock : !firrtl.clock + // CHECK-EICG-DAG: firrtl.matchingconnect %[[TEST_EN:.+]], %test_en : !firrtl.uint<1> + // CHECK-EICG-DAG: firrtl.matchingconnect %[[EN:.+]], %en : !firrtl.uint<1> + // CHECK-EICG-DAG: %[[CLK]] = firrtl.wire : !firrtl.clock + // CHECK-EICG-DAG: %[[TEST_EN]] = firrtl.wire : !firrtl.uint<1> + // CHECK-EICG-DAG: %[[EN]] = firrtl.wire : !firrtl.uint<1> + // CHECK-EICG-DAG: %3 = firrtl.int.generic "circt_clock_gate" %[[CLK]], %[[EN]], %[[TEST_EN]] : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.clock %ckg_in, %ckg_test_en, %ckg_en, %ckg_out = firrtl.instance ckg @LegacyClockGate(in in: !firrtl.clock, in test_en: !firrtl.uint<1>, in en: !firrtl.uint<1>, out out: !firrtl.clock) firrtl.matchingconnect %ckg_in, %clock : !firrtl.clock - firrtl.matchingconnect %ckg_test_en, %en : !firrtl.uint<1> + firrtl.matchingconnect %ckg_test_en, %test_en : !firrtl.uint<1> + firrtl.matchingconnect %ckg_en, %en : !firrtl.uint<1> + } +} + +// ----- + +firrtl.circuit "EICGWrapperPortName" { + firrtl.extmodule @BadClockGate(in in: !firrtl.clock, + // expected-error @below {{expected port named 'test_en'}} + in en: !firrtl.uint<1>, + in test_en: !firrtl.uint<1>, + out out: !firrtl.clock) + attributes { defname = "EICG_wrapper" } + + firrtl.module @EICGWrapperPortName(in %clock: !firrtl.clock, in %test_en: !firrtl.uint<1>, in %en: !firrtl.uint<1>) { + %ckg_in, %ckg_en, %ckg_test_en, %ckg_out = firrtl.instance ckg @BadClockGate(in in: !firrtl.clock, in en: !firrtl.uint<1>, in test_en: !firrtl.uint<1>, out out: !firrtl.clock) + firrtl.matchingconnect %ckg_in, %clock : !firrtl.clock + firrtl.matchingconnect %ckg_test_en, %test_en : !firrtl.uint<1> firrtl.matchingconnect %ckg_en, %en : !firrtl.uint<1> } } diff --git a/test/Dialect/FIRRTL/lower-intmodules.mlir b/test/Dialect/FIRRTL/lower-intmodules.mlir index 1c9113a11f96..1add51ad8907 100644 --- a/test/Dialect/FIRRTL/lower-intmodules.mlir +++ b/test/Dialect/FIRRTL/lower-intmodules.mlir @@ -49,8 +49,8 @@ firrtl.circuit "ProbeIntrinsicTest" { // 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-LABEL: firrtl.module @ProbeIntrinsicTest + firrtl.module @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.generic "circt_fpga_probe" [[DATA]], [[CLOCK]] : (!firrtl.uint, !firrtl.clock) -> () diff --git a/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir b/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir index a83b2f67a850..ca88fdbeb129 100644 --- a/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir +++ b/test/Dialect/FIRRTL/lower-intrinsics-errors.mlir @@ -1,7 +1,7 @@ // RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl.module(firrtl-lower-intrinsics)))' -verify-diagnostics --split-input-file %s firrtl.circuit "UnknownIntrinsic" { - firrtl.module private @UnknownIntrinsic(in %data: !firrtl.uint<32>) { + firrtl.module @UnknownIntrinsic(in %data: !firrtl.uint<32>) { %0 = firrtl.wire : !firrtl.uint<32> // expected-error @below {{unknown intrinsic}} // expected-error @below {{failed to legalize}} diff --git a/test/Dialect/FIRRTL/lower-intrinsics.mlir b/test/Dialect/FIRRTL/lower-intrinsics.mlir index fb0199c7f470..a9ce815628d3 100644 --- a/test/Dialect/FIRRTL/lower-intrinsics.mlir +++ b/test/Dialect/FIRRTL/lower-intrinsics.mlir @@ -155,7 +155,7 @@ firrtl.circuit "Foo" { firrtl.int.generic "circt_fpga_probe" %data, %clock : (!firrtl.uint<32>, !firrtl.clock) -> () } - // CHECK-LABEL: firrtl.module private @DPIIntrinsicTest(in %clock: !firrtl.clock, in %enable: !firrtl.uint<1>, in %in1: !firrtl.uint<8>, in %in2: !firrtl.uint<8>) + // CHECK-LABEL: firrtl.module private @DPIIntrinsicTest firrtl.module private @DPIIntrinsicTest(in %clock : !firrtl.clock, in %enable : !firrtl.uint<1>, in %in1: !firrtl.uint<8>, in %in2: !firrtl.uint<8>) { // CHECK-NEXT: %0 = firrtl.int.dpi.call "clocked_result"(%in1, %in2) clock %clock enable %enable {inputNames = ["foo", "bar"], outputName = "baz"} : (!firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> %0 = firrtl.int.generic "circt_dpi_call" %clock, %enable, %in1, %in2 : (!firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8>, !firrtl.uint<8>) -> !firrtl.uint<8> diff --git a/test/Dialect/FIRRTL/lower-layers-errors.mlir b/test/Dialect/FIRRTL/lower-layers-errors.mlir new file mode 100644 index 000000000000..5d8e45986a26 --- /dev/null +++ b/test/Dialect/FIRRTL/lower-layers-errors.mlir @@ -0,0 +1,16 @@ +// RUN: circt-opt -firrtl-lower-layers -split-input-file -verify-diagnostics %s + +firrtl.circuit "NonPassiveSubaccess" { + firrtl.layer @A bind {} + firrtl.module @NonPassiveSubaccess( + in %a: !firrtl.vector, b flip: uint<1>>, 2>, + in %b: !firrtl.uint<1> + ) { + // expected-note @below {{the layerblock is defined here}} + firrtl.layerblock @A { + %n = firrtl.node %b : !firrtl.uint<1> + // expected-error @below {{'firrtl.subaccess' op has a non-passive operand and captures a value defined outside its enclosing bind-convention layerblock}} + %0 = firrtl.subaccess %a[%b] : !firrtl.vector, b flip: uint<1>>, 2>, !firrtl.uint<1> + } + } +} diff --git a/test/Dialect/FIRRTL/lower-layers.mlir b/test/Dialect/FIRRTL/lower-layers.mlir index 2104b976d5d7..74b6f7af71f7 100644 --- a/test/Dialect/FIRRTL/lower-layers.mlir +++ b/test/Dialect/FIRRTL/lower-layers.mlir @@ -201,6 +201,29 @@ firrtl.circuit "Test" { } } + // Test that subfield, subindex, and subaccess are moved out of layerblocks to + // avoid capturing non-passive types. + // + // CHECK: firrtl.module private @[[SubOpsInLayerBlock_A:[A-Za-z0-9_]+]] + // CHECK-SAME: in %[[port:[A-Za-z0-9_]+]]: !firrtl.uint<1> + // CHECK-NEXT: firrtl.node %[[port]] + // CHECK-NEXT: } + // CHECK: firrtl.module @SubOpsInLayerBlock + // CHECK-NEXT: firrtl.subaccess + // CHECK-NEXT: firrtl.subindex + // CHECK-NEXT: firrtl.subfield + firrtl.module @SubOpsInLayerBlock( + in %a: !firrtl.vector, b flip: uint<2>>, 2>, 2>, + in %b: !firrtl.uint<1> + ) { + firrtl.layerblock @A { + %0 = firrtl.subaccess %a[%b] : !firrtl.vector, b flip: uint<2>>, 2>, 2>, !firrtl.uint<1> + %1 = firrtl.subindex %0[0] : !firrtl.vector, b flip: uint<2>>, 2> + %2 = firrtl.subfield %1[a] : !firrtl.bundle, b flip: uint<2>> + %3 = firrtl.node %2 : !firrtl.uint<1> + } + } + //===--------------------------------------------------------------------===// // Connecting/Defining Refs //===--------------------------------------------------------------------===// @@ -694,3 +717,22 @@ firrtl.circuit "Foo" attributes { // // CHECK: sv.verbatim // CHECK-SAME: #hw.output_file<"testbench{{/|\\\\}}layers_Foo_A.sv", excludeFromFileList> + + +// ----- +// Check that we correctly implement the verilog header and footer for B. + +firrtl.circuit "Foo" { + firrtl.layer @A bind { + firrtl.layer @X bind {} + } + firrtl.layer @B bind {} + firrtl.module @Foo() {} +} + +// CHECK: firrtl.circuit "Foo" { +// CHECK: sv.verbatim "`ifndef layers_Foo_B\0A`define layers_Foo_B" {output_file = #hw.output_file<"layers_Foo_B.sv", excludeFromFileList>} +// CHECK: firrtl.module @Foo() { +// CHECK: } +// CHECK: sv.verbatim "`endif // layers_Foo_B" {output_file = #hw.output_file<"layers_Foo_B.sv", excludeFromFileList>} +// CHECK: } diff --git a/test/Dialect/FIRRTL/parse-basic.fir b/test/Dialect/FIRRTL/parse-basic.fir index 961cfba9e712..89e60d66f03f 100644 --- a/test/Dialect/FIRRTL/parse-basic.fir +++ b/test/Dialect/FIRRTL/parse-basic.fir @@ -1670,6 +1670,20 @@ circuit IntegerArithmetic : ; CHECK: firrtl.propassign %e, [[E]] propassign e, integer_shr(a, b) +;// ----- +FIRRTL version 4.0.0 + +; CHECK-LABEL: firrtl.circuit "PropertyListOps" +circuit PropertyListOps : + public module PropertyListOps : + input a : List + input b : List + output c : List + + ; CHECK: [[C:%.+]] = firrtl.list.concat %a, %b + ; CHECK: firrtl.propassign %c, [[C]] + propassign c, list_concat(a, b) + ;// ----- FIRRTL version 3.1.0 @@ -1907,3 +1921,41 @@ circuit GenericIntrinsics: ; CHECK-NEXT: %[[SZ:.+]] = firrtl.int.generic "circt_isX" ; CHECK-NEXT: "circt_verif_assert" %[[SZ]] intrinsic(circt_verif_assert, intrinsic(circt_isX: UInt<1>, data)) + +;// ----- +FIRRTL version 4.0.0 +; CHECK-LABEL: circuit "Foo" +circuit Foo: + + ; CHECK-LABEL: firrtl.module @Foo(in %data: !firrtl.uint<32>, in %c: !firrtl.uint<1>, out %out: !firrtl.uint<32>) + public module Foo: + input data : UInt<32> + input c : UInt<1> + output out : UInt<32> + + when c: + node add1 = add(data, UInt<32>(1)) + out <= add1 + else: + out <= data + + ; CHECK-LABEL: firrtl.module @FooTest(in %s_foo_c: !firrtl.uint<1>, in %s_foo_data: !firrtl.uint<32>) + public module FooTest: + input s_foo_c : UInt<1> + input s_foo_data : UInt<32> + + ; CHECK-NEXT: %foo_data, %foo_c, %foo_out = firrtl.instance foo interesting_name @Foo(in data: !firrtl.uint<32>, in c: !firrtl.uint<1>, out out: !firrtl.uint<32>) + inst foo of Foo + ; CHECK-NEXT: firrtl.matchingconnect %foo_c, %s_foo_c : !firrtl.uint<1> + connect foo.c, s_foo_c + ; CHECK-NEXT: firrtl.matchingconnect %foo_data, %s_foo_data : !firrtl.uint<32> + connect foo.data, s_foo_data + ; CHECK-NEXT: %0 = firrtl.eq %foo_out, %s_foo_data : (!firrtl.uint<32>, !firrtl.uint<32>) -> !firrtl.uint<1> + ; CHECK-NEXT: %1 = firrtl.int.generic "circt_ltl_implication" %s_foo_c, %0 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<1> + ; CHECK-NEXT: firrtl.int.generic "circt_verif_assert" %1 : (!firrtl.uint<1>) -> () + intrinsic(circt_verif_assert, intrinsic(circt_ltl_implication : UInt<1>, s_foo_c, eq(foo.out, s_foo_data))) + + ; CHECK: firrtl.formal @testFormal of @FooTest bound 20 + formal testFormal of FooTest, bound = 20 + + diff --git a/test/Dialect/FIRRTL/parse-errors.fir b/test/Dialect/FIRRTL/parse-errors.fir index 0ce93e169073..87cc7c60153b 100644 --- a/test/Dialect/FIRRTL/parse-errors.fir +++ b/test/Dialect/FIRRTL/parse-errors.fir @@ -1026,6 +1026,42 @@ circuit Top: ; 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 4.0.0 + +; CHECK-LABEL: firrtl.circuit "Top" +circuit Top : + public module Top : + input a : Integer + output b : List + + ; expected-error @below {{unexpected expression of type '!firrtl.integer' in List concat expression}} + propassign b, list_concat(a) + +;// ----- +FIRRTL version 4.0.0 + +; CHECK-LABEL: firrtl.circuit "Top" +circuit Top : + public module Top : + input a : List + input b : List + output c : List + + ; expected-error @below {{unexpected expression of type '!firrtl.list' in List concat expression of type '!firrtl.list'}} + propassign c, list_concat(a, b) + +;// ----- +FIRRTL version 4.0.0 + +; CHECK-LABEL: firrtl.circuit "Top" +circuit Top : + public module Top : + output c : List + + ; expected-error @below {{need at least one List to concatenate}} + propassign c, list_concat() + ;// ----- FIRRTL version 3.3.0 diff --git a/test/Dialect/FIRRTL/passive-wire.mlir b/test/Dialect/FIRRTL/passive-wire.mlir index 4a7c2e301562..a728295e4359 100644 --- a/test/Dialect/FIRRTL/passive-wire.mlir +++ b/test/Dialect/FIRRTL/passive-wire.mlir @@ -42,4 +42,4 @@ firrtl.circuit "TopLevel" { // CHECK: firrtl.ref.define %b, %0 : !firrtl.probe, 2>>> } -} \ No newline at end of file +} diff --git a/test/Dialect/FIRRTL/round-trip.mlir b/test/Dialect/FIRRTL/round-trip.mlir index 96384fde936b..1c6ae9434ed2 100644 --- a/test/Dialect/FIRRTL/round-trip.mlir +++ b/test/Dialect/FIRRTL/round-trip.mlir @@ -104,4 +104,20 @@ firrtl.module @PropertyArithmetic() { %4 = firrtl.integer.shr %0, %1 : (!firrtl.integer, !firrtl.integer) -> !firrtl.integer } +// CHECK-LABEL: firrtl.module @PropertyListOps +firrtl.module @PropertyListOps() { + %0 = firrtl.integer 0 + %1 = firrtl.integer 1 + %2 = firrtl.integer 2 + + // CHECK: [[L0:%.+]] = firrtl.list.create %0, %1 + %l0 = firrtl.list.create %0, %1 : !firrtl.list + + // CHECK: [[L1:%.+]] = firrtl.list.create %2 + %l1 = firrtl.list.create %2 : !firrtl.list + + // CHECK: firrtl.list.concat [[L0]], [[L1]] : !firrtl.list + %concat = firrtl.list.concat %l0, %l1 : !firrtl.list +} + } diff --git a/test/Dialect/FIRRTL/specialize-layers.mlir b/test/Dialect/FIRRTL/specialize-layers.mlir index 102e49856062..c6f0a042a09e 100644 --- a/test/Dialect/FIRRTL/specialize-layers.mlir +++ b/test/Dialect/FIRRTL/specialize-layers.mlir @@ -115,6 +115,21 @@ firrtl.circuit "LayerDisableInARow" attributes { firrtl.extmodule @LayerDisableInARow() } +// CHECK: firrtl.circuit "LayerblockEnableNestedChildren" +// CHECK-NOT: firrtl.layer +firrtl.circuit "LayerblockEnableNestedChildren" attributes { + enable_layers = [@A, @A::@B, @A::@C] +} { + firrtl.layer @A bind { + firrtl.layer @B bind { + } + firrtl.layer @C bind { + } + } + firrtl.module @LayerblockEnableNestedChildren() { + } +} + //===----------------------------------------------------------------------===// // LayerBlock Specialization //===----------------------------------------------------------------------===// @@ -202,7 +217,7 @@ firrtl.circuit "LayerblockDisableB" attributes { } } } - + //===----------------------------------------------------------------------===// // Default Specialization //===----------------------------------------------------------------------===// @@ -254,7 +269,7 @@ firrtl.circuit "LayerDefaultDisable" attributes { //===----------------------------------------------------------------------===// // CHECK-LABEL: firrtl.circuit "EnableLayerB" -firrtl.circuit "EnableLayerB" +firrtl.circuit "EnableLayerB" attributes { enable_layers = [@A::@B] } { @@ -266,13 +281,13 @@ attributes { firrtl.option @Option { firrtl.option_case @Option } - + firrtl.module public @EnableLayerB() { firrtl.layerblock @A { firrtl.layerblock @A::@B { firrtl.layerblock @A::@B::@C { // CHECK: firrtl.instance instance {layers = [@A, @A, @A::@B_C]} @ExtModule() - firrtl.instance instance {layers = [@A, @A::@B, @A::@B::@C]} @ExtModule() + firrtl.instance instance {layers = [@A, @A::@B, @A::@B::@C]} @ExtModule() // CHECK: firrtl.instance_choice instance_choice // CHECK-SAME: {layers = [@A, @A, @A::@B_C]} @ExtModule firrtl.instance_choice instance_choice @@ -343,8 +358,8 @@ firrtl.circuit "ProbeOpsEnableA" attributes { } // CHECK: firrtl.instance instance @ExtModule(out out: !firrtl.probe>) - firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) - + firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) + // CHECK: firrtl.instance_choice instance_choice @ExtModule // CHECK-SAME: alternatives @Option { @Option -> @ExtModule } // CHECK-SAME: (out out: !firrtl.probe>) @@ -352,7 +367,7 @@ firrtl.circuit "ProbeOpsEnableA" attributes { alternatives @Option { @Option -> @ExtModule } (out out : !firrtl.probe, @A>) } - + firrtl.extmodule @ExtModule(out out : !firrtl.probe, @A>) } @@ -392,7 +407,7 @@ firrtl.circuit "ProbeOpsDisableA" attributes { } // CHECK: firrtl.instance instance @ExtModule() - firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) + firrtl.instance instance @ExtModule(out out : !firrtl.probe, @A>) // CHECK: firrtl.instance_choice instance_choice @ExtModule // CHECK-SAME: alternatives @Option { @Option -> @ExtModule } // CHECK-SAME: () @@ -429,13 +444,13 @@ firrtl.circuit "HierPathDelete" attributes { firrtl.module @HierPathDelete() { firrtl.instance middle sym @middle @Middle() } - + firrtl.module @Middle() { firrtl.layerblock @Layer { firrtl.instance leaf sym @leaf @Leaf() } } - + firrtl.extmodule @Leaf() // CHECK-NOT: hw.hierpath private @DeletedPath [@Deleted] @@ -463,13 +478,13 @@ firrtl.circuit "HierPathDelete2" attributes { firrtl.instance middle sym @middle @Middle() } } - + firrtl.module @Middle() { firrtl.layerblock @Layer { firrtl.instance leaf sym @leaf @Leaf() } } - + firrtl.extmodule @Leaf() // CHECK-NOT: hw.hierpath private @DeletedPath [@Deleted] @@ -495,11 +510,11 @@ firrtl.circuit "Annotations" attributes { firrtl.instance leaf sym @leaf @Leaf(in in : !firrtl.uint<1>) } } - + // CHECK: firrtl.module @Leaf(in %in: !firrtl.uint<1>) { firrtl.module @Leaf(in %in : !firrtl.uint<1> [{circt.nonlocal = @Path}]) attributes {annotations = [{circt.nonlocal = @Path}]} { - + // CHECK: %w = firrtl.wire : !firrtl.uint<1> %w = firrtl.wire {annotations = [{circt.nonlocal = @Path}]} : !firrtl.uint<1> diff --git a/test/Dialect/HW/flatten-io.mlir b/test/Dialect/HW/flatten-io.mlir index 7273c4d6356e..b76e79fb5f6d 100644 --- a/test/Dialect/HW/flatten-io.mlir +++ b/test/Dialect/HW/flatten-io.mlir @@ -37,11 +37,11 @@ hw.module @level2(in %in : !Struct2, out out: !Struct2) { hw.type_scope @foo { hw.typedecl @bar : !Struct1 } -!ScopedStruct = !hw.typealias<@foo::@bar,!Struct1> +!ScopedStruct = !hw.typealias<@foo::@bar, !Struct1> // BASIC-LABEL: hw.module @scoped(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-NEXT: %0 = hw.struct_create (%in.a, %in.b) : !hw.struct -// BASIC-NEXT: %a, %b = hw.struct_explode %0 : !hw.struct +// BASIC-NEXT: %0 = hw.struct_create (%in.a, %in.b) : !hw.typealias<@foo::@bar, !hw.struct> +// BASIC-NEXT: %a, %b = hw.struct_explode %0 : !hw.typealias<@foo::@bar, !hw.struct> // BASIC-NEXT: hw.output %arg0, %a, %b, %arg1 : i32, i1, i2, i32 // BASIC-NEXT: } hw.module @scoped(in %arg0 : i32, in %in : !ScopedStruct, in %arg1: i32, out out0 : i32, out out: !ScopedStruct, out out1: i32) { diff --git a/test/Dialect/HWArith/basic.mlir b/test/Dialect/HWArith/basic.mlir index 3a8b89eb61dd..9285c61de1aa 100644 --- a/test/Dialect/HWArith/basic.mlir +++ b/test/Dialect/HWArith/basic.mlir @@ -33,6 +33,4 @@ hw.module @test1() { %12 = hwarith.cast %11 : (si10) -> i9 // CHECK: %13 = hwarith.icmp eq %5, %10 : ui2, si9 %13 = hwarith.icmp eq %5, %10 : ui2, si9 - // CHECK: %14 = hwarith.cast %13 : (ui1) -> i1 - %14 = hwarith.cast %13 : (ui1) -> i1 } diff --git a/test/Dialect/Handshake/split-merge.mlir b/test/Dialect/Handshake/split-merge.mlir index dd28703ca154..02ea3b69f185 100644 --- a/test/Dialect/Handshake/split-merge.mlir +++ b/test/Dialect/Handshake/split-merge.mlir @@ -59,4 +59,4 @@ handshake.func @m3(%in0 : i32, %in1 : i32, %in2 : i32) -> (i32) { handshake.func @m5(%in0 : i32, %in1 : i32, %in2 : i32, %in3 : i32, %in4 : i32) -> (i32) { %out = handshake.merge %in0, %in1, %in2, %in3, %in4 : i32 return %out : i32 -} \ No newline at end of file +} diff --git a/test/Dialect/Ibis/Transforms/containers_to_hw.mlir b/test/Dialect/Ibis/Transforms/containers_to_hw.mlir index 417c237e624e..b77656b6023f 100644 --- a/test/Dialect/Ibis/Transforms/containers_to_hw.mlir +++ b/test/Dialect/Ibis/Transforms/containers_to_hw.mlir @@ -163,3 +163,16 @@ ibis.container "Foo" sym @B top_level { hw.module.extern @D_Foo(in %theExternModule : i1) hw.module.extern @Foo(in %theExternModule : i1) + +// ----- + +// Test that containers with names that alias with the design op are not +// de-aliased. + +// CHECK: hw.module @D + +ibis.design @D { + ibis.container "D" sym @D top_level { + %this = ibis.this <@D::@D> + } +} diff --git a/test/Dialect/LLHD/Canonicalization/probeCSE.mlir b/test/Dialect/LLHD/Canonicalization/probeCSE.mlir index c1b968830c0b..8568ec6ddf57 100644 --- a/test/Dialect/LLHD/Canonicalization/probeCSE.mlir +++ b/test/Dialect/LLHD/Canonicalization/probeCSE.mlir @@ -18,7 +18,8 @@ hw.module @checkPrbDceAndCseIn(inout %arg0 : i32, inout %arg1 : i32, inout %arg2 // CHECK-LABEL: @checkPrbDceButNotCse hw.module @checkPrbDceButNotCse(inout %arg0 : i32, inout %arg1 : i32, inout %arg2 : i32) { - // CHECK-NEXT: llhd.process + %prb = llhd.prb %arg0 : !hw.inout + // CHECK: llhd.process llhd.process { // CHECK-NEXT: llhd.constant_time %time = llhd.constant_time <0ns, 1d, 0e> @@ -26,7 +27,7 @@ hw.module @checkPrbDceButNotCse(inout %arg0 : i32, inout %arg1 : i32, inout %arg // CHECK-NEXT: [[P1:%.*]] = llhd.prb %1 = llhd.prb %arg0 : !hw.inout // CHECK-NEXT: llhd.wait - llhd.wait (%arg0: !hw.inout), ^bb1 + llhd.wait (%prb: i32), ^bb1 // CHECK-NEXT: ^bb1: ^bb1: // CHECK-NEXT: [[P2:%.*]] = llhd.prb diff --git a/test/Dialect/LLHD/IR/basic.mlir b/test/Dialect/LLHD/IR/basic.mlir new file mode 100644 index 000000000000..618d1a05b0db --- /dev/null +++ b/test/Dialect/LLHD/IR/basic.mlir @@ -0,0 +1,219 @@ +// RUN: circt-opt %s | circt-opt | FileCheck %s + +// CHECK-LABEL: @basic +// CHECK-SAME: (in [[IN0:%.+]] : i32, out out0 : i32) +hw.module @basic(in %in0 : i32, out out0 : i32) { + // CHECK: %{{.*}} = llhd.delay [[IN0]] by <0ns, 1d, 0e> : i32 + %0 = llhd.delay %in0 by <0ns, 1d, 0e> : i32 + hw.output %0 : i32 +} + +// CHECK-LABEL: @connect_ports +// CHECK-SAME: (inout %[[IN:.+]] : [[TYPE:.+]], inout %[[OUT:.+]] : [[TYPE]]) +// CHECK-NEXT: llhd.con %[[IN]], %[[OUT]] : !hw.inout<[[TYPE]]> +hw.module @connect_ports(inout %in: i32, inout %out: i32) { + llhd.con %in, %out : !hw.inout +} + +// CHECK-LABEL: @sigExtract +hw.module @sigExtract(inout %arg0 : i32, in %arg1 : i5) { + // CHECK-NEXT: %{{.*}} = llhd.sig.extract %arg0 from %arg1 : (!hw.inout) -> !hw.inout + %1 = llhd.sig.extract %arg0 from %arg1 : (!hw.inout) -> !hw.inout +} + +// CHECK-LABEL: @sigArray +hw.module @sigArray(inout %arg0 : !hw.array<5xi1>, in %arg1 : i3) { + // CHECK-NEXT: %{{.*}} = llhd.sig.array_slice %arg0 at %arg1 : (!hw.inout>) -> !hw.inout> + %0 = llhd.sig.array_slice %arg0 at %arg1 : (!hw.inout>) -> !hw.inout> + // CHECK-NEXT: %{{.*}} = llhd.sig.array_get %arg0[%arg1] : !hw.inout> + %1 = llhd.sig.array_get %arg0[%arg1] : !hw.inout> +} + +// CHECK-LABEL: @sigStructExtract +hw.module @sigStructExtract(inout %arg0 : !hw.struct) { + // CHECK-NEXT: %{{.*}} = llhd.sig.struct_extract %arg0["foo"] : !hw.inout> + %0 = llhd.sig.struct_extract %arg0["foo"] : !hw.inout> + // CHECK-NEXT: %{{.*}} = llhd.sig.struct_extract %arg0["baz"] : !hw.inout> + %1 = llhd.sig.struct_extract %arg0["baz"] : !hw.inout> +} + +// CHECK-LABEL: @check_var +// CHECK-SAME: %[[INT:.*]]: i32 +// CHECK-SAME: %[[ARRAY:.*]]: !hw.array<3xi1> +// CHECK-SAME: %[[TUP:.*]]: !hw.struct +func.func @check_var(%int : i32, %array : !hw.array<3xi1>, %tup : !hw.struct) { + // CHECK-NEXT: %{{.*}} = llhd.var %[[INT]] : i32 + %0 = llhd.var %int : i32 + // CHECK-NEXT: %{{.*}} = llhd.var %[[ARRAY]] : !hw.array<3xi1> + %1 = llhd.var %array : !hw.array<3xi1> + // CHECK-NEXT: %{{.*}} = llhd.var %[[TUP]] : !hw.struct + %2 = llhd.var %tup : !hw.struct + + return +} + +// CHECK-LABEL: @check_load +// CHECK-SAME: %[[INT:.*]]: !llhd.ptr +// CHECK-SAME: %[[ARRAY:.*]]: !llhd.ptr> +// CHECK-SAME: %[[TUP:.*]]: !llhd.ptr> +func.func @check_load(%int : !llhd.ptr, %array : !llhd.ptr>, %tup : !llhd.ptr>) { + // CHECK-NEXT: %{{.*}} = llhd.load %[[INT]] : !llhd.ptr + %0 = llhd.load %int : !llhd.ptr + // CHECK-NEXT: %{{.*}} = llhd.load %[[ARRAY]] : !llhd.ptr> + %1 = llhd.load %array : !llhd.ptr> + // CHECK-NEXT: %{{.*}} = llhd.load %[[TUP]] : !llhd.ptr> + %2 = llhd.load %tup : !llhd.ptr> + + return + +} + +// CHECK-LABEL: @check_store +// CHECK-SAME: %[[INT:.*]]: !llhd.ptr +// CHECK-SAME: %[[INTC:.*]]: i32 +// CHECK-SAME: %[[ARRAY:.*]]: !llhd.ptr> +// CHECK-SAME: %[[ARRAYC:.*]]: !hw.array<3xi1> +// CHECK-SAME: %[[TUP:.*]]: !llhd.ptr> +// CHECK-SAME: %[[TUPC:.*]]: !hw.struct +func.func @check_store(%int : !llhd.ptr, %intC : i32 , %array : !llhd.ptr>, %arrayC : !hw.array<3xi1>, %tup : !llhd.ptr>, %tupC : !hw.struct) { + // CHECK-NEXT: llhd.store %[[INT]], %[[INTC]] : !llhd.ptr + llhd.store %int, %intC : !llhd.ptr + // CHECK-NEXT: llhd.store %[[ARRAY]], %[[ARRAYC]] : !llhd.ptr> + llhd.store %array, %arrayC : !llhd.ptr> + // CHECK-NEXT: llhd.store %[[TUP]], %[[TUPC]] : !llhd.ptr> + llhd.store %tup, %tupC : !llhd.ptr> + + return +} + +// CHECK-LABEL: @checkSigInst +hw.module @checkSigInst() { + // CHECK: %[[CI1:.*]] = hw.constant + %cI1 = hw.constant 0 : i1 + // CHECK-NEXT: %sigI1 = llhd.sig %[[CI1]] : i1 + %sigI1 = llhd.sig %cI1 : i1 + // CHECK-NEXT: %[[CI64:.*]] = hw.constant + %cI64 = hw.constant 0 : i64 + // CHECK-NEXT: %sigI64 = llhd.sig %[[CI64]] : i64 + %sigI64 = llhd.sig %cI64 : i64 + + // CHECK-NEXT: %[[TUP:.*]] = hw.struct_create + %tup = hw.struct_create (%cI1, %cI64) : !hw.struct + // CHECK-NEXT: %sigTup = llhd.sig %[[TUP]] : !hw.struct + %sigTup = llhd.sig %tup : !hw.struct + + // CHECK-NEXT: %[[ARRAY:.*]] = hw.array_create + %array = hw.array_create %cI1, %cI1 : i1 + // CHECK-NEXT: %sigArray = llhd.sig %[[ARRAY]] : !hw.array<2xi1> + %sigArray = llhd.sig %array : !hw.array<2xi1> +} + +// CHECK-LABEL: @checkPrb +hw.module @checkPrb(inout %arg0 : i1, inout %arg1 : i64, inout %arg2 : !hw.array<3xi8>, inout %arg3 : !hw.struct) { + // CHECK: %{{.*}} = llhd.prb %arg0 : !hw.inout + %0 = llhd.prb %arg0 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb %arg1 : !hw.inout + %1 = llhd.prb %arg1 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb %arg2 : !hw.inout> + %2 = llhd.prb %arg2 : !hw.inout> + // CHECK-NEXT: %{{.*}} = llhd.prb %arg3 : !hw.inout> + %3 = llhd.prb %arg3 : !hw.inout> +} + +// CHECK-LABEL: @checkOutput +hw.module @checkOutput(in %arg0 : i32) { + %t = llhd.constant_time <0ns, 1d, 0e> + // CHECK: %{{.+}} = llhd.output %arg0 after %{{.*}} : i32 + %0 = llhd.output %arg0 after %t : i32 + // CHECK-NEXT: %{{.+}} = llhd.output "sigName" %arg0 after %{{.*}} : i32 + %1 = llhd.output "sigName" %arg0 after %t : i32 +} + +// CHECK-LABEL: @checkDrv +hw.module @checkDrv(inout %arg0 : i1, inout %arg1 : i64, in %arg2 : i1, + in %arg3 : i64, inout %arg5 : !hw.array<3xi8>, + inout %arg6 : !hw.struct, + in %arg7 : !hw.array<3xi8>, in %arg8 : !hw.struct) { + + %t = llhd.constant_time <0ns, 1d, 0e> + // CHECK: llhd.drv %arg0, %arg2 after %{{.*}} : !hw.inout + llhd.drv %arg0, %arg2 after %t : !hw.inout + // CHECK-NEXT: llhd.drv %arg1, %arg3 after %{{.*}} : !hw.inout + llhd.drv %arg1, %arg3 after %t : !hw.inout + // CHECK-NEXT: llhd.drv %arg1, %arg3 after %{{.*}} if %arg2 : !hw.inout + llhd.drv %arg1, %arg3 after %t if %arg2 : !hw.inout + // CHECK-NEXT: llhd.drv %arg5, %arg7 after %{{.*}} : !hw.inout> + llhd.drv %arg5, %arg7 after %t : !hw.inout> + // CHECK-NEXT: llhd.drv %arg6, %arg8 after %{{.*}} : !hw.inout> + llhd.drv %arg6, %arg8 after %t : !hw.inout> +} + +// CHECK-LABEL: @check_wait_0 +hw.module @check_wait_0 () { + // CHECK-NEXT: llhd.process + llhd.process { + // CHECK: llhd.wait ^[[BB:.*]] + llhd.wait ^bb1 + // CHECK-NEXT: ^[[BB]] + ^bb1: + llhd.halt + } +} + +// CHECK-LABEL: @check_wait_1 +hw.module @check_wait_1 () { + // CHECK-NEXT: llhd.process + llhd.process { + // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time + %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> + // CHECK-NEXT: llhd.wait for %[[TIME]], ^[[BB:.*]](%[[TIME]] : !llhd.time) + llhd.wait for %time, ^bb1(%time: !llhd.time) + // CHECK-NEXT: ^[[BB]](%[[T:.*]]: !llhd.time): + ^bb1(%t: !llhd.time): + llhd.halt + } +} + +// CHECK: @check_wait_2(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { +hw.module @check_wait_2 (inout %arg0 : i64, inout %arg1 : i1) { + // CHECK: [[PRB0:%.+]] = llhd.prb %arg0 + %prb0 = llhd.prb %arg0 : !hw.inout + // CHECK: [[PRB1:%.+]] = llhd.prb %arg1 + %prb1 = llhd.prb %arg1 : !hw.inout + // CHECK-NEXT: llhd.process + llhd.process { + // CHECK-NEXT: llhd.wait ([[PRB0]], [[PRB1]] : i64, i1), ^[[BB:.*]](%[[ARG1]] : !hw.inout) + llhd.wait (%prb0, %prb1 : i64, i1), ^bb1(%arg1 : !hw.inout) + // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout): + ^bb1(%a: !hw.inout): + llhd.halt + } +} + +// CHECK: hw.module @check_wait_3(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { +hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { + // CHECK: [[PRB0:%.+]] = llhd.prb %arg0 + %prb0 = llhd.prb %arg0 : !hw.inout + // CHECK: [[PRB1:%.+]] = llhd.prb %arg1 + %prb1 = llhd.prb %arg1 : !hw.inout + // CHECK-NEXT: llhd.process + llhd.process { + // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time + %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> + // CHECK-NEXT: llhd.wait for %[[TIME]], ([[PRB0]], [[PRB1]] : i64, i1), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !hw.inout, !hw.inout) + llhd.wait for %time, (%prb0, %prb1 : i64, i1), ^bb1(%arg1, %arg0 : !hw.inout, !hw.inout) + // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout, %[[B:.*]]: !hw.inout): + ^bb1(%a: !hw.inout, %b: !hw.inout): + llhd.halt + } +} + +// CHECK-LABEL: @FinalProcess +hw.module @FinalProcess () { + // CHECK-NEXT: llhd.final { + // CHECK-NEXT: llhd.halt + // CHECK-NEXT: } + llhd.final { + llhd.halt + } +} diff --git a/test/Dialect/LLHD/IR/connect-errors.mlir b/test/Dialect/LLHD/IR/connect-errors.mlir deleted file mode 100644 index fe80231cb4f9..000000000000 --- a/test/Dialect/LLHD/IR/connect-errors.mlir +++ /dev/null @@ -1,16 +0,0 @@ -// RUN: circt-opt %s -split-input-file -verify-diagnostics - -// expected-note @+1 {{prior use here}} -hw.module @connect_different_types(inout %in: i8, inout %out: i32) { - // expected-error @+1 {{use of value '%out' expects different type}} - llhd.con %in, %out : !hw.inout -} - -// ----- - -hw.module @connect_non_signals(inout %in: i32, inout %out: i32) { - %0 = llhd.prb %in : !hw.inout - %1 = llhd.prb %out : !hw.inout - // expected-error @+1 {{'llhd.con' op operand #0 must be InOutType, but got 'i32'}} - llhd.con %0, %1 : i32 -} diff --git a/test/Dialect/LLHD/IR/connect.mlir b/test/Dialect/LLHD/IR/connect.mlir deleted file mode 100644 index 9ffe5a77a833..000000000000 --- a/test/Dialect/LLHD/IR/connect.mlir +++ /dev/null @@ -1,8 +0,0 @@ -// RUN: circt-opt %s -split-input-file | FileCheck %s - -// CHECK-LABEL: @connect_ports -// CHECK-SAME: (inout %[[IN:.+]] : [[TYPE:.+]], inout %[[OUT:.+]] : [[TYPE]]) -// CHECK-NEXT: llhd.con %[[IN]], %[[OUT]] : !hw.inout<[[TYPE]]> -hw.module @connect_ports(inout %in: i32, inout %out: i32) { - llhd.con %in, %out : !hw.inout -} diff --git a/test/Dialect/LLHD/IR/errors.mlir b/test/Dialect/LLHD/IR/errors.mlir new file mode 100644 index 000000000000..48d0d2232a74 --- /dev/null +++ b/test/Dialect/LLHD/IR/errors.mlir @@ -0,0 +1,115 @@ +// RUN: circt-opt %s --split-input-file --verify-diagnostics + +hw.module @errors(in %in0: i32, out out0: i8) { + // expected-error @below {{requires the same type for all operands and results}} + %0 = "llhd.delay"(%in0) {delay = #llhd.time<0ns, 1d, 0e>} : (i32) -> i8 + hw.output %0 : i8 +} + +// ----- + +// expected-note @+1 {{prior use here}} +hw.module @connect_different_types(inout %in: i8, inout %out: i32) { + // expected-error @+1 {{use of value '%out' expects different type}} + llhd.con %in, %out : !hw.inout +} + +// ----- + +hw.module @connect_non_signals(inout %in: i32, inout %out: i32) { + %0 = llhd.prb %in : !hw.inout + %1 = llhd.prb %out : !hw.inout + // expected-error @+1 {{'llhd.con' op operand #0 must be InOutType, but got 'i32'}} + llhd.con %0, %1 : i32 +} + +// ----- + +hw.module @illegal_signal_to_array(inout %sig : !hw.array<3xi32>, in %ind : i2) { + // expected-error @+1 {{'llhd.sig.array_slice' op result #0 must be InOutType of an ArrayType values, but got '!hw.array<3xi32>'}} + %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.array<3xi32> +} + +// ----- + +hw.module @illegal_array_element_type_mismatch(inout %sig : !hw.array<3xi32>, in %ind : i2) { + // expected-error @+1 {{arrays element type must match}} + %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.inout> +} + +// ----- + +hw.module @illegal_result_array_too_big(inout %sig : !hw.array<3xi32>, in %ind : i2) { + // expected-error @+1 {{width of result type has to be smaller than or equal to the input type}} + %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.inout> +} + +// ----- + +hw.module @illegal_sig_to_int(inout %s : i32, in %ind : i5) { + // expected-error @+1 {{'llhd.sig.extract' op result #0 must be InOutType of a signless integer bitvector values, but got 'i10'}} + %0 = llhd.sig.extract %s from %ind : (!hw.inout) -> i10 +} + +// ----- + +hw.module @illegal_sig_to_int_to_wide(inout %s : i32, in %ind : i5) { + // expected-error @+1 {{width of result type has to be smaller than or equal to the input type}} + %0 = llhd.sig.extract %s from %ind : (!hw.inout) -> !hw.inout +} + +// ----- + +hw.module @extract_element_tuple_index_out_of_bounds(inout %tup : !hw.struct) { + // expected-error @+1 {{invalid field name specified}} + %0 = llhd.sig.struct_extract %tup["foobar"] : !hw.inout> +} + +// ----- + +// expected-note @+1 {{prior use here}} +func.func @check_illegal_store(%i1Ptr : !llhd.ptr, %i32Const : i32) { + // expected-error @+1 {{use of value '%i32Const' expects different type than prior uses: 'i1' vs 'i32'}} + llhd.store %i1Ptr, %i32Const : !llhd.ptr + + return +} + +// ----- + +// expected-error @+1 {{unknown type `illegaltype` in dialect `llhd`}} +func.func @illegaltype(%arg0: !llhd.illegaltype) { + return +} + +// ----- + +// expected-error @+2 {{unknown attribute `illegalattr` in dialect `llhd`}} +func.func @illegalattr() { + %0 = llhd.constant_time #llhd.illegalattr : i1 + return +} + +// ----- + +// expected-error @+3 {{failed to verify that type of 'init' and underlying type of 'signal' have to match.}} +hw.module @check_illegal_sig() { + %cI1 = hw.constant 0 : i1 + %sig1 = "llhd.sig"(%cI1) {name="foo"} : (i1) -> !hw.inout +} + +// ----- + +// expected-error @+2 {{failed to verify that type of 'result' and underlying type of 'signal' have to match.}} +hw.module @check_illegal_prb(inout %sig : i1) { + %prb = "llhd.prb"(%sig) {} : (!hw.inout) -> i32 +} + +// ----- + +// expected-error @+4 {{failed to verify that type of 'value' and underlying type of 'signal' have to match.}} +hw.module @check_illegal_drv(inout %sig : i1) { + %c = hw.constant 0 : i32 + %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> + "llhd.drv"(%sig, %c, %time) {} : (!hw.inout, i32, !llhd.time) -> () +} diff --git a/test/Dialect/LLHD/IR/extract-errors.mlir b/test/Dialect/LLHD/IR/extract-errors.mlir deleted file mode 100644 index f4378b6db712..000000000000 --- a/test/Dialect/LLHD/IR/extract-errors.mlir +++ /dev/null @@ -1,53 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics - -func.func @illegal_signal_to_array(%sig : !hw.inout>, %ind: i2) { - // expected-error @+1 {{'llhd.sig.array_slice' op result #0 must be InOutType of an ArrayType values, but got '!hw.array<3xi32>'}} - %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.array<3xi32> - - return -} - -// ----- - -func.func @illegal_array_element_type_mismatch(%sig : !hw.inout>, %ind: i2) { - // expected-error @+1 {{arrays element type must match}} - %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.inout> - - return -} - -// ----- - -func.func @illegal_result_array_too_big(%sig : !hw.inout>, %ind: i2) { - // expected-error @+1 {{width of result type has to be smaller than or equal to the input type}} - %0 = llhd.sig.array_slice %sig at %ind : (!hw.inout>) -> !hw.inout> - - return -} - -// ----- - -func.func @illegal_sig_to_int(%s : !hw.inout, %ind: i5) { - // expected-error @+1 {{'llhd.sig.extract' op result #0 must be InOutType of a signless integer bitvector values, but got 'i10'}} - %0 = llhd.sig.extract %s from %ind : (!hw.inout) -> i10 - - return -} - -// ----- - -func.func @illegal_sig_to_int_to_wide(%s : !hw.inout, %ind: i5) { - // expected-error @+1 {{width of result type has to be smaller than or equal to the input type}} - %0 = llhd.sig.extract %s from %ind : (!hw.inout) -> !hw.inout - - return -} - -// ----- - -func.func @extract_element_tuple_index_out_of_bounds(%tup : !hw.inout>) { - // expected-error @+1 {{invalid field name specified}} - %0 = llhd.sig.struct_extract %tup["foobar"] : !hw.inout> - - return -} diff --git a/test/Dialect/LLHD/IR/extract.mlir b/test/Dialect/LLHD/IR/extract.mlir deleted file mode 100644 index 3f9eab31023e..000000000000 --- a/test/Dialect/LLHD/IR/extract.mlir +++ /dev/null @@ -1,29 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s - -// CHECK-LABEL: @sigExtract -func.func @sigExtract (%arg0 : !hw.inout, %arg1: i5) -> () { - // CHECK-NEXT: %{{.*}} = llhd.sig.extract %arg0 from %arg1 : (!hw.inout) -> !hw.inout - %1 = llhd.sig.extract %arg0 from %arg1 : (!hw.inout) -> !hw.inout - - return -} - -// CHECK-LABEL: @sigArray -func.func @sigArray (%arg0 : !hw.inout>, %arg1: i3) -> () { - // CHECK-NEXT: %{{.*}} = llhd.sig.array_slice %arg0 at %arg1 : (!hw.inout>) -> !hw.inout> - %0 = llhd.sig.array_slice %arg0 at %arg1 : (!hw.inout>) -> !hw.inout> - // CHECK-NEXT: %{{.*}} = llhd.sig.array_get %arg0[%arg1] : !hw.inout> - %1 = llhd.sig.array_get %arg0[%arg1] : !hw.inout> - - return -} - -// CHECK-LABEL: @sigStructExtract -func.func @sigStructExtract(%arg0 : !hw.inout>) { - // CHECK-NEXT: %{{.*}} = llhd.sig.struct_extract %arg0["foo"] : !hw.inout> - %0 = llhd.sig.struct_extract %arg0["foo"] : !hw.inout> - // CHECK-NEXT: %{{.*}} = llhd.sig.struct_extract %arg0["baz"] : !hw.inout> - %1 = llhd.sig.struct_extract %arg0["baz"] : !hw.inout> - - return -} diff --git a/test/Dialect/LLHD/IR/memory-errors.mlir b/test/Dialect/LLHD/IR/memory-errors.mlir deleted file mode 100644 index e836b69d6eee..000000000000 --- a/test/Dialect/LLHD/IR/memory-errors.mlir +++ /dev/null @@ -1,9 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics - -// expected-note @+1 {{prior use here}} -func.func @check_illegal_store(%i1Ptr : !llhd.ptr, %i32Const : i32) { - // expected-error @+1 {{use of value '%i32Const' expects different type than prior uses: 'i1' vs 'i32'}} - llhd.store %i1Ptr, %i32Const : !llhd.ptr - - return -} diff --git a/test/Dialect/LLHD/IR/memory.mlir b/test/Dialect/LLHD/IR/memory.mlir deleted file mode 100644 index d4a6226a9f44..000000000000 --- a/test/Dialect/LLHD/IR/memory.mlir +++ /dev/null @@ -1,49 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s - -// CHECK-LABEL: @check_var -// CHECK-SAME: %[[INT:.*]]: i32 -// CHECK-SAME: %[[ARRAY:.*]]: !hw.array<3xi1> -// CHECK-SAME: %[[TUP:.*]]: !hw.struct -func.func @check_var(%int : i32, %array : !hw.array<3xi1>, %tup : !hw.struct) { - // CHECK-NEXT: %{{.*}} = llhd.var %[[INT]] : i32 - %0 = llhd.var %int : i32 - // CHECK-NEXT: %{{.*}} = llhd.var %[[ARRAY]] : !hw.array<3xi1> - %1 = llhd.var %array : !hw.array<3xi1> - // CHECK-NEXT: %{{.*}} = llhd.var %[[TUP]] : !hw.struct - %2 = llhd.var %tup : !hw.struct - - return -} - -// CHECK-LABEL: @check_load -// CHECK-SAME: %[[INT:.*]]: !llhd.ptr -// CHECK-SAME: %[[ARRAY:.*]]: !llhd.ptr> -// CHECK-SAME: %[[TUP:.*]]: !llhd.ptr> -func.func @check_load(%int : !llhd.ptr, %array : !llhd.ptr>, %tup : !llhd.ptr>) { - // CHECK-NEXT: %{{.*}} = llhd.load %[[INT]] : !llhd.ptr - %0 = llhd.load %int : !llhd.ptr - // CHECK-NEXT: %{{.*}} = llhd.load %[[ARRAY]] : !llhd.ptr> - %1 = llhd.load %array : !llhd.ptr> - // CHECK-NEXT: %{{.*}} = llhd.load %[[TUP]] : !llhd.ptr> - %2 = llhd.load %tup : !llhd.ptr> - - return - -} -// CHECK-LABEL: @check_store -// CHECK-SAME: %[[INT:.*]]: !llhd.ptr -// CHECK-SAME: %[[INTC:.*]]: i32 -// CHECK-SAME: %[[ARRAY:.*]]: !llhd.ptr> -// CHECK-SAME: %[[ARRAYC:.*]]: !hw.array<3xi1> -// CHECK-SAME: %[[TUP:.*]]: !llhd.ptr> -// CHECK-SAME: %[[TUPC:.*]]: !hw.struct -func.func @check_store(%int : !llhd.ptr, %intC : i32 , %array : !llhd.ptr>, %arrayC : !hw.array<3xi1>, %tup : !llhd.ptr>, %tupC : !hw.struct) { - // CHECK-NEXT: llhd.store %[[INT]], %[[INTC]] : !llhd.ptr - llhd.store %int, %intC : !llhd.ptr - // CHECK-NEXT: llhd.store %[[ARRAY]], %[[ARRAYC]] : !llhd.ptr> - llhd.store %array, %arrayC : !llhd.ptr> - // CHECK-NEXT: llhd.store %[[TUP]], %[[TUPC]] : !llhd.ptr> - llhd.store %tup, %tupC : !llhd.ptr> - - return -} diff --git a/test/Dialect/LLHD/IR/reg.mlir b/test/Dialect/LLHD/IR/reg.mlir deleted file mode 100644 index 2f43a21dd7de..000000000000 --- a/test/Dialect/LLHD/IR/reg.mlir +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: circt-opt %s -split-input-file -verify-diagnostics | circt-opt | FileCheck %s - -// CHECK-LABEL: @check_reg -// CHECK-SAME: %[[IN64:.*]] : i64 -hw.module @check_reg(inout %in64 : i64) { - // CHECK: %[[C1:.*]] = hw.constant - %c1 = hw.constant 0 : i1 - // CHECK-NEXT: %[[C64:.*]] = hw.constant - %c64 = hw.constant 0 : i64 - // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time - %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> - // one trigger with optional gate - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64) : !hw.inout - "llhd.reg"(%in64, %c64, %c1, %time, %c1) {modes=[0], gateMask=[1], operandSegmentSizes=array} : (!hw.inout, i64, i1, !llhd.time, i1) -> () - // two triggers with optional gates - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !hw.inout) : !hw.inout - "llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1, %c1) {modes=[0,1], gateMask=[1,2], operandSegmentSizes=array} : (!hw.inout, i64, !hw.inout, i1, i1, !llhd.time, !llhd.time, i1, i1) -> () - // two triggers with only one optional gate - // CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !hw.inout) : !hw.inout - "llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1) {modes=[0,1], gateMask=[0,1], operandSegmentSizes=array} : (!hw.inout, i64, !hw.inout, i1, i1, !llhd.time, !llhd.time, i1) -> () -} - -// TODO: add verification tests inout reg-errors.mlir (expected-error tests) diff --git a/test/Dialect/LLHD/IR/signal-errors.mlir b/test/Dialect/LLHD/IR/signal-errors.mlir deleted file mode 100644 index b2ded8d52c8f..000000000000 --- a/test/Dialect/LLHD/IR/signal-errors.mlir +++ /dev/null @@ -1,30 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics - -// expected-error @+3 {{failed to verify that type of 'init' and underlying type of 'signal' have to match.}} -hw.module @check_illegal_sig() { - %cI1 = hw.constant 0 : i1 - %sig1 = "llhd.sig"(%cI1) {name="foo"} : (i1) -> !hw.inout -} - -// ----- - -// expected-error @+2 {{failed to verify that type of 'result' and underlying type of 'signal' have to match.}} -hw.module @check_illegal_prb(inout %sig : i1) { - %prb = "llhd.prb"(%sig) {} : (!hw.inout) -> i32 -} - -// ----- - -// expected-error @+4 {{failed to verify that type of 'value' and underlying type of 'signal' have to match.}} -hw.module @check_illegal_drv(inout %sig : i1) { - %c = hw.constant 0 : i32 - %time = llhd.constant_time #llhd.time<1ns, 0d, 0e> - "llhd.drv"(%sig, %c, %time) {} : (!hw.inout, i32, !llhd.time) -> () -} - -// ----- - -func.func @illegal_sig_parent (%arg0: i1) { - // expected-error @+1 {{expects parent op to be one of 'hw.module, llhd.process'}} - %0 = llhd.sig "sig" %arg0 : i1 -} diff --git a/test/Dialect/LLHD/IR/signal.mlir b/test/Dialect/LLHD/IR/signal.mlir deleted file mode 100644 index 8d7590ad76fa..000000000000 --- a/test/Dialect/LLHD/IR/signal.mlir +++ /dev/null @@ -1,67 +0,0 @@ -// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s - -// CHECK-LABEL: checkSigInst -hw.module @checkSigInst() { - // CHECK: %[[CI1:.*]] = hw.constant - %cI1 = hw.constant 0 : i1 - // CHECK-NEXT: %{{.*}} = llhd.sig "sigI1" %[[CI1]] : i1 - %sigI1 = llhd.sig "sigI1" %cI1 : i1 - // CHECK-NEXT: %[[CI64:.*]] = hw.constant - %cI64 = hw.constant 0 : i64 - // CHECK-NEXT: %{{.*}} = llhd.sig "sigI64" %[[CI64]] : i64 - %sigI64 = llhd.sig "sigI64" %cI64 : i64 - - // CHECK-NEXT: %[[TUP:.*]] = hw.struct_create - %tup = hw.struct_create (%cI1, %cI64) : !hw.struct - // CHECK-NEXT: %{{.*}} = llhd.sig "sigTup" %[[TUP]] : !hw.struct - %sigTup = llhd.sig "sigTup" %tup : !hw.struct - - // CHECK-NEXT: %[[ARRAY:.*]] = hw.array_create - %array = hw.array_create %cI1, %cI1 : i1 - // CHECK-NEXT: %{{.*}} = llhd.sig "sigArray" %[[ARRAY]] : !hw.array<2xi1> - %sigArray = llhd.sig "sigArray" %array : !hw.array<2xi1> -} - -// CHECK-LABEL: checkPrb -func.func @checkPrb(%arg0 : !hw.inout, %arg1 : !hw.inout, %arg2 : !hw.inout>, %arg3 : !hw.inout>) { - // CHECK: %{{.*}} = llhd.prb %arg0 : !hw.inout - %0 = llhd.prb %arg0 : !hw.inout - // CHECK-NEXT: %{{.*}} = llhd.prb %arg1 : !hw.inout - %1 = llhd.prb %arg1 : !hw.inout - // CHECK-NEXT: %{{.*}} = llhd.prb %arg2 : !hw.inout> - %2 = llhd.prb %arg2 : !hw.inout> - // CHECK-NEXT: %{{.*}} = llhd.prb %arg3 : !hw.inout> - %3 = llhd.prb %arg3 : !hw.inout> - - return -} - -// CHECK-LABEL: checkOutput -func.func @checkOutput(%arg0: i32, %arg1: !llhd.time) { - // CHECK-NEXT: %{{.+}} = llhd.output %arg0 after %arg1 : i32 - %0 = llhd.output %arg0 after %arg1 : i32 - // CHECK-NEXT: %{{.+}} = llhd.output "sigName" %arg0 after %arg1 : i32 - %1 = llhd.output "sigName" %arg0 after %arg1 : i32 - - return -} - -// CHECK-LABEL: checkDrv -func.func @checkDrv(%arg0 : !hw.inout, %arg1 : !hw.inout, %arg2 : i1, - %arg3 : i64, %arg4 : !llhd.time, %arg5 : !hw.inout>, - %arg6 : !hw.inout>, - %arg7 : !hw.array<3xi8>, %arg8 : !hw.struct) { - - // CHECK-NEXT: llhd.drv %arg0, %arg2 after %arg4 : !hw.inout - llhd.drv %arg0, %arg2 after %arg4 : !hw.inout - // CHECK-NEXT: llhd.drv %arg1, %arg3 after %arg4 : !hw.inout - llhd.drv %arg1, %arg3 after %arg4 : !hw.inout - // CHECK-NEXT: llhd.drv %arg1, %arg3 after %arg4 if %arg2 : !hw.inout - llhd.drv %arg1, %arg3 after %arg4 if %arg2 : !hw.inout - // CHECK-NEXT: llhd.drv %arg5, %arg7 after %arg4 : !hw.inout> - llhd.drv %arg5, %arg7 after %arg4 : !hw.inout> - // CHECK-NEXT: llhd.drv %arg6, %arg8 after %arg4 : !hw.inout> - llhd.drv %arg6, %arg8 after %arg4 : !hw.inout> - - return -} diff --git a/test/Dialect/LLHD/IR/type-and-attribute-parsing.mlir b/test/Dialect/LLHD/IR/type-and-attribute-parsing.mlir deleted file mode 100644 index 3f795c74a6aa..000000000000 --- a/test/Dialect/LLHD/IR/type-and-attribute-parsing.mlir +++ /dev/null @@ -1,14 +0,0 @@ -// RUN: circt-opt %s --split-input-file --verify-diagnostics - -// expected-error @+1 {{unknown type `illegaltype` in dialect `llhd`}} -func.func @illegaltype(%arg0: !llhd.illegaltype) { - return -} - -// ----- - -// expected-error @+2 {{unknown attribute `illegalattr` in dialect `llhd`}} -func.func @illegalattr() { - %0 = llhd.constant_time #llhd.illegalattr : i1 - return -} diff --git a/test/Dialect/LLHD/IR/wait.mlir b/test/Dialect/LLHD/IR/wait.mlir deleted file mode 100644 index d512e81a0f72..000000000000 --- a/test/Dialect/LLHD/IR/wait.mlir +++ /dev/null @@ -1,59 +0,0 @@ -// RUN: circt-opt %s | circt-opt | FileCheck %s - -// Test Overview: -// * 0 observed signals, no time, successor without arguments -// * 0 observed signals, with time, sucessor with arguments -// * 2 observed signals, no time, successor with arguments -// * 2 observed signals, with time, successor with arguments - -// CHECK-LABEL: @check_wait_0 -hw.module @check_wait_0 () { - // CHECK-NEXT: llhd.process - llhd.process { - // CHECK: llhd.wait ^[[BB:.*]] - "llhd.wait"() [^bb1] {operandSegmentSizes=array} : () -> () - // CHECK-NEXT: ^[[BB]] - ^bb1: - llhd.halt - } -} - -// CHECK-LABEL: @check_wait_1 -hw.module @check_wait_1 () { - // CHECK-NEXT: llhd.process - llhd.process { - // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time - %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> - // CHECK-NEXT: llhd.wait for %[[TIME]], ^[[BB:.*]](%[[TIME]] : !llhd.time) - "llhd.wait"(%time, %time) [^bb1] {operandSegmentSizes=array} : (!llhd.time, !llhd.time) -> () - // CHECK-NEXT: ^[[BB]](%[[T:.*]]: !llhd.time): - ^bb1(%t: !llhd.time): - llhd.halt - } -} - -// CHECK: @check_wait_2(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { -hw.module @check_wait_2 (inout %arg0 : i64, inout %arg1 : i1) { - // CHECK-NEXT: llhd.process - llhd.process { - // CHECK-NEXT: llhd.wait (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]] : !hw.inout) - "llhd.wait"(%arg0, %arg1, %arg1) [^bb1] {operandSegmentSizes=array} : (!hw.inout, !hw.inout, !hw.inout) -> () - // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout): - ^bb1(%a: !hw.inout): - llhd.halt - } -} - -// CHECK: hw.module @check_wait_3(inout %[[ARG0:.*]] : i64, inout %[[ARG1:.*]] : i1) { -hw.module @check_wait_3 (inout %arg0 : i64, inout %arg1 : i1) { - // CHECK-NEXT: llhd.process - llhd.process { - // CHECK-NEXT: %[[TIME:.*]] = llhd.constant_time - %time = llhd.constant_time #llhd.time<0ns, 0d, 0e> - // CHECK-NEXT: llhd.wait for %[[TIME]], (%[[ARG0]], %[[ARG1]] : !hw.inout, !hw.inout), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !hw.inout, !hw.inout) - "llhd.wait"(%arg0, %arg1, %time, %arg1, %arg0) [^bb1] {operandSegmentSizes=array} : (!hw.inout, !hw.inout, !llhd.time, !hw.inout, !hw.inout) -> () - // CHECK: ^[[BB]](%[[A:.*]]: !hw.inout, %[[B:.*]]: !hw.inout): - ^bb1(%a: !hw.inout, %b: !hw.inout): - llhd.halt - } -} diff --git a/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir b/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir index 9cdf16d1f013..3ab23494b85a 100644 --- a/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir +++ b/test/Dialect/LLHD/Transforms/earlyCodeMotion.mlir @@ -123,6 +123,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK-LABEL: @loop // CHECK-SAME: (inout %[[VAL_0:.*]] : i2) +// CHECK: [[PRB:%.+]] = llhd.prb %in_i // CHECK: llhd.process // CHECK: %[[VAL_1:.*]] = hw.constant 0 : i32 // CHECK: %[[VAL_2:.*]] = hw.constant 2 : i32 @@ -138,7 +139,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK: %[[VAL_8:.*]] = llhd.prb %[[VAL_0]] : !hw.inout // CHECK: cf.cond_br %[[VAL_7]], ^[[BB4:.+]], ^[[BB3:.+]] // CHECK: ^[[BB3]]: -// CHECK: llhd.wait (%[[VAL_0]] : !hw.inout), ^[[BB1]] +// CHECK: llhd.wait ([[PRB]] : i2), ^[[BB1]] // CHECK: ^[[BB4]]: // CHECK: %[[VAL_9:.*]] = llhd.load %[[VAL_5]] : !llhd.ptr // CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_9]], %[[VAL_4]] : i32 @@ -146,6 +147,7 @@ hw.module @check_blockarg(inout %sig : i32) { // CHECK: cf.br ^[[BB2]] // CHECK: } hw.module @loop(inout %in_i : i2) { + %prb0 = llhd.prb %in_i : !hw.inout llhd.process { // TR: -1 cf.br ^body @@ -162,7 +164,7 @@ hw.module @loop(inout %in_i : i2) { cf.cond_br %2, ^loop_continue, ^check ^check: // TR: 1 - llhd.wait (%in_i : !hw.inout), ^body + llhd.wait (%prb0 : i2), ^body ^loop_continue: // TR: 1 %3 = hw.constant 0 : i2 @@ -177,6 +179,8 @@ hw.module @loop(inout %in_i : i2) { // CHECK-LABEL: @complicated // CHECK-SAME: (inout %[[VAL_0:.*]] : i1, inout %[[VAL_1:.*]] : i1, inout %[[VAL_2:.*]] : i1, inout %[[VAL_3:.*]] : i1, inout %[[VAL_4:.*]] : i1) +// CHECK: [[PRB_CLK:%.+]] = llhd.prb %[[VAL_1]] +// CHECK: [[PRB_RST:%.+]] = llhd.prb %[[VAL_0]] // CHECK: llhd.process // CHECK: %[[ALLSET:.*]] = hw.constant true // CHECK: %[[VAL_5:.*]] = hw.constant false @@ -191,7 +195,7 @@ hw.module @loop(inout %in_i : i2) { // CHECK: %[[VAL_10:.*]] = llhd.prb %[[VAL_0]] : !hw.inout // CHECK: %[[VAL_11:.*]] = comb.icmp eq %[[VAL_9]], %[[VAL_5]] : i1 // CHECK: %[[VAL_12:.*]] = comb.icmp ne %[[VAL_10]], %[[VAL_5]] : i1 -// CHECK: llhd.wait (%[[VAL_1]], %[[VAL_0]] : !hw.inout, !hw.inout), ^[[BB3:.+]] +// CHECK: llhd.wait ([[PRB_CLK]], [[PRB_RST]] : i1, i1), ^[[BB3:.+]] // CHECK: ^[[BB3]]: // CHECK: %[[VAL_13:.*]] = llhd.prb %[[VAL_3]] : !hw.inout // CHECK: llhd.store %[[VAL_8]], %[[VAL_13]] : !llhd.ptr @@ -221,6 +225,8 @@ hw.module @loop(inout %in_i : i2) { // CHECK: cf.br ^[[BB1]] // CHECK: } hw.module @complicated(inout %rst_ni: i1, inout %clk_i: i1, inout %async_ack_i: i1, inout %ack_src_q: i1, inout %ack_q: i1) { + %prb_clk = llhd.prb %clk_i : !hw.inout + %prb_rst = llhd.prb %rst_ni : !hw.inout llhd.process { %allset = hw.constant 1 : i1 // TR: -1 @@ -234,7 +240,7 @@ hw.module @complicated(inout %rst_ni: i1, inout %clk_i: i1, inout %async_ack_i: // TR: 2 %clk_i_prb = llhd.prb %clk_i : !hw.inout %rst_ni_prb = llhd.prb %rst_ni : !hw.inout - llhd.wait (%clk_i, %rst_ni : !hw.inout, !hw.inout), ^check + llhd.wait (%prb_clk, %prb_rst : i1, i1), ^check ^check: // TR: 0 %2 = llhd.prb %ack_src_q : !hw.inout diff --git a/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir b/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir index 50cc527fce9c..2c259e61e333 100644 --- a/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir +++ b/test/Dialect/LLHD/Transforms/memoryToBlockArgument.mlir @@ -262,6 +262,7 @@ hw.module @multiple_store_one_block() { // CHECK-LABEL: @loop // CHECK-SAME: (inout %[[VAL_0:.*]] : i2) +// CHECK: [[PRB:%.+]] = llhd.prb %[[VAL_0]] // CHECK: cf.br ^bb1 // CHECK: ^bb1: // CHECK: %[[VAL_1:.*]] = hw.constant 0 : i32 @@ -271,7 +272,7 @@ hw.module @multiple_store_one_block() { // CHECK: %[[VAL_4:.*]] = comb.icmp ult %[[VAL_2]], %[[VAL_3]] : i32 // CHECK: cf.cond_br %[[VAL_4]], ^bb4, ^bb3 // CHECK: ^bb3: -// CHECK: llhd.wait (%[[VAL_0]] : !hw.inout), ^bb1 +// CHECK: llhd.wait ([[PRB]] : i2), ^bb1 // CHECK: ^bb4: // CHECK: %[[VAL_5:.*]] = hw.constant 0 : i2 // CHECK: %[[VAL_6:.*]] = hw.constant 1 : i32 @@ -279,6 +280,7 @@ hw.module @multiple_store_one_block() { // CHECK: cf.br ^bb2(%[[VAL_7]] : i32) // CHECK: } hw.module @loop(inout %in_i : i2) { + %prb = llhd.prb %in_i : !hw.inout llhd.process { cf.br ^body ^body: @@ -291,7 +293,7 @@ hw.module @loop(inout %in_i : i2) { %2 = comb.icmp ult %i_ld, %1 : i32 cf.cond_br %2, ^loop_continue, ^check ^check: - llhd.wait (%in_i : !hw.inout), ^body + llhd.wait (%prb : i2), ^body ^loop_continue: %3 = hw.constant 0 : i2 %5 = hw.constant 1 : i32 diff --git a/test/Dialect/LLHD/Transforms/processLowering.mlir b/test/Dialect/LLHD/Transforms/processLowering.mlir index c29335419820..8420c3cb80e4 100644 --- a/test/Dialect/LLHD/Transforms/processLowering.mlir +++ b/test/Dialect/LLHD/Transforms/processLowering.mlir @@ -23,31 +23,41 @@ hw.module @simpleWait() { // Check wait with observing probed signals // CHECK-LABEL: hw.module @prbAndWait hw.module @prbAndWait(inout %arg0 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout llhd.process { // CHECK-NEXT: %{{.*}} = llhd.prb // CHECK-NEXT: hw.output cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - llhd.wait (%arg0 : !hw.inout), ^bb1 + llhd.wait (%1 : i64), ^bb1 } } // Check wait with observing probed signals // CHECK-LABEL: hw.module @prbAndWaitMoreObserved hw.module @prbAndWaitMoreObserved(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb + %2 = llhd.prb %arg1 : !hw.inout llhd.process { // CHECK-NEXT: %{{.*}} = llhd.prb // CHECK-NEXT: hw.output cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1 + llhd.wait (%1, %2 : i64, i64), ^bb1 } } // CHECK-LABEL: hw.module @muxedSignal hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout + // CHECK-NEXT: %{{.*}} = llhd.prb + %2 = llhd.prb %arg1 : !hw.inout llhd.process { cf.br ^bb1 ^bb1: @@ -58,28 +68,32 @@ hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64) { %cond = hw.constant true %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout %0 = llhd.prb %sig : !hw.inout - llhd.wait (%arg0, %arg1 : !hw.inout, !hw.inout), ^bb1 + llhd.wait (%1, %2 : i64, i64), ^bb1 } } // CHECK-LABEL: hw.module @muxedSignal2 hw.module @muxedSignal2(inout %arg0 : i64, inout %arg1 : i64) { + // CHECK-NEXT: %{{.*}} = hw.constant + // CHECK-NEXT: %{{.*}} = comb.mux + // CHECK-NEXT: %{{.*}} = llhd.prb + %cond = hw.constant true + %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout + %0 = llhd.prb %sig : !hw.inout llhd.process { cf.br ^bb1 ^bb1: - // CHECK-NEXT: %{{.*}} = hw.constant - // CHECK-NEXT: %{{.*}} = comb.mux - // CHECK-NEXT: %{{.*}} = llhd.prb + // CHECK-NEXT: comb.and + %1 = comb.and %0, %0 : i64 // CHECK-NEXT: hw.output - %cond = hw.constant true - %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout - %0 = llhd.prb %sig : !hw.inout - llhd.wait (%sig : !hw.inout), ^bb1 + llhd.wait (%0 : i64), ^bb1 } } // CHECK-LABEL: hw.module @partialSignal hw.module @partialSignal(inout %arg0 : i64) { + // CHECK-NEXT: %{{.*}} = llhd.prb + %1 = llhd.prb %arg0 : !hw.inout llhd.process { cf.br ^bb1 ^bb1: @@ -90,6 +104,6 @@ hw.module @partialSignal(inout %arg0 : i64) { %c = hw.constant 16 : i6 %sig = llhd.sig.extract %arg0 from %c : (!hw.inout) -> !hw.inout %0 = llhd.prb %sig : !hw.inout - llhd.wait (%arg0 : !hw.inout), ^bb1 + llhd.wait (%1 : i64), ^bb1 } } diff --git a/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir b/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir index 7e2a6dfe6d83..0ecf43626355 100644 --- a/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir +++ b/test/Dialect/LLHD/Transforms/processLoweringErrors.mlir @@ -6,7 +6,7 @@ hw.module @prbAndWaitNotObserved(inout %arg0 : i64) { cf.br ^bb1 ^bb1: %0 = llhd.prb %arg0 : !hw.inout - // expected-error @+1 {{during process-lowering: the wait terminator is required to have all probed signals as arguments}} + // expected-error @+1 {{during process-lowering: the wait terminator is required to have values used in the process as arguments}} llhd.wait ^bb1 } } @@ -17,9 +17,10 @@ hw.module @prbAndWaitNotObserved(inout %arg0 : i64) { hw.module @blockArgumentsNotAllowed(inout %arg0 : i64) { // expected-error @+1 {{during process-lowering: the second block (containing the llhd.wait) is not allowed to have arguments}} llhd.process { - cf.br ^bb1(%arg0 : !hw.inout) - ^bb1(%a : !hw.inout): - llhd.wait ^bb1(%a : !hw.inout) + %prb = llhd.prb %arg0 : !hw.inout + cf.br ^bb1(%prb : i64) + ^bb1(%a : i64): + llhd.wait ^bb1(%a: i64) } } @@ -83,7 +84,7 @@ hw.module @muxedSignal(inout %arg0 : i64, inout %arg1 : i64, inout %arg2 : i1) { %cond = llhd.prb %arg2 : !hw.inout %sig = comb.mux %cond, %arg0, %arg1 : !hw.inout %0 = llhd.prb %sig : !hw.inout - // expected-error @+1 {{during process-lowering: the wait terminator is required to have all probed signals as arguments}} - llhd.wait (%arg0, %arg2 : !hw.inout, !hw.inout), ^bb1 + // expected-error @+1 {{during process-lowering: the wait terminator is required to have values used in the process as arguments}} + llhd.wait (%cond : i1), ^bb1 } } diff --git a/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir b/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir index d6bce05e2164..fb5c8d03946c 100644 --- a/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir +++ b/test/Dialect/LLHD/Transforms/temporal-code-motion.mlir @@ -9,36 +9,36 @@ hw.module @basic(in %cond: i1) { // CHECK: [[V1:%.+]] = llhd.constant_time <0ns, 0d, 1e> %0 = llhd.constant_time <0ns, 1d, 0e> %1 = llhd.constant_time <0ns, 0d, 1e> - // CHECK: [[V2:%.+]] = llhd.sig "a" - // CHECK: [[V3:%.+]] = llhd.sig "b" - // CHECK: [[V4:%.+]] = llhd.sig "c" - // CHECK: [[V5:%.+]] = llhd.sig "d" - // CHECK: [[V6:%.+]] = llhd.sig "e" - // CHECK: [[V7:%.+]] = llhd.sig "f" - // CHECK: [[V8:%.+]] = llhd.sig "g" - // CHECK: [[V9:%.+]] = llhd.sig "h" - // CHECK: [[V10:%.+]] = llhd.sig "i" - // CHECK: [[V11:%.+]] = llhd.sig "j" - // CHECK: [[V12:%.+]] = llhd.sig "k" - // CHECK: [[V13:%.+]] = llhd.sig "l" - // CHECK: [[V13_1:%.+]] = llhd.sig "m" - // CHECK: [[V13_2:%.+]] = llhd.sig "n" - // CHECK: [[V13_3:%.+]] = llhd.sig "o" - %2 = llhd.sig "a" %false : i1 - %3 = llhd.sig "b" %false : i1 - %4 = llhd.sig "c" %false : i1 - %5 = llhd.sig "d" %false : i1 - %6 = llhd.sig "e" %false : i1 - %7 = llhd.sig "f" %false : i1 - %8 = llhd.sig "g" %c0_i4 : i4 - %9 = llhd.sig "h" %c0_i4 : i4 - %10 = llhd.sig "i" %c0_i4 : i4 - %11 = llhd.sig "j" %false : i1 - %12 = llhd.sig "k" %c0_i5 : i5 - %13 = llhd.sig "l" %c0_i5 : i5 - %14 = llhd.sig "m" %c0_i5 : i5 - %15 = llhd.sig "n" %c0_i5 : i5 - %16 = llhd.sig "o" %c0_i5 : i5 + // CHECK: %c = llhd.sig + // CHECK: %d = llhd.sig + // CHECK: %e = llhd.sig + // CHECK: %f = llhd.sig + // CHECK: %g = llhd.sig + // CHECK: %h = llhd.sig + // CHECK: %k = llhd.sig + // CHECK: %l = llhd.sig + // CHECK: %m = llhd.sig + // CHECK: %n = llhd.sig + // CHECK: %o = llhd.sig + %c = llhd.sig %false : i1 + %d = llhd.sig %false : i1 + %e = llhd.sig %false : i1 + %f = llhd.sig %false : i1 + %g = llhd.sig %c0_i4 : i4 + %h = llhd.sig %c0_i4 : i4 + %k = llhd.sig %c0_i5 : i5 + %l = llhd.sig %c0_i5 : i5 + %m = llhd.sig %c0_i5 : i5 + %n = llhd.sig %c0_i5 : i5 + %o = llhd.sig %c0_i5 : i5 + + %prb_k = llhd.prb %k : !hw.inout + %prb_c = llhd.prb %c : !hw.inout + %prb_e = llhd.prb %e : !hw.inout + %prb_h = llhd.prb %h : !hw.inout + %prb_d = llhd.prb %d : !hw.inout + %prb_f = llhd.prb %f : !hw.inout + %prb_g = llhd.prb %g : !hw.inout // COM: Check that an auxillary block is created and all drives are moved to // COM: the exit block with the correct enable condition @@ -49,45 +49,45 @@ hw.module @basic(in %cond: i1) { // CHECK: ^[[BB1]]: ^bb1: // CHECK: llhd.wait ({{.*}}), ^[[BB2:.+]] - llhd.wait (%12, %4, %6, %9, %5, %7, %8 : !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout, !hw.inout), ^bb2 + llhd.wait (%prb_k, %prb_c, %prb_e, %prb_h, %prb_d, %prb_f, %prb_g : i5, i1, i1, i4, i1, i1, i4), ^bb2 // CHECK: ^[[BB2]]: ^bb2: - // CHECK: [[V14:%.+]] = llhd.prb [[V12]] - // CHECK: [[V15:%.+]] = llhd.prb [[V4]] - // CHECK: [[V16:%.+]] = llhd.prb [[V6]] - // CHECK: [[V17:%.+]] = llhd.prb [[V9]] + // CHECK: [[V14:%.+]] = llhd.prb %k + // CHECK: [[V15:%.+]] = llhd.prb %c + // CHECK: [[V16:%.+]] = llhd.prb %e + // CHECK: [[V17:%.+]] = llhd.prb %h // CHECK: [[V18:%.+]] = comb.concat %false{{.*}}, [[V17]] : i1, i4 - // CHECK: [[V19:%.+]] = llhd.prb [[V5]] - // CHECK: [[V20:%.+]] = llhd.prb [[V7]] - // CHECK: [[V21:%.+]] = llhd.prb [[V12]] - // CHECK: [[V22:%.+]] = llhd.prb [[V8]] + // CHECK: [[V19:%.+]] = llhd.prb %d + // CHECK: [[V20:%.+]] = llhd.prb %f + // CHECK: [[V21:%.+]] = llhd.prb %k + // CHECK: [[V22:%.+]] = llhd.prb %g // CHECK: [[V23:%.+]] = comb.concat %false{{.*}}, [[V22]] : i1, i4 // CHECK: [[V24:%.+]] = comb.sub [[V21]], [[V23]] : i5 - // CHECK: [[V25:%.+]] = llhd.prb [[V12]] - // CHECK: [[V26:%.+]] = llhd.prb [[V8]] + // CHECK: [[V25:%.+]] = llhd.prb %k + // CHECK: [[V26:%.+]] = llhd.prb %g // CHECK: [[V27:%.+]] = comb.concat %false{{.*}}, [[V26]] : i1, i4 // CHECK: [[V28:%.+]] = comb.add [[V25]], [[V27]] : i5 // CHECK: cf.cond_br [[V15]], ^[[BB3:.+]], ^[[BB4:.+]] - %25 = llhd.prb %12 : !hw.inout - llhd.drv %12, %25 after %1 : !hw.inout - %26 = llhd.prb %4 : !hw.inout - %27 = llhd.prb %6 : !hw.inout - %28 = llhd.prb %9 : !hw.inout + %25 = llhd.prb %k : !hw.inout + llhd.drv %k, %25 after %1 : !hw.inout + %26 = llhd.prb %c : !hw.inout + %27 = llhd.prb %e : !hw.inout + %28 = llhd.prb %h : !hw.inout %29 = comb.concat %false, %28 : i1, i4 - %30 = llhd.prb %5 : !hw.inout - %31 = llhd.prb %7 : !hw.inout - %32 = llhd.prb %12 : !hw.inout - %33 = llhd.prb %8 : !hw.inout + %30 = llhd.prb %d : !hw.inout + %31 = llhd.prb %f : !hw.inout + %32 = llhd.prb %k : !hw.inout + %33 = llhd.prb %g : !hw.inout %34 = comb.concat %false, %33 : i1, i4 %35 = comb.sub %32, %34 : i5 - %36 = llhd.prb %12 : !hw.inout - %37 = llhd.prb %8 : !hw.inout + %36 = llhd.prb %k : !hw.inout + %37 = llhd.prb %g : !hw.inout %38 = comb.concat %false, %37 : i1, i4 %39 = comb.add %36, %38 : i5 cf.cond_br %26, ^bb3, ^bb4 // CHECK: ^[[BB3]]: ^bb3: - llhd.drv %13, %c0_i5 after %1 if %cond : !hw.inout + llhd.drv %l, %c0_i5 after %1 if %cond : !hw.inout // CHECK: cf.br ^[[BB10:.+]] cf.br ^bb1 // CHECK: ^[[BB4]]: @@ -96,7 +96,7 @@ hw.module @basic(in %cond: i1) { cf.cond_br %27, ^bb5, ^bb6 // CHECK: ^[[BB5]]: ^bb5: - llhd.drv %14, %29 after %1 : !hw.inout + llhd.drv %m, %29 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB6]]: @@ -109,28 +109,28 @@ hw.module @basic(in %cond: i1) { cf.cond_br %31, ^bb8, ^bb9 // CHECK: ^[[BB8]]: ^bb8: - llhd.drv %15, %35 after %1 : !hw.inout + llhd.drv %n, %35 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB9]]: ^bb9: - llhd.drv %16, %39 after %1 : !hw.inout + llhd.drv %o, %39 after %1 : !hw.inout // CHECK: cf.br ^[[BB10]] cf.br ^bb1 // CHECK: ^[[BB10]]: - // CHECK: llhd.drv [[V12]], [[V14]] after [[V1]] if %true{{.*}} : !hw.inout + // CHECK: llhd.drv %k, [[V14]] after [[V1]] if %true{{.*}} : !hw.inout // CHECK: [[V29:%.+]] = comb.and %true{{.*}}, [[V15]] : i1 // CHECK: [[V30:%.+]] = comb.or %false{{.*}}, [[V29]] : i1 // CHECK: [[V31:%.+]] = comb.and %cond, [[V30]] : i1 - // CHECK: llhd.drv [[V13]], %c0_i5 after [[V1]] if [[V31]] : !hw.inout + // CHECK: llhd.drv %l, %c0_i5 after [[V1]] if [[V31]] : !hw.inout // CHECK: [[V33:%.+]] = comb.xor [[V15]], %true{{.*}} : i1 // CHECK: [[V34:%.+]] = comb.and %true{{.*}}, [[V33]] : i1 // CHECK: [[V35:%.+]] = comb.or %false{{.*}}, [[V34]] : i1 // CHECK: [[V36:%.+]] = comb.and [[V35]], [[V16]] : i1 // CHECK: [[V37:%.+]] = comb.or %false{{.*}}, [[V36]] : i1 - // CHECK: llhd.drv [[V13_1]], [[V18]] after [[V1]] if [[V37]] : !hw.inout + // CHECK: llhd.drv %m, [[V18]] after [[V1]] if [[V37]] : !hw.inout // CHECK: [[V40:%.+]] = comb.xor [[V16]], %true{{.*}} : i1 // CHECK: [[V41:%.+]] = comb.and [[V35]], [[V40]] : i1 @@ -139,12 +139,12 @@ hw.module @basic(in %cond: i1) { // CHECK: [[V44:%.+]] = comb.or %false{{.*}}, [[V43]] : i1 // CHECK: [[V45:%.+]] = comb.and [[V44]], [[V20]] : i1 // CHECK: [[V46:%.+]] = comb.or %false{{.*}}, [[V45]] : i1 - // CHECK: llhd.drv [[V13_2]], [[V24]] after [[V1]] if [[V46]] : !hw.inout + // CHECK: llhd.drv %n, [[V24]] after [[V1]] if [[V46]] : !hw.inout // CHECK: [[V49:%.+]] = comb.xor [[V20]], %true{{.*}} : i1 // CHECK: [[V50:%.+]] = comb.and [[V44]], [[V49]] : i1 // CHECK: [[V51:%.+]] = comb.or %false{{.*}}, [[V50]] : i1 - // CHECK: llhd.drv [[V13_3]], [[V28]] after [[V1]] if [[V51]] : !hw.inout + // CHECK: llhd.drv %o, [[V28]] after [[V1]] if [[V51]] : !hw.inout // CHECK: cf.br ^[[BB1]] } @@ -154,27 +154,27 @@ hw.module @basic(in %cond: i1) { ^bb1: llhd.wait ^bb2 ^bb2: - // CHECK: [[V20:%.+]] = llhd.prb [[V13]] - // CHECK-NEXT: [[V21:%.+]] = llhd.prb [[V13_1]] - %20 = llhd.prb %13 : !hw.inout - %21 = llhd.prb %14 : !hw.inout + // CHECK: [[V20:%.+]] = llhd.prb %l + // CHECK-NEXT: [[V21:%.+]] = llhd.prb %m + %20 = llhd.prb %l : !hw.inout + %21 = llhd.prb %m : !hw.inout - // CHECK-NEXT: llhd.drv [[V12]], [[V21]] after [[V1]] : !hw.inout - llhd.drv %12, %20 after %1 : !hw.inout - llhd.drv %12, %21 after %1 : !hw.inout + // CHECK-NEXT: llhd.drv %k, [[V21]] after [[V1]] : !hw.inout + llhd.drv %k, %20 after %1 : !hw.inout + llhd.drv %k, %21 after %1 : !hw.inout // CHECK-NEXT: [[V22:%.+]] = comb.mux %cond, [[V21]], [[V20]] - // CHECK-NEXT: llhd.drv [[V13]], [[V22]] after [[V1]] : !hw.inout - llhd.drv %13, %20 after %1 : !hw.inout - llhd.drv %13, %21 after %1 if %cond : !hw.inout + // CHECK-NEXT: llhd.drv %l, [[V22]] after [[V1]] : !hw.inout + llhd.drv %l, %20 after %1 : !hw.inout + llhd.drv %l, %21 after %1 if %cond : !hw.inout // CHECK-NEXT: [[V23:%.+]] = comb.xor %cond, %true // CHECK-NEXT: [[V24:%.+]] = comb.mux %cond, [[V21]], [[V20]] // CHECK-NEXT: [[V25:%.+]] = comb.or %cond, [[V23]] - // CHECK-NEXT: llhd.drv [[V13_1]], [[V24]] after [[V1]] if [[V25]] : !hw.inout + // CHECK-NEXT: llhd.drv %m, [[V24]] after [[V1]] if [[V25]] : !hw.inout %22 = comb.xor %cond, %true : i1 - llhd.drv %14, %20 after %1 if %22 : !hw.inout - llhd.drv %14, %21 after %1 if %cond : !hw.inout + llhd.drv %m, %20 after %1 if %22 : !hw.inout + llhd.drv %m, %21 after %1 if %cond : !hw.inout // CHECK-NEXT: cf.br cf.br ^bb1 diff --git a/test/Dialect/Moore/attrs-error.mlir b/test/Dialect/Moore/attrs-error.mlir new file mode 100644 index 000000000000..fdb90e70eaa0 --- /dev/null +++ b/test/Dialect/Moore/attrs-error.mlir @@ -0,0 +1,9 @@ +// RUN: circt-opt --verify-diagnostics --split-input-file %s + +// expected-error @below {{integer literal requires at least 6 bits, but attribute specifies only 3}} +hw.constant false {foo = #moore.fvint<42 : 3>} + +// ----- + +// expected-error @below {{integer literal requires at least 1 bits, but attribute specifies only 0}} +hw.constant false {foo = #moore.fvint<1 : 0>} diff --git a/test/Dialect/Moore/attrs.mlir b/test/Dialect/Moore/attrs.mlir new file mode 100644 index 000000000000..5978e118ecd4 --- /dev/null +++ b/test/Dialect/Moore/attrs.mlir @@ -0,0 +1,14 @@ +// RUN: circt-opt %s | circt-opt | FileCheck %s + +// CHECK: #moore.fvint<0 : 0> +hw.constant false {foo = #moore.fvint<0 : 0>} +// CHECK: #moore.fvint<42 : 32> +hw.constant false {foo = #moore.fvint<42 : 32>} +// CHECK: #moore.fvint<-42 : 32> +hw.constant false {foo = #moore.fvint<-42 : 32>} +// CHECK: #moore.fvint<1234567890123456789012345678901234567890 : 131> +hw.constant false {foo = #moore.fvint<1234567890123456789012345678901234567890 : 131>} +// CHECK: #moore.fvint +hw.constant false {foo = #moore.fvint} +// CHECK: #moore.fvint +hw.constant false {foo = #moore.fvint} diff --git a/test/Dialect/Moore/basic.mlir b/test/Dialect/Moore/basic.mlir index e6f372df8167..7b589d3582e3 100644 --- a/test/Dialect/Moore/basic.mlir +++ b/test/Dialect/Moore/basic.mlir @@ -77,12 +77,12 @@ moore.module @Module() { // CHECK: moore.procedure always_comb { // CHECK: moore.procedure always_latch { // CHECK: moore.procedure always_ff { - moore.procedure initial {} - moore.procedure final {} - moore.procedure always {} - moore.procedure always_comb {} - moore.procedure always_latch {} - moore.procedure always_ff {} + moore.procedure initial { moore.return } + moore.procedure final { moore.return } + moore.procedure always { moore.return } + moore.procedure always_comb { moore.return } + moore.procedure always_latch { moore.return } + moore.procedure always_ff { moore.return } // CHECK: %[[TMP1:.+]] = moore.read %v2 // CHECK: moore.assign %v1, %[[TMP1]] : i1 @@ -100,6 +100,7 @@ moore.module @Module() { moore.nonblocking_assign %v1, %4 : i1 // CHECK: %a = moore.variable : %a = moore.variable : + moore.return } } @@ -139,12 +140,26 @@ moore.module @Expressions( // CHECK-SAME: in [[REF_STRUCT1:%.+]] : !moore.ref> in %refStruct1: !moore.ref> ) { + // CHECK: moore.constant 0 : i0 + moore.constant 0 : i0 + // CHECK: moore.constant 0 : i1 + moore.constant 0 : i1 + // CHECK: moore.constant 1 : i1 + moore.constant 1 : i1 // CHECK: moore.constant 0 : i32 moore.constant 0 : i32 // CHECK: moore.constant -2 : i2 moore.constant 2 : i2 // CHECK: moore.constant -2 : i2 moore.constant -2 : i2 + // CHECK: moore.constant 1311768467463790320 : i64 + moore.constant h123456789ABCDEF0 : i64 + // CHECK: moore.constant h123456789ABCDEF0XZ : l72 + moore.constant h123456789ABCDEF0XZ : l72 + // CHECK: moore.constant 10 : i8 + moore.constant b1010 : i8 + // CHECK: moore.constant b1010XZ : l8 + moore.constant b1010XZ : l8 // CHECK: moore.conversion [[A]] : !moore.i32 -> !moore.l32 moore.conversion %a : !moore.i32 -> !moore.l32 @@ -275,6 +290,9 @@ moore.module @Expressions( moore.yield %b : i32 } + // CHECK: moore.array_create [[A]], [[B]] : !moore.i32, !moore.i32 -> array<2 x i32> + moore.array_create %a, %b : !moore.i32, !moore.i32 -> array<2 x i32> + // CHECK: moore.struct_create [[A]], [[B]] : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> moore.struct_create %a, %b : !moore.i32, !moore.i32 -> struct<{a: i32, b: i32}> // CHECK: moore.struct_extract [[STRUCT1]], "a" : struct<{a: i32, b: i32}> -> i32 @@ -292,3 +310,22 @@ moore.module @GraphRegion() { %1 = moore.add %0, %0 : i32 %0 = moore.constant 0 : i32 } + +// CHECK-LABEL: func.func @WaitEvent +func.func @WaitEvent(%arg0: !moore.i1, %arg1: !moore.i1) { + // CHECK: moore.wait_event { + moore.wait_event { + // CHECK: moore.detect_event any %arg0 : i1 + moore.detect_event any %arg0 : i1 + // CHECK: moore.detect_event posedge %arg0 : i1 + moore.detect_event posedge %arg0 : i1 + // CHECK: moore.detect_event negedge %arg0 : i1 + moore.detect_event negedge %arg0 : i1 + // CHECK: moore.detect_event edge %arg0 : i1 + moore.detect_event edge %arg0 : i1 + // CHECK: moore.detect_event any %arg0 if %arg1 : i1 + moore.detect_event any %arg0 if %arg1 : i1 + } + // CHECK: } + return +} diff --git a/test/Dialect/Moore/canonicalizers.mlir b/test/Dialect/Moore/canonicalizers.mlir index ad0e90639e09..590dbbfbe581 100644 --- a/test/Dialect/Moore/canonicalizers.mlir +++ b/test/Dialect/Moore/canonicalizers.mlir @@ -10,32 +10,169 @@ func.func @Casts(%arg0: !moore.i1) -> (!moore.i1, !moore.i1) { return %0, %1 : !moore.i1, !moore.i1 } -// CHECK-LABEL: moore.module @SingleAssign -moore.module @SingleAssign() { +// CHECK-LABEL: moore.module @OptimizeUniquelyAssignedVars +moore.module @OptimizeUniquelyAssignedVars(in %u: !moore.i42, in %v: !moore.i42, in %w: !moore.i42) { + // Unique continuous assignments to variables should remove the `ref` + // indirection and instead directly propagate the assigned value to readers. + // CHECK-NOT: moore.assign // CHECK-NOT: moore.variable - // CHECK: %a = moore.assigned_variable %0 : - %a = moore.variable : - // CHECK: %0 = moore.constant 32 : i32 - %0 = moore.constant 32 : i32 - // CHECK: moore.assign %a, %0 : i32 - moore.assign %a, %0 : i32 - moore.output + // CHECK: %a = moore.assigned_variable %u : i42 + // CHECK: dbg.variable "a", %a : !moore.i42 + moore.assign %a, %u : i42 + %a = moore.variable : + %3 = moore.read %a : + dbg.variable "a", %3 : !moore.i42 + + // Continuous assignments to variables should override the initial value. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.constant 9001 + // CHECK-NOT: moore.variable + // CHECK: %b = moore.assigned_variable %v : i42 + // CHECK: dbg.variable "b", %b : !moore.i42 + moore.assign %b, %v : i42 + %0 = moore.constant 9001 : i42 + %b = moore.variable %0 : + %4 = moore.read %b : + dbg.variable "b", %4 : !moore.i42 + + // Unique continuous assignments to nets should remove the `ref` + // indirection and instead directly propagate the assigned value to readers. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.net wire + // CHECK: %c = moore.assigned_variable %w : i42 + // CHECK: dbg.variable "c", %c : !moore.i42 + moore.assign %c, %w : i42 + %c = moore.net wire : + %5 = moore.read %c : + dbg.variable "c", %5 : !moore.i42 + + // Variables without names should not create an `assigned_variable`. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.variable + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "d", %u : !moore.i42 + moore.assign %1, %u : i42 + %1 = moore.variable : + %6 = moore.read %1 : + dbg.variable "d", %6 : !moore.i42 + + // Nets without names should not create an `assigned_variable`. + // CHECK-NOT: moore.assign + // CHECK-NOT: moore.net wire + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "e", %v : !moore.i42 + moore.assign %2, %v : i42 + %2 = moore.net wire : + %7 = moore.read %2 : + dbg.variable "e", %7 : !moore.i42 } -// CHECK-LABEL: moore.module @MultiAssign -moore.module @MultiAssign() { +// CHECK-LABEL: moore.module @DontOptimizeVarsWithMultipleAssigns +moore.module @DontOptimizeVarsWithMultipleAssigns() { + %0 = moore.constant 1337 : i42 + %1 = moore.constant 9001 : i42 + + // CHECK: %a = moore.variable + // CHECK: moore.assign %a + // CHECK: moore.assign %a + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: dbg.variable "a", [[TMP]] + %a = moore.variable : + moore.assign %a, %0 : i42 + moore.assign %a, %1 : i42 + %2 = moore.read %a : + dbg.variable "a", %2 : !moore.i42 + + // CHECK: %b = moore.net + // CHECK: moore.assign %b + // CHECK: moore.assign %b + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: dbg.variable "b", [[TMP]] + %b = moore.net wire : + moore.assign %b, %0 : i42 + moore.assign %b, %1 : i42 + %3 = moore.read %b : + dbg.variable "b", %3 : !moore.i42 + + // CHECK: %c = moore.net + // CHECK: moore.assign %c + // CHECK: [[TMP:%.+]] = moore.read %c + // CHECK: dbg.variable "c", [[TMP]] + %c = moore.net wire %0 : + moore.assign %c, %1 : i42 + %4 = moore.read %c : + dbg.variable "c", %4 : !moore.i42 +} + +// CHECK-LABEL: moore.module @DontOptimizeVarsWithNonReadUses +moore.module @DontOptimizeVarsWithNonReadUses(in %u: !moore.i42, in %v: !moore.i42) { + // CHECK: %a = moore.variable + // CHECK: moore.assign %a, %u + // CHECK: func.call @useRef(%a) + // CHECK: [[TMP:%.+]] = moore.read %a + // CHECK: dbg.variable "a", [[TMP]] + %a = moore.variable : + moore.assign %a, %u : i42 + func.call @useRef(%a) : (!moore.ref) -> () + %2 = moore.read %a : + dbg.variable "a", %2 : !moore.i42 + + // CHECK: %b = moore.net wire %u + // CHECK: moore.assign %b, %v + // CHECK: func.call @useRef(%b) + // CHECK: [[TMP:%.+]] = moore.read %b + // CHECK: dbg.variable "b", [[TMP]] + %b = moore.net wire %u : + moore.assign %b, %v : i42 + func.call @useRef(%b) : (!moore.ref) -> () + %3 = moore.read %b : + dbg.variable "b", %3 : !moore.i42 + + // Unique continuous assigns should be folded into net definitions even if the + // net has non-read uses. + // CHECK: %c = moore.net wire %u + // CHECK-NOT: moore.assign %c + // CHECK: func.call @useRef(%c) + %c = moore.net wire : + moore.assign %c, %u : i42 + func.call @useRef(%c) : (!moore.ref) -> () +} + +func.func private @useRef(%arg0: !moore.ref) + +// CHECK-LABEL: moore.module @DropRedundantVars +moore.module @DropRedundantVars(in %a : !moore.i42, out b : !moore.i42, out c : !moore.i42) { + // CHECK: [[C9001:%.+]] = moore.constant 9001 : i42 + %c9001_i42 = moore.constant 9001 : i42 + + // Remove variables that shadow an input port of the same name. // CHECK-NOT: moore.assigned_variable - // CHECK: %a = moore.variable : - %a = moore.variable : - // CHECK: %0 = moore.constant 32 : i32 - %0 = moore.constant 32 : i32 - // CHECK: moore.assign %a, %0 : i32 - moore.assign %a, %0 : i32 - // CHECK: %1 = moore.constant 64 : i32 - %1 = moore.constant 64 : i32 - // CHECK: moore.assign %a, %1 : i32 - moore.assign %a, %1 : i32 - moore.output + // CHECK: dbg.variable "a", %a + %0 = moore.assigned_variable name "a" %a : i42 + dbg.variable "a", %0 : !moore.i42 + + // Variables that shadow an input port of a different name should remain. + // CHECK: %a2 = moore.assigned_variable + // CHECK: dbg.variable "a2", %a + %a2 = moore.assigned_variable %a : i42 + dbg.variable "a2", %a2 : !moore.i42 + + // Chained variables with the same name should be reduced to just one. + // CHECK: %v = moore.assigned_variable %a + // CHECK-NOT: moore.assigned_variable + // CHECK: dbg.variable "v", %v + %1 = moore.assigned_variable name "v" %a : i42 + %2 = moore.assigned_variable name "v" %1 : i42 + dbg.variable "v", %2 : !moore.i42 + + // Remove variables that shadow an output port of the same name. Variables + // that shadow an output port of a different name should remain. + // CHECK-NOT: %b = moore.assigned_variable + // CHECK: %w = moore.assigned_variable [[C9001]] + // CHECK: moore.output [[C9001]], %w + %b = moore.assigned_variable %c9001_i42 : i42 + %w = moore.assigned_variable %c9001_i42 : i42 + moore.output %b, %w : !moore.i42, !moore.i42 } // CHECK-LABEL: func.func @StructExtractFold1 @@ -107,3 +244,64 @@ func.func @StructInjectFold3(%arg0: !moore.struct<{a: i32, b: i32}>) -> (!moore. %3 = moore.struct_inject %2, "a", %1 : struct<{a: i32, b: i32}>, i32 return %3 : !moore.struct<{a: i32, b: i32}> } + +// CHECK-LABEL: func.func @ConvertConstantTwoToFourValued +func.func @ConvertConstantTwoToFourValued() -> (!moore.l42) { + // CHECK: [[TMP:%.+]] = moore.constant 9001 : l42 + // CHECK-NOT: moore.conversion + // CHECK: return [[TMP]] : + %0 = moore.constant 9001 : i42 + %1 = moore.conversion %0 : !moore.i42 -> !moore.l42 + return %1 : !moore.l42 +} + +// CHECK-LABEL: func.func @ConvertConstantFourToTwoValued +func.func @ConvertConstantFourToTwoValued() -> (!moore.i42) { + // CHECK: [[TMP:%.+]] = moore.constant 8 : i42 + // CHECK-NOT: moore.conversion + // CHECK: return [[TMP]] : + %0 = moore.constant b1XZ0 : l42 + %1 = moore.conversion %0 : !moore.l42 -> !moore.i42 + return %1 : !moore.i42 +} + +// CHECK-LABEL: func @Pow +func.func @Pow(%arg0 : !moore.l32) -> (!moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32) { + // CHECK-NEXT: [[V0:%.+]] = moore.constant 1 : l32 + // CHECK-NEXT: [[V1:%.+]] = moore.constant 0 : l32 + %0 = moore.constant 0 : l32 + %1 = moore.constant 1 : l32 + %2 = moore.constant 2 : l32 + + %3 = moore.pows %1, %arg0 : l32 + %4 = moore.pows %arg0, %0 : l32 + + %5 = moore.powu %1, %arg0 : l32 + %6 = moore.powu %arg0, %0 : l32 + + // CHECK-NEXT: [[V2:%.+]] = moore.shl [[V0]], %arg0 + // CHECK-NEXT: [[V3:%.+]] = moore.slt %arg0, [[V1]] + // CHECK-NEXT: [[V4:%.+]] = moore.conditional [[V3]] + // CHECK-NEXT: moore.yield [[V1]] + // CHECK-NEXT: } { + // CHECK-NEXT: moore.yield [[V2]] + // CHECK-NEXT: } + %7 = moore.pows %2, %arg0 : l32 + + // CHECK-NEXT: [[V5:%.+]] = moore.shl [[V0]], %arg0 + %8 = moore.powu %2, %arg0 : l32 + + // CHECK-NEXT: return [[V0]], [[V0]], [[V0]], [[V0]], [[V4]], [[V5]] : + return %3, %4, %5, %6, %7, %8 : !moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32, !moore.l32 +} + +// CHECK-LABEL: func.func @MoveInitialOutOfSSAVariable +func.func @MoveInitialOutOfSSAVariable() { + // CHECK: [[TMP:%.+]] = moore.constant 9001 + %0 = moore.constant 9001 : i42 + // CHECK: [[VAR:%.+]] = moore.variable : + // CHECK-NEXT: moore.blocking_assign [[VAR]], [[TMP]] + %1 = moore.variable %0 : + func.call @useRef(%1) : (!moore.ref) -> () + return +} diff --git a/test/Dialect/Moore/errors.mlir b/test/Dialect/Moore/errors.mlir index de561d2dcb07..fa8b26bacd4c 100644 --- a/test/Dialect/Moore/errors.mlir +++ b/test/Dialect/Moore/errors.mlir @@ -53,18 +53,23 @@ moore.module @Foo(out a: !moore.string) { // ----- -// expected-error @below {{constant out of range for result type '!moore.i1'}} +// expected-error @below {{value requires 6 bits, but result type only has 1}} moore.constant 42 : !moore.i1 // ----- -// expected-error @below {{constant out of range for result type '!moore.i1'}} +// expected-error @below {{value requires 2 bits, but result type only has 1}} moore.constant -2 : !moore.i1 // ----- +// expected-error @below {{value contains X or Z bits, but result type '!moore.i4' only allows two-valued bits}} +moore.constant b10XZ : !moore.i4 + +// ----- + // expected-error @below {{attribute width 9 does not match return type's width 8}} -"moore.constant" () {value = 42 : i9} : () -> !moore.i8 +"moore.constant" () {value = #moore.fvint<42 : 9>} : () -> !moore.i8 // ----- @@ -74,7 +79,7 @@ moore.yield %0 : i8 // ----- -%0 = moore.constant true : i1 +%0 = moore.constant 1 : i1 %1 = moore.constant 42 : i8 %2 = moore.constant 42 : i32 @@ -87,7 +92,7 @@ moore.conditional %0 : i1 -> i32 { // ----- -%0 = moore.constant true : i1 +%0 = moore.constant 1 : i1 %1 = moore.constant 42 : i32 %2 = moore.constant 42 : i8 diff --git a/test/Dialect/Moore/lower-concatref.mlir b/test/Dialect/Moore/lower-concatref.mlir index 391fad9efc52..2183ccd8a580 100644 --- a/test/Dialect/Moore/lower-concatref.mlir +++ b/test/Dialect/Moore/lower-concatref.mlir @@ -43,6 +43,7 @@ // CHECK: moore.nonblocking_assign %v, %[[TMP2]] : l42 moore.nonblocking_assign %7, %8 : l9002 } + moore.return } moore.output } @@ -73,6 +74,7 @@ moore.module @Nested() { // CHECK: %[[TMP5:.+]] = moore.extract %[[TMP2]] from 0 : i96 -> i32 // CHECK: moore.blocking_assign %y, %[[TMP5]] : i32 moore.blocking_assign %3, %6 : i96 + moore.return } moore.output } diff --git a/test/Dialect/Moore/mem2reg.mlir b/test/Dialect/Moore/mem2reg.mlir index b5246e6c556b..4c48d971d269 100644 --- a/test/Dialect/Moore/mem2reg.mlir +++ b/test/Dialect/Moore/mem2reg.mlir @@ -1,33 +1,48 @@ -// RUN: circt-opt --mem2reg %s | FileCheck %s +// RUN: circt-opt --mem2reg --verify-diagnostics %s | FileCheck %s -// CHECK-LABEL: moore.module @LocalVar() { -moore.module @LocalVar() { - // CHECK: %x = moore.variable : - // CHECK: %y = moore.variable : - // CHECK: %z = moore.variable : - %x = moore.variable : - %y = moore.variable : - %z = moore.variable : - moore.procedure always_comb { - // CHECK: %0 = moore.read %x - // CHECK: %1 = moore.constant 1 : i32 - // CHECK: %2 = moore.add %0, %1 : i32 - // CHECK: moore.blocking_assign %z, %2 : i32 - // CHECK: %3 = moore.constant 1 : i32 - // CHECK: %4 = moore.add %2, %3 : i32 - // CHECK: moore.blocking_assign %y, %4 : i32 - %a = moore.variable : - %0 = moore.read %x : - %1 = moore.constant 1 : i32 - %2 = moore.add %0, %1 : i32 - moore.blocking_assign %a, %2 : i32 - %3 = moore.read %a : - moore.blocking_assign %z, %3 : i32 - %4 = moore.read %a : - %5 = moore.constant 1 : i32 - %6 = moore.add %4, %5 : i32 - moore.blocking_assign %a, %6 : i32 - %7 = moore.read %a : - moore.blocking_assign %y, %7 : i32 - } +// CHECK-LABEL: func.func @Basic( +func.func @Basic() -> !moore.i42 { + // CHECK: [[TMP:%.*]] = moore.constant 9001 : i42 + // CHECK-NOT: = moore.variable + // CHECK-NOT: = moore.blocking_assign + // CHECK-NOT: = moore.read + %0 = moore.constant 9001 : i42 + %1 = moore.variable : + moore.blocking_assign %1, %0 : i42 + %2 = moore.read %1 : + // CHECK: return [[TMP]] : !moore.i42 + return %2 : !moore.i42 +} + +// CHECK-LABEL: func.func @ControlFlow( +func.func @ControlFlow(%arg0: i1, %arg1: !moore.l8) -> !moore.l8 { + // CHECK-NOT: moore.variable + // CHECK: [[DEFAULT:%.+]] = moore.constant hXX : l8 + %0 = moore.variable : + // CHECK: cf.cond_br %arg0, ^[[BB1:.+]], ^[[BB2:.+]]([[DEFAULT]] : !moore.l8) + cf.cond_br %arg0, ^bb1, ^bb2 +^bb1: + // CHECK-NOT: moore.blocking_assign + // CHECK: cf.br ^[[BB2]](%arg1 : !moore.l8) + moore.blocking_assign %0, %arg1 : l8 + cf.br ^bb2 +^bb2: + // CHECK: ^[[BB2]]([[TMP:%.+]]: !moore.l8): + // CHECK-NOT: moore.read + // CHECK: return [[TMP]] + %1 = moore.read %0 : + return %1 : !moore.l8 +} + +// CHECK-LABEL: func.func @InitialValueDoesNotDominateDefault( +func.func @InitialValueDoesNotDominateDefault() { + cf.br ^bb1 +^bb1: + %0 = moore.constant 0 : i32 + %1 = moore.variable %0 : + cf.br ^bb2 +^bb2: + %2 = moore.read %1 : + moore.blocking_assign %1, %2 : i32 + cf.br ^bb1 } diff --git a/test/Dialect/Moore/simplify-procedures.mlir b/test/Dialect/Moore/simplify-procedures.mlir index f829549eb099..1e8f4c7102ed 100644 --- a/test/Dialect/Moore/simplify-procedures.mlir +++ b/test/Dialect/Moore/simplify-procedures.mlir @@ -9,7 +9,8 @@ moore.module @Foo() { // CHECK: moore.procedure always_comb moore.procedure always_comb { // CHECK: [[TMP:%.+]] = moore.read %a - // CHECK: [[LOCAL_A:%.+]] = moore.variable [[TMP]] + // CHECK: [[LOCAL_A:%.+]] = moore.variable + // CHECK: moore.blocking_assign [[LOCAL_A]], [[TMP]] // CHECK: [[C1:%.+]] = moore.constant 1 // CHECK: moore.blocking_assign [[LOCAL_A]], [[C1]] @@ -33,16 +34,21 @@ moore.module @Foo() { %3 = moore.constant 1 : i32 %4 = moore.add %2, %3 : i32 moore.blocking_assign %a, %4 : i32 + + moore.return } // CHECK: moore.procedure always_comb moore.procedure always_comb { // CHECK: [[TMP:%.+]] = moore.read %a - // CHECK: [[LOCAL_A:%.+]] = moore.variable %0 + // CHECK: [[LOCAL_A:%.+]] = moore.variable + // CHECK: moore.blocking_assign [[LOCAL_A]], [[TMP]] // CHECK: [[TMP:%.+]] = moore.read [[LOCAL_A]] // CHECK: moore.blocking_assign %y, [[TMP]] %0 = moore.read %a : moore.blocking_assign %y, %0 : i32 + + moore.return } } diff --git a/test/Dialect/OM/round-trip.mlir b/test/Dialect/OM/round-trip.mlir index f00cc9c9f732..738e895e434c 100644 --- a/test/Dialect/OM/round-trip.mlir +++ b/test/Dialect/OM/round-trip.mlir @@ -158,6 +158,22 @@ om.class @ListCreate() { om.class.field @list_field, %list : !om.list> } +// CHECK-LABEL: @ListConcat +om.class @ListConcat() { + %0 = om.constant #om.integer<0 : i8> : !om.integer + %1 = om.constant #om.integer<1 : i8> : !om.integer + %2 = om.constant #om.integer<2 : i8> : !om.integer + + // CHECK: [[L0:%.+]] = om.list_create %0, %1 + %l0 = om.list_create %0, %1 : !om.integer + + // CHECK: [[L1:%.+]] = om.list_create %2 + %l1 = om.list_create %2 : !om.integer + + // CHECK: om.list_concat [[L0]], [[L1]] + %concat = om.list_concat %l0, %l1 : !om.list +} + // CHECK-LABEL: @Integer om.class @IntegerConstant() { // CHECK: %[[const1:.+]] = om.constant #om.integer<36755551979133953793 : i67> : !om.integer diff --git a/test/Dialect/Sim/lower-dpi.mlir b/test/Dialect/Sim/lower-dpi.mlir new file mode 100644 index 000000000000..dc9211f126dd --- /dev/null +++ b/test/Dialect/Sim/lower-dpi.mlir @@ -0,0 +1,50 @@ +// RUN: circt-opt --sim-lower-dpi-func %s | FileCheck %s + +sim.func.dpi @foo(out arg0: i32, in %arg1: i32, out arg2: i32) +// CHECK-LABEL: func.func private @foo(!llvm.ptr, i32, !llvm.ptr) +// CHECK-LABEL: func.func @foo_wrapper(%arg0: i32) -> (i32, i32) { +// CHECK-NEXT: %0 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: %2 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %3 = llvm.alloca %2 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: call @foo(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +// CHECK-NEXT: %4 = llvm.load %1 : !llvm.ptr -> i32 +// CHECK-NEXT: %5 = llvm.load %3 : !llvm.ptr -> i32 +// CHECK-NEXT: return %4, %5 : i32, i32 +// CHECK-NEXT: } + +// CHECK-LABEL: func.func @bar_wrapper(%arg0: i32) -> (i32, i32) { +// CHECK-NEXT: %0 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %1 = llvm.alloca %0 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: %2 = llvm.mlir.constant(1 : i64) : i64 +// CHECK-NEXT: %3 = llvm.alloca %2 x i32 : (i64) -> !llvm.ptr +// CHECK-NEXT: call @bar_c_name(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +// CHECK-NEXT: %4 = llvm.load %1 : !llvm.ptr -> i32 +// CHECK-NEXT: %5 = llvm.load %3 : !llvm.ptr -> i32 +// CHECK-NEXT: return %4, %5 : i32, i32 +// CHECK-NEXT: } +// CHECK-LABEL: func.func @bar_c_name + +sim.func.dpi @bar(out arg0: i32, in %arg1: i32, out arg2: i32) attributes {verilogName="bar_c_name"} +func.func @bar_c_name(%arg0: !llvm.ptr, %arg1: i32, %arg2: !llvm.ptr) { + func.return +} + +// CHECK-LABEL: func.func private @baz_c_name(!llvm.ptr, i32, !llvm.ptr) +// CHECK-LABEL: func.func @baz_wrapper(%arg0: i32) -> (i32, i32) +// CHECK: call @baz_c_name(%1, %arg0, %3) : (!llvm.ptr, i32, !llvm.ptr) -> () +sim.func.dpi @baz(out arg0: i32, in %arg1: i32, out arg2: i32) attributes {verilogName="baz_c_name"} + +// CHECK-LABEL: hw.module @dpi_call +hw.module @dpi_call(in %clock : !seq.clock, in %enable : i1, in %in: i32, + out o1: i32, out o2: i32, out o3: i32, out o4: i32, out o5: i32, out o6: i32) { + // CHECK-NEXT: %0:2 = sim.func.dpi.call @foo_wrapper(%in) clock %clock : (i32) -> (i32, i32) + // CHECK-NEXT: %1:2 = sim.func.dpi.call @bar_wrapper(%in) : (i32) -> (i32, i32) + // CHECK-NEXT: %2:2 = sim.func.dpi.call @baz_wrapper(%in) : (i32) -> (i32, i32) + // CHECK-NEXT: hw.output %0#0, %0#1, %1#0, %1#1, %2#0, %2#1 : i32, i32, i32, i32, i32, i32 + %0, %1 = sim.func.dpi.call @foo(%in) clock %clock : (i32) -> (i32, i32) + %2, %3 = sim.func.dpi.call @bar(%in) : (i32) -> (i32, i32) + %4, %5 = sim.func.dpi.call @baz(%in) : (i32) -> (i32, i32) + + hw.output %0, %1, %2, %3, %4, %5 : i32, i32, i32, i32, i32, i32 +} diff --git a/test/Dialect/Sim/round-trip.mlir b/test/Dialect/Sim/round-trip.mlir index a98b7242decf..bbbe07d4d931 100644 --- a/test/Dialect/Sim/round-trip.mlir +++ b/test/Dialect/Sim/round-trip.mlir @@ -19,14 +19,15 @@ hw.module @stop_finish(in %clock : !seq.clock, in %cond : i1) { // CHECK-LABEL: sim.func.dpi @dpi(out arg0 : i1, in %arg1 : i1, out arg2 : i1) sim.func.dpi @dpi(out arg0: i1, in %arg1: i1, out arg2: i1) +func.func private @func(%arg1: i1) -> (i1, i1) hw.module @dpi_call(in %clock : !seq.clock, in %enable : i1, in %in: i1) { // CHECK: sim.func.dpi.call @dpi(%in) clock %clock enable %enable : (i1) -> (i1, i1) %0, %1 = sim.func.dpi.call @dpi(%in) clock %clock enable %enable: (i1) -> (i1, i1) // CHECK: sim.func.dpi.call @dpi(%in) clock %clock : (i1) -> (i1, i1) %2, %3 = sim.func.dpi.call @dpi(%in) clock %clock : (i1) -> (i1, i1) - // CHECK: sim.func.dpi.call @dpi(%in) enable %enable : (i1) -> (i1, i1) - %4, %5 = sim.func.dpi.call @dpi(%in) enable %enable : (i1) -> (i1, i1) - // CHECK: sim.func.dpi.call @dpi(%in) : (i1) -> (i1, i1) - %6, %7 = sim.func.dpi.call @dpi(%in) : (i1) -> (i1, i1) + // CHECK: sim.func.dpi.call @func(%in) enable %enable : (i1) -> (i1, i1) + %4, %5 = sim.func.dpi.call @func(%in) enable %enable : (i1) -> (i1, i1) + // CHECK: sim.func.dpi.call @func(%in) : (i1) -> (i1, i1) + %6, %7 = sim.func.dpi.call @func(%in) : (i1) -> (i1, i1) } diff --git a/test/Dialect/Sim/sim-errors.mlir b/test/Dialect/Sim/sim-errors.mlir index 393636f0ce28..a70360b1cdfe 100644 --- a/test/Dialect/Sim/sim-errors.mlir +++ b/test/Dialect/Sim/sim-errors.mlir @@ -42,3 +42,12 @@ hw.module @proc_print_sv() { sim.proc.print %lit } } + +// ----- + +hw.module.extern @non_func(out arg0: i1, in %arg1: i1, out arg2: i1) + +hw.module @dpi_call(in %clock : !seq.clock, in %in: i1) { + // expected-error @below {{callee must be 'sim.dpi.func' or 'func.func' but got 'hw.module.extern'}} + %0, %1 = sim.func.dpi.call @non_func(%in) : (i1) -> (i1, i1) +} diff --git a/test/Dialect/Verif/basic.mlir b/test/Dialect/Verif/basic.mlir index 1f53b83bd706..4a87a9ab56c8 100644 --- a/test/Dialect/Verif/basic.mlir +++ b/test/Dialect/Verif/basic.mlir @@ -58,70 +58,40 @@ verif.formal @formal1(k = 20) { // CHECK-LABEL: hw.module @Bar hw.module @Bar(in %foo : i8, out "" : i8, out "1" : i8) { - // CHECK: verif.contract(%foo) : (i8) { - verif.contract (%foo) : (i8) { - // CHECK: ^bb0(%[[ARG:.+]]: i8, %[[OUT:.+]]: i8, %[[OUT1:.+]]: i8): - ^bb0(%arg1 : i8, %bar.0 : i8, %bar.1 : i8): + // CHECK: %[[C1:.+]] = hw.constant + %c1_8 = hw.constant 1 : i8 + // CHECK: %[[O1:.+]] = comb.add + %to0 = comb.add bin %foo, %c1_8 : i8 + // CHECK: %[[O2:.+]] = comb.sub + %to1 = comb.sub bin %foo, %c1_8 : i8 + + // CHECK: %[[OUT:.+]]:2 = verif.contract(%[[O1]], %[[O2]]) : (i8, i8) -> (i8, i8) { + %o0, %o1 = verif.contract (%to0, %to1) : (i8, i8) -> (i8, i8) { + // CHECK: ^bb0(%[[BAR0:.+]]: i8, %[[BAR1:.+]]: i8): + ^bb0(%bar.0 : i8, %bar.1 : i8): // CHECK: %[[C0:.+]] = hw.constant 0 : i8 %c0_8 = hw.constant 0 : i8 - // CHECK: %[[PREC:.+]] = comb.icmp bin ugt %[[ARG]], %[[C0]] : i8 - %prec = comb.icmp bin ugt %arg1, %c0_8 : i8 + // CHECK: %[[PREC:.+]] = comb.icmp bin ugt %foo, %[[C0]] : i8 + %prec = comb.icmp bin ugt %foo, %c0_8 : i8 // CHECK: verif.require %[[PREC]] : i1 verif.require %prec : i1 - // CHECK: %[[P0:.+]] = comb.icmp bin ugt %[[OUT]], %[[ARG]] : i8 - %post = comb.icmp bin ugt %bar.0, %arg1 : i8 - // CHECK: %[[P1:.+]] = comb.icmp bin ult %[[OUT1]], %[[ARG]] : i8 - %post1 = comb.icmp bin ult %bar.1, %arg1 : i8 + // CHECK: %[[P0:.+]] = comb.icmp bin ugt %[[BAR0]], %foo : i8 + %post = comb.icmp bin ugt %bar.0, %foo : i8 + // CHECK: %[[P1:.+]] = comb.icmp bin ult %[[BAR1]], %foo : i8 + %post1 = comb.icmp bin ult %bar.1, %foo : i8 // CHECK: verif.ensure %[[P0]] : i1 verif.ensure %post : i1 // CHECK: verif.ensure %[[P1]] : i1 verif.ensure %post1 : i1 - } - // CHECK: %[[C1:.+]] = hw.constant - %c1_8 = hw.constant 1 : i8 - // CHECK: %[[O1:.+]] = comb.add - %o0 = comb.add bin %foo, %c1_8 : i8 - // CHECK: %[[O2:.+]] = comb.sub - %o1 = comb.sub bin %foo, %c1_8 : i8 + // CHECK: verif.yield %[[BAR0]], %[[BAR1]] : i8, i8 + verif.yield %bar.0, %bar.1 : i8, i8 + } + // CHECK-LABEL: hw.output hw.output %o0, %o1 : i8, i8 } -// CHECK-LABEL: hw.module @Foo1 -hw.module @Foo1(in %0 "0": i1, in %1 "1": i1, out "" : i8, out "1" : i8) { - // CHECK: %[[C42:.+]] = hw.constant 42 : i8 - %c42_8 = hw.constant 42 : i8 - // CHECK: %[[OUTS:.+]]:2 = verif.instance(%[[C42]]) : (i8) -> (i8, i8) { - %bar.0, %bar.1 = verif.instance (%c42_8) : (i8) -> (i8, i8) { - // CHECK: ^bb0(%[[ARG:.+]]: i8): - ^bb0(%arg1: i8): - // CHECK: %[[C0:.+]] = hw.constant 0 : i8 - %c0_8 = hw.constant 0 : i8 - // CHECK: %[[PREC:.+]] = comb.icmp bin ugt %[[ARG]], %[[C0]] : i8 - %prec = comb.icmp bin ugt %arg1, %c0_8 : i8 - // CHECK: verif.assert %[[PREC]] : i1 - verif.assert %prec : i1 - - // CHECK: %[[OUT0:.+]] = verif.symbolic_input : i8 - %bar.0 = verif.symbolic_input : i8 - // CHECK: %[[OUT1:.+]] = verif.symbolic_input : i8 - %bar.1 = verif.symbolic_input : i8 - // CHECK: %[[P0:.+]] = comb.icmp bin ugt %[[OUT0]], %[[ARG]] : i8 - %post = comb.icmp bin ugt %bar.0, %arg1 : i8 - // CHECK: %[[P1:.+]] = comb.icmp bin ult %[[OUT1]], %[[ARG]] : i8 - %post1 = comb.icmp bin ult %bar.1, %arg1 : i8 - // CHECK: verif.assume %[[P0]] : i1 - verif.assume %post : i1 - // CHECK: verif.assume %[[P1]] : i1 - verif.assume %post1 : i1 - // CHECK: verif.yield %[[OUT0]], %[[OUT1]] : i8, i8 - verif.yield %bar.0, %bar.1 : i8, i8 - } - // CHECK: hw.output %[[OUTS]]#0, %[[OUTS]]#1 : i8, i8 - hw.output %bar.0, %bar.1 : i8, i8 - } - //===----------------------------------------------------------------------===// // Print-related // Must be inside hw.module to ensure that the dialect is loaded. diff --git a/test/Tools/circt-lec/merge-inputs-error.mlir b/test/Tools/circt-lec/merge-inputs-error.mlir new file mode 100644 index 000000000000..4911f17d427b --- /dev/null +++ b/test/Tools/circt-lec/merge-inputs-error.mlir @@ -0,0 +1,14 @@ +// RUN: split-file %s %t +// RUN: not circt-lec %t/a.mlir %t/b.mlir --c1 top_a --c2 top_not_exist 2>&1 | FileCheck %s + +// CHECK: module "top_not_exist" was not found in the second module + +//--- a.mlir +hw.module @top_a(in %a : i8, out b : i8) { + hw.output %a : i8 +} + +//--- b.mlir +hw.module @top_b(in %a : i8, out b : i8) { + hw.output %a : i8 +} diff --git a/test/Tools/circt-lec/merge-inputs.mlir b/test/Tools/circt-lec/merge-inputs.mlir new file mode 100644 index 000000000000..edba84a12140 --- /dev/null +++ b/test/Tools/circt-lec/merge-inputs.mlir @@ -0,0 +1,42 @@ +// RUN: split-file %s %t +// RUN: circt-lec %t/a.mlir %t/b.mlir --c1 top_a --c2 top_b --emit-mlir | FileCheck %s + +//--- a.mlir +hw.module @foo(in %a : i8, out b : i8) { + %c1_i8 = hw.constant 1 : i8 + %add = comb.add %a, %c1_i8: i8 + hw.output %add : i8 +} +hw.module @top_a(in %a : i8, out b : i8) { + %foo.b = hw.instance "foo" @foo(a: %a: i8) -> (b: i8) + hw.output %foo.b : i8 +} + +//--- b.mlir +hw.module @foo(in %a : i8, out b : i8) { + %c2_i8 = hw.constant 2 : i8 + %add = comb.add %a, %c2_i8: i8 + hw.output %add : i8 +} + +hw.module @top_b(in %a : i8, out b : i8) { + %foo.b = hw.instance "foo" @foo(a: %a: i8) -> (b: i8) + hw.output %foo.b : i8 +} + +// Check constants to make sure a.mlir and b.mlir are properly merged. +// CHECK-LABEL: func.func @foo_0(%arg0: !smt.bv<8>) -> !smt.bv<8> +// CHECK-NEXT: %c2_bv8 = smt.bv.constant #smt.bv<2> +// CHECK-NEXT: %0 = smt.bv.add %arg0, %c2_bv8 +// CHECK-NEXT: return %0 + +// CHECK-LABEL: func.func @foo(%arg0: !smt.bv<8>) -> !smt.bv<8> +// CHECK-NEXT: %c1_bv8 = smt.bv.constant #smt.bv<1> +// CHECK-NEXT: %0 = smt.bv.add %arg0, %c1_bv8 +// CHECK-NEXT: return %0 + +// CHECK-LABEL: func.func @top_a +// CHECK: %[[RESULT1:.+]] = func.call @foo(%[[ARG:.+]]) +// CHECK-NEXT: %[[RESULT2:.+]] = func.call @foo_0(%[[ARG]]) +// CHECK-NEXT: %[[VAL:.+]] = smt.distinct %[[RESULT1]], %[[RESULT2]] +// CHECK-NEXT: smt.assert %[[VAL]] diff --git a/test/firtool/async-reset-anno.fir b/test/firtool/async-reset-anno.fir index e6a14ddde84f..bb94d8fdde23 100644 --- a/test/firtool/async-reset-anno.fir +++ b/test/firtool/async-reset-anno.fir @@ -1,22 +1,24 @@ ; RUN: firtool %s -parse-only | circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-infer-resets))' | FileCheck %s --check-prefixes COMMON,POST-INFER-RESETS ; RUN: firtool %s -parse-only | circt-opt -pass-pipeline='builtin.module(firrtl.circuit(firrtl-infer-resets,firrtl.module(firrtl-sfc-compat)))' | FileCheck %s --check-prefixes COMMON,POST-SFC-COMPAT -; Check that FullAsyncResetAnnotation exists after infer-resets pass +; Check that FullResetAnnotation exists after infer-resets pass ; but is deleted after sfc-compat FIRRTL version 3.3.0 circuit test :%[[ -{ "class":"sifive.enterprise.firrtl.FullAsyncResetAnnotation", - "target":"~test|test>reset" }, -{ "class":"sifive.enterprise.firrtl.FullAsyncResetAnnotation", - "target":"~test|foo>r" } +{ "class":"circt.FullResetAnnotation", + "target":"~test|test>reset", + "resetType":"async" }, +{ "class":"circt.FullResetAnnotation", + "target":"~test|foo>r", + "resetType":"async" } ]] ; COMMON-LABEL: module @test module test : input clock : Clock input reset : AsyncReset - ; POST-INFER-RESETS: [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}] - ; POST-SFC-COMPAT-NOT: [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}] + ; POST-INFER-RESETS: [{class = "circt.FullResetAnnotation", resetType = "async"}] + ; POST-SFC-COMPAT-NOT: [{class = "circt.FullResetAnnotation", resetType = "async"}] input in : { foo : UInt<8>, bar : UInt<8>} output out : { foo : UInt<8>, bar : UInt<8>} connect out, in @@ -28,7 +30,7 @@ circuit test :%[[ input in : { foo : UInt<8>, bar : UInt<8>} output out : { foo : UInt<8>, bar : UInt<8>} - ; POST-INFER-RESETS: [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}] - ; POST-SFC-COMPAT-NOT: [{class = "sifive.enterprise.firrtl.FullAsyncResetAnnotation"}] + ; POST-INFER-RESETS: [{class = "circt.FullResetAnnotation", resetType = "async"}] + ; POST-SFC-COMPAT-NOT: [{class = "circt.FullResetAnnotation", resetType = "async"}] wire r : AsyncReset connect out, in diff --git a/test/firtool/async-reset.fir b/test/firtool/async-reset.fir index 9588be9ccc0a..66e0ed48f123 100644 --- a/test/firtool/async-reset.fir +++ b/test/firtool/async-reset.fir @@ -3,8 +3,9 @@ FIRRTL version 3.3.0 ; CHECK-LABEL: module test( circuit test :%[[{ - "class":"sifive.enterprise.firrtl.FullAsyncResetAnnotation", - "target":"~test|test>reset" + "class":"circt.FullResetAnnotation", + "target":"~test|test>reset", + "resetType":"async" }]] module test : input clock : Clock @@ -31,8 +32,9 @@ circuit test :%[[{ ; CHECK-LABEL: module test_wire( FIRRTL version 3.3.0 circuit test_wire :%[[{ - "class":"sifive.enterprise.firrtl.FullAsyncResetAnnotation", - "target":"~test_wire|test_wire>reset" + "class":"circt.FullResetAnnotation", + "target":"~test_wire|test_wire>reset", + "resetType":"async" }]] module test_wire : input clock : Clock @@ -56,3 +58,63 @@ circuit test_wire :%[[{ connect reg1, in connect reg2, reg1 connect out, reg2 + +;// ----- +; CHECK-LABEL: module test_sync( +FIRRTL version 3.3.0 +circuit test_sync :%[[{ + "class":"circt.FullResetAnnotation", + "target":"~test_sync|test_sync>reset", + "resetType":"sync" + }]] + module test_sync : + input clock : Clock + input reset : UInt<1> + input in : { foo : UInt<8>, bar : UInt<8>} + output out : { foo : UInt<8>, bar : UInt<8>} + + wire reg1_w : { foo : UInt<8>, bar : UInt<8>} + invalidate reg1_w.bar + invalidate reg1_w.foo + connect reg1_w.foo, UInt<8>(0hc) + invalidate reg1_w.bar + ; CHECK: always @(posedge clock) begin + ; CHECK-NEXT: if (reset) begin + ; CHECK-NEXT: reg1_foo <= 8'hC; + ; CHECK-NEXT: reg1_bar <= 8'h0; + regreset reg1 : { foo : UInt<8>, bar : UInt<8>}, clock, reset, reg1_w + wire reg2 : { foo : UInt<8>, bar : UInt<8>} + connect reg1, in + connect reg2, reg1 + connect out, reg2 + +;// ----- +; CHECK-LABEL: module test_wire_sync( +FIRRTL version 3.3.0 +circuit test_wire_sync :%[[{ + "class":"circt.FullResetAnnotation", + "target":"~test_wire_sync|test_wire_sync>reset", + "resetType":"sync" + }]] + module test_wire_sync : + input clock : Clock + input rst : UInt<1> + input in : { foo : UInt<8>, bar : UInt<8>} + output out : { foo : UInt<8>, bar : UInt<8>} + + node reset = rst + + wire reg1_w : { foo : UInt<8>, bar : UInt<8>} + invalidate reg1_w.bar + invalidate reg1_w.foo + connect reg1_w.foo, UInt<8>(0hc) + invalidate reg1_w.bar + ; CHECK: always @(posedge clock) begin + ; CHECK-NEXT: if (rst) begin + ; CHECK-NEXT: reg1_foo <= 8'hC; + ; CHECK-NEXT: reg1_bar <= 8'h0; + regreset reg1 : { foo : UInt<8>, bar : UInt<8>}, clock, reset, reg1_w + wire reg2 : { foo : UInt<8>, bar : UInt<8>} + connect reg1, in + connect reg2, reg1 + connect out, reg2 diff --git a/test/firtool/dpi.fir b/test/firtool/dpi.fir index 1e5af1ae57b4..f4bf89d6a541 100644 --- a/test/firtool/dpi.fir +++ b/test/firtool/dpi.fir @@ -2,31 +2,44 @@ FIRRTL version 4.0.0 circuit DPI: -; CHECK-LABEL: import "DPI-C" context function void clocked_result( +; CHECK-LABEL: `ifndef __CIRCT_DPI_IMPORT_CLOCKED_RESULT +; CHECK-NEXT: import "DPI-C" context function void clocked_result( ; CHECK-NEXT: input byte foo, ; CHECK-NEXT: bar, ; CHECK-NEXT: output byte baz ; CHECK-NEXT: ); +; CHECK: `define __CIRCT_DPI_IMPORT_CLOCKED_RESULT +; CHECK-NEXT: `endif -; CHECK-LABEL: import "DPI-C" context function void clocked_void( +; CHECK-LABEL: `ifndef __CIRCT_DPI_IMPORT_CLOCKED_VOID +; CHECK-NEXT: import "DPI-C" context function void clocked_void( ; CHECK-NEXT: input byte in_0, -; CHECK-NEXT: in_1 +; CHECK-NEXT: in_1, +; CHECK-NEXT: in_2[] ; CHECK-NEXT: ); +; CHECK: `define __CIRCT_DPI_IMPORT_CLOCKED_VOID +; CHECK-NEXT: `endif -; CHECK-LABEL: import "DPI-C" context function void unclocked_result( + +; CHECK-LABEL: `ifndef __CIRCT_DPI_IMPORT_UNCLOCKED_RESULT +; CHECK-NEXT: import "DPI-C" context function void unclocked_result( ; CHECK-NEXT: input byte in_0, ; CHECK-NEXT: in_1, ; CHECK-NEXT: output byte out_0 ; CHECK-NEXT: ); +; CHECK: `define __CIRCT_DPI_IMPORT_UNCLOCKED_RESULT +; CHECK-NEXT: `endif ; CHECK-LABEL: module DPI( ; CHECK: logic [7:0] [[TMP:_.+]]; ; CHECK-NEXT: reg [7:0] [[RESULT1:_.+]]; +; CHECK-NEXT: wire [7:0] [[OPEN_ARRAY:_.+]][0:1]; +; CHECK-NEXT: assign [[OPEN_ARRAY]] = '{in_0, in_1}; ; CHECK-NEXT: always @(posedge clock) begin ; CHECK-NEXT: if (enable) begin ; CHECK-NEXT: clocked_result(in_0, in_1, [[TMP]]); ; CHECK-NEXT: [[RESULT1]] <= [[TMP]]; -; CHECK-NEXT: clocked_void(in_0, in_1); +; CHECK-NEXT: clocked_void(in_0, in_1, [[OPEN_ARRAY]]); ; CHECK-NEXT: end ; CHECK-NEXT: end // always @(posedge) ; CHECK-NEXT: reg [7:0] [[RESULT2:_.+]]; @@ -47,7 +60,7 @@ circuit DPI: output out : UInt<8>[2] node result1 = intrinsic(circt_dpi_call : UInt<8>, clock, enable, in[0], in[1]) - intrinsic(circt_dpi_call, clock, enable, in[0], in[1]) + intrinsic(circt_dpi_call, clock, enable, in[0], in[1], in) node result2 = intrinsic(circt_dpi_call : UInt<8>, enable, in[0], in[1]) out[0] <= result1 diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir index 8342bba67d5a..930e72ba1015 100644 --- a/test/firtool/lower-layers.fir +++ b/test/firtool/lower-layers.fir @@ -1,4 +1,4 @@ -; RUN: firtool -verilog %s | FileCheck %s +; RUN: firtool %s -disable-all-randomization -split-input-file | 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 @@ -67,6 +67,113 @@ circuit Foo: %[[ ; CHECK: module Foo(); ; CHECK: wire d = Foo.bar.verification.c_probe; ; CHECK: Bar bar ( - ; CHECK: .a (d) + ; CHECK: .a (d) ; CHECK: ); ; CHECK: endmodule + +; // ----- + +; 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 + public 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: FILE "layers_TestHarness_Verification.sv" +; CHECK: `ifndef layers_TestHarness_Verification +; CHECK: `define layers_TestHarness_Verification +; CHECK: bind DUT DUT_Verification verification ( +; CHECK: .clock (clock), +; CHECK: .a (a) +; CHECK: ); +; CHECK: bind TestHarness TestHarness_Verification verification ( +; CHECK: .dut_trace (TestHarness.dut.verification.pc_d_probe), +; CHECK: .clock (clock), +; CHECK: .reset (reset) +; CHECK: ); +; CHECK: `endif // layers_TestHarness_Verification diff --git a/test/firtool/lower-layers2.fir b/test/firtool/lower-layers2.fir deleted file mode 100644 index 13c4e566c79f..000000000000 --- a/test/firtool/lower-layers2.fir +++ /dev/null @@ -1,106 +0,0 @@ -; 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 - public 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 verification ( -; CHECK: .clock (clock), -; CHECK: .a (a) -; CHECK: ); -; CHECK: bind TestHarness TestHarness_Verification verification ( -; CHECK: .dut_trace (TestHarness.dut.verification.pc_d_probe), -; CHECK: .clock (clock), -; CHECK: .reset (reset) -; CHECK: ); -; CHECK: `endif // layers_TestHarness_Verification diff --git a/test/firtool/prefixMemory.fir b/test/firtool/prefixMemory.fir index 253b0c31a964..974cfee939ef 100644 --- a/test/firtool/prefixMemory.fir +++ b/test/firtool/prefixMemory.fir @@ -37,7 +37,7 @@ circuit Foo : %[[ input writeAddr : UInt<3> input writeData : UInt<32> - ; REPL-FIR: firrtl.instance mem sym @{{[^ ]+}} @prefix1_mem + ; REPL-FIR: firrtl.instance mem sym @{{[^ ]+}} {annotations = [{circt.nonlocal = @memNLA, class = "circt.tracker", id = distinct[0]<>}]} @prefix1_mem ; REPL-HW: hw.instance "mem" sym @{{[^ ]+}} @prefix1_mem ; SIM-FIR: firrtl.mem ; SIM-FIR-SAME: name = "mem" @@ -69,7 +69,6 @@ circuit Foo : %[[ mem.MPORT.data <= writeData mem.MPORT.mask <= UInt<1>(0h1) - ; CHECK-FIR-LABEL: firrtl.module private @prefix2_Baz ; CHECK-HW-LABEL: hw.module private @prefix2_Baz module Baz : input clock : Clock @@ -80,7 +79,8 @@ circuit Foo : %[[ input writeAddr : UInt<3> input writeData : UInt<32> - ; REPL-FIR: firrtl.instance mem sym @{{[^ ]+}} @prefix2_mem + ; REPL-FIR: firrtl.module private @prefix2_Baz + ; REPL-FIR-NEXT: firrtl.instance mem sym @sym {annotations = [{circt.nonlocal = @memNLA_0, class = "circt.tracker", id = distinct[1]<>}]} @prefix2_mem_0 ; REPL-HW: hw.instance "mem" sym @{{[^ ]+}} @prefix2_mem ; SIM-FIR: firrtl.mem ; SIM-FIR-SAME: name = "mem" diff --git a/test/firtool/specialize-layers-2.fir b/test/firtool/specialize-layers-2.fir deleted file mode 100644 index c733179b17ec..000000000000 --- a/test/firtool/specialize-layers-2.fir +++ /dev/null @@ -1,11 +0,0 @@ -; RUN: firtool %s -disable-layers=X,Y -; RUN: firtool %s -enable-layers=X,Y - -; https://github.com/llvm/circt/issues/7345 -; Check that we can specify more than one layer in the command line options. - -FIRRTL version 4.0.0 -circuit Foo: - layer X, bind: - layer Y, bind: - public module Foo: diff --git a/test/firtool/specialize-layers-cli.fir b/test/firtool/specialize-layers-cli.fir new file mode 100644 index 000000000000..128b2eeddddf --- /dev/null +++ b/test/firtool/specialize-layers-cli.fir @@ -0,0 +1,21 @@ +; RUN: firtool -parse-only %s | FileCheck %s --check-prefixes=NONE +; RUN: firtool -parse-only -disable-layers=A %s | FileCheck %s --check-prefixes=DISABLEA +; RUN: firtool -parse-only -enable-layers=A %s | FileCheck %s --check-prefixes=ENABLEA +; RUN: firtool -parse-only -enable-layers=A.B %s | FileCheck %s --check-prefixes=ENABLEB +; RUN: firtool -parse-only -disable-layers=A,A.B %s | FileCheck %s --check-prefixes=DISABLEBOTH +; RUN: firtool -parse-only -disable-layers=A -enable-layers=A.B %s | FileCheck %s --check-prefixes=BOTH + +; Check that the command line options are working correctly. +; https://github.com/llvm/circt/issues/7345 + +FIRRTL version 4.0.0 +; NONE: firrtl.circuit "Foo" { +; DISABLEA: firrtl.circuit "Foo" attributes {disable_layers = [@A]} { +; ENABLEA: firrtl.circuit "Foo" attributes {enable_layers = [@A]} { +; ENABLEB: firrtl.circuit "Foo" attributes {enable_layers = [@A::@B]} { +; DISABLEBOTH: firrtl.circuit "Foo" attributes {disable_layers = [@A, @A::@B]} { +; BOTH: firrtl.circuit "Foo" attributes {disable_layers = [@A], enable_layers = [@A::@B]} { +circuit Foo: + layer A, bind: + layer B, bind: + public module Foo: diff --git a/test/firtool/specialize-layers-1.fir b/test/firtool/specialize-layers.fir similarity index 100% rename from test/firtool/specialize-layers-1.fir rename to test/firtool/specialize-layers.fir diff --git a/test/lit.cfg.py b/test/lit.cfg.py index 0340cc25f881..26b9a164dead 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -60,8 +60,8 @@ tools = [ 'arcilator', 'circt-as', 'circt-capi-ir-test', 'circt-capi-om-test', 'circt-capi-firrtl-test', 'circt-capi-firtool-test', 'circt-dis', - 'circt-reduce', 'circt-translate', 'firtool', 'hlstool', 'om-linker', - 'ibistool' + 'circt-lec', 'circt-reduce', 'circt-translate', 'firtool', 'hlstool', + 'om-linker', 'ibistool' ] if "CIRCT_OPT_CHECK_IR_ROUNDTRIP" in os.environ: diff --git a/tools/arcilator/CMakeLists.txt b/tools/arcilator/CMakeLists.txt index 1257bc173107..69a7068e1811 100644 --- a/tools/arcilator/CMakeLists.txt +++ b/tools/arcilator/CMakeLists.txt @@ -1,7 +1,8 @@ if(ARCILATOR_JIT_ENABLED) add_compile_definitions(ARCILATOR_ENABLE_JIT) + add_subdirectory(jit-env) set(ARCILATOR_JIT_LLVM_COMPONENTS native) - set(ARCILATOR_JIT_DEPS MLIRExecutionEngine) + set(ARCILATOR_JIT_DEPS MLIRExecutionEngine arc-jit-env) endif() set(LLVM_LINK_COMPONENTS Support ${ARCILATOR_JIT_LLVM_COMPONENTS}) @@ -20,6 +21,7 @@ target_link_libraries(arcilator CIRCTOM CIRCTSeqToSV CIRCTSeqTransforms + CIRCTSimTransforms CIRCTSupport CIRCTTransforms MLIRBuiltinToLLVMIRTranslation @@ -45,3 +47,9 @@ configure_file(arcilator-runtime.h ${CIRCT_TOOLS_DIR}/arcilator-runtime.h) add_custom_target(arcilator-runtime-header SOURCES ${CIRCT_TOOLS_DIR}/arcilator-runtime.h) + +if(ARCILATOR_JIT_ENABLED) + target_include_directories(arcilator PRIVATE + $ + ) +endif() diff --git a/tools/arcilator/arcilator-header-cpp.py b/tools/arcilator/arcilator-header-cpp.py index 6a1545d950fc..113aa3094be2 100755 --- a/tools/arcilator/arcilator-header-cpp.py +++ b/tools/arcilator/arcilator-header-cpp.py @@ -63,12 +63,13 @@ class StateHierarchy: class ModelInfo: name: str numStateBytes: int + initialFnSym: str states: List[StateInfo] io: List[StateInfo] hierarchy: List[StateHierarchy] def decode(d: dict) -> "ModelInfo": - return ModelInfo(d["name"], d["numStateBytes"], + return ModelInfo(d["name"], d["numStateBytes"], d.get("initialFnSym", ""), [StateInfo.decode(d) for d in d["states"]], list(), list()) @@ -240,6 +241,8 @@ def indent(s: str, amount: int = 1): io.name = io.name + "_" print('extern "C" {') + if model.initialFnSym: + print(f"void {model.name}_initial(void* state);") print(f"void {model.name}_eval(void* state);") print('}') @@ -297,8 +300,11 @@ def indent(s: str, amount: int = 1): print(f" {model.name}View view;") print() print( - f" {model.name}() : storage({model.name}Layout::numStateBytes, 0), view(&storage[0]) {{}}" + f" {model.name}() : storage({model.name}Layout::numStateBytes, 0), view(&storage[0]) {{" ) + if model.initialFnSym: + print(f" {model.initialFnSym}(&storage[0]);") + print(" }") print(f" void eval() {{ {model.name}_eval(&storage[0]); }}") print( f" ValueChangeDump<{model.name}Layout> vcd(std::basic_ostream &os) {{" diff --git a/tools/arcilator/arcilator-runtime.h b/tools/arcilator/arcilator-runtime.h index 396343b8ee03..ee65bebe4a16 100644 --- a/tools/arcilator/arcilator-runtime.h +++ b/tools/arcilator/arcilator-runtime.h @@ -1,12 +1,67 @@ // NOLINTBEGIN #pragma once #include +#include #include +#include #include #include #include #include +// Sanity checks for binary compatibility +#ifdef __BYTE_ORDER__ +#if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) +#error Unsupported endianess +#endif +#endif +static_assert(sizeof(int) == 4, "Unsupported ABI"); +static_assert(sizeof(long long) == 8, "Unsupported ABI"); + +// --- Exports to the IR --- + +#ifdef _WIN32 +#define ARC_EXPORT extern "C" __declspec(dllexport) +#else +#define ARC_EXPORT extern "C" __attribute__((visibility("default"))) +#endif + +#ifndef ARC_NO_LIBC_EXPORTS + +// libc Adapters +ARC_EXPORT int _arc_libc_fprintf(FILE *stream, const char *format, ...) { + int result; + va_list args; + va_start(args, format); + result = vfprintf(stream, format, args); + va_end(args); + return result; +} + +ARC_EXPORT int _arc_libc_fputs(const char *str, FILE *stream) { + return fputs(str, stream); +} + +ARC_EXPORT int _arc_libc_fputc(int ch, FILE *stream) { + return fputc(ch, stream); +} + +#endif // ARC_NO_LIBC_EXPORTS + +// Runtime Environment calls + +#define ARC_ENV_DECL_GET_PRINT_STREAM(idarg) \ + ARC_EXPORT FILE *_arc_env_get_print_stream(uint32_t idarg) + +#ifndef ARC_NO_DEFAULT_GET_PRINT_STREAM +ARC_ENV_DECL_GET_PRINT_STREAM(id) { + (void)id; + return stderr; +} +#endif // ARC_NO_DEFAULT_GET_PRINT_STREAM + +// ---------------- + struct Signal { const char *name; unsigned offset; diff --git a/tools/arcilator/arcilator.cpp b/tools/arcilator/arcilator.cpp index 1348e5a979cf..2ecfcbe8c0e8 100644 --- a/tools/arcilator/arcilator.cpp +++ b/tools/arcilator/arcilator.cpp @@ -22,6 +22,8 @@ #include "circt/Dialect/Emit/EmitDialect.h" #include "circt/Dialect/HW/HWPasses.h" #include "circt/Dialect/Seq/SeqPasses.h" +#include "circt/Dialect/Sim/SimDialect.h" +#include "circt/Dialect/Sim/SimPasses.h" #include "circt/InitAllDialects.h" #include "circt/InitAllPasses.h" #include "circt/Support/Passes.h" @@ -52,6 +54,7 @@ #include "mlir/Target/LLVMIR/Export.h" #include "mlir/Transforms/GreedyPatternRewriteDriver.h" #include "mlir/Transforms/Passes.h" +#include "llvm/ADT/ScopeExit.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/Support/CommandLine.h" @@ -62,6 +65,10 @@ #include "llvm/Support/TargetSelect.h" #include "llvm/Support/ToolOutputFile.h" +#ifdef ARCILATOR_ENABLE_JIT +#include "arcilator-jit-env.h" +#endif + #include using namespace mlir; @@ -213,6 +220,10 @@ static llvm::cl::opt "simulation to run when output is set to run"), llvm::cl::init("entry"), llvm::cl::cat(mainCategory)); +static llvm::cl::list sharedLibs{ + "shared-libs", llvm::cl::desc("Libraries to link dynamically"), + llvm::cl::MiscFlags::CommaSeparated, llvm::cl::cat(mainCategory)}; + //===----------------------------------------------------------------------===// // Main Tool Logic //===----------------------------------------------------------------------===// @@ -249,6 +260,7 @@ static void populateHwModuleToArcPipeline(PassManager &pm) { opts.tapMemories = observeMemories; pm.addPass(arc::createInferMemoriesPass(opts)); } + pm.addPass(sim::createLowerDPIFunc()); pm.addPass(createCSEPass()); pm.addPass(arc::createArcCanonicalizerPass()); @@ -430,11 +442,25 @@ static LogicalResult processBuffer( return failure(); } + auto envInitResult = arc_jit_runtime_env_init(); + if (envInitResult != 0) { + llvm::errs() << "Initialization of arcilator runtime library failed (" + << envInitResult << ").\n"; + return failure(); + } + + auto envDeinit = + llvm::make_scope_exit([] { arc_jit_runtime_env_deinit(); }); + + SmallVector sharedLibraries(sharedLibs.begin(), + sharedLibs.end()); + mlir::ExecutionEngineOptions engineOptions; engineOptions.jitCodeGenOptLevel = llvm::CodeGenOptLevel::Aggressive; engineOptions.transformer = mlir::makeOptimizingTransformer( /*optLevel=*/3, /*sizeLevel=*/0, /*targetMachine=*/nullptr); + engineOptions.sharedLibPaths = sharedLibraries; auto executionEngine = mlir::ExecutionEngine::create(module.get(), engineOptions); @@ -567,6 +593,7 @@ static LogicalResult executeArcilator(MLIRContext &context) { mlir::scf::SCFDialect, om::OMDialect, seq::SeqDialect, + sim::SimDialect, sv::SVDialect >(); // clang-format on diff --git a/tools/arcilator/jit-env/CMakeLists.txt b/tools/arcilator/jit-env/CMakeLists.txt new file mode 100644 index 000000000000..0b5dbe13577c --- /dev/null +++ b/tools/arcilator/jit-env/CMakeLists.txt @@ -0,0 +1,32 @@ +add_library(arc-jit-env SHARED arcilator-jit-env.cpp) + +if(WIN32) +# Put the DLL into the binary directory so we don't have +# to worry about it not being found. +set_target_properties(arc-jit-env + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + RUNTIME_OUTPUT_DIRECTORY ${CIRCT_TOOLS_DIR}$<0:> + CXX_VISIBILITY_PRESET "default" +) +install(TARGETS arc-jit-env + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT arc-jit-env +) + +else() + +set_target_properties(arc-jit-env + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib + CXX_VISIBILITY_PRESET "default" +) +install(TARGETS arc-jit-env + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT arc-jit-env +) + +endif() + diff --git a/tools/arcilator/jit-env/arcilator-jit-env.cpp b/tools/arcilator/jit-env/arcilator-jit-env.cpp new file mode 100644 index 000000000000..fa33e3a8f815 --- /dev/null +++ b/tools/arcilator/jit-env/arcilator-jit-env.cpp @@ -0,0 +1,20 @@ +//===- arcilator-jit-env.cpp - Internal arcilator JIT API -----------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Implementation of the arcilator JIT runtime environment API facing +// the arcilator.cpp. +// +//===----------------------------------------------------------------------===// + +#include "../arcilator-runtime.h" + +#define ARCJITENV_EXPORTS +#include "arcilator-jit-env.h" + +ARCJITENV_API int arc_jit_runtime_env_init(void) { return 0; } +ARCJITENV_API void arc_jit_runtime_env_deinit(void) { return; } diff --git a/tools/arcilator/jit-env/arcilator-jit-env.h b/tools/arcilator/jit-env/arcilator-jit-env.h new file mode 100644 index 000000000000..f3dab859539d --- /dev/null +++ b/tools/arcilator/jit-env/arcilator-jit-env.h @@ -0,0 +1,34 @@ +//===- arcilator-jit-env.h - Internal arcilator JIT API -------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Declarations of the JIT runtime environment API facing the arcilator.cpp. +// +//===----------------------------------------------------------------------===// + +#pragma once + +#ifdef _WIN32 + +#ifdef ARCJITENV_EXPORTS +#define ARCJITENV_API extern "C" __declspec(dllexport) +#else +#define ARCJITENV_API extern "C" __declspec(dllimport) +#endif // ARCJITENV_EXPORTS + +#else + +#define ARCJITENV_API extern "C" __attribute__((visibility("default"))) + +#endif // _WIN32 + +// These don't do anything at the moment. It is still +// required to call them to make sure the library +// is linked and loaded before the JIT engine starts. + +ARCJITENV_API int arc_jit_runtime_env_init(void); +ARCJITENV_API void arc_jit_runtime_env_deinit(void); diff --git a/tools/circt-lec/circt-lec.cpp b/tools/circt-lec/circt-lec.cpp index ec03638e7e2a..3a8e0c0ff63b 100644 --- a/tools/circt-lec/circt-lec.cpp +++ b/tools/circt-lec/circt-lec.cpp @@ -71,9 +71,9 @@ static cl::opt secondModuleName( cl::desc("Specify a named module for the second circuit of the comparison"), cl::value_desc("module name"), cl::cat(mainCategory)); -static cl::opt inputFilename(cl::Positional, cl::Required, - cl::desc(""), - cl::cat(mainCategory)); +static cl::list inputFilenames(cl::Positional, cl::OneOrMore, + cl::desc(""), + cl::cat(mainCategory)); static cl::opt outputFilename("o", cl::desc("Output filename"), cl::value_desc("filename"), @@ -122,6 +122,65 @@ static cl::opt outputFormat( // Tool implementation //===----------------------------------------------------------------------===// +// Move all operations in `src` to `dest`. Rename all symbols in `src` to avoid +// conflict. +static FailureOr mergeModules(ModuleOp dest, ModuleOp src, + StringAttr name) { + + SymbolTable destTable(dest), srcTable(src); + StringAttr newName = {}; + for (auto &op : src.getOps()) { + if (SymbolOpInterface symbol = dyn_cast(op)) { + auto oldSymbol = symbol.getNameAttr(); + auto result = srcTable.renameToUnique(&op, {&destTable}); + if (failed(result)) + return src->emitError() << "failed to rename symbol " << oldSymbol; + + if (oldSymbol == name) { + assert(!newName && "symbol must be unique"); + newName = *result; + } + } + } + + if (!newName) + return src->emitError() + << "module " << name << " was not found in the second module"; + + dest.getBody()->getOperations().splice(dest.getBody()->begin(), + src.getBody()->getOperations()); + return newName; +} + +// Parse one or two MLIR modules and merge it into a single module. +static FailureOr> +parseAndMergeModules(MLIRContext &context, TimingScope &ts) { + auto parserTimer = ts.nest("Parse and merge MLIR input(s)"); + + if (inputFilenames.size() > 2) { + llvm::errs() << "more than 2 files are provided!\n"; + return failure(); + } + + auto module = parseSourceFile(inputFilenames[0], &context); + if (!module) + return failure(); + + if (inputFilenames.size() == 2) { + auto moduleOpt = parseSourceFile(inputFilenames[1], &context); + if (!moduleOpt) + return failure(); + auto result = mergeModules(module.get(), moduleOpt.get(), + StringAttr::get(&context, secondModuleName)); + if (failed(result)) + return failure(); + + secondModuleName.setValue(result->getValue().str()); + } + + return module; +} + /// This functions initializes the various components of the tool and /// orchestrates the work to be done. static LogicalResult executeLEC(MLIRContext &context) { @@ -130,15 +189,12 @@ static LogicalResult executeLEC(MLIRContext &context) { applyDefaultTimingManagerCLOptions(tm); auto ts = tm.getRootScope(); - OwningOpRef module; - { - auto parserTimer = ts.nest("Parse MLIR input"); - // Parse the provided input files. - module = parseSourceFile(inputFilename, &context); - } - if (!module) + auto parsedModule = parseAndMergeModules(context, ts); + if (failed(parsedModule)) return failure(); + OwningOpRef module = std::move(parsedModule.value()); + // Create the output directory or output file depending on our mode. std::optional> outputFile; std::string errorMessage; diff --git a/tools/circt-reduce/circt-reduce.cpp b/tools/circt-reduce/circt-reduce.cpp index 0b9d289b18a9..399be4d96666 100644 --- a/tools/circt-reduce/circt-reduce.cpp +++ b/tools/circt-reduce/circt-reduce.cpp @@ -19,6 +19,7 @@ #include "circt/Reduce/GenericReductions.h" #include "circt/Reduce/Tester.h" #include "circt/Support/Version.h" +#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h" #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMDialect.h" #include "mlir/Dialect/SCF/IR/SCF.h" @@ -424,7 +425,8 @@ int main(int argc, char **argv) { // Register all the dialects and create a context to work wtih. mlir::DialectRegistry registry; registerAllDialects(registry); - registry.insert(); + registry.insert(); arc::registerReducePatternDialectInterface(registry); firrtl::registerReducePatternDialectInterface(registry); hw::registerReducePatternDialectInterface(registry); diff --git a/tools/circt-verilog/circt-verilog.cpp b/tools/circt-verilog/circt-verilog.cpp index 36c84f67f9d1..f07fc252a34d 100644 --- a/tools/circt-verilog/circt-verilog.cpp +++ b/tools/circt-verilog/circt-verilog.cpp @@ -15,7 +15,9 @@ #include "circt/Conversion/ImportVerilog.h" #include "circt/Conversion/MooreToCore.h" #include "circt/Dialect/Moore/MoorePasses.h" +#include "circt/Support/Passes.h" #include "circt/Support/Version.h" +#include "mlir/IR/AsmState.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/Pass/PassManager.h" #include "mlir/Support/FileUtilities.h" @@ -216,27 +218,62 @@ struct CLOptions { static CLOptions opts; -/// Parse specified files that had been translated into Moore dialect IR. After -/// that simplify these files like deleting local variables, and then emit the -/// resulting Moore dialect IR . -static LogicalResult populateMooreTransforms(mlir::PassManager &pm) { - auto &modulePM = pm.nest(); - modulePM.addPass(moore::createLowerConcatRefPass()); - modulePM.addPass(moore::createSimplifyProceduresPass()); +//===----------------------------------------------------------------------===// +// Pass Pipeline +//===----------------------------------------------------------------------===// - pm.addPass(mlir::createSROA()); - pm.addPass(mlir::createMem2Reg()); +/// Optimize and simplify the Moore dialect IR. +static void populateMooreTransforms(PassManager &pm) { + { + // Perform an initial cleanup and preprocessing across all + // modules/functions. + auto &anyPM = pm.nestAny(); + anyPM.addPass(mlir::createCSEPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); + } - // TODO: like dedup pass. + { + // Perform module-specific transformations. + auto &modulePM = pm.nest(); + modulePM.addPass(moore::createLowerConcatRefPass()); + // TODO: Enable the following once it not longer interferes with @(...) + // event control checks. The introduced dummy variables make the event + // control observe a static local variable that never changes, instead of + // observing a module-wide signal. + // modulePM.addPass(moore::createSimplifyProceduresPass()); + } - return success(); + { + // Perform a final cleanup across all modules/functions. + auto &anyPM = pm.nestAny(); + anyPM.addPass(mlir::createSROA()); + anyPM.addPass(mlir::createMem2Reg()); + anyPM.addPass(mlir::createCSEPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); + } } /// Convert Moore dialect IR into core dialect IR -static LogicalResult populateMooreToCoreLowering(mlir::PassManager &pm) { +static void populateMooreToCoreLowering(PassManager &pm) { + // Perform the conversion. pm.addPass(createConvertMooreToCorePass()); - return success(); + { + // Conversion to the core dialects likely uncovers new canonicalization + // opportunities. + auto &anyPM = pm.nestAny(); + anyPM.addPass(mlir::createCSEPass()); + anyPM.addPass(mlir::createCanonicalizerPass()); + } +} + +/// Populate the given pass manager with transformations as configured by the +/// command line options. +static void populatePasses(PassManager &pm) { + populateMooreTransforms(pm); + if (opts.loweringMode == LoweringMode::OutputIRMoore) + return; + populateMooreToCoreLowering(pm); } //===----------------------------------------------------------------------===// @@ -308,23 +345,23 @@ static LogicalResult executeWithSources(MLIRContext *context, if (failed(importVerilog(sourceMgr, context, ts, module.get(), &options))) return failure(); - PassManager pm(context); - if (opts.loweringMode == LoweringMode::OutputIRMoore || - opts.loweringMode == LoweringMode::OutputIRHW) { - - // Simplify the Moore dialect IR. - if (failed(populateMooreTransforms(pm))) + // If the user requested for the files to be only linted, the module remains + // empty and there is nothing left to do. + if (opts.loweringMode == LoweringMode::OnlyLint) + return success(); + + // If the user requested anything besides simply parsing the input, run the + // appropriate transformation passes according to the command line options. + if (opts.loweringMode != LoweringMode::OnlyParse) { + PassManager pm(context); + pm.enableVerifier(true); + if (failed(applyPassManagerCLOptions(pm))) + return failure(); + populatePasses(pm); + if (failed(pm.run(module.get()))) return failure(); - - if (opts.loweringMode == LoweringMode::OutputIRHW) - // Convert Moore IR into core IR. - if (failed(populateMooreToCoreLowering(pm))) - return failure(); } - if (failed(pm.run(module.get()))) - return failure(); - // Print the final MLIR. module->print(outputFile->os()); outputFile->keep(); diff --git a/tools/om-linker/om-linker.cpp b/tools/om-linker/om-linker.cpp index e5a4da154198..bd1e8fea3577 100644 --- a/tools/om-linker/om-linker.cpp +++ b/tools/om-linker/om-linker.cpp @@ -20,6 +20,7 @@ #include "circt/Dialect/SV/SVDialect.h" #include "circt/Dialect/Verif/VerifDialect.h" #include "circt/Support/Version.h" +#include "mlir/Bytecode/BytecodeWriter.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/Threading.h" #include "mlir/Parser/Parser.h" diff --git a/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp b/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp index 8b9e24b0a86b..abd9547c4632 100644 --- a/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp +++ b/unittests/Dialect/OM/Evaluator/EvaluatorTests.cpp @@ -919,4 +919,115 @@ TEST(EvaluatorTests, IntegerBinaryArithmeticWidthMismatch) { .getValue()); } +TEST(EvaluatorTests, ListConcat) { + StringRef mod = "om.class @ListConcat() {" + " %0 = om.constant #om.integer<0 : i8> : !om.integer" + " %1 = om.constant #om.integer<1 : i8> : !om.integer" + " %2 = om.constant #om.integer<2 : i8> : !om.integer" + " %l0 = om.list_create %0, %1 : !om.integer" + " %l1 = om.list_create %2 : !om.integer" + " %concat = om.list_concat %l0, %l1 : !om.list" + " om.class.field @result, %concat : !om.list" + "}"; + + 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, "ListConcat"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + auto finalList = + llvm::cast(fieldValue.get())->getElements(); + + ASSERT_EQ(3, finalList.size()); + + ASSERT_EQ(0, llvm::cast(finalList[0].get()) + ->getAs() + .getValue() + .getValue()); + + ASSERT_EQ(1, llvm::cast(finalList[1].get()) + ->getAs() + .getValue() + .getValue()); + + ASSERT_EQ(2, llvm::cast(finalList[2].get()) + ->getAs() + .getValue() + .getValue()); +} + +TEST(EvaluatorTests, ListConcatField) { + StringRef mod = + "om.class @ListField() {" + " %0 = om.constant #om.integer<2 : i8> : !om.integer" + " %1 = om.list_create %0 : !om.integer" + " om.class.field @value, %1 : !om.list" + "}" + "om.class @ListConcatField() {" + " %listField = om.object @ListField() : () -> !om.class.type<@ListField>" + " %0 = om.constant #om.integer<0 : i8> : !om.integer" + " %1 = om.constant #om.integer<1 : i8> : !om.integer" + " %l0 = om.list_create %0, %1 : !om.integer" + " %l1 = om.object.field %listField, [@value] : " + "(!om.class.type<@ListField>) -> !om.list" + " %concat = om.list_concat %l0, %l1 : !om.list" + " om.class.field @result, %concat : !om.list" + "}"; + + 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, "ListConcatField"), {}); + + ASSERT_TRUE(succeeded(result)); + + auto fieldValue = llvm::cast(result.value().get()) + ->getField("result") + .value(); + + auto finalList = + llvm::cast(fieldValue.get())->getElements(); + + ASSERT_EQ(3, finalList.size()); + + ASSERT_EQ(0, llvm::cast(finalList[0].get()) + ->getAs() + .getValue() + .getValue()); + + ASSERT_EQ(1, llvm::cast(finalList[1].get()) + ->getAs() + .getValue() + .getValue()); + + ASSERT_EQ(2, llvm::cast(finalList[2].get()) + ->getAs() + .getValue() + .getValue()); +} + } // namespace diff --git a/unittests/Support/CMakeLists.txt b/unittests/Support/CMakeLists.txt index 48431537442c..005e2389e396 100644 --- a/unittests/Support/CMakeLists.txt +++ b/unittests/Support/CMakeLists.txt @@ -1,4 +1,5 @@ add_circt_unittest(CIRCTSupportTests + FVIntTest.cpp JSONTest.cpp PrettyPrinterTest.cpp ) diff --git a/unittests/Support/FVIntTest.cpp b/unittests/Support/FVIntTest.cpp new file mode 100644 index 000000000000..eae7c1b0af86 --- /dev/null +++ b/unittests/Support/FVIntTest.cpp @@ -0,0 +1,137 @@ +//===- FVIntTest.cpp - Four-valued integer unit tests ===------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Unit tests for the `FVInt` class. +// +//===----------------------------------------------------------------------===// + +#include "circt/Support/FVInt.h" +#include "gtest/gtest.h" + +using namespace circt; + +namespace { + +TEST(FVIntTest, Resizing) { + ASSERT_EQ(FVInt::fromString("01", 2).zext(5), FVInt::fromString("00001", 2)); + ASSERT_EQ(FVInt::fromString("01", 2).sext(5), FVInt::fromString("00001", 2)); + ASSERT_EQ(FVInt::fromString("10", 2).zext(5), FVInt::fromString("00010", 2)); + ASSERT_EQ(FVInt::fromString("10", 2).sext(5), FVInt::fromString("11110", 2)); + ASSERT_EQ(FVInt::fromString("X1", 2).zext(5), FVInt::fromString("000X1", 2)); + ASSERT_EQ(FVInt::fromString("X1", 2).sext(5), FVInt::fromString("XXXX1", 2)); + ASSERT_EQ(FVInt::fromString("Z1", 2).zext(5), FVInt::fromString("000Z1", 2)); + ASSERT_EQ(FVInt::fromString("Z1", 2).sext(5), FVInt::fromString("ZZZZ1", 2)); +} + +TEST(FVIntTest, Basics) { + ASSERT_TRUE(FVInt::getZero(42).isZero()); + ASSERT_TRUE(FVInt::getAllOnes(42).isAllOnes()); + ASSERT_TRUE(FVInt::getAllX(42).isAllX()); + ASSERT_TRUE(FVInt::getAllZ(42).isAllZ()); + + ASSERT_FALSE(FVInt::getZero(42).hasUnknown()); + ASSERT_FALSE(FVInt::getAllOnes(42).hasUnknown()); + ASSERT_TRUE(FVInt::getAllX(42).hasUnknown()); + ASSERT_TRUE(FVInt::getAllZ(42).hasUnknown()); + + auto x = FVInt::fromString("01XZ", 2); + ASSERT_EQ(x.toAPInt(false), 0b0100); + ASSERT_EQ(x.toAPInt(true), 0b0111); + ASSERT_EQ(x.getBit(0), FVInt::Z); + ASSERT_EQ(x.getBit(1), FVInt::X); + ASSERT_EQ(x.getBit(2), FVInt::V1); + ASSERT_EQ(x.getBit(3), FVInt::V0); + ASSERT_EQ(FVInt::V1, 1); + ASSERT_EQ(FVInt::V0, 0); + + ASSERT_EQ(FVInt(32, 9001), FVInt(32, 9001)); + ASSERT_EQ(FVInt(32, 9001), 9001); + ASSERT_EQ(9001, FVInt(32, 9001)); + + ASSERT_NE(FVInt(32, 9001), FVInt(32, 1337)); + ASSERT_NE(FVInt(32, 9001), 1337); + ASSERT_NE(9001, FVInt(32, 1337)); +} + +TEST(FVIntTest, StringConversion) { + auto v = FVInt::fromString("ZX1001XZ", 2); + ASSERT_EQ(v.getZeroBits(), 0b00011000); + ASSERT_EQ(v.getOneBits(), 0b00100100); + ASSERT_EQ(v.getXBits(), 0b01000010); + ASSERT_EQ(v.getZBits(), 0b10000001); + + ASSERT_EQ(FVInt::getZero(0).toString(2), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(8), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(10), StringRef("0")); + ASSERT_EQ(FVInt::getZero(0).toString(16), StringRef("0")); + + // Parsing/printing without unknown values. + ASSERT_EQ(FVInt::fromString("10101100", 2).toString(2), + StringRef("10101100")); + ASSERT_EQ(FVInt::fromString("1234567", 8).toString(8), StringRef("1234567")); + ASSERT_EQ(FVInt::fromString("1234567890", 10).toString(10), + StringRef("1234567890")); + ASSERT_EQ(FVInt::fromString("1234567890ABCDEF", 16).toString(16), + "1234567890ABCDEF"); + ASSERT_EQ(FVInt::fromString("1234567890abcdef", 16).toString(16, false), + "1234567890abcdef"); + + // Parsing/printing with unknown values. + ASSERT_EQ(FVInt::fromString("10XZ1XZ0", 2).toString(2), + StringRef("10XZ1XZ0")); + ASSERT_EQ(FVInt::fromString("10xz1xz0", 2).toString(2, false), + StringRef("10xz1xz0")); + ASSERT_EQ(FVInt::fromString("1234XZ567", 8).toString(8), + StringRef("1234XZ567")); + ASSERT_EQ(FVInt::fromString("1234xz567", 8).toString(8, false), + StringRef("1234xz567")); + ASSERT_EQ(FVInt::fromString("12345XZ67890ABCDEF", 16).toString(16), + StringRef("12345XZ67890ABCDEF")); + ASSERT_EQ(FVInt::fromString("12345xz67890abcdef", 16).toString(16, false), + StringRef("12345xz67890abcdef")); + + // Narrow <4 bit integers printed as hex. + ASSERT_EQ(FVInt::fromString("10", 2).toString(16), StringRef("2")); +} + +TEST(FVIntTest, LogicOps) { + auto a = FVInt::fromString("01XZ01XZ01XZ01XZ", 2); + auto b = FVInt::fromString("00001111XXXXZZZZ", 2); + auto c = FVInt::fromString("01XZ", 2); + + ASSERT_EQ(~c, FVInt::fromString("10XX", 2)); + ASSERT_EQ(a & b, FVInt::fromString("000001XX0XXX0XXX", 2)); + ASSERT_EQ(a | b, FVInt::fromString("01XX1111X1XXX1XX", 2)); + ASSERT_EQ(a ^ b, FVInt::fromString("01XX10XXXXXXXXXX", 2)); +} + +TEST(FVIntTest, ArithmeticOps) { + auto a = FVInt::fromString("123").zext(32); + auto b = FVInt::fromString("234").zext(32); + auto c = FVInt::fromString("1XZ", 16).zext(32); + + ASSERT_EQ(-a, uint32_t(-123)); + ASSERT_TRUE((-c).isAllX()); + + ASSERT_EQ(a + 1, FVInt::fromString("124").zext(32)); + ASSERT_EQ(1 + b, FVInt::fromString("235").zext(32)); + ASSERT_EQ(a + b, FVInt::fromString("357").zext(32)); + ASSERT_TRUE((a + c).isAllX()); + + ASSERT_EQ(a - 1, FVInt::fromString("122").zext(32)); + ASSERT_EQ(234 - a, FVInt::fromString("111").zext(32)); + ASSERT_EQ(b - a, FVInt::fromString("111").zext(32)); + ASSERT_TRUE((a - c).isAllX()); + + ASSERT_EQ(a * 2, FVInt::fromString("246").zext(32)); + ASSERT_EQ(2 * b, FVInt::fromString("468").zext(32)); + ASSERT_EQ(a * b, FVInt::fromString("28782").zext(32)); + ASSERT_TRUE((a * c).isAllX()); +} + +} // namespace diff --git a/utils/linkify.sh b/utils/linkify.sh new file mode 100755 index 000000000000..1113552eaaea --- /dev/null +++ b/utils/linkify.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# This script linkifies (i.e. makes clickable in the terminal) text that appears +# to be a pull request or issue reference (e.g. #12345 or PR12345) or a +# 40-character commit hash (e.g. abc123). You can configure git to automatically +# send the output of commands that pipe their output through a pager, such as +# `git log` and `git show`, through this script by running this command from +# within your CIRCT checkout: +# +# git config core.pager 'utils/linkify.sh | pager' +# +# Consider: +# +# git config core.pager 'utils/linkify.sh | ${PAGER:-less}' +# +# The pager command is run from the root of the repository even if the git +# command is run from a subdirectory, so the relative path should always work. +# +# It requires OSC 8 support in the terminal. For a list of compatible terminals, +# see https://github.com/Alhadis/OSC8-Adoption +# +# Copied from upstream LLVM's llvm/utils/git/linkify. + +sed \ + -e 's,\(#\|\bPR\)\([0-9]\+\),\x1b]8;;https://github.com/llvm/circt/issues/\2\x1b\\\0\x1b]8;;\x1b\\,gi' \ + -e 's,[0-9a-f]\{40\},\x1b]8;;https://github.com/llvm/circt/commit/\0\x1b\\\0\x1b]8;;\x1b\\,g'