diff --git a/docs/Dialects/Seq/RationaleSeq.md b/docs/Dialects/Seq/RationaleSeq.md index d2e8b24e01b4..ada4a05d0852 100644 --- a/docs/Dialects/Seq/RationaleSeq.md +++ b/docs/Dialects/Seq/RationaleSeq.md @@ -56,7 +56,7 @@ addressing, meaning that this operation sets / reads the entire value. - **reset**: Signal to set the state to 'resetValue'. Optional. - **resetValue**: A value which the state is set to upon reset. Required iff 'reset' is present. -- **powerOn**: A value which will be assigned to the register upon system power-on. +- **initialValue**: An initial value for registers. This represents a system power-on value in FPGA. - **name**: A name for the register, defaults to `""`. Inferred from the textual SSA value name, or passed explicitly in builder APIs. The name will be passed to the `sv.reg` during lowering. @@ -64,7 +64,7 @@ the `sv.reg` during lowering. passed to the `sv.reg` during lowering if present. ```mlir -%q = seq.compreg %input, %clk [ reset %reset, %resetValue ] [powerOn %powerOn] : $type(input) +%q = seq.compreg %input, %clk [ reset %reset, %resetValue ] [initial %initial] : $type(input) ``` Upon initialization, the state is defined to be uninitialized. @@ -329,3 +329,36 @@ The fifo operation is configurable with the following parameters: Like `seq.hlmem` there are no guarantees that all possible fifo configuration are able to be lowered. Available lowering passes will pattern match on the requested fifo configuration and attempt to provide a legal lowering. + +## State Initialization + +While ASICs might not have explicit initial values, they are crucial for simulation +and FPGA implementation. FPGA designs often require constant initial values, while +simulation allows for more complex initialization using expressions like function calls +`$random`, `$readmem`, and `$fopen`. + +In order to represent various kinds of initialization, `seq.initial` op and `!seq.immutable` type +are introduced. The `seq.initial` operation produces values with types wrapped in `!seq.immutable`. +The `!seq.immutable` type wrapper prevents initial values from depending on time-variant values. +Stateful operations typically require corresponding initial values with the `!seq.immutable` type. +This ensures that the initial state of the operation is well-defined and independent of time-variant factors. + +Example Input: +```mlir +%r_init, %u_init = seq.initial { + %rand = sv.macro.ref.se @RANDOM() : () -> i32 + %c0_i32 = hw.constant 0 : i32 + seq.yield %rand, %c0_i32 : i32, i32 +} : !seq.immutable, !seq.immutable + +%r = seq.compreg %i, %clk initial %r_init : i32 +%u = seq.compreg %i, %clk initial %u_init : i32 +``` + +Output Verilog: +```verilog +reg [31:0] r; +initial + r = `RANDOM; +reg [31:0] u = 32'h0; +``` diff --git a/include/circt-c/Dialect/Seq.h b/include/circt-c/Dialect/Seq.h index f6e00d34539c..d39f73659b77 100644 --- a/include/circt-c/Dialect/Seq.h +++ b/include/circt-c/Dialect/Seq.h @@ -24,6 +24,15 @@ MLIR_CAPI_EXPORTED bool seqTypeIsAClock(MlirType type); /// Creates an seq clock type MLIR_CAPI_EXPORTED MlirType seqClockTypeGet(MlirContext ctx); +/// If the type is an immutable type +MLIR_CAPI_EXPORTED bool seqTypeIsAImmutable(MlirType type); + +/// Creates a seq immutable type +MLIR_CAPI_EXPORTED MlirType seqImmutableTypeGet(MlirType type); + +/// Creates a seq immutable type +MLIR_CAPI_EXPORTED MlirType seqImmutableTypeGetInnerType(MlirType type); + #ifdef __cplusplus } #endif diff --git a/include/circt/Dialect/Seq/SeqOps.h b/include/circt/Dialect/Seq/SeqOps.h index 3d6f8ea29b2c..ac09831be4bb 100644 --- a/include/circt/Dialect/Seq/SeqOps.h +++ b/include/circt/Dialect/Seq/SeqOps.h @@ -59,6 +59,17 @@ struct FirMemory { FirMemory(hw::HWModuleGeneratedOp op); }; +// Helper functions to create constant initial values. +mlir::TypedValue +createConstantInitialValue(OpBuilder builder, Location loc, + mlir::IntegerAttr attr); +mlir::TypedValue +createConstantInitialValue(OpBuilder builder, Operation *constantLike); + +// Helper function to unwrap an immutable type value and get yield value in +// initial op. +Value unwrapImmutableValue(mlir::TypedValue immutableVal); + } // namespace seq } // namespace circt diff --git a/include/circt/Dialect/Seq/SeqOps.td b/include/circt/Dialect/Seq/SeqOps.td index c93a50fefb65..7f05ffdb84df 100644 --- a/include/circt/Dialect/Seq/SeqOps.td +++ b/include/circt/Dialect/Seq/SeqOps.td @@ -14,6 +14,7 @@ include "circt/Dialect/HW/HWOpInterfaces.td" include "circt/Dialect/HW/HWTypes.td" +include "circt/Dialect/Sim/SimTypes.td" include "circt/Dialect/Seq/SeqAttributes.td" include "circt/Dialect/Seq/SeqOpInterfaces.td" include "mlir/Interfaces/InferTypeOpInterface.td" @@ -39,16 +40,16 @@ def CompRegOp : SeqOp<"compreg", OptionalAttr:$name, Optional:$reset, Optional:$resetValue, - Optional:$powerOnValue, + Optional:$initialValue, OptionalAttr:$inner_sym ); let results = (outs AnyType:$data); let assemblyFormat = [{ (`sym` $inner_sym^)? `` custom($name) $input `,` $clk (`reset` $reset^ `,` $resetValue)? - (`powerOn` $powerOnValue^)? attr-dict `:` type($data) + (`initial` $initialValue^)? attr-dict `:` type($data) custom(ref(type($data)), ref($resetValue), type($resetValue)) - custom(ref(type($data)), ref($powerOnValue), type($powerOnValue)) + custom(ref(type($data)), ref($initialValue), type($initialValue)) }]; let hasVerifier = 1; @@ -58,22 +59,24 @@ def CompRegOp : SeqOp<"compreg", return build($_builder, $_state, input.getType(), input, clk, /*name*/ StringAttr(), /*reset*/ Value(), /*resetValue*/ Value(), - /*powerOnValue*/ Value(), hw::InnerSymAttr()); + /*initialValue*/ Value(), + hw::InnerSymAttr()); }]>, /// Create a register with an inner_sym matching the register's name. OpBuilder<(ins "Value":$input, "Value":$clk, "StringAttrOrRef":$name), [{ auto nameAttr = name.get($_builder.getContext()); return build($_builder, $_state, input.getType(), input, clk, nameAttr, /*reset*/ Value(), /*resetValue*/ Value(), - /*powerOnValue*/ Value(), hw::InnerSymAttr::get(nameAttr)); + /*initialValue*/ Value(), + hw::InnerSymAttr::get(nameAttr)); }]>, /// Create a register with a reset, with an inner_sym matching the - /// register's name, and optional power-on value. + /// register's name, and optional power-on value. OpBuilder<(ins "Value":$input, "Value":$clk, "Value":$reset, "Value":$rstValue, - "StringAttrOrRef":$name, CArg<"Value", "{}">:$powerOnValue), [{ + "StringAttrOrRef":$name, CArg<"Value", "{}">:$initialValue), [{ auto nameAttr = name.get($_builder.getContext()); return build($_builder, $_state, input.getType(), input, clk, nameAttr, - reset, rstValue, powerOnValue, hw::InnerSymAttr::get(nameAttr)); + reset, rstValue, initialValue, hw::InnerSymAttr::get(nameAttr)); }]> ]; } @@ -96,16 +99,16 @@ def CompRegClockEnabledOp : SeqOp<"compreg.ce", OptionalAttr:$name, Optional:$reset, Optional:$resetValue, - Optional:$powerOnValue, + Optional:$initialValue, OptionalAttr:$inner_sym ); let results = (outs AnyType:$data); let assemblyFormat = [{ (`sym` $inner_sym^)? `` custom($name) $input `,` $clk `,` $clockEnable (`reset` $reset^ `,` $resetValue)? - (`powerOn` $powerOnValue^)? attr-dict `:` type($data) + (`initial` $initialValue^)? attr-dict `:` type($data) custom(ref(type($data)), ref($resetValue), type($resetValue)) - custom(ref(type($data)), ref($powerOnValue), type($powerOnValue)) + custom(ref(type($data)), ref($initialValue), type($initialValue)) }]; let hasVerifier = 1; @@ -120,11 +123,11 @@ def CompRegClockEnabledOp : SeqOp<"compreg.ce", }]>, OpBuilder<(ins "Value":$input, "Value":$clk, "Value":$ce, "Value":$reset, "Value":$rstValue, "StringRef":$name, - CArg<"Value", "{}">:$powerOnValue), + CArg<"Value", "{}">:$initialValue), [{ auto nameAttr = StringAttr::get($_builder.getContext(), name); return build($_builder, $_state, input.getType(), input, clk, ce, - nameAttr, reset, rstValue, powerOnValue, + nameAttr, reset, rstValue, initialValue, hw::InnerSymAttr::get(nameAttr)); }]>, ]; @@ -697,3 +700,43 @@ def FromClockOp : SeqOp<"from_clock", [Pure]> { let hasFolder = 1; let hasCanonicalizeMethod = 1; } + +def InitialOp : SeqOp<"initial", [SingleBlock, + SingleBlockImplicitTerminator<"YieldOp">, + RecursivelySpeculatable, + RecursiveMemoryEffects, IsolatedFromAbove]> { + let summary = "Operation that produces values for initialization"; + let description = [{ + `seq.initial` op creates values wrapped types with !seq.immutable. + See the Seq dialect rationale for a longer description. + }]; + + let arguments = (ins); + let results = (outs Variadic); // seq.immutable values + let regions = (region SizedRegion<1>:$body); + let hasVerifier = 1; + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins CArg<"TypeRange", "{}">:$resultTypes, CArg<"std::function", "{}">:$ctor)> + ]; + + let assemblyFormat = [{ + $body attr-dict `:` type(results) + }]; + + let extraClassDeclaration = [{ + Block *getBodyBlock() { return &getBody().front(); } + }]; +} + +def YieldOp : SeqOp<"yield", + [Pure, Terminator, HasParent<"InitialOp">]> { + let summary = "Yield values"; + + let arguments = (ins Variadic:$operands); + let builders = [ + OpBuilder<(ins), "build($_builder, $_state, std::nullopt);"> + ]; + + let assemblyFormat = "attr-dict ($operands^ `:` type($operands))?"; +} diff --git a/include/circt/Dialect/Seq/SeqTypes.td b/include/circt/Dialect/Seq/SeqTypes.td index bddee693f443..776871d2af6c 100644 --- a/include/circt/Dialect/Seq/SeqTypes.td +++ b/include/circt/Dialect/Seq/SeqTypes.td @@ -93,4 +93,25 @@ def ClockType : SeqType<"Clock"> { let mnemonic = "clock"; } +def ImmutableTypeImpl : SeqType<"Immutable"> { + let mnemonic = "immutable"; + + let summary = "Value type that is immutable after initialization"; + let parameters = (ins "::mlir::Type":$innerType); + + let assemblyFormat = "`<` $innerType `>`"; + + let builders = [ + TypeBuilderWithInferredContext<(ins "Type":$innerType), [{ + auto *ctx = innerType.getContext(); + return $_get(ctx, innerType); + }]> + ]; +} + +// A handle to refer to circt::seq::ImmutableType in ODS. +def ImmutableType : DialectType($_self)">, + "an ImmutableType", "::circt::seq::ImmutableType">; + #endif // CIRCT_DIALECT_SEQ_SEQTYPES diff --git a/integration_test/Bindings/Python/dialects/seq.py b/integration_test/Bindings/Python/dialects/seq.py index de6befbc96c1..db37bcc9bdaf 100644 --- a/integration_test/Bindings/Python/dialects/seq.py +++ b/integration_test/Bindings/Python/dialects/seq.py @@ -24,11 +24,14 @@ def top(module): # CHECK: %[[RESET_VAL:.+]] = hw.constant 0 reg_reset = hw.ConstantOp.create(i32, 0).result - # CHECK: %[[POWERON_VAL:.+]] = hw.constant 42 poweron_value = hw.ConstantOp.create(i32, 42).result # CHECK: %[[INPUT_VAL:.+]] = hw.constant 45 reg_input = hw.ConstantOp.create(i32, 45).result - # CHECK: %[[DATA_VAL:.+]] = seq.compreg %[[INPUT_VAL]], %clk reset %rst, %[[RESET_VAL]] powerOn %[[POWERON_VAL]] + # CHECK-NEXT: %[[POWERON_VAL:.+]] = seq.initial { + # CHECK-NEXT: %[[C42:.+]] = hw.constant 42 : i32 + # CHECK-NEXT: seq.yield %[[C42]] : i32 + # CHECK-NEXT: } : !seq.immutable + # CHECK: %[[DATA_VAL:.+]] = seq.compreg %[[INPUT_VAL]], %clk reset %rst, %[[RESET_VAL]] initial %[[POWERON_VAL]] reg = seq.CompRegOp(i32, reg_input, module.clk, diff --git a/lib/Bindings/Python/SeqModule.cpp b/lib/Bindings/Python/SeqModule.cpp index d9ccf730e5f2..97d6ebcff968 100644 --- a/lib/Bindings/Python/SeqModule.cpp +++ b/lib/Bindings/Python/SeqModule.cpp @@ -33,4 +33,13 @@ void circt::python::populateDialectSeqSubmodule(py::module &m) { return cls(seqClockTypeGet(ctx)); }, py::arg("cls"), py::arg("context") = py::none()); + + mlir_type_subclass(m, "ImmutableType", seqTypeIsAImmutable) + .def_classmethod("get", + [](py::object cls, MlirType innerType) { + return cls(seqImmutableTypeGet(innerType)); + }) + .def_property_readonly("inner_type", [](MlirType self) { + return seqImmutableTypeGetInnerType(self); + }); } diff --git a/lib/Bindings/Python/dialects/seq.py b/lib/Bindings/Python/dialects/seq.py index 35eba447898d..548418702b4b 100644 --- a/lib/Bindings/Python/dialects/seq.py +++ b/lib/Bindings/Python/dialects/seq.py @@ -3,13 +3,14 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception from . import hw +from . import seq from .._mlir_libs._circt._seq import * from ..dialects._ods_common import _cext as _ods_cext -from ..ir import IntegerType, OpView, StringAttr +from ..ir import IntegerType, OpView, StringAttr, InsertionPoint from ..support import BackedgeBuilder, NamedValueOpView from ._seq_ops_gen import * from ._seq_ops_gen import _Dialect -from .seq import CompRegOp +from .seq import CompRegOp, InitialOp # Create a computational register whose input is the given value, and is clocked @@ -92,6 +93,21 @@ def __init__(self, operands += [None, None] if power_on_value is not None: + if isinstance(power_on_value.type, seq.ImmutableType): + pass + else: + if power_on_value.owner is None: + assert False, "Initial value must not be port" + elif isinstance(power_on_value.owner.opview, hw.ConstantOp): + init = InitialOp([seq.ImmutableType.get(power_on_value.type)]) + init.body.blocks.append() + with InsertionPoint(init.body.blocks[0]): + cloned_constant = power_on_value.owner.clone() + seq.YieldOp(cloned_constant) + + power_on_value = init.results[0] + else: + assert False, "Non-constant initial value not supported" operands.append(power_on_value) operand_segment_sizes.append(1) else: diff --git a/lib/CAPI/Dialect/Seq.cpp b/lib/CAPI/Dialect/Seq.cpp index 424aab03fb3d..108fb4f8d594 100644 --- a/lib/CAPI/Dialect/Seq.cpp +++ b/lib/CAPI/Dialect/Seq.cpp @@ -26,3 +26,15 @@ bool seqTypeIsAClock(MlirType type) { MlirType seqClockTypeGet(MlirContext ctx) { return wrap(ClockType::get(unwrap(ctx))); } + +bool seqTypeIsAImmutable(MlirType type) { + return llvm::isa(unwrap(type)); +} + +MlirType seqImmutableTypeGet(MlirType innerType) { + return wrap(ImmutableType::get(unwrap(innerType))); +} + +MlirType seqImmutableTypeGetInnerType(MlirType type) { + return wrap(llvm::cast(unwrap(type)).getInnerType()); +} diff --git a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp index a8c84afda534..366c9c37981a 100644 --- a/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp +++ b/lib/Conversion/ConvertToArcs/ConvertToArcs.cpp @@ -26,10 +26,27 @@ using llvm::MapVector; static bool isArcBreakingOp(Operation *op) { return op->hasTrait() || isa(op) || + seq::InitialOp, seq::ClockGateOp, sim::DPICallOp>(op) || op->getNumResults() > 1; } +static LogicalResult convertInitialValue(seq::CompRegOp reg, + SmallVectorImpl &values) { + if (!reg.getInitialValue()) + return values.push_back({}), success(); + + auto init = circt::seq::unwrapImmutableValue(reg.getInitialValue()); + if (!init.getDefiningOp()) + return reg.emitError() << "non-constant initial value not supported"; + + // Clone the initial value to the top-level. + auto *op = init.getDefiningOp()->clone(); + reg->getBlock()->getOperations().insert(Block::iterator(reg), op); + auto result = op->getResult(0); + values.push_back(result); + return success(); +} + //===----------------------------------------------------------------------===// // Conversion //===----------------------------------------------------------------------===// @@ -83,7 +100,12 @@ LogicalResult Converter::runOnModule(HWModuleOp module) { // Find all arc-breaking operations in this module and assign them an index. arcBreakers.clear(); arcBreakerIndices.clear(); + SmallVector initialOps; for (Operation &op : *module.getBodyBlock()) { + if (isa(&op)) { + initialOps.push_back(cast(&op)); + continue; + } if (op.getNumRegions() > 0) return op.emitOpError("has regions; not supported by ConvertToArcs"); if (!isArcBreakingOp(&op) && !isa(&op)) @@ -109,6 +131,10 @@ LogicalResult Converter::runOnModule(HWModuleOp module) { extractArcs(module); if (failed(absorbRegs(module))) return failure(); + + for (auto init : initialOps) + init->erase(); + return success(); } @@ -308,7 +334,8 @@ LogicalResult Converter::absorbRegs(HWModuleOp module) { } } - initialValues.push_back(regOp.getPowerOnValue()); + if (failed(convertInitialValue(regOp, initialValues))) + return failure(); absorbedRegs.push_back(regOp); // If we absorb a register into the arc, the arc effectively produces that @@ -421,7 +448,8 @@ 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()); + if (failed(convertInitialValue(regOp, initialValues))) + return failure(); } regToOutputMapping.push_back(it->second); } diff --git a/lib/Conversion/HWToBTOR2/HWToBTOR2.cpp b/lib/Conversion/HWToBTOR2/HWToBTOR2.cpp index 357f17eec249..dd0fce8fc143 100644 --- a/lib/Conversion/HWToBTOR2/HWToBTOR2.cpp +++ b/lib/Conversion/HWToBTOR2/HWToBTOR2.cpp @@ -874,24 +874,26 @@ struct ConvertHWToBTOR2Pass // Check for initial values which must be emitted before the state in // btor2 - Value pov = reg.getPowerOnValue(); + auto init = reg.getInitialValue(); // Generate state instruction (represents the register declaration) genState(reg, w, regName); - if (pov) { - // Check that the powerOn value is a non-null constant - if (!isa_and_nonnull(pov.getDefiningOp())) + if (init) { + // Check that the initial value is a non-null constant + auto initialConstant = circt::seq::unwrapImmutableValue(init) + .getDefiningOp(); + if (!initialConstant) reg->emitError("PowerOn Value must be constant!!"); // Visit the powerOn Value to generate the constant - dispatchTypeOpVisitor(pov.getDefiningOp()); + dispatchTypeOpVisitor(initialConstant); // Add it to the list of visited operations - handledOps.insert(pov.getDefiningOp()); + handledOps.insert(initialConstant); // Finally generate the init statement - genInit(reg, pov, w); + genInit(reg, initialConstant, w); } // Record the operation for future `next` instruction generation @@ -914,7 +916,8 @@ struct ConvertHWToBTOR2Pass .Case( + seq::InitialOp, sv::AlwaysFFOp, seq::FromClockOp, seq::InitialOp, + seq::YieldOp, hw::OutputOp, hw::HWModuleOp>( [&](auto expr) { ignore(op); }) // Make sure that the design only contains one clock diff --git a/lib/Conversion/LTLToCore/LTLToCore.cpp b/lib/Conversion/LTLToCore/LTLToCore.cpp index d4a3d1a8aa4e..b6482ac3e19a 100644 --- a/lib/Conversion/LTLToCore/LTLToCore.cpp +++ b/lib/Conversion/LTLToCore/LTLToCore.cpp @@ -53,7 +53,8 @@ struct HasBeenResetOpConversion : OpConversionPattern { ConversionPatternRewriter &rewriter) const override { auto i1 = rewriter.getI1Type(); // Generate the constant used to set the register value - Value constZero = rewriter.create(op.getLoc(), i1, 0); + Value constZero = seq::createConstantInitialValue( + rewriter, op->getLoc(), rewriter.getIntegerAttr(i1, 0)); // Generate the constant used to enegate the Value constOne = rewriter.create(op.getLoc(), i1, 1); diff --git a/lib/Conversion/PipelineToHW/PipelineToHW.cpp b/lib/Conversion/PipelineToHW/PipelineToHW.cpp index 2b03be21ca22..5c0d1a2ed2d7 100644 --- a/lib/Conversion/PipelineToHW/PipelineToHW.cpp +++ b/lib/Conversion/PipelineToHW/PipelineToHW.cpp @@ -374,8 +374,8 @@ class PipelineInlineLowering : public PipelineLowering { } else { auto stageRegPrefix = getStagePrefix(stageIndex); auto enableRegName = (stageRegPrefix.strref() + "_enable").str(); - Value enableRegResetVal = - builder.create(loc, APInt(1, 0, false)).getResult(); + hw::ConstantOp enableRegResetVal = + builder.create(loc, APInt(1, 0, false)); switch (stageKind) { case StageKind::Continuous: @@ -406,7 +406,9 @@ class PipelineInlineLowering : public PipelineLowering { if (enablePowerOnValues) { llvm::TypeSwitch(stageEnabled.getDefiningOp()) .Case([&](auto op) { - op.getPowerOnValueMutable().assign(enableRegResetVal); + op.getInitialValueMutable().assign( + circt::seq::createConstantInitialValue(builder, + enableRegResetVal)); }); } } diff --git a/lib/Conversion/SeqToSV/SeqToSV.cpp b/lib/Conversion/SeqToSV/SeqToSV.cpp index e0668acee135..6faa2a64c052 100644 --- a/lib/Conversion/SeqToSV/SeqToSV.cpp +++ b/lib/Conversion/SeqToSV/SeqToSV.cpp @@ -57,15 +57,100 @@ struct SeqToSVPass : public impl::LowerSeqToSVBase { } // namespace circt namespace { +struct ModuleLoweringState { + ModuleLoweringState(HWModuleOp module) + : initalOpLowering(module), module(module) {} + + struct InitialOpLowering { + InitialOpLowering(hw::HWModuleOp module) + : builder(module.getModuleBody()), module(module) {} + + ~InitialOpLowering() { + for (auto [placeHolder, _] : mapping) + placeHolder.getDefiningOp()->erase(); + } + + // Lower initial ops. + LogicalResult lower(); + LogicalResult lower(seq::InitialOp initialOp); + + Value + lookupImmutableValue(mlir::TypedValue immut) const { + return mapping.lookup(immut); + } + + sv::InitialOp getSVInitial() const { return svInitialOp; } + + private: + sv::InitialOp svInitialOp = {}; + // A mapping from a dummy immutable value to the actual initial value + // defined in SV initial op. + MapVector, Value> mapping; + + OpBuilder builder; + hw::HWModuleOp module; + } initalOpLowering; + + struct FragmentInfo { + bool needsRegFragment = false; + bool needsMemFragment = false; + } fragment; + + HWModuleOp module; +}; + +LogicalResult ModuleLoweringState::InitialOpLowering::lower() { + auto loweringFailed = module + .walk([&](seq::InitialOp initialOp) { + if (failed(lower(initialOp))) + return mlir::WalkResult::interrupt(); + return mlir::WalkResult::advance(); + }) + .wasInterrupted(); + return LogicalResult::failure(loweringFailed); +} + +LogicalResult +ModuleLoweringState::InitialOpLowering::lower(seq::InitialOp initialOp) { + if (!svInitialOp) + svInitialOp = builder.create(initialOp->getLoc()); + auto loc = initialOp.getLoc(); + llvm::SmallVector results; + + auto yieldOp = cast(initialOp.getBodyBlock()->getTerminator()); + + for (auto [result, operand] : + llvm::zip(initialOp.getResults(), yieldOp->getOperands())) { + auto placeholder = + builder + .create( + loc, ArrayRef{result.getType()}, ArrayRef{}) + ->getResult(0); + result.replaceAllUsesWith(placeholder); + mapping.insert( + {cast>(placeholder), operand}); + } + + svInitialOp.getBodyBlock()->getOperations().splice( + svInitialOp.begin(), initialOp.getBodyBlock()->getOperations()); + + assert(initialOp->use_empty()); + initialOp->erase(); + yieldOp->erase(); + return success(); +} + /// Lower CompRegOp to `sv.reg` and `sv.alwaysff`. Use a posedge clock and /// synchronous reset. template class CompRegLower : public OpConversionPattern { public: - CompRegLower(TypeConverter &typeConverter, MLIRContext *context, - bool lowerToAlwaysFF) + CompRegLower( + TypeConverter &typeConverter, MLIRContext *context, bool lowerToAlwaysFF, + const MapVector &moduleLoweringStates) : OpConversionPattern(typeConverter, context), - lowerToAlwaysFF(lowerToAlwaysFF) {} + lowerToAlwaysFF(lowerToAlwaysFF), + moduleLoweringStates(moduleLoweringStates) {} using OpAdaptor = typename OpConversionPattern::OpAdaptor; @@ -76,10 +161,9 @@ class CompRegLower : public OpConversionPattern { auto regTy = ConversionPattern::getTypeConverter()->convertType(reg.getType()); - auto svReg = rewriter.create(loc, regTy, reg.getNameAttr(), - reg.getInnerSymAttr(), - reg.getPowerOnValue()); + reg.getInnerSymAttr()); + svReg->setDialectAttrs(reg->getDialectAttrs()); circt::sv::setSVAttributes(svReg, circt::sv::getSVAttributes(reg)); @@ -116,6 +200,28 @@ class CompRegLower : public OpConversionPattern { } } + // Lower initial values. + if (auto init = reg.getInitialValue()) { + auto module = reg->template getParentOfType(); + const auto &initial = + moduleLoweringStates.find(module.getModuleNameAttr()) + ->second.initalOpLowering; + + Value initialValue = initial.lookupImmutableValue(init); + + if (auto op = initialValue.getDefiningOp(); + op && op->hasTrait()) { + auto clonedConstant = rewriter.clone(*op); + rewriter.moveOpBefore(clonedConstant, svReg); + svReg.getInitMutable().assign(clonedConstant->getResult(0)); + } else { + OpBuilder::InsertionGuard guard(rewriter); + auto in = initial.getSVInitial(); + rewriter.setInsertionPointToEnd(in.getBodyBlock()); + rewriter.create(reg->getLoc(), svReg, initialValue); + } + } + rewriter.replaceOp(reg, regVal); return success(); } @@ -126,6 +232,7 @@ class CompRegLower : public OpConversionPattern { private: bool lowerToAlwaysFF; + const MapVector &moduleLoweringStates; }; /// Create the assign. @@ -226,6 +333,7 @@ class ClockMuxLowering : public OpConversionPattern { struct SeqToSVTypeConverter : public TypeConverter { SeqToSVTypeConverter() { addConversion([&](Type type) { return type; }); + addConversion([&](seq::ImmutableType type) { return type.getInnerType(); }); addConversion([&](seq::ClockType type) { return IntegerType::get(type.getContext(), 1); }); @@ -420,39 +528,40 @@ void SeqToSVPass::runOnOperation() { } // Lower memories and registers in modules in parallel. - bool needsRegRandomization = false; - bool needsMemRandomization = false; - - struct FragmentInfo { - bool needsRegFragment; - bool needsMemFragment; - }; - DenseMap moduleFragmentInfo; - llvm::sys::SmartMutex fragmentsMutex; - - mlir::parallelForEach(&getContext(), modules, [&](HWModuleOp module) { - SeqToSVTypeConverter typeConverter; - FirRegLowering regLowering(typeConverter, module, disableRegRandomization, - emitSeparateAlwaysBlocks); - regLowering.lower(); - if (regLowering.needsRegRandomization()) { - if (!disableRegRandomization) { - llvm::sys::SmartScopedLock lock(fragmentsMutex); - moduleFragmentInfo[module].needsRegFragment = true; - } - needsRegRandomization = true; - } - numSubaccessRestored += regLowering.numSubaccessRestored; - - if (auto *it = memsByModule.find(module); it != memsByModule.end()) { - memLowering.lowerMemoriesInModule(module, it->second); - if (!disableMemRandomization) { - llvm::sys::SmartScopedLock lock(fragmentsMutex); - moduleFragmentInfo[module].needsMemFragment = true; - } - needsMemRandomization = true; - } - }); + std::atomic needsRegRandomization = false; + std::atomic needsMemRandomization = false; + + MapVector moduleLoweringStates; + for (auto module : circuit.getOps()) + moduleLoweringStates.try_emplace(module.getModuleNameAttr(), + ModuleLoweringState(module)); + + mlir::parallelForEach( + &getContext(), moduleLoweringStates, [&](auto &moduleAndState) { + auto &state = moduleAndState.second; + auto module = state.module; + SeqToSVTypeConverter typeConverter; + FirRegLowering regLowering(typeConverter, module, + disableRegRandomization, + emitSeparateAlwaysBlocks); + regLowering.lower(); + if (regLowering.needsRegRandomization()) { + if (!disableRegRandomization) { + state.fragment.needsRegFragment = true; + } + needsRegRandomization = true; + } + numSubaccessRestored += regLowering.numSubaccessRestored; + + if (auto *it = memsByModule.find(module); it != memsByModule.end()) { + memLowering.lowerMemoriesInModule(module, it->second); + if (!disableMemRandomization) { + state.fragment.needsMemFragment = true; + } + needsMemRandomization = true; + } + (void)state.initalOpLowering.lower(); + }); auto randomInitFragmentName = FlatSymbolRefAttr::get(context, "RANDOM_INIT_FRAGMENT"); @@ -461,11 +570,15 @@ void SeqToSVPass::runOnOperation() { auto randomInitMemFragmentName = FlatSymbolRefAttr::get(context, "RANDOM_INIT_MEM_FRAGMENT"); - for (auto &[module, info] : moduleFragmentInfo) { - assert((info.needsRegFragment || info.needsMemFragment) && - "module should use memories or registers"); + for (auto &[_, state] : moduleLoweringStates) { + const auto &info = state.fragment; + if (!info.needsRegFragment && !info.needsMemFragment) { + // If neither is emitted, just skip it. + continue; + } SmallVector fragmentAttrs; + auto module = state.module; if (auto others = module->getAttrOfType(emit::getFragmentsAttrName())) fragmentAttrs = llvm::to_vector(others); @@ -487,10 +600,10 @@ void SeqToSVPass::runOnOperation() { target.markUnknownOpDynamicallyLegal(isLegalOp); RewritePatternSet patterns(context); - patterns.add>(typeConverter, context, - lowerToAlwaysFF); - patterns.add>(typeConverter, context, - lowerToAlwaysFF); + patterns.add>(typeConverter, context, lowerToAlwaysFF, + moduleLoweringStates); + patterns.add>( + typeConverter, context, lowerToAlwaysFF, moduleLoweringStates); patterns.add>(typeConverter, context); patterns.add>(typeConverter, context); patterns.add(typeConverter, context); diff --git a/lib/Dialect/Arc/Transforms/StripSV.cpp b/lib/Dialect/Arc/Transforms/StripSV.cpp index dbe400e81bfb..21232439c628 100644 --- a/lib/Dialect/Arc/Transforms/StripSV.cpp +++ b/lib/Dialect/Arc/Transforms/StripSV.cpp @@ -156,13 +156,14 @@ void StripSVPass::runOnOperation() { 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())); + presetValue = circt::seq::createConstantInitialValue( + builder, reg.getLoc(), + IntegerAttr::get(reg.getType(), *reg.getPreset())); } Value compReg = builder.create( reg.getLoc(), next.getType(), next, reg.getClk(), reg.getNameAttr(), - Value{}, Value{}, /*powerOnValue*/ presetValue, + Value{}, Value{}, /*initialValue*/ presetValue, reg.getInnerSymAttr()); reg.replaceAllUsesWith(compReg); opsToDelete.push_back(reg); diff --git a/lib/Dialect/Seq/SeqOps.cpp b/lib/Dialect/Seq/SeqOps.cpp index 4428cab6fd77..be93665a9f35 100644 --- a/lib/Dialect/Seq/SeqOps.cpp +++ b/lib/Dialect/Seq/SeqOps.cpp @@ -12,6 +12,7 @@ #include "circt/Dialect/Seq/SeqOps.h" #include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/Sim/SimTypes.h" #include "circt/Support/CustomDirectiveImpl.h" #include "circt/Support/FoldUtils.h" #include "mlir/IR/Builders.h" @@ -84,6 +85,20 @@ static void printOptionalTypeMatch(OpAsmPrinter &p, Operation *op, Type refType, // Nothing to do - this is strictly an implicit parsing helper. } +static ParseResult parseOptionalImmutableTypeMatch( + OpAsmParser &parser, Type refType, + std::optional operand, Type &type) { + if (operand) + type = seq::ImmutableType::get(refType); + return success(); +} + +static void printOptionalImmutableTypeMatch(OpAsmPrinter &p, Operation *op, + Type refType, Value operand, + Type type) { + // Nothing to do - this is strictly an implicit parsing helper. +} + //===----------------------------------------------------------------------===// // ReadPortOp //===----------------------------------------------------------------------===// @@ -1004,6 +1019,63 @@ FirMemory::FirMemory(hw::HWModuleGeneratedOp op) { initIsInline = op->getAttrOfType("initIsInline").getValue(); } +LogicalResult InitialOp::verify() { + auto *terminator = this->getBody().front().getTerminator(); + if (terminator->getOperands().size() != getNumResults()) + return emitError() << "result type doesn't match with the terminator"; + for (auto [lhs, rhs] : + llvm::zip(terminator->getOperands().getTypes(), getResultTypes())) { + if (cast(rhs).getInnerType() != lhs) + return emitError() << cast(rhs).getInnerType() + << " is expected but got " << lhs; + } + + return success(); +} +void InitialOp::build(OpBuilder &builder, OperationState &result, + TypeRange resultTypes, std::function ctor) { + OpBuilder::InsertionGuard guard(builder); + + builder.createBlock(result.addRegion()); + SmallVector types; + for (auto t : resultTypes) + types.push_back(seq::ImmutableType::get(t)); + + result.addTypes(types); + + if (ctor) + ctor(); +} + +TypedValue +circt::seq::createConstantInitialValue(OpBuilder builder, Location loc, + mlir::IntegerAttr attr) { + auto initial = builder.create(loc, attr.getType(), [&]() { + auto constant = builder.create(loc, attr); + builder.create(loc, ArrayRef{constant}); + }); + return cast>(initial->getResult(0)); +} + +mlir::TypedValue +circt::seq::createConstantInitialValue(OpBuilder builder, Operation *op) { + assert(op->getNumResults() == 1 && + op->hasTrait()); + auto initial = + builder.create(op->getLoc(), op->getResultTypes(), [&]() { + auto clonedOp = builder.clone(*op); + builder.create(op->getLoc(), clonedOp->getResults()); + }); + return cast>(initial.getResult(0)); +} + +Value circt::seq::unwrapImmutableValue(TypedValue value) { + auto resultNum = cast(value).getResultNumber(); + auto initialOp = value.getDefiningOp(); + assert(initialOp); + return initialOp.getBodyBlock()->getTerminator()->getOperand(resultNum); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/Seq/Transforms/LowerSeqShiftReg.cpp b/lib/Dialect/Seq/Transforms/LowerSeqShiftReg.cpp index 100114a2e312..8eace7fe6b03 100644 --- a/lib/Dialect/Seq/Transforms/LowerSeqShiftReg.cpp +++ b/lib/Dialect/Seq/Transforms/LowerSeqShiftReg.cpp @@ -36,18 +36,27 @@ struct ShiftRegLowering : public OpConversionPattern { ConversionPatternRewriter &rewriter) const final { Value in = adaptor.getInput(); auto baseName = op.getName(); + Value init = {}; + if (auto powerOn = adaptor.getPowerOnValue()) { + if (auto op = powerOn.getDefiningOp()) { + if (op->hasTrait()) + init = createConstantInitialValue(rewriter, op); + } + + if (!init) + return op->emitError() << "non-constant initial value is not supported"; + } + for (size_t i = 0; i < op.getNumElements(); ++i) { StringAttr name; if (baseName.has_value()) name = rewriter.getStringAttr(baseName.value() + "_sh" + Twine(i + 1)); in = rewriter.create( op.getLoc(), in, adaptor.getClk(), adaptor.getClockEnable(), - adaptor.getReset(), adaptor.getResetValue(), name, - op.getPowerOnValue()); + adaptor.getReset(), adaptor.getResetValue(), name, init); } - op.replaceAllUsesWith(in); - rewriter.eraseOp(op); + rewriter.replaceOp(op, in); return success(); } }; @@ -64,7 +73,7 @@ void LowerSeqShiftRegPass::runOnOperation() { ConversionTarget target(ctxt); target.addIllegalOp(); - target.addLegalDialect(); + target.addLegalDialect(); RewritePatternSet patterns(&ctxt); patterns.add(&ctxt); diff --git a/lib/Tools/circt-bmc/ExternalizeRegisters.cpp b/lib/Tools/circt-bmc/ExternalizeRegisters.cpp index 0393ff760dda..1003f0ebe218 100644 --- a/lib/Tools/circt-bmc/ExternalizeRegisters.cpp +++ b/lib/Tools/circt-bmc/ExternalizeRegisters.cpp @@ -93,8 +93,8 @@ void ExternalizeRegistersPass::runOnOperation() { regOp.emitError("registers with reset signals not yet supported"); return signalPassFailure(); } - if (regOp.getPowerOnValue()) { - regOp.emitError("registers with power-on values not yet supported"); + if (regOp.getInitialValue()) { + regOp.emitError("registers with initial values not yet supported"); return signalPassFailure(); } addedInputs[module.getSymNameAttr()].push_back(regOp.getType()); diff --git a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir index 047a82f59eb3..3f1fa6b67751 100644 --- a/test/Conversion/ConvertToArcs/convert-to-arcs.mlir +++ b/test/Conversion/ConvertToArcs/convert-to-arcs.mlir @@ -120,22 +120,26 @@ hw.module.extern private @Reshuffling2(out z0: i4, out z1: i4, out z2: i4, out z // 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: hw.instance "x" @Reshuffling2() // 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 + %init0, %init1, %init2 = seq.initial { + %cst1 = hw.constant 1 : i4 + %cst2 = hw.constant 2 : i4 + %cst3 = hw.constant 3 : i4 + seq.yield %cst1, %cst2, %cst3 : i4, i4, i4 + } : !seq.immutable, !seq.immutable, !seq.immutable + %5 = seq.compreg %x.z1, %clockA initial %init0 : i4 + %6 = seq.compreg %x.z2, %clockB initial %init1 : i4 + %7 = seq.compreg %x.z3, %clockB initial %init2 : i4 hw.output %4, %5, %6, %7 : i4, i4, i4, i4 } // CHECK-NEXT: } @@ -236,8 +240,11 @@ hw.module @TrivialWithInit(in %clock: !seq.clock, in %i0: i4, in %reset: i1, out // 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 + %init = seq.initial { + %cst2 = hw.constant 2 : i4 + seq.yield %cst2: i4 + } : !seq.immutable + %foo = seq.compreg %i0, %clock reset %reset, %0 initial %init: i4 hw.output %foo : i4 } // CHECK-NEXT: } diff --git a/test/Conversion/HWToBTOR2/init.mlir b/test/Conversion/HWToBTOR2/init.mlir index 9975b4b04dd8..b133816207cc 100644 --- a/test/Conversion/HWToBTOR2/init.mlir +++ b/test/Conversion/HWToBTOR2/init.mlir @@ -10,9 +10,14 @@ module { //CHECK: [[NID3:[0-9]+]] constd [[NID0]] 0 %false = hw.constant false //CHECK: [[INIT:[0-9]+]] init [[NID0]] [[NID2]] [[NID3]] - %reg = seq.compreg %false, %clock reset %reset, %false powerOn %false : i1 + %init = seq.initial { + %false_0 = hw.constant false + seq.yield %false_0 : i1 + } : !seq.immutable + //CHECK: [[RESET:[0-9]+]] constd [[NID0]] 0 + %reg = seq.compreg %false, %clock reset %reset, %false initial %init : i1 - //CHECK: [[NID4:[0-9]+]] eq [[NID0]] [[NID2]] [[NID3]] + //CHECK: [[NID4:[0-9]+]] eq [[NID0]] [[NID2]] [[RESET]] %10 = comb.icmp bin eq %reg, %false : i1 sv.always posedge %0 { @@ -20,7 +25,7 @@ module { //CHECK: [[NID6:[0-9]+]] bad [[NID5]] sv.assert %10, immediate } - //CHECK: [[NID7:[0-9]+]] ite [[NID0]] [[NID1]] [[NID3]] [[NID3]] + //CHECK: [[NID7:[0-9]+]] ite [[NID0]] [[NID1]] [[RESET]] [[RESET]] //CHECK: [[NID8:[0-9]+]] next [[NID0]] [[NID2]] [[NID7]] } diff --git a/test/Conversion/LTLToCore/assertproperty.mlir b/test/Conversion/LTLToCore/assertproperty.mlir index aac8141796ec..b49263007032 100644 --- a/test/Conversion/LTLToCore/assertproperty.mlir +++ b/test/Conversion/LTLToCore/assertproperty.mlir @@ -6,11 +6,14 @@ module { hw.module @test(in %clock : !seq.clock, in %reset : i1, in %a : i1) { //CHECK: [[CLK:%.+]] = seq.from_clock %clock %0 = seq.from_clock %clock + // CHECK-NEXT: %[[INIT:.+]] = seq.initial { + // CHECK-NEXT: %false = hw.constant false + // CHECK-NEXT: seq.yield %false : i1 + // CHECK-NEXT: } : !seq.immutable - //CHECK: %false = hw.constant false //CHECK: %true = hw.constant true //CHECK: [[TMP:%.+]] = comb.or %reset, %hbr : i1 - //CHECK: %hbr = seq.compreg [[TMP]], %clock powerOn %false : i1 + //CHECK: %hbr = seq.compreg [[TMP]], %clock initial %[[INIT]] : i1 %1 = verif.has_been_reset %0, sync %reset //CHECK: [[TMP1:%.+]] = comb.xor %reset, %true : i1 diff --git a/test/Conversion/PipelineToHW/test_poweron.mlir b/test/Conversion/PipelineToHW/test_poweron.mlir index 367cdf57f23a..d9f311a8a9b8 100644 --- a/test/Conversion/PipelineToHW/test_poweron.mlir +++ b/test/Conversion/PipelineToHW/test_poweron.mlir @@ -1,17 +1,21 @@ // RUN: circt-opt --lower-pipeline-to-hw="enable-poweron-values" %s | FileCheck %s -// CHECK-LABEL: hw.module @testPowerOn(in +// CHECK-LABEL: hw.module @testinitial(in // CHECK-SAME: %[[VAL_0:.*]] : i32, in %[[VAL_1:.*]] : i32, in %[[VAL_2:.*]] : i1, in %[[VAL_3:.*]] : !seq.clock, in %[[VAL_4:.*]] : i1, out out0 : i32, out out1 : i1) { // CHECK: %[[VAL_5:.*]] = comb.sub %[[VAL_0]], %[[VAL_1]] : i32 // CHECK: %[[VAL_6:.*]] = seq.compreg sym @p0_stage0_reg0 %[[VAL_5]], %[[VAL_3]] : i32 // CHECK: %[[VAL_7:.*]] = seq.compreg sym @p0_stage0_reg1 %[[VAL_0]], %[[VAL_3]] : i32 // CHECK: %[[VAL_8:.*]] = hw.constant false -// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]] reset %[[VAL_4]], %[[VAL_8]] powerOn %[[VAL_8]] : i1 +// CHECK: %[[VAL_9:.*]] = seq.compreg sym @p0_stage1_enable %[[VAL_2]], %[[VAL_3]] reset %[[VAL_4]], %[[VAL_8]] initial %[[INIT:.+]] : i1 +// CHECK: %[[INIT]] = seq.initial { +// CHECK: %false_0 = hw.constant false +// CHECK: seq.yield %false_0 : i1 +// CHECK: } : !seq.immutable // CHECK: %[[VAL_10:.*]] = comb.add %[[VAL_6]], %[[VAL_7]] : i32 // CHECK: hw.output %[[VAL_10]], %[[VAL_9]] : i32, i1 // CHECK: } -hw.module @testPowerOn(in %arg0: i32, in %arg1: i32, in %go: i1, in %clk: !seq.clock, in %rst: i1, out out0: i32, out out1: i1) { +hw.module @testinitial(in %arg0: i32, in %arg1: i32, in %go: i1, in %clk: !seq.clock, in %rst: i1, out out0: i32, out out1: i1) { %0:2 = pipeline.scheduled(%a0 : i32 = %arg0, %a1 : i32 = %arg1) clock(%clk) reset(%rst) go(%go) entryEn(%s0_enable) -> (out: i32){ %1 = comb.sub %a0,%a1 : i32 pipeline.stage ^bb1 regs(%1 : i32, %a0 : i32) diff --git a/test/Dialect/Seq/compreg.mlir b/test/Dialect/Seq/compreg.mlir index 71a3c44008c3..bebff7ff1dcb 100644 --- a/test/Dialect/Seq/compreg.mlir +++ b/test/Dialect/Seq/compreg.mlir @@ -2,9 +2,8 @@ // RUN: circt-opt %s -verify-diagnostics --lower-seq-to-sv | circt-opt -verify-diagnostics | FileCheck %s --check-prefix=SV // RUN: circt-opt %s -verify-diagnostics --lower-seq-to-sv='lower-to-always-ff=false' | FileCheck %s --check-prefix=ALWAYS hw.module @top(in %clk: !seq.clock, in %rst: i1, in %i: i32, in %s: !hw.struct) { - %rv = hw.constant 0 : i32 - %r0 = seq.compreg %i, %clk reset %rst, %rv : i32 + %r0 = seq.compreg %i, %clk reset %rst, %c0_i32 : i32 seq.compreg %i, %clk : i32 // CHECK: %{{.+}} = seq.compreg %i, %clk reset %rst, %c0_i32 : i32 // CHECK: %{{.+}} = seq.compreg %i, %clk : i32 @@ -73,12 +72,23 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1, in %i: i32, in %s: !hw.struct + %rv = seq.initial { + %c0_i32_0 = hw.constant 0 : i32 + seq.yield %c0_i32_0 : i32 + } : !seq.immutable + + %c0_i32 = hw.constant 0 : i32 + + %withinitial = seq.compreg sym @withinitial %i, %clk reset %rst, %c0_i32 initial %rv : i32 + // SV: %withinitial = sv.reg init %{{c0_i32.*}} sym @withinitial : !hw.inout } hw.module @top_ce(in %clk: !seq.clock, in %rst: i1, in %ce: i1, in %i: i32) { %rv = hw.constant 0 : i32 + %init = seq.initial { + %c0_i32 = hw.constant 0 : i32 + seq.yield %c0_i32 : i32 + } : !seq.immutable %r0 = seq.compreg.ce %i, %clk, %ce reset %rst, %rv : i32 // CHECK: %r0 = seq.compreg.ce %i, %clk, %ce reset %rst, %c0_i32 : i32 @@ -103,8 +113,8 @@ hw.module @top_ce(in %clk: !seq.clock, in %rst: i1, in %ce: i1, in %i: i32) { // ALWAYS: } // ALWAYS: } - %withPowerOn = seq.compreg.ce sym @withPowerOn %i, %clk, %ce reset %rst, %rv powerOn %rv : i32 - // SV: %withPowerOn = sv.reg init %c0_i32 sym @withPowerOn : !hw.inout + %withinitial = seq.compreg.ce sym @withinitial %i, %clk, %ce reset %rst, %rv initial %init : i32 + // SV: %withinitial = sv.reg init %{{c0_i32.*}} sym @withinitial : !hw.inout } // SV-LABEL: @reg_of_clock_type @@ -126,3 +136,19 @@ hw.module @reg_of_clock_type(in %clk: !seq.clock, in %rst: i1, in %i: !seq.clock // SV: hw.output [[REG1_VAL]] : i1 hw.output %r1 : !seq.clock } + +hw.module @init_with_call(in %clk: !seq.clock, in %rst: i1, in %i: i32, in %s: !hw.struct) { + // SV: sv.initial { + // SV-NEXT: %1 = sv.system "random"() : () -> i32 + // SV-NEXT: sv.bpassign %reg, %1 : i32 + // SV-NEXT: } + %init = seq.initial { + %rand = sv.system "random"() : () -> i32 + seq.yield %rand : i32 + } : !seq.immutable + + // SV: %reg = sv.reg : !hw.inout + %c0_i32 = hw.constant 0 : i32 + + %reg = seq.compreg %i, %clk initial %init : i32 +} diff --git a/test/Dialect/Seq/errors.mlir b/test/Dialect/Seq/errors.mlir index b2f56142ae37..5a547ba32193 100644 --- a/test/Dialect/Seq/errors.mlir +++ b/test/Dialect/Seq/errors.mlir @@ -18,3 +18,23 @@ hw.module @fifo3(in %clk : !seq.clock, in %rst : i1, in %in : i32, in %rdEn : i1 // expected-error @+1 {{'seq.fifo' op almost empty threshold must be <= FIFO depth}} %out, %full, %empty, %almostFull, %almostEmpty = seq.fifo depth 3 almost_full 1 almost_empty 4 in %in rdEn %rdEn wrEn %wrEn clk %clk rst %rst : i32 } + +// ----- + +hw.module @init() { + // expected-error @+1 {{result type doesn't match with the terminator}} + %0 = seq.initial { + %1 = hw.constant 32: i32 + seq.yield %1, %1: i32, i32 + }: !seq.immutable +} + +// ----- + +hw.module @init() { + // expected-error @+1 {{'i32' is expected but got 'i16'}} + %0 = seq.initial { + %1 = hw.constant 32: i16 + seq.yield %1: i16 + }: !seq.immutable +} diff --git a/test/Dialect/Seq/round-trip.mlir b/test/Dialect/Seq/round-trip.mlir index 8be9ae7c305d..eae52047d482 100644 --- a/test/Dialect/Seq/round-trip.mlir +++ b/test/Dialect/Seq/round-trip.mlir @@ -97,3 +97,12 @@ hw.module @clock_inv(in %clock: !seq.clock) { // CHECK: seq.clock_inv %clock %inv = seq.clock_inv %clock } + +// CHECK-LABEL: @init +hw.module @init() { + // CHECK-NEXT: seq.initial + %0 = seq.initial { + %1 = hw.constant 32: i32 + seq.yield %1: i32 + }: !seq.immutable +} diff --git a/test/Dialect/Seq/shiftreg.mlir b/test/Dialect/Seq/shiftreg.mlir index eda06e4a7ce1..931da38c4b87 100644 --- a/test/Dialect/Seq/shiftreg.mlir +++ b/test/Dialect/Seq/shiftreg.mlir @@ -7,9 +7,13 @@ // LO: %r0_sh1 = seq.compreg.ce sym @r0_sh1 %i, %clk, %ce : i32 // LO: %r0_sh2 = seq.compreg.ce sym @r0_sh2 %r0_sh1, %clk, %ce : i32 // LO: %r0_sh3 = seq.compreg.ce sym @r0_sh3 %r0_sh2, %clk, %ce : i32 -// LO: %myShiftReg_sh1 = seq.compreg.ce sym @myShiftReg_sh1 %i, %clk, %ce reset %rst, %c0_i32 powerOn %c0_i32 : i32 -// LO: %myShiftReg_sh2 = seq.compreg.ce sym @myShiftReg_sh2 %myShiftReg_sh1, %clk, %ce reset %rst, %c0_i32 powerOn %c0_i32 : i32 -// LO: %myShiftReg_sh3 = seq.compreg.ce sym @myShiftReg_sh3 %myShiftReg_sh2, %clk, %ce reset %rst, %c0_i32 powerOn %c0_i32 : i32 +// LO %0 = seq.initial { +// LO %c0_i32_0 = hw.constant 0 : i32 +// LO seq.yield %c0_i32_0 : i32 +// LO } : !seq.immutable +// LO: %myShiftReg_sh1 = seq.compreg.ce sym @myShiftReg_sh1 %i, %clk, %ce reset %rst, %c0_i32 initial %0 : i32 +// LO: %myShiftReg_sh2 = seq.compreg.ce sym @myShiftReg_sh2 %myShiftReg_sh1, %clk, %ce reset %rst, %c0_i32 initial %0 : i32 +// LO: %myShiftReg_sh3 = seq.compreg.ce sym @myShiftReg_sh3 %myShiftReg_sh2, %clk, %ce reset %rst, %c0_i32 initial %0 : i32 // LO: hw.output %r0_sh3, %myShiftReg_sh3 : i32, i32 hw.module @top(in %clk: !seq.clock, in %rst: i1, in %ce: i1, in %i: i32, out out1 : i32, out out2 : i32) { diff --git a/test/Tools/circt-bmc/externalize-registers-errors.mlir b/test/Tools/circt-bmc/externalize-registers-errors.mlir index 37bf76aa1deb..2371c30c07a5 100644 --- a/test/Tools/circt-bmc/externalize-registers-errors.mlir +++ b/test/Tools/circt-bmc/externalize-registers-errors.mlir @@ -27,9 +27,13 @@ hw.module @reg_with_reset(in %clk: !seq.clock, in %rst: i1, in %in: i32, out out // ----- -hw.module @reg_with_poweron(in %clk: !seq.clock, in %in: i32, out out: i32) { - %c0_i32 = hw.constant 0 : i32 - // expected-error @below {{registers with power-on values not yet supported}} - %1 = seq.compreg %in, %clk powerOn %c0_i32 : i32 +hw.module @reg_with_initial(in %clk: !seq.clock, in %in: i32, out out: i32) { + %init = seq.initial { + %c0_i32 = hw.constant 0 : i32 + seq.yield %c0_i32 : i32 + } : !seq.immutable + + // expected-error @below {{registers with initial values not yet supported}} + %1 = seq.compreg %in, %clk initial %init : i32 hw.output %1 : i32 }