diff --git a/include/circt/Dialect/Arc/ArcPasses.td b/include/circt/Dialect/Arc/ArcPasses.td index 90e97a84a061..1eb6225c32ae 100644 --- a/include/circt/Dialect/Arc/ArcPasses.td +++ b/include/circt/Dialect/Arc/ArcPasses.td @@ -366,4 +366,23 @@ def StripSV : Pass<"arc-strip-sv", "mlir::ModuleOp"> { ]; } +def ResolveXMRRef : Pass<"arc-resolve-xmr", "mlir::ModuleOp"> { + let summary = "Resolve sv.xmr.ref into direct signal references"; + let description = [{ + This pass traverses hw.hierpath metadata to replace sv.xmr.ref operations + with direct SSA value references, preserving observability before flattening. + }]; + + let options = [ + Option<"lowerBlackBoxInternalToZero", "lower-blackbox-internal-to-zero", + "bool", "false", + "Lower unresolved references to internal blackbox symbols to constant zero."> + ]; + + let dependentDialects = [ + "circt::sv::SVDialect", + "circt::hw::HWDialect" + ]; +} + #endif // CIRCT_DIALECT_ARC_ARCPASSES_TD diff --git a/lib/Dialect/Arc/Transforms/CMakeLists.txt b/lib/Dialect/Arc/Transforms/CMakeLists.txt index cdfb010a8f79..ecb37effa53d 100644 --- a/lib/Dialect/Arc/Transforms/CMakeLists.txt +++ b/lib/Dialect/Arc/Transforms/CMakeLists.txt @@ -21,6 +21,7 @@ add_circt_dialect_library(CIRCTArcTransforms MergeTaps.cpp MuxToControlFlow.cpp PrintCostModel.cpp + ResolveXMRRef.cpp SimplifyVariadicOps.cpp SplitFuncs.cpp SplitLoops.cpp diff --git a/lib/Dialect/Arc/Transforms/ResolveXMRRef.cpp b/lib/Dialect/Arc/Transforms/ResolveXMRRef.cpp new file mode 100644 index 000000000000..071a12d888da --- /dev/null +++ b/lib/Dialect/Arc/Transforms/ResolveXMRRef.cpp @@ -0,0 +1,581 @@ +//===- ResolveXmrRef.cpp --------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/Arc/ArcPasses.h" +#include "circt/Dialect/HW/HWOpInterfaces.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/HW/HWTypes.h" +#include "circt/Dialect/HW/InnerSymbolTable.h" +#include "circt/Dialect/SV/SVOps.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/raw_ostream.h" + +namespace circt { +namespace arc { +#define GEN_PASS_DEF_RESOLVEXMRREF +#include "circt/Dialect/Arc/ArcPasses.h.inc" +} // namespace arc +} // namespace circt + +using namespace circt; +using namespace arc; + +static void appendEscaped(std::string &out, StringRef text) { + for (char c : text) { + if (c == '\\' || c == '|' || c == ':') + out.push_back('\\'); + out.push_back(c); + } +} + +static std::string buildPathSuffixKey(ArrayAttr pathArray, unsigned index) { + std::string key; + for (unsigned i = index, e = pathArray.size(); i < e; ++i) { + auto ref = cast(pathArray[i]); + appendEscaped(key, ref.getModule().getValue()); + key.push_back(':'); + appendEscaped(key, ref.getName().getValue()); + key.push_back('|'); + } + return key; +} + +static std::string buildBoreKey(hw::HWModuleOp childMod, ArrayAttr pathArray, + unsigned index, Type targetType) { + std::string key; + appendEscaped(key, childMod.getModuleName()); + key.push_back('|'); + key.append(buildPathSuffixKey(pathArray, index)); + key.push_back('|'); + llvm::raw_string_ostream os(key); + targetType.print(os); + os.flush(); + return key; +} + +static bool isConditionallyGuarded(Operation *op) { + for (Operation *parent = op->getParentOp(); parent; + parent = parent->getParentOp()) + if (isa(parent)) + return true; + return false; +} + +static std::string buildCaptureKey(hw::HWModuleOp payloadMod, + ArrayAttr pathArray, Type targetType) { + std::string key; + appendEscaped(key, payloadMod.getModuleName()); + key.push_back('|'); + key.append(buildPathSuffixKey(pathArray, 0)); + key.push_back('|'); + llvm::raw_string_ostream os(key); + targetType.print(os); + os.flush(); + return key; +} + +namespace { +struct ResolveXMRRefPass + : public arc::impl::ResolveXMRRefBase { + using Base = arc::impl::ResolveXMRRefBase; + using Base::Base; + + llvm::StringMap borePortByKey; + llvm::StringMap captureInputPortByKey; + llvm::StringMap nextBoreOrdinalByModule; + bool bindContextDiagnosedFailure = false; + + void runOnOperation() override; + Value boreRecursively(ArrayAttr pathArray, unsigned index, + hw::HWModuleOp currentMod, SymbolTable &symTable, + StringAttr targetSym, Type targetType); + Value getInstancePortValue(hw::InstanceOp inst, unsigned portIdx, + mlir::Operation *moduleOp); + std::string createUniqueBoredPortName(hw::HWModuleOp mod, + StringAttr targetSym); + std::string createUniqueCapturePortName(hw::HWModuleOp mod, + StringAttr targetSym); + enum class XMRUseMode { ReadOnly, Unsupported }; + XMRUseMode classifyXMRUse(sv::XMRRefOp xmrRefOp); + Value resolveViaBindContext(sv::XMRRefOp xmrRefOp, hw::HierPathOp pathOp, + hw::HWModuleOp payloadMod, SymbolTable &symTable, + Type targetType); +}; +} // namespace + +ResolveXMRRefPass::XMRUseMode +ResolveXMRRefPass::classifyXMRUse(sv::XMRRefOp xmrRefOp) { + bool hasRead = false; + for (Operation *user : xmrRefOp->getUsers()) { + if (isa(user)) { + hasRead = true; + continue; + } + + xmrRefOp.emitError() + << "unsupported sv.xmr.ref use by '" << user->getName() + << "'; only read-only uses (sv.read_inout) are supported"; + return XMRUseMode::Unsupported; + } + + if (!hasRead) { + xmrRefOp.emitError("sv.xmr.ref has no read uses; write/other uses are not " + "supported yet"); + return XMRUseMode::Unsupported; + } + + return XMRUseMode::ReadOnly; +} + +std::string ResolveXMRRefPass::createUniqueBoredPortName(hw::HWModuleOp mod, + StringAttr targetSym) { + auto moduleName = mod.getModuleName(); + unsigned &ordinal = nextBoreOrdinalByModule[moduleName]; + + while (true) { + std::string candidate = "xmr_bored_" + targetSym.getValue().str() + "_" + + std::to_string(ordinal++); + + bool exists = false; + for (unsigned i = 0, e = mod.getNumPorts(); i < e; ++i) { + if (mod.getPort(i).name.getValue() == candidate) { + exists = true; + break; + } + } + if (!exists) + return candidate; + } +} + +std::string +ResolveXMRRefPass::createUniqueCapturePortName(hw::HWModuleOp mod, + StringAttr targetSym) { + auto moduleName = mod.getModuleName(); + unsigned &ordinal = nextBoreOrdinalByModule[moduleName]; + + while (true) { + std::string candidate = "xmr_capture_" + targetSym.getValue().str() + "_" + + std::to_string(ordinal++); + + bool exists = false; + for (unsigned i = 0, e = mod.getNumPorts(); i < e; ++i) { + if (mod.getPort(i).name.getValue() == candidate) { + exists = true; + break; + } + } + if (!exists) + return candidate; + } +} + +Value ResolveXMRRefPass::resolveViaBindContext(sv::XMRRefOp xmrRefOp, + hw::HierPathOp pathOp, + hw::HWModuleOp payloadMod, + SymbolTable &symTable, + Type targetType) { + bindContextDiagnosedFailure = false; + auto pathArray = pathOp.getNamepath(); + auto root = dyn_cast(pathArray[0]); + if (!root) + return nullptr; + + SmallVector bindInstances; + bool hasConditionalBind = false; + auto module = getOperation(); + module.walk([&](sv::BindOp bindOp) { + auto boundInst = bindOp.getReferencedInstance(nullptr); + if (!boundInst) + return; + + if (boundInst.getModuleName() != payloadMod.getModuleName()) + return; + + if (bindOp.getInstance().getModule() != root.getModule()) + return; + + if (isConditionallyGuarded(bindOp)) { + hasConditionalBind = true; + return; + } + + bindInstances.push_back(boundInst); + }); + + if (hasConditionalBind) { + xmrRefOp.emitError("cannot resolve XMR through conditionally guarded " + "sv.bind; only unconditional sv.bind is supported"); + bindContextDiagnosedFailure = true; + return nullptr; + } + + if (bindInstances.empty()) + return nullptr; + + if (bindInstances.size() != 1) { + xmrRefOp.emitError("bind-context XMR requires a unique unconditional " + "sv.bind instance"); + bindContextDiagnosedFailure = true; + return nullptr; + } + + auto hostModule = bindInstances.front()->getParentOfType(); + if (!hostModule || hostModule.getModuleName() != root.getModule()) { + xmrRefOp.emitError("bind host module mismatch while resolving XMR path"); + bindContextDiagnosedFailure = true; + return nullptr; + } + + Value hostValue = boreRecursively(pathArray, 0, hostModule, symTable, + pathOp.ref(), targetType); + if (!hostValue) + return nullptr; + + llvm::SmallPtrSet bindInstSet; + for (auto inst : bindInstances) + bindInstSet.insert(inst.getOperation()); + + auto uses = SymbolTable::getSymbolUses(payloadMod, module); + if (!uses) + return nullptr; + + for (auto use : *uses) { + auto userInst = dyn_cast(use.getUser()); + if (!userInst) + continue; + if (!bindInstSet.contains(userInst.getOperation())) { + xmrRefOp.emitError("payload module has non-bind instances; refusing to " + "resolve bind-context XMR ambiguously"); + bindContextDiagnosedFailure = true; + return nullptr; + } + } + + std::string captureKey = buildCaptureKey(payloadMod, pathArray, targetType); + unsigned inputPortIdx; + auto captureIt = captureInputPortByKey.find(captureKey); + if (captureIt == captureInputPortByKey.end()) { + std::string portName = + createUniqueCapturePortName(payloadMod, pathOp.ref()); + + OpBuilder b(payloadMod.getContext()); + auto appended = + payloadMod.appendInput(b.getStringAttr(portName), targetType); + inputPortIdx = appended.second.getArgNumber(); + captureInputPortByKey[captureKey] = inputPortIdx; + + SmallVector instances; + instances.reserve(bindInstSet.size()); + for (auto use : *uses) + if (auto userInst = dyn_cast(use.getUser())) + instances.push_back(userInst); + + for (auto userInst : instances) { + SmallVector operands(userInst.getOperands()); + if (!bindInstSet.contains(userInst.getOperation())) { + xmrRefOp.emitError("payload module has non-bind instances; refusing to " + "resolve bind-context XMR ambiguously"); + bindContextDiagnosedFailure = true; + return nullptr; + } + operands.push_back(hostValue); + + OpBuilder ib(userInst); + auto newInst = hw::InstanceOp::create( + ib, userInst.getLoc(), payloadMod, userInst.getInstanceNameAttr(), + operands, userInst.getParameters(), userInst.getInnerSymAttr()); + + for (unsigned i = 0; i < userInst.getNumResults(); ++i) + userInst.getResult(i).replaceAllUsesWith(newInst.getResult(i)); + + if (userInst.getDoNotPrint()) + newInst.setDoNotPrintAttr(UnitAttr::get(newInst.getContext())); + + userInst.erase(); + } + + return payloadMod.getBodyBlock()->getArgument(inputPortIdx); + } else { + inputPortIdx = captureIt->second; + + auto bindInst = bindInstances.front(); + if (inputPortIdx >= bindInst.getNumOperands()) { + xmrRefOp.emitError("bind instance is missing required capture input " + "operand for previously materialized XMR"); + bindContextDiagnosedFailure = true; + return nullptr; + } + + return bindInst.getOperand(inputPortIdx); + } +} + +void ResolveXMRRefPass::runOnOperation() { + auto module = getOperation(); + SymbolTable symTable(module); + borePortByKey.clear(); + captureInputPortByKey.clear(); + nextBoreOrdinalByModule.clear(); + bool failed = false; + + SmallVector opsToErase; + + module.walk([&](sv::XMRRefOp xmrRefOp) { + if (failed) + return; + + if (classifyXMRUse(xmrRefOp) != XMRUseMode::ReadOnly) { + failed = true; + return; + } + + auto pathOp = xmrRefOp.getReferencedPath(nullptr); + if (!pathOp) { + xmrRefOp.emitError("unable to resolve path for XMR reference"); + failed = true; + return; + } + + StringAttr leafModName = pathOp.leafMod(); + Operation *leafMod = symTable.lookup(leafModName); + if (!leafMod) { + xmrRefOp.emitError("leaf module not found in symbol table"); + failed = true; + return; + } + + OpBuilder builder(xmrRefOp); + Value resolvedValue = nullptr; + + Type targetType = cast(xmrRefOp.getType()).getElementType(); + + ArrayAttr pathArray = pathOp.getNamepath(); + auto currentModule = xmrRefOp->getParentOfType(); + + resolvedValue = boreRecursively(pathArray, 0, currentModule, symTable, + pathOp.ref(), targetType); + + if (!resolvedValue && pathOp.isComponent()) { + resolvedValue = resolveViaBindContext(xmrRefOp, pathOp, currentModule, + symTable, targetType); + if (!resolvedValue && bindContextDiagnosedFailure) { + failed = true; + return; + } + } + + if (!resolvedValue) { + if (isa(leafMod)) { + xmrRefOp.emitError("unable to resolve XMR into internal blackbox " + "symbol; rerun with " + "--arc-resolve-xmr=lower-blackbox-internal-to-zero " + "to force zero-lowering"); + failed = true; + return; + } + if (pathOp.isComponent()) { + xmrRefOp.emitError("unable to resolve component path"); + failed = true; + return; + } + xmrRefOp.emitError("unsupported XMR reference type"); + failed = true; + return; + } + + if (resolvedValue) { + for (OpOperand &use : + llvm::make_early_inc_range(xmrRefOp.getResult().getUses())) { + if (auto readOp = dyn_cast(use.getOwner())) { + readOp.getResult().replaceAllUsesWith(resolvedValue); + opsToErase.push_back(readOp); + } + } + opsToErase.push_back(xmrRefOp); + } + }); + + if (failed) + return signalPassFailure(); + + module.walk([&](hw::HierPathOp pathOp) { + if (pathOp->use_empty()) { + opsToErase.push_back(pathOp); + } + }); + + for (Operation *op : opsToErase) + op->erase(); +} + +Value ResolveXMRRefPass::boreRecursively(ArrayAttr pathArray, unsigned index, + hw::HWModuleOp currentMod, + SymbolTable &symTable, + StringAttr targetSym, + Type targetType) { + auto innerRef = cast(pathArray[index]); + StringAttr symName = innerRef.getName(); + + if (index == pathArray.size() - 1) { + hw::InnerSymbolTable ist(currentMod); + auto target = ist.lookup(symName); + if (!target) + return nullptr; + + if (target.isPort()) { + unsigned pIdx = target.getPort(); + if (currentMod.getPort(pIdx).isOutput()) { + auto outOp = + cast(currentMod.getBodyBlock()->getTerminator()); + unsigned oIdx = 0; + for (unsigned i = 0; i < pIdx; ++i) + if (currentMod.getPort(i).isOutput()) + oIdx++; + return outOp.getOperand(oIdx); + } + unsigned aIdx = 0; + for (unsigned i = 0; i < pIdx; ++i) + if (!currentMod.getPort(i).isOutput()) + aIdx++; + return currentMod.getBodyBlock()->getArgument(aIdx); + } + return target.getOp()->getNumResults() > 0 ? target.getOp()->getResult(0) + : nullptr; + } + + hw::InnerSymbolTable currentIST(currentMod); + auto instOp = + dyn_cast_or_null(currentIST.lookup(symName).getOp()); + if (!instOp) + return nullptr; + + Operation *childModOp = symTable.lookup(instOp.getModuleNameAttr().getAttr()); + if (!childModOp) + return nullptr; + + auto nextRef = cast(pathArray[index + 1]); + StringAttr nextSym = nextRef.getName(); + + hw::InnerSymbolTable childIST(childModOp); + auto nextTarget = childIST.lookup(nextSym); + + if (nextTarget && nextTarget.isPort()) { + return getInstancePortValue(instOp, nextTarget.getPort(), childModOp); + } + + if (isa(childModOp)) { + if (lowerBlackBoxInternalToZero) { + instOp.emitWarning() << "XMR target '" << nextSym + << "' is internal to blackbox. Lowering to 0."; + OpBuilder b(instOp); + return hw::ConstantOp::create(b, instOp.getLoc(), targetType, 0); + } + return nullptr; + } + + auto childMod = cast(childModOp); + std::string boreKey = + buildBoreKey(childMod, pathArray, index + 1, targetType); + if (auto it = borePortByKey.find(boreKey); it != borePortByKey.end()) { + if (it->second >= instOp.getNumResults()) + return nullptr; + return instOp.getResult(it->second); + } + + Value childVal = boreRecursively(pathArray, index + 1, childMod, symTable, + targetSym, targetType); + if (!childVal) + return nullptr; + + std::string portName = createUniqueBoredPortName(childMod, targetSym); + + OpBuilder mb(childMod.getContext()); + hw::PortInfo newPort; + newPort.name = mb.getStringAttr(portName); + newPort.dir = hw::ModulePort::Direction::Output; + newPort.type = targetType; + + SmallVector> newOutputs; + newOutputs.push_back({childMod.getNumOutputPorts(), newPort}); + childMod.modifyPorts({}, newOutputs, {}, {}); + + auto outOp = cast(childMod.getBodyBlock()->getTerminator()); + outOp->insertOperands(outOp->getNumOperands(), childVal); + unsigned resIdx = childMod.getNumOutputPorts() - 1; + borePortByKey[boreKey] = resIdx; + + auto top = getOperation(); + auto uses = SymbolTable::getSymbolUses(childMod, top); + if (!uses) + return nullptr; + + SmallVector instances; + for (auto use : *uses) + if (auto userInst = dyn_cast(use.getUser())) + instances.push_back(userInst); + + Value boredValue; + for (auto userInst : instances) { + SmallVector operands(userInst.getOperands()); + OpBuilder b(userInst); + auto newInst = hw::InstanceOp::create( + b, userInst.getLoc(), childMod, userInst.getInstanceNameAttr(), + operands, userInst.getParameters(), userInst.getInnerSymAttr()); + + for (unsigned i = 0; i < userInst.getNumResults(); ++i) + userInst.getResult(i).replaceAllUsesWith(newInst.getResult(i)); + + if (userInst.getDoNotPrint()) + newInst.setDoNotPrintAttr(UnitAttr::get(newInst.getContext())); + + if (userInst == instOp) + boredValue = newInst.getResult(resIdx); + + userInst.erase(); + } + + return boredValue; +} + +Value ResolveXMRRefPass::getInstancePortValue(hw::InstanceOp inst, + unsigned portIdx, + mlir::Operation *moduleOp) { + unsigned outIdx = 0; + unsigned inOrInoutIdx = 0; + + for (unsigned i = 0; i < portIdx; ++i) { + hw::PortInfo port; + if (auto mod = dyn_cast(moduleOp)) + port = mod.getPort(i); + else + port = cast(moduleOp).getPort(i); + + if (port.isOutput()) + outIdx++; + else + inOrInoutIdx++; + } + + hw::PortInfo targetPort; + if (auto mod = dyn_cast(moduleOp)) + targetPort = mod.getPort(portIdx); + else + targetPort = cast(moduleOp).getPort(portIdx); + + if (targetPort.isOutput()) { + if (outIdx < inst.getNumResults()) + return inst.getResult(outIdx); + } else { + if (inOrInoutIdx < inst.getNumOperands()) + return inst.getOperand(inOrInoutIdx); + } + + return nullptr; +} diff --git a/lib/Tools/arcilator/pipelines.cpp b/lib/Tools/arcilator/pipelines.cpp index 01515045ba9f..37bd9bdfce78 100644 --- a/lib/Tools/arcilator/pipelines.cpp +++ b/lib/Tools/arcilator/pipelines.cpp @@ -33,6 +33,7 @@ using namespace arc; void circt::populateArcPreprocessingPipeline( OpPassManager &pm, const ArcPreprocessingOptions &options) { + pm.addPass(arc::createResolveXMRRef()); pm.addPass(om::createStripOMPass()); pm.addPass(emit::createStripEmitPass()); pm.addPass(createLowerFirMemPass()); diff --git a/test/Dialect/Arc/resolve-xmr-ref-bind.mlir b/test/Dialect/Arc/resolve-xmr-ref-bind.mlir new file mode 100644 index 000000000000..cc00a051065c --- /dev/null +++ b/test/Dialect/Arc/resolve-xmr-ref-bind.mlir @@ -0,0 +1,26 @@ +// RUN: circt-opt --arc-resolve-xmr %s | FileCheck %s + +module { + hw.hierpath @bindPath [@Host::@src] + + // CHECK-LABEL: hw.module @Payload + // CHECK-SAME: in %xmr_capture_src_{{[0-9]+}} : i8 + // CHECK: hw.output %{{.+}} : i8 + hw.module @Payload(out o : i8) { + %x = sv.xmr.ref @bindPath : !hw.inout + %r = sv.read_inout %x : !hw.inout + hw.output %r : i8 + } + + // CHECK-LABEL: hw.module @Host + // CHECK-SAME: in %src_in : i8 + // CHECK-SAME: out out : i8 + // CHECK-NEXT: %[[P:.+]] = hw.instance "payload" sym @payload @Payload(xmr_capture_src_{{[0-9]+}}: %src_in: i8) -> (o: i8) {doNotPrint} + // CHECK-NEXT: hw.output %[[P]] : i8 + hw.module @Host(in %src_in : i8 {hw.exportPort = #hw}, out out : i8) { + %p = hw.instance "payload" sym @payload @Payload() -> (o: i8) {doNotPrint} + hw.output %p : i8 + } + + sv.bind <@Host::@payload> +} diff --git a/test/Dialect/Arc/resolve-xmr-ref-blackbox-zero.mlir b/test/Dialect/Arc/resolve-xmr-ref-blackbox-zero.mlir new file mode 100644 index 000000000000..028310afb404 --- /dev/null +++ b/test/Dialect/Arc/resolve-xmr-ref-blackbox-zero.mlir @@ -0,0 +1,18 @@ +// RUN: circt-opt --arc-resolve-xmr=lower-blackbox-internal-to-zero %s --verify-diagnostics | FileCheck %s + +module { + hw.hierpath @bbInternal [@Top::@bb, @BlackBox::@hidden] + + hw.module.extern @BlackBox(out out_clk : i1 {hw.exportPort = #hw}) + + // CHECK-LABEL: hw.module @Top + // CHECK: %[[FALSE:.+]] = hw.constant false + // CHECK: hw.output %[[FALSE]] : i1 + hw.module @Top(out o : i1) { + // expected-warning @below {{internal to blackbox. Lowering to 0.}} + hw.instance "bb" sym @bb @BlackBox() -> (out_clk: i1) + %x = sv.xmr.ref @bbInternal : !hw.inout + %r = sv.read_inout %x : !hw.inout + hw.output %r : i1 + } +} diff --git a/test/Dialect/Arc/resolve-xmr-ref-errors.mlir b/test/Dialect/Arc/resolve-xmr-ref-errors.mlir new file mode 100644 index 000000000000..b50528d93d71 --- /dev/null +++ b/test/Dialect/Arc/resolve-xmr-ref-errors.mlir @@ -0,0 +1,123 @@ +// RUN: circt-opt --arc-resolve-xmr %s --verify-diagnostics --split-input-file + +// ----- + +module { + hw.hierpath @p [@Top::@mid, @Mid::@leaf, @Leaf::@sig] + + hw.module @Leaf() { + %c = hw.constant 1 : i1 + %w = hw.wire %c sym @sig : i1 + hw.output + } + + hw.module @Mid() { + hw.instance "leaf" sym @leaf @Leaf() -> () + hw.output + } + + hw.module @Top() { + hw.instance "mid" sym @mid @Mid() -> () + // expected-error @below {{unsupported sv.xmr.ref use by 'sv.assign'; only read-only uses (sv.read_inout) are supported}} + %x = sv.xmr.ref @p : !hw.inout + %t = hw.constant true + sv.assign %x, %t : i1 + hw.output + } +} + +// ----- + +module { + hw.hierpath @p [@Top::@mid, @Mid::@leaf, @Leaf::@sig] + + hw.module @Leaf() { + %c = hw.constant 0 : i1 + %w = hw.wire %c sym @sig : i1 + hw.output + } + + hw.module @Mid() { + hw.instance "leaf" sym @leaf @Leaf() -> () + hw.output + } + + hw.module @Top() { + hw.instance "mid" sym @mid @Mid() -> () + // expected-error @below {{sv.xmr.ref has no read uses; write/other uses are not supported yet}} + %x = sv.xmr.ref @p : !hw.inout + hw.output + } +} + +// ----- + +module { + hw.hierpath @bbInternal [@Top::@bb, @BlackBox::@hidden] + + hw.module.extern @BlackBox(out out_clk : i1 {hw.exportPort = #hw}) + + hw.module @Top(out o : i1) { + hw.instance "bb" sym @bb @BlackBox() -> (out_clk: i1) + // expected-error @below {{unable to resolve XMR into internal blackbox symbol; rerun with --arc-resolve-xmr=lower-blackbox-internal-to-zero to force zero-lowering}} + %x = sv.xmr.ref @bbInternal : !hw.inout + %r = sv.read_inout %x : !hw.inout + hw.output %r : i1 + } +} + +// ----- + +module { + sv.macro.decl @COND + hw.hierpath @bindPath [@Host::@src] + + hw.module @Payload(out o : i8) { + // expected-error @below {{cannot resolve XMR through conditionally guarded sv.bind; only unconditional sv.bind is supported}} + %x = sv.xmr.ref @bindPath : !hw.inout + %r = sv.read_inout %x : !hw.inout + hw.output %r : i8 + } + + hw.module @Host(in %src_in : i8 {hw.exportPort = #hw}, out out : i8) { + %src = hw.constant 7 : i8 + hw.instance "payload" sym @payload @Payload() -> (o: i8) {doNotPrint} + hw.output %src : i8 + } + + sv.ifdef @COND { + sv.bind <@Host::@payload> + } +} + +// ----- + +module { + hw.hierpath @bindPath [@Host::@src] + + hw.module @Payload(out o : i8) { + // expected-error @below {{payload module has non-bind instances; refusing to resolve bind-context XMR ambiguously}} + %x = sv.xmr.ref @bindPath : !hw.inout + %r = sv.read_inout %x : !hw.inout + hw.output %r : i8 + } + + hw.module @Host(in %src_in : i8 {hw.exportPort = #hw}, out out : i8) { + hw.instance "payload" sym @payload @Payload() -> (o: i8) {doNotPrint} + hw.output %src_in : i8 + } + + hw.module @Top() { + %src = hw.constant 9 : i8 + hw.instance "host" @Host(src_in: %src: i8) -> (out: i8) + hw.instance "other" @Other() -> (o: i8) + hw.output + } + + hw.module @Other(out o : i8) { + %p = hw.instance "payload2" sym @payload2 @Payload() -> (o: i8) + hw.output %p : i8 + } + + sv.bind <@Host::@payload> +} diff --git a/test/Dialect/Arc/resolve-xmr-ref.mlir b/test/Dialect/Arc/resolve-xmr-ref.mlir new file mode 100644 index 000000000000..0f592d815e9a --- /dev/null +++ b/test/Dialect/Arc/resolve-xmr-ref.mlir @@ -0,0 +1,193 @@ +// RUN: circt-opt --arc-resolve-xmr=lower-blackbox-internal-to-zero %s | FileCheck %s + +module { + hw.hierpath @intSigPath [@Top::@mid_inst, @Mid::@leaf_li, @LeafInternal::@signal] + hw.hierpath @intPortPath [@Top::@mid_inst, @Mid::@leaf_li, @LeafInternal::@in_data] + hw.hierpath @extPortPath [@Top::@mid_inst, @Mid::@cg_inst, @ClockGate::@out_clk] + hw.hierpath @extSigPath [@Top::@mid_inst, @Mid::@cg_inst, @ClockGate::@secret_node] + hw.hierpath @extNoUsedPath [@Top::@mid_inst, @Mid::@cg_inst, @ClockGate::@noused_node] + + // Reuse checks: same source path should reuse the same bored ports. + hw.hierpath @reusePathA [@TopReuse::@mid_inst_0, @MidReuse::@leaf_shared, @LeafReuse::@sig] + hw.hierpath @reusePathB [@TopReuse::@mid_inst_1, @MidReuse::@leaf_shared, @LeafReuse::@sig] + + // Collision checks: same target symbol name on different path suffixes should + // not be merged into one bored port. + hw.hierpath @collisionA [@TopCollision::@mid_inst, @MidCollision::@leaf_a, @LeafA::@data] + hw.hierpath @collisionB [@TopCollision::@mid_inst, @MidCollision::@leaf_b, @LeafB::@data] + + // Collision checks (same type): same target symbol name and type on different + // suffix paths should still produce distinct bored ports. + hw.hierpath @sameTypeCollisionA [@TopSameTypeCollision::@mid_inst, @MidSameTypeCollision::@leaf_a, @LeafSameTypeA::@sig] + hw.hierpath @sameTypeCollisionB [@TopSameTypeCollision::@mid_inst, @MidSameTypeCollision::@leaf_b, @LeafSameTypeB::@sig] + + // CHECK-LABEL: hw.module @LeafInternal + // CHECK-SAME: out xmr_bored_signal_{{[0-9]+}} : i32 + hw.module @LeafInternal(in %in_data : i32 {hw.exportPort = #hw}) { + %c42_i32 = hw.constant 42 : i32 + %wire_sig = hw.wire %c42_i32 sym @signal : i32 + // CHECK: hw.output %wire_sig : i32 + hw.output + } + + // CHECK-LABEL: hw.module.extern @ClockGate + hw.module.extern @ClockGate(out out_clk : i1 {hw.exportPort = #hw}) + + // CHECK-LABEL: hw.module @Mid + // CHECK-SAME: out xmr_bored_signal_{{[0-9]+}} : i32 + // CHECK-SAME: out xmr_bored_in_data_{{[0-9]+}} : i32 + // CHECK-SAME: out xmr_bored_out_clk_{{[0-9]+}} : i1 + // CHECK-SAME: out xmr_bored_secret_node_{{[0-9]+}} : i1 + hw.module @Mid() { + %c0 = hw.constant 0 : i32 + // CHECK: %[[LI_OUT:.+]] = hw.instance "leaf_li" sym @leaf_li @LeafInternal(in_data: %{{.+}}: i32) -> (xmr_bored_signal_{{[0-9]+}}: i32) + hw.instance "leaf_li" sym @leaf_li @LeafInternal(in_data: %c0 : i32) -> () + + // CHECK: %[[FALSE:.+]] = hw.constant false + // CHECK: %[[CG_OUT:.+]] = hw.instance "cg_inst" sym @cg_inst @ClockGate() -> (out_clk: i1) + %cg_clk = hw.instance "cg_inst" sym @cg_inst @ClockGate() -> (out_clk: i1) + + // CHECK: hw.output %[[LI_OUT]], %{{.+}}, %[[CG_OUT]], %[[FALSE]] : i32, i32, i1, i1 + hw.output + } + + // CHECK-LABEL: hw.module @Top + // CHECK: %[[MID_OUT_SIG:.+]], %[[MID_OUT_PORT:.+]], %[[MID_OUT_CLK:.+]], %[[MID_OUT_SEC:.+]] = hw.instance "mid_inst" sym @mid_inst @Mid() -> (xmr_bored_signal_{{[0-9]+}}: i32, xmr_bored_in_data_{{[0-9]+}}: i32, xmr_bored_out_clk_{{[0-9]+}}: i1, xmr_bored_secret_node_{{[0-9]+}}: i1) + hw.module @Top(out out_int_sig : i32, out out_int_port : i32, + out out_ext_port : i1, out out_ext_sig : i1) { + hw.instance "mid_inst" sym @mid_inst @Mid() -> () + + %0 = sv.xmr.ref @intSigPath : !hw.inout + %1 = sv.read_inout %0 : !hw.inout + %2 = sv.xmr.ref @intPortPath : !hw.inout + %3 = sv.read_inout %2 : !hw.inout + %4 = sv.xmr.ref @extPortPath : !hw.inout + %5 = sv.read_inout %4 : !hw.inout + %6 = sv.xmr.ref @extSigPath : !hw.inout + %7 = sv.read_inout %6 : !hw.inout + + // CHECK: hw.output %[[MID_OUT_SIG]], %[[MID_OUT_PORT]], %[[MID_OUT_CLK]], %[[MID_OUT_SEC]] : i32, i32, i1, i1 + hw.output %1, %3, %5, %7 : i32, i32, i1, i1 + } + + // CHECK-LABEL: hw.module @LeafReuse + // CHECK-SAME: out xmr_bored_sig_{{[0-9]+}} : i8 + hw.module @LeafReuse() { + %c = hw.constant 7 : i8 + %s = hw.wire %c sym @sig : i8 + // CHECK: hw.output %s : i8 + hw.output + } + + // CHECK-LABEL: hw.module @MidReuse + // CHECK-SAME: out xmr_bored_sig_{{[0-9]+}} : i8 + hw.module @MidReuse() { + // CHECK: %[[L0:.+]] = hw.instance "leaf_shared" sym @leaf_shared @LeafReuse() -> (xmr_bored_sig_{{[0-9]+}}: i8) + hw.instance "leaf_shared" sym @leaf_shared @LeafReuse() -> () + // CHECK: hw.output %[[L0]] : i8 + hw.output + } + + // Same downstream path suffixes should reuse a single bored output in + // @MidReuse even when reached via different top-level instances. + // CHECK-LABEL: hw.module @TopReuse + // CHECK: %[[M0:.+]] = hw.instance "mid_inst_0" sym @mid_inst_0 @MidReuse() -> (xmr_bored_sig_{{[0-9]+}}: i8) + // CHECK: %[[M1:.+]] = hw.instance "mid_inst_1" sym @mid_inst_1 @MidReuse() -> (xmr_bored_sig_{{[0-9]+}}: i8) + // CHECK: hw.output %[[M0]], %[[M1]], %[[M0]], %[[M1]] : i8, i8, i8, i8 + hw.module @TopReuse(out a0 : i8, out a1 : i8, out b0 : i8, out b1 : i8) { + hw.instance "mid_inst_0" sym @mid_inst_0 @MidReuse() -> () + hw.instance "mid_inst_1" sym @mid_inst_1 @MidReuse() -> () + + %ra0 = sv.xmr.ref @reusePathA : !hw.inout + %rb0 = sv.xmr.ref @reusePathB : !hw.inout + %ra1 = sv.xmr.ref @reusePathA : !hw.inout + %rb1 = sv.xmr.ref @reusePathB : !hw.inout + %va0 = sv.read_inout %ra0 : !hw.inout + %vb0 = sv.read_inout %rb0 : !hw.inout + %va1 = sv.read_inout %ra1 : !hw.inout + %vb1 = sv.read_inout %rb1 : !hw.inout + hw.output %va0, %vb0, %va1, %vb1 : i8, i8, i8, i8 + } + + // CHECK-LABEL: hw.module @LeafA + // CHECK-SAME: out xmr_bored_data_{{[0-9]+}} : i1 + hw.module @LeafA() { + %c = hw.constant true + %s = hw.wire %c sym @data : i1 + hw.output + } + + // CHECK-LABEL: hw.module @LeafB + // CHECK-SAME: out xmr_bored_data_{{[0-9]+}} : i4 + hw.module @LeafB() { + %c = hw.constant 3 : i4 + %s = hw.wire %c sym @data : i4 + hw.output + } + + // Two different suffix paths share target symbol name `data` but differ in + // type. They must not collide into one bored output. + // CHECK-LABEL: hw.module @MidCollision + // CHECK-SAME: out xmr_bored_data_{{[0-9]+}} : i1 + // CHECK-SAME: out xmr_bored_data_{{[0-9]+}} : i4 + // CHECK: %[[LA:.+]] = hw.instance "leaf_a" sym @leaf_a @LeafA() -> (xmr_bored_data_{{[0-9]+}}: i1) + // CHECK: %[[LB:.+]] = hw.instance "leaf_b" sym @leaf_b @LeafB() -> (xmr_bored_data_{{[0-9]+}}: i4) + // CHECK: hw.output %[[LA]], %[[LB]] : i1, i4 + hw.module @MidCollision() { + hw.instance "leaf_a" sym @leaf_a @LeafA() -> () + hw.instance "leaf_b" sym @leaf_b @LeafB() -> () + hw.output + } + + // CHECK-LABEL: hw.module @TopCollision + // CHECK: %[[MC0:.+]], %[[MC1:.+]] = hw.instance "mid_inst" sym @mid_inst @MidCollision() -> (xmr_bored_data_{{[0-9]+}}: i1, xmr_bored_data_{{[0-9]+}}: i4) + // CHECK: hw.output %[[MC0]], %[[MC1]] : i1, i4 + hw.module @TopCollision(out out_a : i1, out out_b : i4) { + hw.instance "mid_inst" sym @mid_inst @MidCollision() -> () + %ca = sv.xmr.ref @collisionA : !hw.inout + %cb = sv.xmr.ref @collisionB : !hw.inout + %va = sv.read_inout %ca : !hw.inout + %vb = sv.read_inout %cb : !hw.inout + hw.output %va, %vb : i1, i4 + } + + // CHECK-LABEL: hw.module @LeafSameTypeA + // CHECK-SAME: out xmr_bored_sig_{{[0-9]+}} : i1 + hw.module @LeafSameTypeA() { + %c = hw.constant true + %s = hw.wire %c sym @sig : i1 + hw.output + } + + // CHECK-LABEL: hw.module @LeafSameTypeB + // CHECK-SAME: out xmr_bored_sig_{{[0-9]+}} : i1 + hw.module @LeafSameTypeB() { + %c = hw.constant false + %s = hw.wire %c sym @sig : i1 + hw.output + } + + // CHECK-LABEL: hw.module @MidSameTypeCollision + // CHECK-SAME: out xmr_bored_sig_{{[0-9]+}} : i1 + // CHECK-SAME: out xmr_bored_sig_{{[0-9]+}} : i1 + // CHECK: %[[LSA:.+]] = hw.instance "leaf_a" sym @leaf_a @LeafSameTypeA() -> (xmr_bored_sig_{{[0-9]+}}: i1) + // CHECK: %[[LSB:.+]] = hw.instance "leaf_b" sym @leaf_b @LeafSameTypeB() -> (xmr_bored_sig_{{[0-9]+}}: i1) + // CHECK: hw.output %[[LSA]], %[[LSB]] : i1, i1 + hw.module @MidSameTypeCollision() { + hw.instance "leaf_a" sym @leaf_a @LeafSameTypeA() -> () + hw.instance "leaf_b" sym @leaf_b @LeafSameTypeB() -> () + hw.output + } + + // CHECK-LABEL: hw.module @TopSameTypeCollision + // CHECK: %[[TSA:.+]], %[[TSB:.+]] = hw.instance "mid_inst" sym @mid_inst @MidSameTypeCollision() -> (xmr_bored_sig_{{[0-9]+}}: i1, xmr_bored_sig_{{[0-9]+}}: i1) + // CHECK: hw.output %[[TSA]], %[[TSB]] : i1, i1 + hw.module @TopSameTypeCollision(out out_a : i1, out out_b : i1) { + hw.instance "mid_inst" sym @mid_inst @MidSameTypeCollision() -> () + %ca = sv.xmr.ref @sameTypeCollisionA : !hw.inout + %cb = sv.xmr.ref @sameTypeCollisionB : !hw.inout + %va = sv.read_inout %ca : !hw.inout + %vb = sv.read_inout %cb : !hw.inout + hw.output %va, %vb : i1, i1 + } +}