diff --git a/include/circt-c/Firtool/Firtool.h b/include/circt-c/Firtool/Firtool.h index beec1f681d..140adda59b 100644 --- a/include/circt-c/Firtool/Firtool.h +++ b/include/circt-c/Firtool/Firtool.h @@ -236,11 +236,11 @@ MLIR_CAPI_EXPORTED MlirLogicalResult circtFirtoolPopulatePreprocessTransforms( MlirPassManager pm, CirctFirtoolFirtoolOptions options); MLIR_CAPI_EXPORTED MlirLogicalResult circtFirtoolPopulateCHIRRTLToLowFIRRTL( - MlirPassManager pm, CirctFirtoolFirtoolOptions options, - MlirStringRef inputFilename); + MlirPassManager pm, CirctFirtoolFirtoolOptions options); MLIR_CAPI_EXPORTED MlirLogicalResult circtFirtoolPopulateLowFIRRTLToHW( - MlirPassManager pm, CirctFirtoolFirtoolOptions options); + MlirPassManager pm, CirctFirtoolFirtoolOptions options, + MlirStringRef inputFilename); MLIR_CAPI_EXPORTED MlirLogicalResult circtFirtoolPopulateHWToSV( MlirPassManager pm, CirctFirtoolFirtoolOptions options); diff --git a/include/circt/Firtool/Firtool.h b/include/circt/Firtool/Firtool.h index e4aa798fde..480d966c8b 100644 --- a/include/circt/Firtool/Firtool.h +++ b/include/circt/Firtool/Firtool.h @@ -444,11 +444,11 @@ LogicalResult populatePreprocessTransforms(mlir::PassManager &pm, const FirtoolOptions &opt); LogicalResult populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, - const FirtoolOptions &opt, - StringRef inputFilename); + const FirtoolOptions &opt); LogicalResult populateLowFIRRTLToHW(mlir::PassManager &pm, - const FirtoolOptions &opt); + const FirtoolOptions &opt, + StringRef inputFilename); LogicalResult populateHWToSV(mlir::PassManager &pm, const FirtoolOptions &opt); diff --git a/lib/CAPI/Firtool/Firtool.cpp b/lib/CAPI/Firtool/Firtool.cpp index 94808e3c60..118004fa35 100644 --- a/lib/CAPI/Firtool/Firtool.cpp +++ b/lib/CAPI/Firtool/Firtool.cpp @@ -340,16 +340,17 @@ circtFirtoolPopulatePreprocessTransforms(MlirPassManager pm, MlirLogicalResult circtFirtoolPopulateCHIRRTLToLowFIRRTL(MlirPassManager pm, - CirctFirtoolFirtoolOptions options, - MlirStringRef inputFilename) { - return wrap(firtool::populateCHIRRTLToLowFIRRTL(*unwrap(pm), *unwrap(options), - unwrap(inputFilename))); + CirctFirtoolFirtoolOptions options) { + return wrap( + firtool::populateCHIRRTLToLowFIRRTL(*unwrap(pm), *unwrap(options))); } MlirLogicalResult circtFirtoolPopulateLowFIRRTLToHW(MlirPassManager pm, - CirctFirtoolFirtoolOptions options) { - return wrap(firtool::populateLowFIRRTLToHW(*unwrap(pm), *unwrap(options))); + CirctFirtoolFirtoolOptions options, + MlirStringRef inputFilename) { + return wrap(firtool::populateLowFIRRTLToHW(*unwrap(pm), *unwrap(options), + unwrap(inputFilename))); } MlirLogicalResult diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index 5d10a8c29e..cd7eac672e 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -13,6 +13,7 @@ #include "circt/Dialect/FIRRTL/FIRRTLUtils.h" #include "circt/Dialect/FIRRTL/Namespace.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Dialect/HW/HierPathCache.h" #include "circt/Dialect/HW/InnerSymbolNamespace.h" #include "circt/Dialect/SV/SVOps.h" #include "circt/Support/Utils.h" @@ -52,6 +53,18 @@ struct ConnectInfo { /// for modules and files, as well as by convention. enum class Delimiter { BindModule = '_', BindFile = '-', InlineMacro = '$' }; +/// This struct contains pre-allocated "safe" names that parallel regions can +/// use to create names in the global namespace. This is allocated per-layer +/// block. +struct LayerBlockGlobals { + + /// If the layer needs to create a module, use this name. + StringRef moduleName; + + /// If the layer needs to create a hw::HierPathOp, use this name. + StringRef hierPathName; +}; + } // namespace // A mapping of an old InnerRefAttr to the new inner symbol and module name that @@ -98,6 +111,16 @@ static SmallString<32> moduleNameForLayer(StringRef moduleName, return result; } +static SmallString<32> hierPathNameForLayer(StringRef moduleName, + SymbolRefAttr layerName) { + SmallString<32> result("__lowerLayers_path"); + appendName(moduleName, result, /*toLower=*/false, + /*delimiter=*/Delimiter::BindModule); + appendName(layerName, result, /*toLower=*/false, + /*delimiter=*/Delimiter::BindModule); + return result; +} + /// For a layerblock `@A::@B::@C`, /// the generated instance is called `a_b_c`. static SmallString<32> instanceNameForLayer(SymbolRefAttr layerName) { @@ -158,8 +181,7 @@ class LowerLayersPass /// Safely build a new module with a given namehint. This handles geting a /// lock to modify the top-level circuit. - FModuleOp buildNewModule(OpBuilder &builder, LayerBlockOp layerBlock, - SmallVectorImpl &ports); + FModuleOp buildNewModule(OpBuilder &builder, LayerBlockOp layerBlock); /// Strip layer colors from the module's interface. FailureOr runOnModuleLike(FModuleLike moduleLike); @@ -174,10 +196,6 @@ class LowerLayersPass /// Update the value's type to remove any layers from any probe types. void removeLayersFromValue(Value value); - /// Remove any layers from the result of the cast. If the cast becomes a nop, - /// remove the cast itself from the IR. - void removeLayersFromRefCast(RefCastOp cast); - /// Lower an inline layerblock to an ifdef block. void lowerInlineLayerBlock(LayerOp layer, LayerBlockOp layerBlock); @@ -195,14 +213,18 @@ class LowerLayersPass /// Indicates exclusive access to modify the circuitNamespace and the circuit. llvm::sys::SmartMutex *circuitMutex; - /// A map of layer blocks to module name that should be created for it. - DenseMap moduleNames; + /// A map of layer blocks to "safe" global names which are fine to create in + /// the circuit namespace. + DenseMap layerBlockGlobals; /// A map from inline layers to their macro names. DenseMap macroNames; /// A mapping of symbol name to layer operation. DenseMap symbolToLayer; + + /// Utility for creating hw::HierPathOp. + hw::HierPathCache *hierPathCache; }; /// Multi-process safe function to build a module in the circuit and return it. @@ -210,15 +232,14 @@ class LowerLayersPass /// generated if there are conflicts with the namehint in the circuit-level /// namespace. FModuleOp LowerLayersPass::buildNewModule(OpBuilder &builder, - LayerBlockOp layerBlock, - SmallVectorImpl &ports) { + LayerBlockOp layerBlock) { auto location = layerBlock.getLoc(); - auto namehint = moduleNames.lookup(layerBlock); + auto namehint = layerBlockGlobals.lookup(layerBlock).moduleName; llvm::sys::SmartScopedLock instrumentationLock(*circuitMutex); FModuleOp newModule = builder.create( location, builder.getStringAttr(namehint), - ConventionAttr::get(builder.getContext(), Convention::Internal), ports, - ArrayAttr{}); + ConventionAttr::get(builder.getContext(), Convention::Internal), + ArrayRef{}, ArrayAttr{}); if (auto dir = getOutputFile(layerBlock.getLayerNameAttr())) { assert(dir.isDirectory()); newModule->setAttr("output_file", dir); @@ -234,22 +255,6 @@ void LowerLayersPass::removeLayersFromValue(Value value) { value.setType(type.removeLayer()); } -void LowerLayersPass::removeLayersFromRefCast(RefCastOp cast) { - auto result = cast.getResult(); - auto oldType = result.getType(); - if (oldType.getLayer()) { - auto input = cast.getInput(); - auto srcType = input.getType(); - auto newType = oldType.removeLayer(); - if (newType == srcType) { - result.replaceAllUsesWith(input); - cast->erase(); - return; - } - result.setType(newType); - } -} - void LowerLayersPass::removeLayersFromPorts(FModuleLike moduleLike) { auto oldTypeAttrs = moduleLike.getPortTypesAttr(); SmallVector newTypeAttrs; @@ -324,47 +329,180 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, StringRef circuitName = circuitOp.getName(); hw::InnerSymbolNamespace ns(moduleOp); + // A cache of nodes we've created for values which are captured, but cannot + // have a symbol attached to them. This is done to avoid creating the same + // node multiple times. + DenseMap nodeCache; + + // Get or create a node within this module. This is used when we need to + // create an XMRDerefOp to an expression. + auto getOrCreateNodeOp = [&](Value operand, ImplicitLocOpBuilder &builder, + StringRef nameHint) -> NodeOp { + auto it = nodeCache.find(operand); + if (it != nodeCache.end()) + return it->getSecond(); + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointAfter(operand.getDefiningOp()); + return nodeCache + .insert({operand, + builder.create(operand.getLoc(), operand, nameHint)}) + .first->getSecond(); + }; + + // Determine the replacement for an operand within the current region. Keep a + // densemap of replacements around to avoid creating the same hardware + // multiple times. + DenseMap replacements; + auto getReplacement = [&](Operation *user, Value value) -> Value { + auto it = replacements.find(value); + if (it != replacements.end()) + return it->getSecond(); + + ImplicitLocOpBuilder localBuilder(value.getLoc(), &getContext()); + Value replacement; + + auto layerBlockOp = user->getParentOfType(); + localBuilder.setInsertionPointToStart(layerBlockOp.getBody()); + + // If the operand is "special", e.g., it has no XMR representation, then we + // need to clone it. + // + // TODO: Change this to recursively clone. This will matter once FString + // operations have operands. + if (type_isa(value.getType())) { + localBuilder.setInsertionPoint(user); + replacement = localBuilder.clone(*value.getDefiningOp())->getResult(0); + replacements.insert({value, replacement}); + return replacement; + } + + // If the operand is an XMR ref, then we _have_ to clone it. + auto *definingOp = value.getDefiningOp(); + if (isa_and_present(definingOp)) { + replacement = localBuilder.clone(*definingOp)->getResult(0); + replacements.insert({value, replacement}); + return replacement; + } + + // Determine the replacement value for the captured operand. There are + // three cases that can occur: + // + // 1. Capturing something zero-width. Create a zero-width constant zero. + // 2. Capture an expression or instance port. Drop a node and XMR deref + // that. + // 3. Capture something that can handle an inner sym. XMR deref that. + // + // Note: (3) can be either an operation or a _module_ port. + auto baseType = type_cast(value.getType()); + if (baseType && baseType.getBitWidthOrSentinel() == 0) { + OpBuilder::InsertionGuard guard(localBuilder); + auto zeroUIntType = UIntType::get(localBuilder.getContext(), 0); + replacement = localBuilder.createOrFold( + value.getType(), localBuilder.create( + zeroUIntType, getIntZerosAttr(zeroUIntType))); + } else { + auto *definingOp = value.getDefiningOp(); + hw::InnerRefAttr innerRef; + if (definingOp) { + // TODO: Always create a node. This is a trade-off between + // optimizations and dead code. By adding the node, this allows the + // original path to be better optimized, but will leave dead code in the + // design. If the node is not created, then the output is less + // optimized. Err on the side of dead code. This _should_ be able to + // be removed eventually [1]. A node should only be necessary for + // operations which cannot have a symbol attached to them, i.e., + // expressions or instance ports. + // + // [1]: https://github.com/llvm/circt/issues/8426 + definingOp = getOrCreateNodeOp(value, localBuilder, "_layer_probe"); + innerRef = getInnerRefTo( + definingOp, [&](auto) -> hw::InnerSymbolNamespace & { return ns; }); + } else { + auto portIdx = cast(value).getArgNumber(); + innerRef = getInnerRefTo( + cast(*moduleOp), portIdx, + [&](auto) -> hw::InnerSymbolNamespace & { return ns; }); + } + + hw::HierPathOp hierPathOp; + { + // TODO: Move to before parallel region to avoid the lock. + auto insertPoint = OpBuilder::InsertPoint(moduleOp->getBlock(), + Block::iterator(moduleOp)); + llvm::sys::SmartScopedLock circuitLock(*circuitMutex); + hierPathOp = hierPathCache->getOrCreatePath( + localBuilder.getArrayAttr({innerRef}), localBuilder.getLoc(), + insertPoint, layerBlockGlobals.lookup(layerBlockOp).hierPathName); + hierPathOp.setVisibility(SymbolTable::Visibility::Private); + } + + replacement = localBuilder.create( + value.getType(), hierPathOp.getSymNameAttr()); + } + + replacements.insert({value, replacement}); + + return replacement; + }; + // A map of instance ops to modules that this pass creates. This is used to // check if this was an instance that we created and to do fast module // dereferencing (avoiding a symbol table). DenseMap createdInstances; - // Post-order traversal that expands a layer block into its parent. For each - // layer block found do the following: + // Check that the preconditions for this pass are met. Reject any ops which + // must have been removed before this runs. + auto opPreconditionCheck = [](Operation *op) -> LogicalResult { + // LowerXMR op removal postconditions. + if (isa(op)) + return op->emitOpError() + << "cannot be handled by the lower-layers pass. This should have " + "already been removed by the lower-xmr pass."; + + return success(); + }; + + // Post-order traversal that expands a layer block into its parent. Because of + // the pass precondition that this runs _after_ `LowerXMR`, not much has to + // happen here. All of the following do happen, though: // - // 1. Create and connect one ref-type output port for each value defined in - // this layer block that drives an instance marked lowerToBind and move - // this instance outside the layer block. - // 2. Create one input port for each value captured by this layer block. - // 3. Create a new module for this layer block and move the (mutated) body of - // 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. + // 1. Any layer coloring is stripped. + // 2. Layers with Inline convention are converted to SV ifdefs. + // 3. Layers with Bind convention are converted to new modules and then + // instantiated at their original location. Any captured values are either + // moved, cloned, or converted to XMR deref ops. + // 4. Move instances created from earlier (3) conversions out of later (3) + // conversions. This is necessary to avoid a SystemVerilog-illegal + // bind-under-bind. (See Section 23.11 of 1800-2023.) + // 5. Keep track of special ops (ops with inner symbols or verbatims) which + // need to have something updated because of the new instance hierarchy + // being created. + // + // Remember, this is post-order, in-order. Child layer blocks are visited + // before parents. Any nested regions _within_ the layer block are also + // visited before the outer layer block. auto result = moduleOp.walk([&](Operation *op) { + if (failed(opPreconditionCheck(op))) + return WalkResult::interrupt(); + // Strip layer requirements from any op that might represent a probe. if (auto wire = dyn_cast(op)) { removeLayersFromValue(wire.getResult()); return WalkResult::advance(); } - if (auto sub = dyn_cast(op)) { - removeLayersFromValue(sub.getResult()); - return WalkResult::advance(); - } if (auto instance = dyn_cast(op)) { instance.setLayers({}); for (auto result : instance.getResults()) removeLayersFromValue(result); return WalkResult::advance(); } - if (auto cast = dyn_cast(op)) { - removeLayersFromRefCast(cast); - return WalkResult::advance(); - } auto layerBlock = dyn_cast(op); if (!layerBlock) return WalkResult::advance(); + // After this point, we are dealing with a layer block. auto layer = symbolToLayer.lookup(layerBlock.getLayerName()); if (layer.getConvention() == LayerConvention::Inline) { @@ -372,116 +510,73 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, return WalkResult::advance(); } + // After this point, we are dealing with a bind convention layer block. assert(layer.getConvention() == LayerConvention::Bind); - Block *body = layerBlock.getBody(0); + + // Clear the replacements so that none are re-used across layer blocks. + replacements.clear(); OpBuilder builder(moduleOp); - - // Ports that need to be created for the module derived from this layer - // block. - SmallVector ports; - - // Connection that need to be made to the instance of the derived module. - SmallVector connectValues; - - // Create an input port for an operand that is captured from outside. - auto createInputPort = [&](Value operand, Location loc) { - const auto &[portName, _] = getFieldName(FieldRef(operand, 0), true); - - // If the value is a ref, we must resolve the ref inside the parent, - // passing the input as a value instead of a ref. Inside the layer, we - // convert (ref.send) the value back into a ref. - auto type = operand.getType(); - if (auto refType = dyn_cast(type)) - type = refType.getType(); - - ports.push_back({builder.getStringAttr(portName), type, Direction::In, - /*symName=*/{}, - /*location=*/loc}); - // Update the layer block's body with arguments as we will swap this body - // into the module when we create it. If this is a ref type, then add a - // refsend to convert from the non-ref type input port. - Value replacement = body->addArgument(type, loc); - if (isa(operand.getType())) { - OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointToStart(body); - replacement = builder.create(loc, replacement); - } - operand.replaceUsesWithIf(replacement, [&](OpOperand &use) { - auto *user = use.getOwner(); - if (!layerBlock->isAncestor(user)) - return false; - if (auto connectLike = dyn_cast(user)) { - if (use.getOperandNumber() == 0) - return false; - } - return true; - }); - - connectValues.push_back({operand, ConnectKind::NonRef}); - }; - - // Set the location intelligently. Use the location of the capture if this - // is a port created for forwarding from a parent layer block to a nested - // layer block. Otherwise, use unknown. - auto getPortLoc = [&](Value port) -> Location { - Location loc = UnknownLoc::get(port.getContext()); - if (auto *destOp = port.getDefiningOp()) - if (auto instOp = dyn_cast(destOp)) { - auto modOpIt = createdInstances.find(instOp); - if (modOpIt != createdInstances.end()) { - auto portNum = cast(port).getResultNumber(); - loc = modOpIt->getSecond().getPortLocation(portNum); - } - } - return loc; - }; - - // Create an output probe port port and adds a ref.define/ref.send to - // drive the port if this was not already capturing a ref type. - auto createOutputPort = [&](Value dest, Value src) { - auto loc = getPortLoc(dest); - auto portNum = ports.size(); - const auto &[portName, _] = getFieldName(FieldRef(dest, 0), true); - - RefType refType; - if (auto oldRef = dyn_cast(dest.getType())) - refType = oldRef; - else - refType = RefType::get( - type_cast(dest.getType()).getPassiveType(), - /*forceable=*/false); - - ports.push_back({builder.getStringAttr(portName), refType, Direction::Out, - /*symName=*/{}, /*location=*/loc}); - Value replacement = body->addArgument(refType, loc); - if (isa(dest.getType())) { - dest.replaceUsesWithIf(replacement, [&](OpOperand &use) { - auto *user = use.getOwner(); - if (!layerBlock->isAncestor(user)) - return false; - if (auto connectLike = dyn_cast(user)) { - if (use.getOperandNumber() == 0) - return true; - } - return false; - }); - connectValues.push_back({dest, ConnectKind::Ref}); - return; - } - connectValues.push_back({dest, ConnectKind::NonRef}); - OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointAfterValue(src); - builder.create( - loc, body->getArgument(portNum), - builder.create(loc, src)->getResult(0)); - }; - SmallVector innerSyms; - SmallVector rwprobes; SmallVector verbatims; + DenseSet spilledSubOps; auto layerBlockWalkResult = layerBlock.walk([&](Operation *op) { + // Error if pass preconditions are not met. + if (failed(opPreconditionCheck(op))) + return WalkResult::interrupt(); + + // Specialized handling of subfields, subindexes, and subaccesses which + // need to be spilled and nodes that referred to spilled nodes. If these + // are kept in the module, then the XMR is going to be bidirectional. 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.) Additionally, if any nodes are seen that are + // transparently referencing a spilled op, spill the node, too. The node + // provides an anchor for an inner symbol (which subfield, subindex, and + // subaccess do not). + if (isa(op)) { + auto input = op->getOperand(0); + if (!firrtl::type_cast(input.getType()).isPassive() && + !isAncestorOfValueOwner(layerBlock, input)) { + op->moveBefore(layerBlock); + spilledSubOps.insert(op); + } + return WalkResult::advance(); + } + if (auto subOp = dyn_cast(op)) { + auto input = subOp.getInput(); + if (firrtl::type_cast(input.getType()).isPassive()) + return WalkResult::advance(); + + if (!isAncestorOfValueOwner(layerBlock, input) && + !isAncestorOfValueOwner(layerBlock, subOp.getIndex())) { + subOp->moveBefore(layerBlock); + spilledSubOps.insert(op); + return WalkResult::advance(); + } + 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(); + } + if (auto nodeOp = dyn_cast(op)) { + auto *definingOp = nodeOp.getInput().getDefiningOp(); + if (definingOp && + spilledSubOps.contains(nodeOp.getInput().getDefiningOp())) { + op->moveBefore(layerBlock); + return WalkResult::advance(); + } + } + // Record any operations inside the layer block which have inner symbols. // Theses may have symbol users which need to be updated. + // + // Note: this needs to _not_ index spilled NodeOps above. if (auto symOp = dyn_cast(op)) if (auto innerSym = symOp.getInnerSymAttr()) innerSyms.push_back(innerSym); @@ -511,119 +606,19 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, return WalkResult::advance(); } - // 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); - return WalkResult::advance(); - } - - if (auto subOp = dyn_cast(op)) { - auto input = subOp.getInput(); - if (firrtl::type_cast(input.getType()).isPassive()) - return WalkResult::advance(); - - if (!isAncestorOfValueOwner(layerBlock, input) && - !isAncestorOfValueOwner(layerBlock, subOp.getIndex())) { - subOp->moveBefore(layerBlock); - return WalkResult::advance(); - } - 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(); - } - - if (auto rwprobe = dyn_cast(op)) { - rwprobes.push_back(rwprobe); - return WalkResult::advance(); - } - - if (auto connect = dyn_cast(op)) { - auto src = connect.getSrc(); - auto dst = connect.getDest(); - auto srcInLayerBlock = isAncestorOfValueOwner(layerBlock, src); - auto dstInLayerBlock = isAncestorOfValueOwner(layerBlock, dst); - if (!srcInLayerBlock && !dstInLayerBlock) { - connect->moveBefore(layerBlock); - return WalkResult::advance(); - } - // Create an input port. - if (!srcInLayerBlock) { - createInputPort(src, op->getLoc()); - return WalkResult::advance(); - } - // Create an output port. - if (!dstInLayerBlock) { - createOutputPort(dst, src); - if (!isa(dst.getType())) - connect.erase(); - return WalkResult::advance(); - } - // Source and destination in layer block. Nothing to do. - return WalkResult::advance(); - } - - // Pre-emptively de-squiggle connections that we are creating. This will - // later be cleaned up by the de-squiggling pass. However, there is no - // point in creating deeply squiggled connections if we don't have to. - // - // This pattern matches the following structure. Move the ref.resolve - // outside the layer block. The matchingconnect will be moved outside in - // the next loop iteration: - // %0 = ... - // %1 = ... - // firrtl.layerblock { - // %2 = ref.resolve %0 - // firrtl.matchingconnect %1, %2 - // } - if (auto refResolve = dyn_cast(op)) - if (refResolve.getResult().hasOneUse() && - refResolve.getRef().getParentBlock() != body) - if (auto connect = dyn_cast( - *refResolve.getResult().getUsers().begin())) - if (connect.getDest().getParentBlock() != body) { - refResolve->moveBefore(layerBlock); - return WalkResult::advance(); - } - - // For any other ops, create input ports for any captured operands. + // Handle captures. For any captured operands, convert them to a suitable + // replacement value. The `getReplacement` function will automatically + // reuse values whenever possible. for (size_t i = 0, e = op->getNumOperands(); i != e; ++i) { auto operand = op->getOperand(i); // If the operand is in this layer block, do nothing. + // + // Note: This check is what avoids handling ConnectOp destinations. if (isAncestorOfValueOwner(layerBlock, operand)) continue; - // If the operand is "special", e.g., it has no port representation, - // then we need to clone it. - // - // TODO: Change this to recursively clone. This will matter once - // FString operations have operands. - if (type_isa(operand.getType())) { - OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPoint(op); - op->setOperand(i, - builder.clone(*operand.getDefiningOp())->getResult(0)); - continue; - } - - // Create a port to capture the operand. - createInputPort(operand, op->getLoc()); + op->setOperand(i, getReplacement(op, operand)); } if (auto verbatim = dyn_cast(op)) @@ -636,33 +631,22 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, return WalkResult::interrupt(); // Create the new module. This grabs a lock to modify the circuit. - FModuleOp newModule = buildNewModule(builder, layerBlock, ports); + FModuleOp newModule = buildNewModule(builder, layerBlock); SymbolTable::setSymbolVisibility(newModule, SymbolTable::Visibility::Private); newModule.getBody().takeBody(layerBlock.getRegion()); LLVM_DEBUG({ - llvm::dbgs() << " New Module: " << moduleNames.lookup(layerBlock) - << "\n"; - llvm::dbgs() << " ports:\n"; - for (size_t i = 0, e = ports.size(); i != e; ++i) { - auto port = ports[i]; - auto value = connectValues[i]; - llvm::dbgs() << " - name: " << port.getName() << "\n" - << " type: " << port.type << "\n" - << " direction: " << port.direction << "\n" - << " value: " << value.value << "\n" - << " kind: " - << (value.kind == ConnectKind::NonRef ? "NonRef" : "Ref") - << "\n"; - } + llvm::dbgs() << " New Module: " + << layerBlockGlobals.lookup(layerBlock).moduleName << "\n"; }); - // Replace the original layer block with an instance. Hook up the instance. - // Intentionally create instance with probe ports which do not have an - // associated layer. This is illegal IR that will be made legal by the end - // of the pass. This is done to avoid having to revisit and rewrite each - // instance everytime it is moved into a parent layer. + // Replace the original layer block with an instance. Hook up the + // instance. Intentionally create instance with probe ports which do + // not have an associated layer. This is illegal IR that will be + // made legal by the end of the pass. This is done to avoid having + // to revisit and rewrite each instance everytime it is moved into a + // parent layer. builder.setInsertionPointAfter(layerBlock); auto instanceName = instanceNameForLayer(layerBlock.getLayerName()); auto instanceOp = builder.create( @@ -695,34 +679,6 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, << splice.second << "\n";); } - // Update RWProbe operations. - for (auto rwprobe : rwprobes) { - auto targetRef = rwprobe.getTarget(); - auto mapped = innerRefMap.find(targetRef); - if (mapped == innerRefMap.end()) { - assert(targetRef.getModule() == moduleOp.getNameAttr()); - auto ist = hw::InnerSymbolTable::get(moduleOp); - if (failed(ist)) - return WalkResult::interrupt(); - auto target = ist->lookup(targetRef.getName()); - assert(target); - auto fieldref = getFieldRefForTarget(target); - rwprobe - .emitError( - "rwprobe capture not supported with bind convention layer") - .attachNote(fieldref.getLoc()) - .append("rwprobe target outside of bind layer"); - return WalkResult::interrupt(); - } - - if (mapped->second.second != newModule.getModuleNameAttr()) - return rwprobe.emitError("rwprobe target refers to different module"), - WalkResult::interrupt(); - - rwprobe.setTargetAttr( - hw::InnerRefAttr::get(mapped->second.second, targetRef.getName())); - } - // Update verbatims that target operations extracted alongside. if (!verbatims.empty()) { mlir::AttrTypeReplacer replacer; @@ -737,34 +693,6 @@ LogicalResult LowerLayersPass::runOnModuleBody(FModuleOp moduleOp, replacer.replaceElementsIn(verbatim); } - // Connect instance ports to values. - assert(ports.size() == connectValues.size() && - "the number of instance ports and values to connect to them must be " - "equal"); - for (unsigned portNum = 0, e = newModule.getNumPorts(); portNum < e; - ++portNum) { - OpBuilder::InsertionGuard guard(builder); - builder.setInsertionPointAfterValue(instanceOp.getResult(portNum)); - if (instanceOp.getPortDirection(portNum) == Direction::In) { - auto src = connectValues[portNum].value; - if (isa(src.getType())) - src = builder.create( - newModule.getPortLocationAttr(portNum), src); - builder.create( - newModule.getPortLocationAttr(portNum), - instanceOp.getResult(portNum), src); - } else if (isa(instanceOp.getResult(portNum).getType()) && - connectValues[portNum].kind == ConnectKind::Ref) - builder.create(getPortLoc(connectValues[portNum].value), - connectValues[portNum].value, - instanceOp.getResult(portNum)); - else - builder.create( - getPortLoc(connectValues[portNum].value), - connectValues[portNum].value, - builder.create(newModule.getPortLocationAttr(portNum), - instanceOp.getResult(portNum))); - } layerBlock.erase(); return WalkResult::advance(); @@ -820,14 +748,22 @@ void LowerLayersPass::runOnOperation() { circuitMutex = &mutex; CircuitNamespace ns(circuitOp); + hw::HierPathCache hpc( + &ns, OpBuilder::InsertPoint(getOperation().getBodyBlock(), + getOperation().getBodyBlock()->begin())); + hierPathCache = &hpc; + for (auto &op : *circuitOp.getBodyBlock()) { // Determine names for all modules that will be created. Do this serially // to avoid non-determinism from creating these in the parallel region. if (auto moduleOp = dyn_cast(op)) { moduleOp->walk([&](LayerBlockOp layerBlockOp) { - auto name = moduleNameForLayer(moduleOp.getModuleName(), - layerBlockOp.getLayerName()); - moduleNames.insert({layerBlockOp, ns.newName(name)}); + auto moduleName = moduleNameForLayer(moduleOp.getModuleName(), + layerBlockOp.getLayerName()); + auto hierPathName = hierPathNameForLayer(moduleOp.getModuleName(), + layerBlockOp.getLayerName()); + layerBlockGlobals.insert( + {layerBlockOp, {ns.newName(moduleName), ns.newName(hierPathName)}}); }); continue; } @@ -980,6 +916,9 @@ void LowerLayersPass::runOnOperation() { for (auto layerOp : llvm::make_early_inc_range(circuitOp.getBodyBlock()->getOps())) layerOp.erase(); + + // Cleanup state. + hierPathCache = nullptr; } std::unique_ptr circt::firrtl::createLowerLayersPass() { diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index 7b42d96282..b59188a12e 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -53,8 +53,7 @@ LogicalResult firtool::populatePreprocessTransforms(mlir::PassManager &pm, } LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, - const FirtoolOptions &opt, - StringRef inputFilename) { + const FirtoolOptions &opt) { // TODO: Ensure instance graph and other passes can handle instance choice // then run this pass after all diagnostic passes have run. pm.addNestedPass(firrtl::createSpecializeOptionPass( @@ -217,6 +216,17 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, pm.nest().nest().addPass( firrtl::createVectorizationPass()); + return success(); +} + +LogicalResult firtool::populateLowFIRRTLToHW(mlir::PassManager &pm, + const FirtoolOptions &opt, + StringRef inputFilename) { + // Lower the ref.resolve and ref.send ops and remove the RefType ports. + // LowerToHW cannot handle RefType so, this pass must be run to remove all + // RefType ports and ops. + pm.nest().addPass(firrtl::createLowerXMRPass()); + // Layer lowering passes. Move operations into layers when possible and // remove layers by converting them to other constructs. This lowering // process can create a few optimization opportunities. @@ -258,21 +268,12 @@ LogicalResult firtool::populateCHIRRTLToLowFIRRTL(mlir::PassManager &pm, : opt.getBlackBoxRootPath(); pm.nest().addPass( firrtl::createBlackBoxReaderPass(blackBoxRoot)); - return success(); -} -LogicalResult firtool::populateLowFIRRTLToHW(mlir::PassManager &pm, - const FirtoolOptions &opt) { // Remove TraceAnnotations and write their updated paths to an output // annotation file. pm.nest().addPass( firrtl::createResolveTracesPass(opt.getOutputAnnotationFilename())); - // Lower the ref.resolve and ref.send ops and remove the RefType ports. - // LowerToHW cannot handle RefType so, this pass must be run to remove all - // RefType ports and ops. - pm.nest().addPass(firrtl::createLowerXMRPass()); - pm.nest().addPass(firrtl::createLowerDPIPass()); pm.nest().addPass(firrtl::createLowerClassesPass()); pm.nest().addPass(om::createVerifyObjectFieldsPass()); diff --git a/test/CAPI/firtool.c b/test/CAPI/firtool.c index 7bd47cb510..462b55c769 100644 --- a/test/CAPI/firtool.c +++ b/test/CAPI/firtool.c @@ -50,11 +50,11 @@ void exportVerilog(MlirContext ctx, bool disableOptimization) { circtFirtoolPopulatePreprocessTransforms(pm, options); assert(mlirLogicalResultIsSuccess(result)); - result = circtFirtoolPopulateCHIRRTLToLowFIRRTL( - pm, options, mlirStringRefCreateFromCString("-")); + result = circtFirtoolPopulateCHIRRTLToLowFIRRTL(pm, options); assert(mlirLogicalResultIsSuccess(result)); - result = circtFirtoolPopulateLowFIRRTLToHW(pm, options); + result = circtFirtoolPopulateLowFIRRTLToHW( + pm, options, mlirStringRefCreateFromCString("-")); assert(mlirLogicalResultIsSuccess(result)); result = circtFirtoolPopulateHWToSV(pm, options); diff --git a/test/Dialect/FIRRTL/lower-layers-errors.mlir b/test/Dialect/FIRRTL/lower-layers-errors.mlir index 62448de2e6..4df3413792 100644 --- a/test/Dialect/FIRRTL/lower-layers-errors.mlir +++ b/test/Dialect/FIRRTL/lower-layers-errors.mlir @@ -10,23 +10,7 @@ firrtl.circuit "NonPassiveSubaccess" { 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> + %0 = firrtl.subaccess %a[%n] : !firrtl.vector, b flip: uint<1>>, 2>, !firrtl.uint<1> } } } - -// ----- - -firrtl.circuit "RWProbeCantMove" { - firrtl.layer @A bind { } - firrtl.module @RWProbeCantMove() attributes {layers = [@A]} { - %z = firrtl.constant 0 : !firrtl.uint<5> - // expected-note @below {{rwprobe target outside of bind layer}} - %w = firrtl.node sym @sym %z : !firrtl.uint<5> - firrtl.layerblock @A { - // expected-error @below {{rwprobe capture not supported with bind convention layer}} - %rw = firrtl.ref.rwprobe <@RWProbeCantMove::@sym> : !firrtl.rwprobe> - } - } -} - diff --git a/test/Dialect/FIRRTL/lower-layers.mlir b/test/Dialect/FIRRTL/lower-layers.mlir index 83086c98f1..70b9e1acb2 100644 --- a/test/Dialect/FIRRTL/lower-layers.mlir +++ b/test/Dialect/FIRRTL/lower-layers.mlir @@ -1,5 +1,6 @@ // RUN: circt-opt -firrtl-lower-layers -split-input-file %s | FileCheck %s +// CHECK-LABEL: firrtl.circuit "Test" firrtl.circuit "Test" { firrtl.module @Test() {} @@ -32,10 +33,6 @@ firrtl.circuit "Test" { firrtl.module @ColoredThings() { // CHECK: %0 = firrtl.wire : !firrtl.probe>> %0 = firrtl.wire : !firrtl.probe>, @A> - // CHECK: %1 = firrtl.ref.sub %0[0] : !firrtl.probe>> - %1 = firrtl.ref.sub %0[0] : !firrtl.probe>, @A> - // CHECK-NOT: firrtl.ref.cast - %2 = firrtl.ref.cast %1 : (!firrtl.probe, @A>) -> !firrtl.probe, @A::@B> } // CHECK-LABEL: @ColoredThingUnderWhen @@ -44,10 +41,6 @@ firrtl.circuit "Test" { firrtl.when %b : !firrtl.uint<1> { // CHECK: %0 = firrtl.wire : !firrtl.probe>> %0 = firrtl.wire : !firrtl.probe>, @A> - // CHECK: %1 = firrtl.ref.sub %0[0] : !firrtl.probe>> - %1 = firrtl.ref.sub %0[0] : !firrtl.probe>, @A> - // CHECK-NOT: firrtl.ref.cast - %2 = firrtl.ref.cast %1 : (!firrtl.probe, @A>) -> !firrtl.probe, @A::@B> } } @@ -71,7 +64,7 @@ firrtl.circuit "Test" { // CHECK-NOT: firrtl.layer @GoodbyeCruelWorld firrtl.layer @GoodbyeCruelWorld bind {} - // CHECK-LABEL @WithLayerBlock + // CHECK-LABEL firrtl.module @WithLayerBlock firrtl.module @WithLayerBlock() { // CHECK-NOT firrtl.layerblock @GoodbyeCruelWorld firrtl.layerblock @GoodbyeCruelWorld { @@ -82,16 +75,20 @@ firrtl.circuit "Test" { // Capture //===--------------------------------------------------------------------===// - // CHECK: firrtl.module private @[[A:.+]](in %[[x:.+]]: !firrtl.uint<1>, in %[[y:.+]]: !firrtl.uint<1>) - // CHECK: %0 = firrtl.add %[[x]], %[[y]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> - // CHECK: } - // CHECK: firrtl.module @CaptureHardware() { - // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> - // CHECK: %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> - // CHECK: %[[p:.+]], %[[q:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: firrtl.matchingconnect %[[q]], %c1_ui1 : !firrtl.uint<1> - // CHECK: firrtl.matchingconnect %[[p]], %c0_ui1 : !firrtl.uint<1> - // CHECK: } + // CHECK: hw.hierpath private @[[CaptureHardware_c0_ui1_path:.+]] [@CaptureHardware::@[[CaptureHardware_c0_ui1_sym:.+]]] + // CHECK-NEXT: hw.hierpath private @[[CaptureHardware_c1_ui1_path:.+]] [@CaptureHardware::@[[CaptureHardware_c1_ui1_sym:.+]]] + // CHECK-NEXT: firrtl.module private @CaptureHardware_A() { + // CHECK-NEXT: %[[c1_ui1:.+]] = firrtl.xmr.deref @[[CaptureHardware_c1_ui1_path]] : !firrtl.uint<1> + // CHECK-NEXT: %[[c0_ui1:.+]] = firrtl.xmr.deref @[[CaptureHardware_c0_ui1_path]] : !firrtl.uint<1> + // CHECK-NEXT: firrtl.add %[[c0_ui1]], %[[c1_ui1]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK-NEXT: } + // CHECK-NEXT: firrtl.module @CaptureHardware() { + // CHECK-NEXT: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK-NEXT: %[[c0_ui1_node:.+]] = firrtl.node sym @[[CaptureHardware_c0_ui1_sym]] %c0_ui1 + // CHECK-NEXT: %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + // CHECK-NEXT: %[[c1_ui1_node:.+]] = firrtl.node sym @[[CaptureHardware_c1_ui1_sym]] %c1_ui1 + // CHECK-NEXT: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @CaptureHardware_A() + // CHECK-NEXT: } firrtl.module @CaptureHardware() { %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> @@ -100,12 +97,13 @@ firrtl.circuit "Test" { } } - // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { - // CHECK: %x = firrtl.node %[[p]] : !firrtl.uint<1> + // CHECK: hw.hierpath private @[[CapturePort_port_path:.+]] [@CapturePort::@[[CapturePort_port_sym:.+]]] + // CHECK: firrtl.module private @CapturePort_A() { + // CHECK: %[[port:.+]] = firrtl.xmr.deref @[[CapturePort_port_path]] : !firrtl.uint<1> + // CHECK: %x = firrtl.node %[[port]] : !firrtl.uint<1> // CHECK: } - // CHECK: firrtl.module @CapturePort(in %in: !firrtl.uint<1>) { - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: firrtl.matchingconnect %[[p]], %in : !firrtl.uint<1> + // CHECK: firrtl.module @CapturePort(in %in: !firrtl.uint<1> sym @[[CapturePort_port_sym]]) { + // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @CapturePort_A // CHECK: } firrtl.module @CapturePort(in %in: !firrtl.uint<1>){ firrtl.layerblock @A { @@ -113,90 +111,82 @@ firrtl.circuit "Test" { } } - // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) - // CHECK: %w = firrtl.wire : !firrtl.uint<1> - // CHECK: firrtl.connect %w, %[[p]] : !firrtl.uint<1> - // CHECK: } - // CHECK: firrtl.module @CaptureHardwareViaConnect() { - // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: firrtl.matchingconnect %[[p]], %c0_ui1 : !firrtl.uint<1> - // CHECK: } - firrtl.module @CaptureHardwareViaConnect() { - %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: hw.hierpath private @[[CaptureConnect_a_path:.+]] [@CaptureConnect::@[[CaptureConnect_a_sym:.+]]] + // CHECK-NEXT: firrtl.module private @CaptureConnect_A() + // CHECK-NEXT: %[[a:.+]] = firrtl.xmr.deref @[[CaptureConnect_a_path]] : !firrtl.uint<1> + // CHECK-NEXT: %b = firrtl.wire : !firrtl.uint<1> + // CHECK-NEXT: firrtl.connect %b, %[[a]] : !firrtl.uint<1> + // CHECK-NEXT: } + // CHECK-NEXT: firrtl.module @CaptureConnect() { + // CHECK-NEXT: %a = firrtl.wire : !firrtl.uint<1> + // CHECK-NEXT: %_layer_probe = firrtl.node sym @[[CaptureConnect_a_sym]] %a + // CHECK-NEXT: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @CaptureConnect_A() + // CHECK-NEXT: } + firrtl.module @CaptureConnect() { + %a = firrtl.wire : !firrtl.uint<1> firrtl.layerblock @A { - %w = firrtl.wire : !firrtl.uint<1> - firrtl.connect %w, %c0_ui1 : !firrtl.uint<1>, !firrtl.uint<1> + %b = firrtl.wire : !firrtl.uint<1> + firrtl.connect %b, %a : !firrtl.uint<1>, !firrtl.uint<1> } } - // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) - // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> - // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @CaptureProbeSrc() { - // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> - // CHECK: %w = firrtl.wire : !firrtl.uint<1> - // CHECK: %0 = firrtl.ref.send %w : !firrtl.uint<1> - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> - // CHECK: firrtl.matchingconnect %[[p]], %1 : !firrtl.uint<1> - // CHECK: } + // CHECK: firrtl.module private @CaptureProbeSrc_A() + // CHECK-NEXT: %0 = firrtl.xmr.deref @xmrPath : !firrtl.uint<1> + // CHECK-NEXT: } + // CHECK-NEXT: firrtl.module @CaptureProbeSrc() { + // CHECK-NEXT: %w = firrtl.wire : !firrtl.uint<1> + // CHECK-NEXT: %w_probe = firrtl.node sym @sym interesting_name %w : !firrtl.uint<1> + // CHECK-NEXT: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @CaptureProbeSrc_A + // CHECK-NEXT: } firrtl.module @CaptureProbeSrc() { - %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> %w = firrtl.wire : !firrtl.uint<1> - %r = firrtl.ref.send %w : !firrtl.uint<1> + %w_probe = firrtl.node sym @sym interesting_name %w : !firrtl.uint<1> firrtl.layerblock @A { - firrtl.ref.resolve %r : !firrtl.probe> + %0 = firrtl.xmr.deref @xmrPath : !firrtl.uint<1> } } - // CHECK: firrtl.module private @[[B:.+]](in %[[p:.+]]: !firrtl.uint<1>, in %[[q:.+]]: !firrtl.uint<1>) - // CHECK: %0 = firrtl.add %[[p]], %[[q]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> - // CHECK: } - // CHECK: firrtl.module private @[[A:.+]](out %[[p:.+]]: !firrtl.probe>, out %[[q:.+]]: !firrtl.probe>) attributes { - // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> - // CHECK: %0 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1> - // CHECK: firrtl.ref.define %[[q]], %0 : !firrtl.probe> - // CHECK: %c0_ui1_1 = firrtl.constant 0 : !firrtl.uint<1> - // CHECK: %1 = firrtl.ref.send %c0_ui1_1 : !firrtl.uint<1> - // CHECK: firrtl.ref.define %[[p]], %1 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @NestedCaptureHardware() { - // CHECK: %[[b1:.+]], %[[b2:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A-B.sv", excludeFromFileList>} @[[B]] - // CHECK: %[[a1:.+]], %[[a2:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: %0 = firrtl.ref.resolve %[[a2]] : !firrtl.probe> - // CHECK: firrtl.matchingconnect %[[b1]], %0 : !firrtl.uint<1> - // CHECK: %1 = firrtl.ref.resolve %[[a1]] : !firrtl.probe> - // CHECK: firrtl.matchingconnect %[[b2]], %1 : !firrtl.uint<1> - // CHECK: } - firrtl.module @NestedCaptureHardware() { + // CHECK: hw.hierpath private @[[NestedCapture_x_path:.+]] [@NestedCapture::@[[inst:.+]], @NestedCapture_A::@[[NestedCapture_a_sym:.+]]] + // CHECK-NEXT: firrtl.module private @NestedCapture_A_B() + // CHECK-NEXT: %0 = firrtl.xmr.deref @[[NestedCapture_x_path]] + // CHECK-NEXT: %1 = firrtl.node %0 : !firrtl.uint<1> + // CHECK-NEXT: } + // CHECK-NEXT: firrtl.module private @NestedCapture_A() { + // CHECK-NEXT: %x = firrtl.wire : !firrtl.uint<1> + // CHECK-NEXT: %_layer_probe = firrtl.node sym @[[NestedCapture_a_sym]] %x + // CHECK-NEXT: } + // CHECK-NEXT: firrtl.module @NestedCapture() { + // CHECK-NEXT: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A-B.sv", excludeFromFileList>} @NestedCapture_A_B() + // CHECK-NEXT: firrtl.instance {{.+}} sym @[[inst]] {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @NestedCapture_A() + // CHECK-NEXT: } + firrtl.module @NestedCapture() { firrtl.layerblock @A { - %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> - %c1_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %x = firrtl.wire : !firrtl.uint<1> firrtl.layerblock @A::@B { - %0 = firrtl.add %c0_ui1, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + %0 = firrtl.node %x : !firrtl.uint<1> } } } - // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) - // CHECK: %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> - // CHECK: firrtl.when %[[p]] : !firrtl.uint<1> { - // CHECK: %0 = firrtl.add %[[p]], %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> - // CHECK: } - // CHECK: } - // CHECK: firrtl.module @WhenUnderLayer() { - // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: firrtl.matchingconnect %[[p]], %c0_ui1 : !firrtl.uint<1> - // CHECK: } + // CHECK: hw.hierpath private @[[WhenUnderLayer_x_path:.+]] [@WhenUnderLayer::@[[WhenUnderLayer_x_sym:.+]]] + // CHECK-NEXT: firrtl.module private @WhenUnderLayer_A() + // CHECK-NEXT: %0 = firrtl.xmr.deref @[[WhenUnderLayer_x_path]] : !firrtl.uint<1> + // CHECK-NEXT: %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + // CHECK-NEXT: firrtl.when %0 : !firrtl.uint<1> { + // CHECK-NEXT: %1 = firrtl.add %0, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK-NEXT: } + // CHECK-NEXT: } + // CHECK-NEXT: firrtl.module @WhenUnderLayer() { + // CHECK-NEXT: %x = firrtl.wire : !firrtl.uint<1> + // CHECK-NEXT: %_layer_probe = firrtl.node sym @[[WhenUnderLayer_x_sym]] %x + // CHECK-NEXT: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @WhenUnderLayer_A + // CHECK-NEXT: } firrtl.module @WhenUnderLayer() { - %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %x = firrtl.wire : !firrtl.uint<1> firrtl.layerblock @A { %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> - firrtl.when %c0_ui1 : !firrtl.uint<1> { - %0 = firrtl.add %c0_ui1, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + firrtl.when %x : !firrtl.uint<1> { + %0 = firrtl.add %x, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> } } } @@ -204,14 +194,17 @@ 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: hw.hierpath private @[[SubOpsInLayerBlock_sub_path:.+]] [@SubOpsInLayerBlock::@[[SubOpsInLayerBlock_sub_sym:.+]]] + // CHECK-NEXT: firrtl.module private @SubOpsInLayerBlock_A() { + // CHECK-NEXT: %0 = firrtl.xmr.deref @[[SubOpsInLayerBlock_sub_path]] + // CHECK-NEXT: firrtl.node %0 // CHECK-NEXT: } - // CHECK: firrtl.module @SubOpsInLayerBlock - // CHECK-NEXT: firrtl.subaccess - // CHECK-NEXT: firrtl.subindex - // CHECK-NEXT: firrtl.subfield + // CHECK: firrtl.module @SubOpsInLayerBlock( + // CHECK-NEXT: %0 = firrtl.subaccess %a[%b] + // CHECK-NEXT: %1 = firrtl.subindex %0[0] + // CHECK-NEXT: %2 = firrtl.subfield %1[a] + // CHECK-NEXT: %3 = firrtl.node %2 : + // CHECK-NEXT: %_layer_probe = firrtl.node sym @[[SubOpsInLayerBlock_sub_sym]] %3 firrtl.module @SubOpsInLayerBlock( in %a: !firrtl.vector, b flip: uint<2>>, 2>, 2>, in %b: !firrtl.uint<1> @@ -221,19 +214,23 @@ firrtl.circuit "Test" { %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> + %4 = firrtl.node %3 : !firrtl.uint<1> } } - // CHECK: firrtl.module private @CaptureInWhen_A( - // CHECK-SAME: in %a: !firrtl.uint<1> - // CHECK-SAME: in %cond: !firrtl.uint<1> - // CHECK-SAME: ) + // CHECK: hw.hierpath private @[[CaptureWhen2_a_path:.+]] [@CaptureWhen2::@[[CaptureWhen2_a_sym:.+]]] + // CHECK-NEXT: hw.hierpath private @[[CaptureWhen2_cond_path:.+]] [@CaptureWhen2::@[[CaptureWhen2_cond_sym:.+]]] + // CHECK-NEXT: firrtl.module private @CaptureWhen2_A() { + // CHECK-NEXT: %0 = firrtl.xmr.deref @[[CaptureWhen2_cond_path]] + // CHECK-NEXT: %1 = firrtl.xmr.deref @[[CaptureWhen2_a_path]] + // CHECK-NEXT: firrtl.when %0 {{.*}} { + // CHECK-NEXT: %b = firrtl.node %1 - // CHECK: firrtl.module @CaptureInWhen( - // CHECK: %a_a, %a_cond = firrtl.instance a - // CHECK-NEXT: firrtl.matchingconnect %a_cond, %cond : - // CHECK-NEXT: firrtl.matchingconnect %a_a, %a : - firrtl.module @CaptureInWhen(in %cond: !firrtl.uint<1>) { + // CHECK: firrtl.module @CaptureWhen2( + // CHECK-NEXT: %a = firrtl.wire + // CHECK-NEXT: %_layer_probe = firrtl.node {{.*}} %a + // CHECK-NEXT: firrtl.instance a + firrtl.module @CaptureWhen2(in %cond: !firrtl.uint<1>) { %a = firrtl.wire : !firrtl.uint<1> firrtl.layerblock @A { firrtl.when %cond : !firrtl.uint<1> { @@ -242,86 +239,58 @@ firrtl.circuit "Test" { } } + // Capture of a zero-width value creates a local zero-width constant zero. + // + // CHECK: firrtl.module private @ZeroWidthCapture_A() { + // CHECK-NEXT: %c0_ui0 = firrtl.constant 0 : !firrtl.uint<0> + // CHECK-NEXT: %b = firrtl.node %c0_ui0 : !firrtl.uint<0> + // CHECK-NEXT: } + // CHECK: firrtl.module @ZeroWidthCapture() { + // CHECK-NEXT: %a = firrtl.wire : !firrtl.uint<0> + // CHECK-NEXT: firrtl.instance a + firrtl.module @ZeroWidthCapture() { + %a = firrtl.wire : !firrtl.uint<0> + firrtl.layerblock @A { + %b = firrtl.node %a : !firrtl.uint<0> + } + } + + // Port capture needs to create a node. + // + // CHECK: hw.hierpath private @[[InstancePortCapture_ext_a_path:.+]] [@InstancePortCapture::@[[InstancePortCapture_ext_a_sym:.+]]] + // CHECK-NEXT: hw.hierpath private @[[InstancePortCapture_ext_b_path:.+]] [@InstancePortCapture::@[[InstancePortCapture_ext_b_sym:.+]]] + // CHECK-NEXT: firrtl.module private @InstancePortCapture_A() { + // CHECK-NEXT: %0 = firrtl.xmr.deref @[[InstancePortCapture_ext_b_path]] + // CHECK-NEXT: %1 = firrtl.xmr.deref @[[InstancePortCapture_ext_a_path]] + // CHECK-NEXT: %a = firrtl.node %1 + // CHECK-NEXT: %b = firrtl.node %0 + // CHECK-NEXT: } + // + // CHECK: firrtl.module @InstancePortCapture() { + // CHECK-NEXT: %ext_a, %ext_b = firrtl.instance ext @InstancePortCapture_ext + // CHECK-NEXT: %_layer_probe = firrtl.node sym @[[InstancePortCapture_ext_b_sym]] %ext_b + // CHECK-NEXT: %_layer_probe_0 = firrtl.node sym @[[InstancePortCapture_ext_a_sym]] %ext_a + // CHECK-NEXT: firrtl.instance {{.*}} + // CHECK-NEXT: } + firrtl.extmodule @InstancePortCapture_ext( + in a: !firrtl.uint<1>, + out b: !firrtl.uint<1> + ) + firrtl.module @InstancePortCapture() { + %ext_a, %ext_b = firrtl.instance ext @InstancePortCapture_ext( + in a: !firrtl.uint<1>, + out b: !firrtl.uint<1> + ) + firrtl.layerblock @A { + %a = firrtl.node %ext_a : !firrtl.uint<1> + %b = firrtl.node %ext_b : !firrtl.uint<1> + } + } + //===--------------------------------------------------------------------===// - // Connecting/Defining Refs + // Cloning of special operations //===--------------------------------------------------------------------===// - // Src and Dst Outside Layerblock. - // - // CHECK: firrtl.module private @[[A:.+]]() { - // CHECK: } - // CHECK: firrtl.module @SrcDstOutside() { - // CHECK: %0 = firrtl.wire : !firrtl.probe> - // CHECK: %1 = firrtl.wire : !firrtl.probe> - // CHECK: firrtl.ref.define %1, %0 : !firrtl.probe> - // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A:.+]]() - // CHECK: } - firrtl.module @SrcDstOutside() { - %0 = firrtl.wire : !firrtl.probe, @A> - %1 = firrtl.wire : !firrtl.probe, @A> - firrtl.layerblock @A { - firrtl.ref.define %1, %0 : !firrtl.probe, @A> - } - } - - // Src Outside Layerblock. - // - // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) - // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> - // CHECK: %1 = firrtl.wire : !firrtl.probe> - // CHECK: firrtl.ref.define %1, %0 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @SrcOutside() { - // CHECK: %0 = firrtl.wire : !firrtl.probe> - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> - // CHECK: firrtl.matchingconnect %[[p]], %1 : !firrtl.uint<1> - // CHECK: } - firrtl.module @SrcOutside() { - %0 = firrtl.wire : !firrtl.probe, @A> - firrtl.layerblock @A { - %1 = firrtl.wire : !firrtl.probe, @A> - firrtl.ref.define %1, %0 : !firrtl.probe, @A> - } - } - - // Dst Outside Layerblock. - // - // CHECK: firrtl.module private @[[A:.+]](out %[[p:.+]]: !firrtl.probe>) - // CHECK: %0 = firrtl.wire : !firrtl.probe> - // CHECK: firrtl.ref.define %[[p]], %0 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @DestOutside() { - // CHECK: %0 = firrtl.wire : !firrtl.probe> - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: firrtl.ref.define %0, %[[p]] : !firrtl.probe> - // CHECK: } - firrtl.module @DestOutside() { - %0 = firrtl.wire : !firrtl.probe, @A> - firrtl.layerblock @A { - %1 = firrtl.wire : !firrtl.probe, @A> - firrtl.ref.define %0, %1 : !firrtl.probe, @A> - } - } - - // Src and Dst Inside Layerblock. - // - // CHECK: firrtl.module private @[[A:.+]]() { - // CHECK: %0 = firrtl.wire : !firrtl.probe> - // CHECK: %1 = firrtl.wire : !firrtl.probe> - // CHECK: firrtl.ref.define %1, %0 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @SrcDstInside() { - // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]]() - // CHECK: } - firrtl.module @SrcDstInside() { - firrtl.layerblock @A { - %0 = firrtl.wire : !firrtl.probe, @A> - %1 = firrtl.wire : !firrtl.probe, @A> - firrtl.ref.define %1, %0 : !firrtl.probe, @A> - } - } - // An FString operation is outside the layer block. This needs to be cloned. // // CHECK: firrtl.module private @[[A:.+]]() { @@ -338,76 +307,23 @@ firrtl.circuit "Test" { } } - //===--------------------------------------------------------------------===// - // Resolving Colored Probes - //===--------------------------------------------------------------------===// - - // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { - // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> - // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @ResolveColoredRefUnderLayerBlock() { - // CHECK: %w = firrtl.wire : !firrtl.probe> - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: %0 = firrtl.ref.resolve %w : !firrtl.probe> - // CHECK: firrtl.matchingconnect %[[p]], %0 : !firrtl.uint<1> - // CHECK: } - firrtl.module @ResolveColoredRefUnderLayerBlock() { - %w = firrtl.wire : !firrtl.probe, @A> + // XMR Ref ops used by force_initial are cloned. + // + // CHECK: firrtl.module private @XmrRef_A() + // CHECK-NEXT: %0 = firrtl.xmr.ref @RefXmrRef_path : !firrtl.rwprobe, @A> + // CHECK-NEXT: %a = firrtl.wire + // CHECK-NEXT: %c1_ui1 = firrtl.constant 1 + // CHECK-NEXT: firrtl.ref.force_initial %c1_ui1, %0, %c1_ui1 + hw.hierpath private @XmrRef_path [@XmrRef::@a] + firrtl.module @XmrRef() { + %0 = firrtl.xmr.ref @RefXmrRef_path : !firrtl.rwprobe, @A> firrtl.layerblock @A { - %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> + %a = firrtl.wire sym @a : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 1 : !firrtl.const.uint<1> + firrtl.ref.force_initial %c1_ui1, %0, %c1_ui1 : !firrtl.const.uint<1>, !firrtl.rwprobe, @A>, !firrtl.const.uint<1> } } - // CHECK: firrtl.module @ResolveColoredRefUnderEnabledLayer() { - // CHECK: %w = firrtl.wire : !firrtl.probe> - // CHECK: %0 = firrtl.ref.resolve %w : !firrtl.probe> - // CHECK: } - firrtl.module @ResolveColoredRefUnderEnabledLayer() attributes {layers=[@A]} { - %w = firrtl.wire : !firrtl.probe, @A> - %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> - } - - // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { - // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> - // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @ResolveColoredRefPortUnderLayerBlock1() { - // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]] - // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> - // CHECK: firrtl.matchingconnect %[[p]], %0 : !firrtl.uint<1> - // CHECK: } - firrtl.module @ResolveColoredRefPortUnderLayerBlock1() { - %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) - firrtl.layerblock @A { - %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> - } - } - - // CHECK: firrtl.module private @[[A:.+]]() { - // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) - // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @ResolveColoredRefPortUnderLayerBlock2() { - // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"layers-Test-A.sv", excludeFromFileList>} @[[A]]() - // CHECK: } - firrtl.module @ResolveColoredRefPortUnderLayerBlock2() { - firrtl.layerblock @A { - %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) - %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> - } - } - - // CHECK: firrtl.module @ResolveColoredRefPortUnderEnabledLayer() { - // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) - // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> - // CHECK: } - firrtl.module @ResolveColoredRefPortUnderEnabledLayer() attributes {layers=[@A]} { - %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) - %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> - } - //===--------------------------------------------------------------------===// // Inline Layers //===--------------------------------------------------------------------===// @@ -497,55 +413,42 @@ firrtl.circuit "Simple" { // CHECK-SAME: define layers_Simple_A" // CHECK-SAME: output_file = #hw.output_file<"layers-Simple-A.sv", excludeFromFileList> // -// CHECK: firrtl.module private @Simple_A_B_C( -// CHECK-NOT: firrtl.module -// CHECK-SAME: in %[[cc_port:[_a-zA-Z0-9]+]]: !firrtl.uint<3> -// CHECK-NEXT: %ccc = firrtl.node %[[cc_port]] +// CHECK: hw.hierpath private @[[Simple_A_B_cc_path:.+]] [@Simple::@a_b, @Simple_A_B::@[[Simple_A_B_cc_sym:.+]]] +// +// CHECK: firrtl.module private @Simple_A_B_C() { +// CHECK-NEXT: %0 = firrtl.xmr.deref @[[Simple_A_B_cc_path]] +// CHECK-NEXT: %ccc = firrtl.node %0 // CHECK-NEXT: } // -// CHECK: firrtl.module private @Simple_A_B( -// CHECK-NOT: firrtl.module -// CHECK-SAME: in %[[b_port:[_a-zA-Z0-9]+]]: !firrtl.uint<2> -// CHECK-SAME: in %[[c_port:[_a-zA-Z0-9]+]]: !firrtl.uint<3> -// CHECK-SAME: out %[[cc_port:[_a-zA-Z0-9_]+]]: !firrtl.probe> -// CHECK-NEXT: %bb = firrtl.node %[[b_port]] -// CHECK-NEXT: %cc = firrtl.node %[[c_port]] -// CHECK-NEXT: %0 = firrtl.ref.send %cc -// CHECK-NEXT: firrtl.ref.define %[[cc_port]], %0 +// CHECK: hw.hierpath private @[[Simple_b_path:.+]] [@Simple::@[[Simple_b_sym:.+]]] +// CHECK-NEXT: hw.hierpath private @[[Simple_A_c_path:.+]] [@Simple::@a, @Simple_A::@[[Simple_A_c_sym:.+]]] +// +// CHECK: firrtl.module private @Simple_A_B() { +// CHECK-NEXT: %0 = firrtl.xmr.deref @[[Simple_A_c_path]] +// CHECK-NEXT: %1 = firrtl.xmr.deref @[[Simple_b_path]] +// CHECK-NEXT: %bb = firrtl.node %1 +// CHECK-NEXT: %cc = firrtl.node %0 +// CHECK-NEXT: %_layer_probe = firrtl.node sym @[[Simple_A_B_cc_sym]] %cc // CHECK-NEXT: } // -// CHECK: firrtl.module private @Simple_A( -// CHECK-NOT: firrtl.module -// CHECK-SAME: in %[[a_port:[_a-zA-Z0-9]+]]: !firrtl.uint<1> -// CHECK-SAME: out %[[c_port:[_a-zA-Z0-9_]+]]: !firrtl.probe> -// CHECK-NEXT: %aa = firrtl.node %[[a_port]] -// CHECK: %[[c_ref:[_a-zA-Z0-9]+]] = firrtl.ref.send %c -// CHECK-NEXT: firrtl.ref.define %[[c_port]], %[[c_ref]] +// CHECK: hw.hierpath private @[[Simple_a_path:.+]] [@Simple::@[[Simple_a_sym:.+]]] +// +// CHECK: firrtl.module private @Simple_A() { +// CHECK-NEXT: %0 = firrtl.xmr.deref @[[Simple_a_path]] +// CHECK-NEXT: %aa = firrtl.node %0 +// CHECK-NEXT: %c = firrtl.wire +// CHECK-NEXT: %_layer_probe = firrtl.node sym @[[Simple_A_c_sym]] %c // CHECK-NEXT: } // // CHECK: firrtl.module @Simple() { -// CHECK-NOT: firrtl.module -// CHECK-NOT: firrtl.layerblock -// CHECK: %[[A_B_C_cc:[_a-zA-Z0-9_]+]] = firrtl.instance a_b_c { -// CHECK-SAME: lowerToBind -// CHECK-SAME: output_file = #hw.output_file<"layers-Simple-A-B-C.sv" -// CHECK-SAME: excludeFromFileList -// CHECK-SAME: @Simple_A_B_C( -// CHECK-NEXT: %[[A_B_b:[_a-zA-Z0-9_]+]], %[[A_B_c:[_a-zA-Z0-9_]+]], %[[A_B_cc:[_a-zA-Z0-9_]+]] = firrtl.instance a_b { -// CHECK-SAME: lowerToBind -// CHECK-SAME: output_file = #hw.output_file<"layers-Simple-A-B.sv", excludeFromFileList> -// CHECK-SAME: @Simple_A_B( -// CHECK-NEXT: %[[A_B_cc_resolve:[_a-zA-Z0-9]+]] = firrtl.ref.resolve %[[A_B_cc]] -// CHECK-NEXT: firrtl.matchingconnect %[[A_B_C_cc]], %[[A_B_cc_resolve]] -// CHECK-NEXT: firrtl.matchingconnect %[[A_B_b]], %b -// CHECK-NEXT: %[[A_a:[_a-zA-Z0-9_]+]], %[[A_c:[_a-zA-Z0-9_]+]] = firrtl.instance a { -// CHECK-SAME: lowerToBind -// CHECK-SAME: output_file = #hw.output_file<"layers-Simple-A.sv", excludeFromFileList> -// CHECK-SAME: @Simple_A( -// CHECK-NEXT: %[[A_c_resolve:[_a-zA-Z0-9]+]] = firrtl.ref.resolve %[[A_c]] -// CHECK-NEXT: firrtl.matchingconnect %[[A_B_c]], %[[A_c_resolve]] -// CHECK-NEXT: firrtl.matchingconnect %[[A_a]], %a -// CHECK: } +// CHECK-NEXT: %a = firrtl.wire +// CHECK-NEXT: %_layer_probe = firrtl.node sym @[[Simple_a_sym]] %a +// CHECK-NEXT: %b = firrtl.wire +// CHECK-NEXT: %_layer_probe_0 = firrtl.node sym @[[Simple_b_sym]] %b +// CHECK-NEXT: firrtl.instance a_b_c {{.*}} +// CHECK-NEXT: firrtl.instance a_b {{.*}} +// CHECK-NEXT: firrtl.instance a {{.*}} +// CHECK-NEXT: } // // CHECK-DAG: sv.verbatim "`endif // layers_Simple_A" // CHECK-SAME: output_file = #hw.output_file<"layers-Simple-A.sv", excludeFromFileList> @@ -556,7 +459,7 @@ firrtl.circuit "Simple" { firrtl.circuit "ModuleNameConflict" { firrtl.layer @A bind {} - firrtl.module private @ModuleNameConflict_A() {} + firrtl.module @ModuleNameConflict_A() {} firrtl.module @ModuleNameConflict() { %a = firrtl.wire : !firrtl.uint<1> firrtl.instance foo @ModuleNameConflict_A() @@ -568,7 +471,8 @@ firrtl.circuit "ModuleNameConflict" { // CHECK-LABEL: firrtl.circuit "ModuleNameConflict" // -// CHECK: firrtl.module private @[[groupModule:[_a-zA-Z0-9_]+]](in +// CHECK: firrtl.module @ModuleNameConflict_A() +// CHECK: firrtl.module private @[[groupModule:[_a-zA-Z0-9_]+]]() // // CHECK: firrtl.module @ModuleNameConflict() // CHECK-NOT: firrtl.module @@ -587,13 +491,16 @@ firrtl.circuit "CaptureHardwareMultipleTimes" { firrtl.extmodule @CaptureHardwareMultipleTimes () - // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) - // CHECK: %0 = firrtl.add %[[p]], %[[p]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK: hw.hierpath private @[[path:.+]] [@CaptureSrcTwice::@[[sym:.+]]] + // + // CHECK: firrtl.module private @[[A:.+]]() + // CHECK: %0 = firrtl.xmr.deref @[[path]] : !firrtl.uint<1> + // CHECK: %1 = firrtl.add %0, %0 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> // CHECK: } // CHECK: firrtl.module @CaptureSrcTwice() { // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> - // CHECK: %[[p:.+]] = firrtl.instance {{.+}} @[[A]] - // CHECK: firrtl.matchingconnect %[[p]], %c0_ui1 : !firrtl.uint<1> + // CHECK: %_layer_probe = firrtl.node sym @[[sym]] %c0_ui1 : !firrtl.uint<1> + // CHECK: firrtl.instance {{.+}} @[[A]] // CHECK: } firrtl.module @CaptureSrcTwice() { %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> @@ -602,53 +509,6 @@ firrtl.circuit "CaptureHardwareMultipleTimes" { } } - // CHECK: firrtl.module private @[[A:.+]](out %[[dst:.+]]: !firrtl.probe>, in %[[src:.+]]: !firrtl.uint<1>) - // CHECK: %0 = firrtl.ref.send %[[src]] : !firrtl.uint<1> - // CHECK: %w2 = firrtl.wire : !firrtl.probe> - // CHECK: firrtl.ref.define %[[dst]], %w2 : !firrtl.probe> - // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @CaptureAsDstThenSrc() { - // CHECK: %w1 = firrtl.wire : !firrtl.probe> - // CHECK: %[[out:.+]], %[[in:.+]] = firrtl.instance {{.+}} @[[A]](out {{.+}}: !firrtl.probe>, in {{.+}}: !firrtl.uint<1>) - // CHECK: %0 = firrtl.ref.resolve %w1 : !firrtl.probe> - // CHECK: firrtl.matchingconnect %[[in]], %0 : !firrtl.uint<1> - // CHECK: firrtl.ref.define %w1, %[[out]] : !firrtl.probe> - // CHECK: } - firrtl.module @CaptureAsDstThenSrc() { - %w1 = firrtl.wire : !firrtl.probe, @A> - firrtl.layerblock @A { - // capture first as a sink. - %w2 = firrtl.wire : !firrtl.probe, @A> - firrtl.ref.define %w1, %w2 : !firrtl.probe, @A> - // Capture again, as a source. - %2 = firrtl.ref.resolve %w1 : !firrtl.probe, @A> - } - } - - // CHECK: firrtl.module private @[[A:.+]](in %[[src:.+]]: !firrtl.uint<1>, out %[[dst:.+]]: !firrtl.probe>) - // CHECK: %0 = firrtl.ref.send %[[src]] : !firrtl.uint<1> - // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> - // CHECK: %w2 = firrtl.wire : !firrtl.probe> - // CHECK: firrtl.ref.define %[[dst]], %w2 : !firrtl.probe> - // CHECK: } - // CHECK: firrtl.module @CaptureAsSrcThenDst() { - // CHECK: %w1 = firrtl.wire : !firrtl.probe> - // CHECK: %[[in:.+]], %[[out:.+]] = firrtl.instance {{.+}} @[[A]] - // CHECK: firrtl.ref.define %w1, %[[out]] : !firrtl.probe> - // CHECK: %0 = firrtl.ref.resolve %w1 : !firrtl.probe> - // CHECK: firrtl.matchingconnect %[[in]], %0 : !firrtl.uint<1> - // CHECK: } - firrtl.module @CaptureAsSrcThenDst() { - %w1 = firrtl.wire : !firrtl.probe, @A> - firrtl.layerblock @A { - // capture first as a source. - %2 = firrtl.ref.resolve %w1 : !firrtl.probe, @A> - // capture again, as a sink. - %w2 = firrtl.wire : !firrtl.probe, @A> - firrtl.ref.define %w1, %w2 : !firrtl.probe, @A> - } - } } // ----- @@ -751,37 +611,6 @@ firrtl.circuit "Foo" { // ----- -// Check rwprobe ops are updated. -// CHECK-LABEL: circuit "RWTH" -firrtl.circuit "RWTH" { - firrtl.layer @T bind { } - firrtl.module @RWTH() attributes {convention = #firrtl, layers = [@T]} { - %d_p = firrtl.instance d @DUT(out p: !firrtl.rwprobe, @T>) - %one = firrtl.constant 1 : !firrtl.uint<1> - firrtl.ref.force_initial %one, %d_p, %one: !firrtl.uint<1>, !firrtl.rwprobe, @T>, !firrtl.uint<1> - } -// CHECK: firrtl.module private @DUT_T(out %p: !firrtl.rwprobe>) { -// CHECK-NEXT: %w = firrtl.wire sym @[[SYM:.+]] : !firrtl.uint<1> -// CHECK-NEXT: %0 = firrtl.ref.rwprobe <@DUT_T::@[[SYM]]> : !firrtl.rwprobe> -// CHECK-NEXT: firrtl.ref.define %p, %0 : !firrtl.rwprobe> -// CHECK-NEXT: } -// CHECK-NEXT: firrtl.module @DUT(out %p: !firrtl.rwprobe>) attributes {convention = #firrtl} { -// CHECK-NEXT: %t_p = firrtl.instance t sym @t {lowerToBind, output_file = #hw.output_file<"layers-RWTH-T.sv", excludeFromFileList>} @DUT_T(out p: !firrtl.rwprobe>) -// CHECK-NEXT: firrtl.ref.define %p, %t_p : !firrtl.rwprobe> -// CHECK-NEXT: } - - firrtl.module @DUT(out %p: !firrtl.rwprobe, @T>) attributes {convention = #firrtl} { - firrtl.layerblock @T { - %w = firrtl.wire sym @sym : !firrtl.uint<1> - %0 = firrtl.ref.rwprobe <@DUT::@sym> : !firrtl.rwprobe> - %1 = firrtl.ref.cast %0 : (!firrtl.rwprobe>) -> !firrtl.rwprobe, @T> - firrtl.ref.define %p, %1 : !firrtl.rwprobe, @T> - } - } -} - -// ----- - // Check sv.verbatim inner refs are updated, as occurs with views under layers. // CHECK-LABEL: circuit "Verbatim" firrtl.circuit "Verbatim" { diff --git a/test/firtool/layer-merge-across-inlined-submodule.fir b/test/firtool/layer-merge-across-inlined-submodule.fir index 0b6109c4b5..65ab4d4aca 100644 --- a/test/firtool/layer-merge-across-inlined-submodule.fir +++ b/test/firtool/layer-merge-across-inlined-submodule.fir @@ -9,9 +9,9 @@ FIRRTL version 4.0.0 ; CHECK-NOT: module Child ; CHECK: module Top_Verification_Assert -; CHECK: assert(p) else $error("before child"); -; CHECK: assert(p) else $error("in child"); -; CHECK: assert(p) else $error("after child"); +; CHECK: assert(Top.p) else $error("before child"); +; CHECK: assert(Top.p) else $error("in child"); +; CHECK: assert(Top.p) else $error("after child"); ; CHECK: endmodule circuit Top: %[[ @@ -29,7 +29,7 @@ circuit Top: %[[ layerblock Verification: layerblock Assert: assert(c, p, e, "in child") - + public module Top: input p : UInt<1> input e : UInt<1> @@ -38,7 +38,7 @@ circuit Top: %[[ layerblock Verification: layerblock Assert: assert(c, p, e, "before child") - + inst child of Child connect child.p, p connect child.e, e diff --git a/test/firtool/layers.fir b/test/firtool/layers.fir index d4298fc910..17bb13a7d9 100644 --- a/test/firtool/layers.fir +++ b/test/firtool/layers.fir @@ -37,42 +37,29 @@ circuit Foo: %[[ node z = x assert(clock, cond, enable, "Test") -; CHECK-LABEL: module Foo_A_B( -; CHECK-NEXT: input x, -; CHECK-NEXT: cond, -; CHECK-NEXT: enable, -; CHECK-NEXT: clock -; CHECK-NEXT: ); -; CHECK: wire y = x; -; CHECK: wire z = x; -; CHECK: always @(posedge clock) begin -; CHECK-NEXT: if (cond & x & enable) -; CHECK-NEXT: assert(cond) else $error("Test"); +; CHECK-LABEL: module Foo_A_B(); +; CHECK: wire y = Foo.a._layer_probe; +; CHECK: wire z = Foo.a._layer_probe; +; CHECK: always @(posedge Foo.clock) begin +; CHECK-NEXT: if (Foo.cond & Foo.a._layer_probe & Foo.enable) +; CHECK-NEXT: assert(Foo.cond) else $error("Test"); ; CHECK-NEXT: end // always @(posedge) ; CHECK-NEXT: endmodule -; CHECK-LABEL: module Foo_A( -; CHECK-NEXT: input in -; CHECK: wire x = in; -; CHECK-NEXT: wire x_probe = x; +; CHECK-LABEL: module Foo_A(); +; CHECK: wire x = Foo.in; +; CHECK-NEXT: wire _layer_probe = x; ; CHECK-NEXT: endmodule ; CHECK-LABEL: FILE "layers-Foo-A-B.sv" ; CHECK: `include "layers-Foo-A.sv" ; CHECK-NEXT: `ifndef layers_Foo_A_B ; CHECK-NEXT: `define layers_Foo_A_B -; CHECK-NEXT: bind Foo Foo_A_B a_b ( -; CHECK-NEXT: .x (Foo.a.x_probe), -; CHECK-NEXT: .cond (cond), -; CHECK-NEXT: .enable (enable), -; CHECK-NEXT: .clock (clock) -; CHECK-NEXT: ); +; CHECK-NEXT: bind Foo Foo_A_B a_b (); ; CHECK-NEXT: `endif // layers_Foo_A_B ; CHECK-LABEL: FILE "layers-Foo-A.sv" ; CHECK: `ifndef layers_Foo_A ; CHECK-NEXT: `define layers_Foo_A -; CHECK-NEXT: bind Foo Foo_A a ( -; CHECK-NEXT: .in (in) -; CHECK-NEXT: ); +; CHECK-NEXT: bind Foo Foo_A a (); ; CHECK-NEXT: `endif // layers_Foo_A diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir index ffac876a78..e471d79e72 100644 --- a/test/firtool/lower-layers.fir +++ b/test/firtool/lower-layers.fir @@ -1,4 +1,4 @@ -; RUN: firtool %s -disable-all-randomization -split-input-file | FileCheck %s +; RUN: firtool %s -disable-all-randomization -split-input-file -advanced-layer-sink -lowering-options=emittedLineLength=1024 | 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 @@ -83,14 +83,11 @@ circuit TestHarness: layer Verification, bind: - ; CHECK: module DUT_Verification( - ; CHECK: input clock, - ; CHECK: input [31:0] a - ; CHECK: ); + ; CHECK: module DUT_Verification(); ; CHECK: reg [31:0] pc_d; ; CHECK: wire [31:0] pc_d_probe = pc_d; - ; CHECK: always @(posedge clock) - ; CHECK: pc_d <= a; + ; CHECK: always @(posedge DUT.clock) + ; CHECK: pc_d <= DUT.a; ; CHECK: endmodule ; CHECK: module DUT( @@ -124,15 +121,11 @@ circuit TestHarness: layerblock Verification: define trace = x - ; CHECK: module TestHarness_Verification( - ; CHECK: input [31:0] dut_trace, - ; CHECK: input clock, - ; CHECK: reset - ; CHECK: ); + ; CHECK: module TestHarness_Verification() ; CHECK: `ifndef SYNTHESIS - ; CHECK: always @(posedge clock) begin - ; CHECK: if ((`PRINTF_COND_) & reset) - ; CHECK: $fwrite(`PRINTF_FD_, "The last PC was: %x", dut_trace); + ; CHECK: always @(posedge TestHarness.clock) begin + ; CHECK: if ((`PRINTF_COND_) & TestHarness.reset) + ; CHECK: $fwrite(`PRINTF_FD_, "The last PC was: %x", TestHarness.dut.verification.pc_d_probe); ; CHECK: end // always @(posedge) ; CHECK: `endif // not def SYNTHESIS ; CHECK: endmodule @@ -167,13 +160,95 @@ circuit TestHarness: ; 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: bind DUT DUT_Verification verification (); +; CHECK: bind TestHarness TestHarness_Verification verification (); ; CHECK: `endif // layers_TestHarness_Verification + +; // ----- + +; This example demonstrates forcing _out_ of a layer into the outer module, a +; parent layer, or into another module. + +FIRRTL version 5.1.0 +circuit Foo: %[[ + {"class": "firrtl.transforms.DontTouchAnnotation", "target": "~|ForceOutOfLayer>a"}, + {"class": "firrtl.transforms.DontTouchAnnotation", "target": "~|Submodule>root"}, + {"class": "firrtl.transforms.DontTouchAnnotation", "target": "~|Submodule>a"}, + {"class": "firrtl.transforms.DontTouchAnnotation", "target": "~|Submodule>b"} +]] + + layer A, bind: + layer B, inline: + + ; Test that forcing out of a layer into the root module works. + ; + ; CHECK: module ForceOutOfLayer_A(); + ; CHECK: initial + ; CHECK-NEXT: force ForceOutOfLayer.a = 2'h1; + ; CHECK: endmodule + ; + ; CHECK: module ForceOutOfLayer(); + ; CHECK: initial + ; CHECK-NEXT: force ForceOutOfLayer.a = 2'h2; + ; CHECK: endmodule + module ForceOutOfLayer: + + wire a: UInt<2> + connect a, UInt<2>(0) + + wire a_probe: RWProbe> + define a_probe = rwprobe(a) + + layerblock A: + force_initial(a_probe, UInt<2>(1)) + + layerblock B: + force_initial(a_probe, UInt<2>(2)) + + ; Test that forcing out of a layer into another works. Test both forcing into + ; the other module and forcing into a layer in the other module. + ; + ; CHECK: module ForceIntoSubmodule_A(); + ; CHECK: initial + ; CHECK-NEXT: force ForceIntoSubmodule.submodule.root = 2'h1; + ; CHECK-NEXT: force ForceIntoSubmodule.submodule.a.a = 2'h1; + ; CHECK: endmodule + ; + ; CHECK: module ForceIntoSubmodule(); + ; CHECK: initial + ; CHECK-NEXT: force ForceIntoSubmodule.submodule.root = 2'h2; + ; CHECK-NEXT: force ForceIntoSubmodule.submodule.b = 2'h2; + ; CHECK: endmodule + module Submodule: + output root_probe: RWProbe> + output a_probe: RWProbe, A> + output b_probe: RWProbe, B> + + wire root: UInt<2> + connect root, UInt<2>(0) + define root_probe = rwprobe(root) + + layerblock A: + wire a: UInt<2> + connect a, UInt<2>(0) + define a_probe = rwprobe(a) + + layerblock B: + wire b: UInt<2> + connect b, UInt<2>(0) + define b_probe = rwprobe(b) + + module ForceIntoSubmodule: + inst submodule of Submodule + + layerblock A: + force_initial(submodule.root_probe, UInt<2>(1)) + force_initial(submodule.a_probe, UInt<2>(1)) + + layerblock B: + force_initial(submodule.root_probe, UInt<2>(2)) + force_initial(submodule.b_probe, UInt<2>(2)) + + public module Foo: + inst forceOutOfLayer of ForceOutOfLayer + inst forceIntoSubmodule of ForceIntoSubmodule diff --git a/tools/firtool/firtool.cpp b/tools/firtool/firtool.cpp index afbd43fc36..b5a2d6a887 100644 --- a/tools/firtool/firtool.cpp +++ b/tools/firtool/firtool.cpp @@ -473,8 +473,7 @@ static LogicalResult processBuffer( if (failed(parsePassPipeline(StringRef(highFIRRTLPassPlugin), pm))) return failure(); - if (failed(firtool::populateCHIRRTLToLowFIRRTL(pm, firtoolOptions, - inputFilename))) + if (failed(firtool::populateCHIRRTLToLowFIRRTL(pm, firtoolOptions))) return failure(); if (!lowFIRRTLPassPlugin.empty()) @@ -484,7 +483,8 @@ static LogicalResult processBuffer( // Lower if we are going to verilog or if lowering was specifically // requested. if (outputFormat != OutputIRFir) { - if (failed(firtool::populateLowFIRRTLToHW(pm, firtoolOptions))) + if (failed( + firtool::populateLowFIRRTLToHW(pm, firtoolOptions, inputFilename))) return failure(); if (!hwPassPlugin.empty()) if (failed(parsePassPipeline(StringRef(hwPassPlugin), pm)))