From 208058c22a57c6ad0676eb9e77483fe803252aa7 Mon Sep 17 00:00:00 2001 From: Schuyler Eldridge Date: Tue, 13 Feb 2024 15:49:07 -0500 Subject: [PATCH] [FIRRTL] Support probes in LowerLayers (#6554) Updates to LowerLayers to support driving layer-associated probes. Signed-off-by: Schuyler Eldridge Co-authored-by: Robert Young --- .../circt/Dialect/FIRRTL/FIRRTLTypesImpl.td | 6 + lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp | 288 +++++++++++--- test/Dialect/FIRRTL/lower-layers.mlir | 355 ++++++++++++++++++ test/firtool/lower-layers.fir | 40 ++ test/firtool/lower-layers2.fir | 106 ++++++ 5 files changed, 743 insertions(+), 52 deletions(-) create mode 100644 test/firtool/lower-layers.fir create mode 100644 test/firtool/lower-layers2.fir diff --git a/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td b/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td index 1d79df3ed84f..b316ce982528 100644 --- a/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td +++ b/include/circt/Dialect/FIRRTL/FIRRTLTypesImpl.td @@ -408,6 +408,12 @@ def RefImpl : FIRRTLImplType<"Ref", let extraClassDeclaration = [{ /// Return the recursive properties of the type. RecursiveTypeProperties getRecursiveTypeProperties() const; + + RefType removeLayer() const { + if (getLayer() == nullptr) + return *this; + return get(getType(), getForceable()); + } }]; } diff --git a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp index 4d5b57d8e1ba..8ca9aa4aee59 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerLayers.cpp @@ -23,14 +23,50 @@ using namespace circt; using namespace firrtl; +//===----------------------------------------------------------------------===// +// Type Conversion +//===----------------------------------------------------------------------===// + +namespace { + +/// Indicates the kind of reference that was captured. +enum class ConnectKind { + /// A normal captured value. This is a read of a value outside the + /// layerblock. + NonRef, + /// A reference. This is a destination of a ref define. + Ref +}; + +struct ConnectInfo { + Value value; + ConnectKind kind; +}; + +} // namespace + class LowerLayersPass : public LowerLayersBase { /// Safely build a new module with a given namehint. This handles geting a /// lock to modify the top-level circuit. FModuleOp buildNewModule(OpBuilder &builder, Location location, Twine namehint, SmallVectorImpl &ports); - /// Function to process each module. - void runOnModule(FModuleOp moduleOp); + /// Strip layer colors from the module's interface. + void runOnModuleLike(FModuleLike moduleLike); + + /// Extract layerblocks and strip probe colors from all ops under the module. + void runOnModuleBody(FModuleOp moduleOp); + + /// Update the module's port types to remove any explicit layer requirements + /// from any probe types. + void removeLayersFromPorts(FModuleLike moduleLike); + + /// Update the value's type to remove any layers from any probe types. + void removeLayersFromValue(Value value); + + /// Remove any layers from the result of the cast. If the cast becomes a nop, + /// remove the cast itself from the IR. + void removeLayersFromRefCast(RefCastOp cast); /// Entry point for the function. void runOnOperation() override; @@ -58,13 +94,79 @@ FModuleOp LowerLayersPass::buildNewModule(OpBuilder &builder, Location location, return newModule; } -/// Process a module to remove any layer blocks it has. -void LowerLayersPass::runOnModule(FModuleOp moduleOp) { +void LowerLayersPass::removeLayersFromValue(Value value) { + auto type = dyn_cast(value.getType()); + if (!type || !type.getLayer()) + return; + value.setType(type.removeLayer()); +} + +void LowerLayersPass::removeLayersFromRefCast(RefCastOp cast) { + auto result = cast.getResult(); + auto oldType = result.getType(); + if (oldType.getLayer()) { + auto input = cast.getInput(); + auto srcType = input.getType(); + auto newType = oldType.removeLayer(); + if (newType == srcType) { + result.replaceAllUsesWith(input); + cast->erase(); + return; + } + result.setType(newType); + } +} + +void LowerLayersPass::removeLayersFromPorts(FModuleLike moduleLike) { + auto oldTypeAttrs = moduleLike.getPortTypesAttr(); + SmallVector newTypeAttrs; + newTypeAttrs.reserve(oldTypeAttrs.size()); + bool changed = false; + + for (auto typeAttr : oldTypeAttrs.getAsRange()) { + if (auto refType = dyn_cast(typeAttr.getValue())) { + if (refType.getLayer()) { + typeAttr = TypeAttr::get(refType.removeLayer()); + changed = true; + } + } + newTypeAttrs.push_back(typeAttr); + } + + if (!changed) + return; + + moduleLike->setAttr(FModuleLike::getPortTypesAttrName(), + ArrayAttr::get(moduleLike.getContext(), newTypeAttrs)); + + if (auto moduleOp = dyn_cast(moduleLike.getOperation())) { + for (auto arg : moduleOp.getBodyBlock()->getArguments()) + removeLayersFromValue(arg); + } +} + +void LowerLayersPass::runOnModuleLike(FModuleLike moduleLike) { LLVM_DEBUG({ - llvm::dbgs() << "Module: " << moduleOp.getModuleName() << "\n"; + llvm::dbgs() << "Module: " << moduleLike.getModuleName() << "\n"; llvm::dbgs() << " Examining Layer Blocks:\n"; }); + // Strip away layers from the interface of the module-like op. + TypeSwitch(moduleLike.getOperation()) + .Case([&](auto op) { + op.setLayers({}); + removeLayersFromPorts(op); + runOnModuleBody(op); + }) + .Case([&](auto op) { + op.setLayers({}); + removeLayersFromPorts(op); + }) + .Case([](auto) {}) + .Default([](auto) { assert(0 && "unknown module-like op"); }); +} + +void LowerLayersPass::runOnModuleBody(FModuleOp moduleOp) { CircuitOp circuitOp = moduleOp->getParentOfType(); StringRef circuitName = circuitOp.getName(); @@ -85,6 +187,26 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { // 4. Instantiate the new module outside the layer block and hook it up. // 5. Erase the layer block. moduleOp.walk([&](Operation *op) { + // Strip layer requirements from any op that might represent a probe. + if (auto wire = dyn_cast(op)) { + removeLayersFromValue(wire.getResult()); + return WalkResult::advance(); + } + if (auto sub = dyn_cast(op)) { + removeLayersFromValue(sub.getResult()); + return WalkResult::advance(); + } + if (auto instance = dyn_cast(op)) { + instance.setLayers({}); + for (auto result : instance.getResults()) + removeLayersFromValue(result); + return WalkResult::advance(); + } + if (auto cast = dyn_cast(op)) { + removeLayersFromRefCast(cast); + return WalkResult::advance(); + } + auto layerBlock = dyn_cast(op); if (!layerBlock) return WalkResult::advance(); @@ -104,29 +226,39 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { // block. SmallVector ports; - // Connectsion that need to be made to the instance of the derived module. - SmallVector connectValues; + // Connection that need to be made to the instance of the derived module. + SmallVector connectValues; // Create an input port for an operand that is captured from outside. - // - // TODO: If we allow capturing reference types, this will need to be - // updated. auto createInputPort = [&](Value operand, Location loc) { auto portNum = ports.size(); auto operandName = getFieldName(FieldRef(operand, 0), true); - ports.push_back({builder.getStringAttr("_" + operandName.first), - operand.getType(), Direction::In, /*sym=*/{}, + // If the value is a ref, we must resolve the ref inside the parent, + // passing the input as a value instead of a ref. Inside the layer, we + // convert (ref.send) the value back into a ref. + auto type = operand.getType(); + if (auto refType = dyn_cast(type)) + type = refType.getType(); + + ports.push_back({builder.getStringAttr("_" + operandName.first), type, + Direction::In, /*sym=*/{}, /*loc=*/loc}); // Update the layer block's body with arguments as we will swap this body - // into the module when we create it. - body->addArgument(operand.getType(), loc); - operand.replaceUsesWithIf(body->getArgument(portNum), - [&](OpOperand &operand) { - return operand.getOwner()->getBlock() == body; - }); - - connectValues.push_back(operand); + // into the module when we create it. If this is a ref type, then add a + // refsend to convert from the non-ref type input port. + body->addArgument(type, loc); + Value replacement = body->getArgument(portNum); + if (isa(operand.getType())) { + OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(body); + replacement = builder.create(loc, replacement); + } + operand.replaceUsesWithIf(replacement, [&](OpOperand &operand) { + return layerBlock->isAncestor(operand.getOwner()); + }); + + connectValues.push_back({operand, ConnectKind::NonRef}); }; // Set the location intelligently. Use the location of the capture if this @@ -145,21 +277,33 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { return loc; }; - // Create an output probe port port and adds the ref.define/ref.send to - // drive the port. + // Create an output probe port port and adds a ref.define/ref.send to + // drive the port if this was not already capturing a ref type. auto createOutputPort = [&](Value dest, Value src) { auto loc = getPortLoc(dest); auto portNum = ports.size(); auto operandName = getFieldName(FieldRef(dest, 0), true); - auto refType = RefType::get( - type_cast(dest.getType()).getPassiveType(), - /*forceable=*/false); + RefType refType; + if (auto oldRef = dyn_cast(dest.getType())) + refType = oldRef; + else + refType = RefType::get( + type_cast(dest.getType()).getPassiveType(), + /*forceable=*/false); ports.push_back({builder.getStringAttr("_" + operandName.first), refType, Direction::Out, /*sym=*/{}, /*loc=*/loc}); body->addArgument(refType, loc); - connectValues.push_back(dest); + if (isa(dest.getType())) { + dest.replaceUsesWithIf(body->getArgument(portNum), + [&](OpOperand &operand) { + return operand.getOwner()->getBlock() == body; + }); + connectValues.push_back({dest, ConnectKind::Ref}); + return; + } + connectValues.push_back({dest, ConnectKind::NonRef}); OpBuilder::InsertionGuard guard(builder); builder.setInsertionPointAfterValue(src); builder.create( @@ -179,11 +323,10 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { // outside the layer block. We will hook it up later once we replace the // layer block with an instance. if (auto instOp = dyn_cast(op)) { - // Ignore any instance which this pass did not create from a nested - // layer block. Instances which are not marked lowerToBind do not need - // to be split out. + // Ignore instances which this pass did not create. if (!createdInstances.contains(instOp)) continue; + LLVM_DEBUG({ llvm::dbgs() << " Found instance created from nested layer block:\n" @@ -194,10 +337,29 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { continue; } + if (auto refSend = dyn_cast(op)) { + auto src = refSend.getBase(); + auto *srcOp = src.getDefiningOp(); + auto srcInLayerBlock = srcOp && layerBlock->isAncestor(srcOp); + if (!srcInLayerBlock) + createInputPort(src, op.getLoc()); + continue; + } + + if (auto refCast = dyn_cast(op)) { + auto *srcOp = refCast.getInput().getDefiningOp(); + auto srcInLayerBlock = srcOp && layerBlock->isAncestor(srcOp); + if (!srcInLayerBlock) + createInputPort(refCast.getInput(), op.getLoc()); + continue; + } + if (auto connect = dyn_cast(op)) { - auto srcInLayerBlock = connect.getSrc().getParentBlock() == body; - auto destInLayerBlock = connect.getDest().getParentBlock() == body; - if (!srcInLayerBlock && !destInLayerBlock) { + auto *srcOp = connect.getSrc().getDefiningOp(); + auto *dstOp = connect.getDest().getDefiningOp(); + auto srcInLayerBlock = srcOp && layerBlock->isAncestor(srcOp); + auto dstInLayerBlock = dstOp && layerBlock->isAncestor(dstOp); + if (!srcInLayerBlock && !dstInLayerBlock) { connect->moveBefore(layerBlock); continue; } @@ -207,18 +369,23 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { continue; } // Create an output port. - if (!destInLayerBlock) { + if (!dstInLayerBlock) { createOutputPort(connect.getDest(), connect.getSrc()); - connect.erase(); + if (!connect.getDest().getType().isa()) + connect.erase(); continue; } // Source and destination in layer block. Nothing to do. continue; } - // Pattern match the following structure. Move the ref.resolve outside - // the layer block. The strictconnect will be moved outside in the next - // loop iteration: + // Pre-emptively de-squiggle connections that we are creating. This will + // later be cleaned up by the de-squiggling pass. However, there is no + // point in creating deeply squiggled connections if we don't have to. + // + // This pattern matches the following structure. Move the ref.resolve + // outside the layer block. The strictconnect will be moved outside in + // the next loop iteration: // %0 = ... // %1 = ... // firrtl.layerblock { @@ -226,7 +393,8 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { // firrtl.strictconnect %1, %2 // } if (auto refResolve = dyn_cast(op)) - if (refResolve.getResult().hasOneUse()) + if (refResolve.getResult().hasOneUse() && + refResolve.getRef().getParentBlock() != body) if (auto connect = dyn_cast( *refResolve.getResult().getUsers().begin())) if (connect.getDest().getParentBlock() != body) { @@ -235,9 +403,12 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { } // For any other ops, create input ports for any captured operands. - for (auto operand : op.getOperands()) - if (operand.getParentBlock() != body) + for (auto operand : op.getOperands()) { + auto *operandOp = operand.getDefiningOp(); + auto operandInBlock = operandOp && layerBlock->isAncestor(operandOp); + if (!operandInBlock) createInputPort(operand, op.getLoc()); + } } // Create the new module. This grabs a lock to modify the circuit. @@ -257,11 +428,18 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { llvm::dbgs() << " - name: " << port.getName() << "\n" << " type: " << port.type << "\n" << " direction: " << port.direction << "\n" - << " value: " << value << "\n"; + << " value: " << value.value << "\n" + << " kind: " + << (value.kind == ConnectKind::NonRef ? "NonRef" : "Ref") + << "\n"; } }); // Replace the original layer block with an instance. Hook up the instance. + // Intentionally create instance with probe ports which do not have an + // associated layer. This is illegal IR that will be made legal by the end + // of the pass. This is done to avoid having to revisit and rewrite each + // instance everytime it is moved into a parent layer. builder.setInsertionPointAfter(layerBlock); auto moduleName = newModule.getModuleName(); auto instanceOp = builder.create( @@ -288,13 +466,22 @@ void LowerLayersPass::runOnModule(FModuleOp moduleOp) { ++portNum) { OpBuilder::InsertionGuard guard(builder); builder.setInsertionPointAfterValue(instanceOp.getResult(portNum)); - if (instanceOp.getPortDirection(portNum) == Direction::In) + if (instanceOp.getPortDirection(portNum) == Direction::In) { + auto src = connectValues[portNum].value; + if (isa(src.getType())) + src = builder.create( + newModule.getPortLocationAttr(portNum), src); builder.create(newModule.getPortLocationAttr(portNum), - instanceOp.getResult(portNum), - connectValues[portNum]); + instanceOp.getResult(portNum), src); + } else if (instanceOp.getResult(portNum).getType().isa() && + connectValues[portNum].kind == ConnectKind::Ref) + builder.create(getPortLoc(connectValues[portNum].value), + connectValues[portNum].value, + instanceOp.getResult(portNum)); else builder.create( - getPortLoc(connectValues[portNum]), connectValues[portNum], + getPortLoc(connectValues[portNum].value), + connectValues[portNum].value, builder.create(newModule.getPortLocationAttr(portNum), instanceOp.getResult(portNum))); } @@ -331,14 +518,11 @@ void LowerLayersPass::runOnOperation() { }); }); - // Early exit if no work to do. - if (moduleNames.empty()) - return markAllAnalysesPreserved(); - // Lower the layer blocks of each module. - SmallVector modules(circuitOp.getBodyBlock()->getOps()); - llvm::parallelForEach(modules, - [&](FModuleOp moduleOp) { runOnModule(moduleOp); }); + SmallVector modules( + circuitOp.getBodyBlock()->getOps()); + parallelForEach(modules, + [this](FModuleLike module) { runOnModuleLike(module); }); // Generate the header and footer of each bindings file. The body will be // populated later when binds are exported to Verilog. This produces text diff --git a/test/Dialect/FIRRTL/lower-layers.mlir b/test/Dialect/FIRRTL/lower-layers.mlir index 393310bff829..17e3f9630440 100644 --- a/test/Dialect/FIRRTL/lower-layers.mlir +++ b/test/Dialect/FIRRTL/lower-layers.mlir @@ -1,11 +1,366 @@ // RUN: circt-opt -firrtl-lower-layers -split-input-file %s | FileCheck %s +firrtl.circuit "Test" { + firrtl.module @Test() {} + + firrtl.layer @A bind { + firrtl.layer @B bind { + firrtl.layer @C bind {} + } + } + firrtl.layer @B bind {} + + firrtl.extmodule @Foo(out o : !firrtl.probe, @A>) + + //===--------------------------------------------------------------------===// + // Removal of Probe Colors + //===--------------------------------------------------------------------===// + + // CHECK-LABEL: @ColoredPorts(out %o: !firrtl.probe>) + firrtl.module @ColoredPorts(out %o: !firrtl.probe, @A>) {} + + // CHECK-LABEL: @ExtColoredPorts(out o: !firrtl.probe>) + firrtl.extmodule @ExtColoredPorts(out o: !firrtl.probe, @A>) + + // CHECK-LABEL: @ColoredPortsOnInstances + firrtl.module @ColoredPortsOnInstances() { + // CHECK: %foo_o = firrtl.instance foo @ColoredPorts(out o: !firrtl.probe>) + %foo_o = firrtl.instance foo @ColoredPorts(out o: !firrtl.probe, @A>) + } + + // CHECK-LABEL: @ColoredThings + firrtl.module @ColoredThings() { + // CHECK: %0 = firrtl.wire : !firrtl.probe>> + %0 = firrtl.wire : !firrtl.probe>, @A> + // CHECK: %1 = firrtl.ref.sub %0[0] : !firrtl.probe>> + %1 = firrtl.ref.sub %0[0] : !firrtl.probe>, @A> + // CHECK-NOT: firrtl.cast + %2 = firrtl.ref.cast %1 : (!firrtl.probe, @A>) -> !firrtl.probe, @A::@B> + } + + // CHECK-LABEL: @ColoredThingUnderWhen + firrtl.module @ColoredThingUnderWhen(in %b : !firrtl.uint<1>) { + // CHECK: firrtl.when %b : !firrtl.uint<1> + firrtl.when %b : !firrtl.uint<1> { + // CHECK: %0 = firrtl.wire : !firrtl.probe>> + %0 = firrtl.wire : !firrtl.probe>, @A> + // CHECK: %1 = firrtl.ref.sub %0[0] : !firrtl.probe>> + %1 = firrtl.ref.sub %0[0] : !firrtl.probe>, @A> + // CHECK-NOT: firrtl.cast + %2 = firrtl.ref.cast %1 : (!firrtl.probe, @A>) -> !firrtl.probe, @A::@B> + } + } + + //===--------------------------------------------------------------------===// + // Removal of Enabled Layers + //===--------------------------------------------------------------------===// + + // CHECK-LABEL: @EnabledLayers() { + firrtl.module @EnabledLayers() attributes {layers = [@A]} {} + + // CHECK-LABEL: @EnabledLayersOnInstance() + firrtl.module @EnabledLayersOnInstance() attributes {layers = [@A]} { + // CHECK: firrtl.instance enabledLayers @EnabledLayers() + firrtl.instance enabledLayers {layers = [@A]} @EnabledLayers() + } + + //===--------------------------------------------------------------------===// + // Removal of Layerblocks and Layers + //===--------------------------------------------------------------------===// + + // CHECK-NOT: firrtl.layer @GoodbyeCruelWorld + firrtl.layer @GoodbyeCruelWorld bind {} + + // CHECK-LABEL @WithLayerBlock + firrtl.module @WithLayerBlock() { + // CHECK-NOT firrtl.layerblock @GoodbyeCruelWorld + firrtl.layerblock @GoodbyeCruelWorld { + } + } + + //===--------------------------------------------------------------------===// + // Capture + //===--------------------------------------------------------------------===// + + // CHECK: firrtl.module private @[[A:.+]](in %[[x:.+]]: !firrtl.uint<1>, in %[[y:.+]]: !firrtl.uint<1>) + // CHECK: %0 = firrtl.add %[[x]], %[[y]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK: } + // CHECK: firrtl.module @CaptureHardware() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + // CHECK: %[[p:.+]], %[[q:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.strictconnect %[[q]], %c1_ui1 : !firrtl.uint<1> + // CHECK: firrtl.strictconnect %[[p]], %c0_ui1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @CaptureHardware() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + firrtl.layerblock @A { + %0 = firrtl.add %c0_ui1, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %x = firrtl.node %[[p]] : !firrtl.uint<1> + // CHECK: } + // CHECK: firrtl.module @CapturePort(in %in: !firrtl.uint<1>) { + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.strictconnect %[[p]], %in : !firrtl.uint<1> + // CHECK: } + firrtl.module @CapturePort(in %in: !firrtl.uint<1>){ + firrtl.layerblock @A { + %x = firrtl.node %in : !firrtl.uint<1> + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %w = firrtl.wire : !firrtl.uint<1> + // CHECK: firrtl.connect %w, %[[p]] : !firrtl.uint<1>, !firrtl.uint<1> + // CHECK: } + // CHECK: firrtl.module @CaptureHardwareViaConnect() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.strictconnect %[[p]], %c0_ui1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @CaptureHardwareViaConnect() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A { + %w = firrtl.wire : !firrtl.uint<1> + firrtl.connect %w, %c0_ui1 : !firrtl.uint<1>, !firrtl.uint<1> + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @CaptureProbeSrc() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %w = firrtl.wire : !firrtl.uint<1> + // CHECK: %0 = firrtl.ref.send %w : !firrtl.uint<1> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[p]], %1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @CaptureProbeSrc() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %w = firrtl.wire : !firrtl.uint<1> + %r = firrtl.ref.send %w : !firrtl.uint<1> + firrtl.layerblock @A { + firrtl.ref.resolve %r : !firrtl.probe> + } + } + + // CHECK: firrtl.module private @[[B:.+]](in %[[p:.+]]: !firrtl.uint<1>, in %[[q:.+]]: !firrtl.uint<1>) + // CHECK: %0 = firrtl.add %[[p]], %[[q]] : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK: } + // CHECK: firrtl.module private @[[A:.+]](out %[[p:.+]]: !firrtl.probe>, out %[[q:.+]]: !firrtl.probe>) attributes { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %0 = firrtl.ref.send %c0_ui1 : !firrtl.uint<1> + // CHECK: firrtl.ref.define %[[q]], %0 : !firrtl.probe> + // CHECK: %c0_ui1_1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.send %c0_ui1_1 : !firrtl.uint<1> + // CHECK: firrtl.ref.define %[[p]], %1 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @NestedCaptureHardware() { + // CHECK: %[[b1:.+]], %[[b2:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A_B.sv", excludeFromFileList>} @[[B]] + // CHECK: %[[a1:.+]], %[[a2:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %0 = firrtl.ref.resolve %[[a2]] : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[b1]], %0 : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %[[a1]] : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[b2]], %1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @NestedCaptureHardware() { + firrtl.layerblock @A { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + %c1_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A::@B { + %0 = firrtl.add %c0_ui1, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + } + } + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + // CHECK: firrtl.when %[[p]] : !firrtl.uint<1> { + // CHECK: %0 = firrtl.add %[[p]], %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + // CHECK: } + // CHECK: } + // CHECK: firrtl.module @WhenUnderLayer() { + // CHECK: %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: firrtl.strictconnect %[[p]], %c0_ui1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @WhenUnderLayer() { + %c0_ui1 = firrtl.constant 0 : !firrtl.uint<1> + firrtl.layerblock @A { + %c1_ui1 = firrtl.constant 1 : !firrtl.uint<1> + firrtl.when %c0_ui1 : !firrtl.uint<1> { + %0 = firrtl.add %c0_ui1, %c1_ui1 : (!firrtl.uint<1>, !firrtl.uint<1>) -> !firrtl.uint<2> + } + } + } + + //===--------------------------------------------------------------------===// + // Connecting/Defining Refs + //===--------------------------------------------------------------------===// + + // Src and Dst Outside Layerblock. + // + // CHECK: firrtl.module private @[[A:.+]]() { + // CHECK: } + // CHECK: firrtl.module @SrcDstOutside() { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: %1 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %1, %0 : !firrtl.probe> + // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A:.+]]() + // CHECK: } + firrtl.module @SrcDstOutside() { + %0 = firrtl.wire : !firrtl.probe, @A> + %1 = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + firrtl.ref.define %1, %0 : !firrtl.probe, @A> + } + } + + // Src Outside Layerblock. + // + // CHECK: firrtl.module private @[[A:.+]](in %_: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.ref.send %_ : !firrtl.uint<1> + // CHECK: %1 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %1, %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @SrcOutside() { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[p]], %1 : !firrtl.uint<1> + // CHECK: } + firrtl.module @SrcOutside() { + %0 = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + %1 = firrtl.wire : !firrtl.probe, @A> + firrtl.ref.define %1, %0 : !firrtl.probe, @A> + } + } + + // Dst Outside Layerblock. + // + // CHECK: firrtl.module private @[[A:.+]](out %[[p:.+]]: !firrtl.probe>) { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: firrtl.ref.define %[[p]], %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @DestOutside() { + // CHECK: %0 = firrtl.wire : !firrtl.probe> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_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<"groups_Test_A.sv", excludeFromFileList>} @[[A]]() + // CHECK: } + firrtl.module @SrcDstInside() { + firrtl.layerblock @A { + %0 = firrtl.wire : !firrtl.probe, @A> + %1 = firrtl.wire : !firrtl.probe, @A> + firrtl.ref.define %1, %0 : !firrtl.probe, @A> + } + } + + //===--------------------------------------------------------------------===// + // Resolving Colored Probes + //===--------------------------------------------------------------------===// + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @ResolveColoredRefUnderLayerBlock() { + // CHECK: %w = firrtl.wire : !firrtl.probe> + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %0 = firrtl.ref.resolve %w : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[p]], %0 : !firrtl.uint<1> + // CHECK: } + firrtl.module @ResolveColoredRefUnderLayerBlock() { + %w = firrtl.wire : !firrtl.probe, @A> + firrtl.layerblock @A { + %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> + } + } + + // CHECK: firrtl.module @ResolveColoredRefUnderEnabledLayer() { + // CHECK: %w = firrtl.wire : !firrtl.probe> + // CHECK: %0 = firrtl.ref.resolve %w : !firrtl.probe> + // CHECK: } + firrtl.module @ResolveColoredRefUnderEnabledLayer() attributes {layers=[@A]} { + %w = firrtl.wire : !firrtl.probe, @A> + %0 = firrtl.ref.resolve %w : !firrtl.probe, @A> + } + + // CHECK: firrtl.module private @[[A:.+]](in %[[p:.+]]: !firrtl.uint<1>) { + // CHECK: %0 = firrtl.ref.send %[[p]] : !firrtl.uint<1> + // CHECK: %1 = firrtl.ref.resolve %0 : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @ResolveColoredRefPortUnderLayerBlock1() { + // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) + // CHECK: %[[p:.+]] = firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]] + // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> + // CHECK: firrtl.strictconnect %[[p]], %0 : !firrtl.uint<1> + // CHECK: } + firrtl.module @ResolveColoredRefPortUnderLayerBlock1() { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + firrtl.layerblock @A { + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } + } + + // CHECK: firrtl.module private @[[A:.+]]() { + // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) + // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> + // CHECK: } + // CHECK: firrtl.module @ResolveColoredRefPortUnderLayerBlock2() { + // CHECK: firrtl.instance {{.+}} {lowerToBind, output_file = #hw.output_file<"groups_Test_A.sv", excludeFromFileList>} @[[A]]() + // CHECK: } + firrtl.module @ResolveColoredRefPortUnderLayerBlock2() { + firrtl.layerblock @A { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } + } + + // CHECK: firrtl.module @ResolveColoredRefPortUnderEnabledLayer() { + // CHECK: %foo_o = firrtl.instance foo @Foo(out o: !firrtl.probe>) + // CHECK: %0 = firrtl.ref.resolve %foo_o : !firrtl.probe> + // CHECK: } + firrtl.module @ResolveColoredRefPortUnderEnabledLayer() attributes {layers=[@A]} { + %foo_o = firrtl.instance foo @Foo(out o : !firrtl.probe, @A>) + %x = firrtl.ref.resolve %foo_o : !firrtl.probe, @A> + } +} + +// ----- + firrtl.circuit "Simple" { firrtl.layer @A bind { firrtl.layer @B bind { firrtl.layer @C bind {} } } + firrtl.module @Simple() { %a = firrtl.wire : !firrtl.uint<1> %b = firrtl.wire : !firrtl.uint<2> diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir new file mode 100644 index 000000000000..00ad1be57074 --- /dev/null +++ b/test/firtool/lower-layers.fir @@ -0,0 +1,40 @@ +; RUN: firtool -verilog %s | FileCheck %s + +; This is an end-to-end example of a test-bench (Foo) enabling verification, +; probing into a device-under-test (Bar), and reading from hardware which is +; only present if the verification layer is enabled. + +FIRRTL version 4.0.0 + +circuit Foo: %[[ + {"class": "firrtl.transforms.DontTouchAnnotation", "target": "~Foo|Bar>c"}, + {"class": "firrtl.transforms.DontTouchAnnotation", "target": "~Foo|Foo>d"} +]] + layer Verification, bind: + + ; CHECK: module Bar_Verification(); + ; CHECK: wire c = 1'h0; + ; CHECK: wire c_probe = c; + ; CHECK: endmodule + + ; CHECK: module Bar(); + ; CHECK: endmodule + module Bar: + input a: UInt<1> + output b: Probe, Verification> + + layerblock Verification: + node c = UInt<1>(0) + define b = probe(c) + + ; CHECK: module Foo(); + ; CHECK: wire d = Foo.bar.bar_Verification.c_probe; + ; CHECK: Bar bar (); + ; CHECK: endmodule + public module Foo enablelayer Verification: + + inst bar of Bar + + node d = read(bar.b) + connect bar.a, d + diff --git a/test/firtool/lower-layers2.fir b/test/firtool/lower-layers2.fir new file mode 100644 index 000000000000..fd49ff7a181b --- /dev/null +++ b/test/firtool/lower-layers2.fir @@ -0,0 +1,106 @@ +; RUN: firtool -verilog -disable-all-randomization %s | FileCheck %s + +; This is an end-to-end example of a test-harness enabling verification, probing +; into a device-under-test, and reading from hardware which is only present if +; the verification layer is enabled. + +FIRRTL version 4.0.0 + +circuit TestHarness: + + layer Verification bind: + + ; CHECK: module DUT_Verification( + ; CHECK: input _clock, + ; CHECK: input [31:0] _a + ; CHECK: ); + ; CHECK: reg [31:0] pc_d; + ; CHECK: wire [31:0] pc_d_probe = pc_d; + ; CHECK: always @(posedge _clock) + ; CHECK: pc_d <= _a; + ; CHECK: endmodule + + ; CHECK: module DUT( + ; CHECK: input clock, + ; CHECK: input [31:0] a, + ; CHECK: output [31:0] b + ; CHECK: ); + ; CHECK: reg [31:0] pc; + ; CHECK: always @(posedge clock) + ; CHECK: pc <= a; + ; CHECK: assign b = pc; + ; CHECK: endmodule + module DUT: + input clock: Clock + input reset: UInt<1> + input a: UInt<32> + output b: UInt<32> + output trace: Probe, Verification> + + reg pc: UInt<32>, clock + connect pc, a + connect b, pc + + wire x : Probe, Verification> + + layerblock Verification: + reg pc_d: UInt<32>, clock + connect pc_d, a + define x = probe(pc_d) + + layerblock Verification: + define trace = x + + ; CHECK: module TestHarness_Verification( + ; CHECK: input [31:0] _dut_trace, + ; CHECK: input _clock, + ; CHECK: _reset + ; CHECK: ); + ; CHECK: `ifndef SYNTHESIS + ; CHECK: always @(posedge _clock) begin + ; CHECK: if ((`PRINTF_COND_) & _reset) + ; CHECK: $fwrite(32'h80000002, "The last PC was: %x", _dut_trace); + ; CHECK: end // always @(posedge) + ; CHECK: `endif // not def SYNTHESIS + ; CHECK: endmodule + + ; CHECK: module TestHarness( + ; CHECK: input clock, + ; CHECK: reset, + ; CHECK: input [31:0] a, + ; CHECK: output [31:0] b + ; CHECK: ); + ; CHECK: DUT dut ( + ; CHECK: .clock (clock), + ; CHECK: .a (a), + ; CHECK: .b (b) + ; CHECK: ); + ; CHECK: endmodule + module TestHarness: + input clock: Clock + input reset: UInt<1> + input a: UInt<32> + output b: UInt<32> + + inst dut of DUT + connect dut.clock, clock + connect dut.reset, reset + connect dut.a, a + connect b, dut.b + + layerblock Verification: + printf(clock, reset, "The last PC was: %x", read(dut.trace)) + +; CHECK: // ----- 8< ----- FILE "groups_TestHarness_Verification.sv" ----- 8< ----- +; CHECK: `ifndef groups_TestHarness_Verification +; CHECK: `define groups_TestHarness_Verification +; CHECK: bind DUT DUT_Verification dUT_Verification ( +; CHECK: ._clock (clock), +; CHECK: ._a (a) +; CHECK: ); +; CHECK: bind TestHarness TestHarness_Verification testHarness_Verification ( +; CHECK: ._dut_trace (TestHarness.dut.dUT_Verification.pc_d_probe), +; CHECK: ._clock (clock), +; CHECK: ._reset (reset) +; CHECK: ); +; CHECK: `endif // groups_TestHarness_Verification