From aac9e3781965a3a8f299ecb2b329d86b0451afe3 Mon Sep 17 00:00:00 2001 From: woshiren Date: Sat, 9 May 2026 19:58:28 +0800 Subject: [PATCH 1/2] [Sim] Extract common logic of ProceduralizeSim for reuse --- include/circt/Dialect/Sim/SimTransforms.h | 44 +++ .../Sim/Transforms/ProceduralizeSim.cpp | 290 ++++++++++-------- test/Dialect/Sim/proceduralize-sim.mlir | 43 +++ 3 files changed, 252 insertions(+), 125 deletions(-) create mode 100644 include/circt/Dialect/Sim/SimTransforms.h diff --git a/include/circt/Dialect/Sim/SimTransforms.h b/include/circt/Dialect/Sim/SimTransforms.h new file mode 100644 index 000000000000..df6ff2e04dcc --- /dev/null +++ b/include/circt/Dialect/Sim/SimTransforms.h @@ -0,0 +1,44 @@ +//===- SimTransforms.h - Sim transform helpers -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This header declares reusable transformation helpers for the Sim dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_SIM_SIMTRANSFORMS_H +#define CIRCT_DIALECT_SIM_SIMTRANSFORMS_H + +#include "mlir/IR/Builders.h" +#include "llvm/ADT/ArrayRef.h" + +namespace circt { +namespace sim { + +struct PrintProceduralizationRequest { + mlir::Location loc; + mlir::Value input; + mlir::Value condition; + mlir::Value stream; + + /// Operation used for diagnostics, if any. + mlir::Operation *anchorOp = nullptr; + + /// Operation to erase once the request has been proceduralized, if any. + mlir::Operation *cleanupRoot = nullptr; +}; + +/// Lower a list of same-clock print requests into a shared `hw.triggered` +/// region containing `sim.proc.print` operations. +mlir::LogicalResult proceduralizePrintsForClock( + mlir::OpBuilder &builder, mlir::Value clock, + llvm::ArrayRef printRequests); + +} // namespace sim +} // namespace circt + +#endif // CIRCT_DIALECT_SIM_SIMTRANSFORMS_H diff --git a/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp b/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp index b108d6720245..9076d03f6139 100644 --- a/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp +++ b/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp @@ -14,11 +14,13 @@ #include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/Seq/SeqOps.h" #include "circt/Dialect/Sim/SimOps.h" +#include "circt/Dialect/Sim/SimTransforms.h" #include "circt/Dialect/Sim/SimTypes.h" #include "circt/Support/Debug.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Pass/Pass.h" -#include "llvm/ADT/IndexedMap.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SetVector.h" #include "llvm/Support/Debug.h" @@ -37,8 +39,16 @@ using namespace circt; using namespace sim; namespace { +static InFlightDiagnostic +emitProceduralizationError(const PrintProceduralizationRequest &request, + const Twine &message) { + if (auto *anchor = request.anchorOp) + return anchor->emitError(message); + return mlir::emitError(request.loc, message); +} + static LogicalResult collectFormatStringFragments( - Value formatString, PrintFormattedOp anchorOp, + Value formatString, const PrintProceduralizationRequest &request, SmallVectorImpl &fragmentList, SmallSetVector &allFStringFragments, SmallSetVector &arguments) { @@ -46,7 +56,8 @@ static LogicalResult collectFormatStringFragments( if (auto concatInput = formatString.getDefiningOp()) { auto isAcyclic = concatInput.getFlattenedInputs(flatString); if (failed(isAcyclic)) { - anchorOp.emitError("Cyclic format string cannot be proceduralized."); + emitProceduralizationError(request, "Cyclic format string cannot be " + "proceduralized."); return failure(); } } else { @@ -57,8 +68,9 @@ static LogicalResult collectFormatStringFragments( for (auto fragment : flatString) { auto *fmtOp = fragment.getDefiningOp(); if (!fmtOp) { - anchorOp.emitError("Proceduralization of format strings passed as block " - "argument is unsupported."); + emitProceduralizationError( + request, "Proceduralization of format strings passed as block " + "argument is unsupported."); return failure(); } fragmentList.push_back(fmtOp); @@ -111,87 +123,140 @@ static Block *getOrCreateConditionBlock(OpBuilder &builder, return prevConditionBlock; } +// Prune the DAGs of formatting fragments left outside of the newly created +// TriggeredOps. +static void cleanupDeadPrintArtifacts(ArrayRef cleanupList) { + SmallVector worklist(cleanupList.begin(), cleanupList.end()); + SmallVector deferred; + SmallDenseSet erasedOps; + + bool noChange = true; + while (!worklist.empty() || !deferred.empty()) { + if (worklist.empty()) { + if (noChange) + break; + worklist = std::move(deferred); + deferred.clear(); + noChange = true; + } + + auto *opToErase = worklist.pop_back_val(); + if (!opToErase || erasedOps.contains(opToErase)) + continue; + + if (opToErase->getUses().empty()) { + if (auto concat = dyn_cast(opToErase)) + for (auto operand : concat.getInputs()) + deferred.push_back(operand.getDefiningOp()); + opToErase->erase(); + erasedOps.insert(opToErase); + noChange = false; + } else { + deferred.push_back(opToErase); + } + } +} + struct ProceduralizeSimPass : impl::ProceduralizeSimBase { public: void runOnOperation() override; private: - LogicalResult proceduralizePrintOps(Value clock, - ArrayRef printOps); - void cleanup(); - - // Mapping Clock -> List of printf ops - SmallMapVector, 2> printfOpMap; - - // List of formatting ops to be pruned after proceduralization. - SmallVector cleanupList; + // Mapping Clock -> List of print requests. + SmallMapVector, 2> + printRequestMap; }; } // namespace -LogicalResult ProceduralizeSimPass::proceduralizePrintOps( - Value clock, ArrayRef printOps) { - +LogicalResult circt::sim::proceduralizePrintsForClock( + OpBuilder &builder, Value clock, + ArrayRef printRequests) { // List of uniqued values to become arguments of the TriggeredOp. SmallSetVector arguments; - // Map print ops -> flattened list of format-string fragments. - SmallDenseMap, 4> printFragmentMap; + // Map print requests -> flattened list of format-string fragments. + SmallDenseMap, + 4> + printFragmentMap; // Map get_file ops -> flattened list of filename format-string fragments. SmallDenseMap, 4> fileNameFragmentMap; // All non-concat format-string fragment ops needed in the triggered body. SmallSetVector allFStringFragments; // Keep get_file ops in first-use order. SmallSetVector getFileOps; - SmallVector locs; SmallDenseSet alwaysEnabledConditions; - SmallVector livePrintOps; + SmallVector livePrintRequests; + SmallVector locs; + SmallVector cleanupList; + SmallVector sourceOpsToErase; - locs.reserve(printOps.size()); - for (auto printOp : printOps) { - if (auto cstCond = printOp.getCondition().getDefiningOp()) { + locs.reserve(printRequests.size()); + for (const auto &request : printRequests) { + if (auto cstCond = request.condition.getDefiningOp()) { if (cstCond.getValue().isZero()) { - printOp.erase(); + if (auto *inputDef = request.input.getDefiningOp()) + cleanupList.push_back(inputDef); + if (auto stream = request.stream) { + if (auto getFileOp = stream.getDefiningOp()) { + cleanupList.push_back(getFileOp); + cleanupList.push_back(getFileOp.getFileName().getDefiningOp()); + } else if (auto *streamDef = stream.getDefiningOp()) { + cleanupList.push_back(streamDef); + } + } + if (request.cleanupRoot) + sourceOpsToErase.push_back(request.cleanupRoot); continue; } - if (cstCond.getValue().isAllOnes()) - alwaysEnabledConditions.insert(printOp.getCondition()); + if (cstCond.getValue().isAllOnes()) { + alwaysEnabledConditions.insert(request.condition); + } else { + arguments.insert(request.condition); + } } else { - arguments.insert(printOp.getCondition()); + arguments.insert(request.condition); } - livePrintOps.push_back(printOp); - locs.push_back(printOp.getLoc()); + livePrintRequests.push_back(&request); + locs.push_back(request.loc); - auto &printFragments = printFragmentMap[printOp]; - if (failed(::collectFormatStringFragments(printOp.getInput(), printOp, - printFragments, - allFStringFragments, arguments))) + auto &printFragments = printFragmentMap[&request]; + if (failed(collectFormatStringFragments(request.input, request, + printFragments, allFStringFragments, + arguments))) return failure(); - if (auto stream = printOp.getStream()) { - auto getFileOp = stream.getDefiningOp(); - if (!getFileOp) { - if (!stream.getDefiningOp()) - printOp.emitError("proceduralization requires stream to be produced " - "by sim.get_file, block arguments are unsupported"); - else - printOp.emitError("proceduralization requires stream to be produced " - "by sim.get_file"); + if (auto stream = request.stream) { + if (auto getFileOp = stream.getDefiningOp()) { + getFileOps.insert(getFileOp); + auto &fileNameFragments = fileNameFragmentMap[getFileOp]; + if (fileNameFragments.empty() && + failed(collectFormatStringFragments( + getFileOp.getFileName(), request, fileNameFragments, + allFStringFragments, arguments))) + return failure(); + } else { + if (!stream.getDefiningOp()) { + emitProceduralizationError( + request, "proceduralization requires stream to be produced by " + "sim.get_file, block arguments are unsupported"); + } else { + emitProceduralizationError(request, + "proceduralization requires stream to be " + "produced by sim.get_file"); + } return failure(); } - getFileOps.insert(getFileOp); - auto &fileNameFragments = fileNameFragmentMap[getFileOp]; - if (fileNameFragments.empty() && - failed(::collectFormatStringFragments( - getFileOp.getFileName(), printOp, fileNameFragments, - allFStringFragments, arguments))) - return failure(); } } - if (livePrintOps.empty()) + if (livePrintRequests.empty()) { + for (auto *op : sourceOpsToErase) + if (op) + op->erase(); + cleanupDeadPrintArtifacts(cleanupList); return success(); + } - OpBuilder builder(livePrintOps.back()); auto fusedLoc = builder.getFusedLoc(locs); SmallVector argVec = arguments.takeVector(); @@ -207,13 +272,6 @@ LogicalResult ProceduralizeSimPass::proceduralizePrintOps( mapping.map(arg, trigOp.getBodyBlock()->getArgument(idx)); builder.setInsertionPointToStart(trigOp.getBodyBlock()); - if (!alwaysEnabledConditions.empty()) { - auto cstTrue = builder.createOrFold( - fusedLoc, IntegerAttr::get(builder.getI1Type(), 1)); - for (auto cstCond : alwaysEnabledConditions) - mapping.map(cstCond, cstTrue); - } - for (auto *fragment : allFStringFragments) { auto original = fragment->getResult(0); if (mapping.lookupOrNull(original)) @@ -224,7 +282,7 @@ LogicalResult ProceduralizeSimPass::proceduralizePrintOps( for (auto getFileOp : getFileOps) { auto &fileNameFragments = fileNameFragmentMap[getFileOp]; - Value clonedFileName = ::rematerializeFormatStringFromFragments( + Value clonedFileName = rematerializeFormatStringFromFragments( fileNameFragments, builder, mapping, getFileOp.getLoc()); auto clonedGetFile = @@ -238,95 +296,77 @@ LogicalResult ProceduralizeSimPass::proceduralizePrintOps( // Materialize print inputs before creating any conditional blocks. // Whether to actually construct strings eagerly/lazily is left to lowering // backends. - SmallDenseMap procPrintInputMap; + SmallDenseMap + procPrintInputMap; // Insert after rematerialized fragments/get_file ops so operands dominate. builder.setInsertionPointToEnd(trigOp.getBodyBlock()); - for (auto printOp : livePrintOps) { - auto &printFragments = printFragmentMap[printOp]; - procPrintInputMap[printOp] = ::rematerializeFormatStringFromFragments( - printFragments, builder, mapping, printOp.getLoc()); + for (auto *request : livePrintRequests) { + auto &printFragments = printFragmentMap[request]; + procPrintInputMap[request] = rematerializeFormatStringFromFragments( + printFragments, builder, mapping, request->loc); } Value prevConditionValue; Block *prevConditionBlock = nullptr; - for (auto printOp : livePrintOps) { - auto condArg = mapping.lookup(printOp.getCondition()); - auto *condBlock = - ::getOrCreateConditionBlock(builder, trigOp, printOp.getLoc(), condArg, + for (auto *request : livePrintRequests) { + if (alwaysEnabledConditions.contains(request->condition)) { + prevConditionValue = Value(); + prevConditionBlock = nullptr; + builder.setInsertionPointToEnd(trigOp.getBodyBlock()); + } else { + auto condArg = mapping.lookup(request->condition); + auto *condBlock = + getOrCreateConditionBlock(builder, trigOp, request->loc, condArg, prevConditionValue, prevConditionBlock); - - builder.setInsertionPoint(condBlock->getTerminator()); - Value procPrintInput = procPrintInputMap[printOp]; + builder.setInsertionPoint(condBlock->getTerminator()); + } Value procPrintStream; - if (auto stream = printOp.getStream()) { + if (auto stream = request->stream) { procPrintStream = mapping.lookupOrNull(stream); if (!procPrintStream) { - printOp.emitError("proceduralization failed to rematerialize stream"); + emitProceduralizationError(*request, + "proceduralization failed to rematerialize " + "stream"); return failure(); } + if (auto *streamDef = stream.getDefiningOp()) + cleanupList.push_back(streamDef); } - PrintFormattedProcOp::create(builder, printOp.getLoc(), procPrintInput, - procPrintStream); - cleanupList.push_back(printOp.getInput().getDefiningOp()); - printOp.erase(); + PrintFormattedProcOp::create(builder, request->loc, + procPrintInputMap[request], procPrintStream); + if (auto *inputDef = request->input.getDefiningOp()) + cleanupList.push_back(inputDef); + if (request->cleanupRoot) + sourceOpsToErase.push_back(request->cleanupRoot); } - return success(); -} - -// Prune the DAGs of formatting fragments left outside of the newly created -// TriggeredOps. -void ProceduralizeSimPass::cleanup() { - SmallVector cleanupNextList; - SmallDenseSet erasedOps; - - bool noChange = true; - while (!cleanupList.empty() || !cleanupNextList.empty()) { - - if (cleanupList.empty()) { - if (noChange) - break; - cleanupList = std::move(cleanupNextList); - cleanupNextList = {}; - noChange = true; - } - auto *opToErase = cleanupList.pop_back_val(); - if (erasedOps.contains(opToErase)) - continue; - - if (opToErase->getUses().empty()) { - // Remove a dead op. If it is a concat remove its operands, too. - if (auto concat = dyn_cast(opToErase)) - for (auto operand : concat.getInputs()) - cleanupNextList.push_back(operand.getDefiningOp()); - opToErase->erase(); - erasedOps.insert(opToErase); - noChange = false; - } else { - // Op still has uses, revisit later. - cleanupNextList.push_back(opToErase); - } - } + for (auto *op : sourceOpsToErase) + if (op) + op->erase(); + cleanupDeadPrintArtifacts(cleanupList); + return success(); } void ProceduralizeSimPass::runOnOperation() { LLVM_DEBUG(debugPassHeader(this) << "\n"); - printfOpMap.clear(); - cleanupList.clear(); + printRequestMap.clear(); auto theModule = getOperation(); - // Collect printf operations grouped by their clock. - theModule.walk( - [&](PrintFormattedOp op) { printfOpMap[op.getClock()].push_back(op); }); - - // Create a hw::TriggeredOp for each clock - for (auto &[clock, printOps] : printfOpMap) - if (failed(proceduralizePrintOps(clock, printOps))) { + // Collect print operations grouped by their clock, preserving IR order. + theModule.walk([&](PrintFormattedOp op) { + printRequestMap[op.getClock()].push_back({op.getLoc(), op.getInput(), + op.getCondition(), op.getStream(), + op, op}); + }); + + // Create a hw::TriggeredOp for each clock. + for (auto &[clock, requests] : printRequestMap) { + OpBuilder builder(requests.back().anchorOp); + if (failed(proceduralizePrintsForClock(builder, clock, requests))) { signalPassFailure(); return; } - - cleanup(); + } } diff --git a/test/Dialect/Sim/proceduralize-sim.mlir b/test/Dialect/Sim/proceduralize-sim.mlir index 49caba248244..d168e133c656 100644 --- a/test/Dialect/Sim/proceduralize-sim.mlir +++ b/test/Dialect/Sim/proceduralize-sim.mlir @@ -363,3 +363,46 @@ hw.module @shared_fmt_between_print_and_get_file( sim.print %msg on %clk if %cond to %file } + +// CHECK-LABEL: @mixed_conditional_and_unconditional +// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk +// CHECK-NEXT: hw.triggered posedge %[[TRG]](%a, %b) : i1, i1 { +// CHECK-NEXT: ^bb0(%[[ARGA:.*]]: i1, %[[ARGB:.*]]: i1): +// CHECK-DAG: %[[L0:.*]] = sim.fmt.literal "cond-a-0" +// CHECK-DAG: %[[L1:.*]] = sim.fmt.literal "always-1" +// CHECK-DAG: %[[L2:.*]] = sim.fmt.literal "cond-a-2" +// CHECK-DAG: %[[L3:.*]] = sim.fmt.literal "cond-b-3" +// CHECK-DAG: %[[L4:.*]] = sim.fmt.literal "always-4" +// CHECK-DAG: %[[L5:.*]] = sim.fmt.literal "cond-b-5" +// CHECK: scf.if %[[ARGA]] { +// CHECK-NEXT: sim.proc.print %[[L0]] +// CHECK-NEXT: } +// CHECK-NEXT: sim.proc.print %[[L1]] +// CHECK-NEXT: scf.if %[[ARGA]] { +// CHECK-NEXT: sim.proc.print %[[L2]] +// CHECK-NEXT: } +// CHECK-NEXT: scf.if %[[ARGB]] { +// CHECK-NEXT: sim.proc.print %[[L3]] +// CHECK-NEXT: } +// CHECK-NEXT: sim.proc.print %[[L4]] +// CHECK-NEXT: scf.if %[[ARGB]] { +// CHECK-NEXT: sim.proc.print %[[L5]] +// CHECK-NEXT: } +// CHECK-NEXT: } +hw.module @mixed_conditional_and_unconditional( + in %clk: !seq.clock, in %a: i1, in %b: i1) { + %true = hw.constant true + + %l0 = sim.fmt.literal "cond-a-0" + sim.print %l0 on %clk if %a + %l1 = sim.fmt.literal "always-1" + sim.print %l1 on %clk if %true + %l2 = sim.fmt.literal "cond-a-2" + sim.print %l2 on %clk if %a + %l3 = sim.fmt.literal "cond-b-3" + sim.print %l3 on %clk if %b + %l4 = sim.fmt.literal "always-4" + sim.print %l4 on %clk if %true + %l5 = sim.fmt.literal "cond-b-5" + sim.print %l5 on %clk if %b +} From f7bfff4785d965b4528f833a7741d7fd13afa29f Mon Sep 17 00:00:00 2001 From: woshiren Date: Tue, 12 May 2026 16:38:27 +0800 Subject: [PATCH 2/2] [FIRRTLToHW] Emit sim.proc.print instead of sim.print --- include/circt/Conversion/Passes.td | 1 + lib/Conversion/FIRRTLToHW/CMakeLists.txt | 1 + lib/Conversion/FIRRTLToHW/LowerToHW.cpp | 103 +++++++++++++----- test/Conversion/FIRRTLToHW/lower-to-core.mlir | 65 ++++++++++- test/firtool/lower-to-core.fir | 12 +- 5 files changed, 147 insertions(+), 35 deletions(-) diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 364e71ed645a..4494cac5bdbc 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -433,6 +433,7 @@ def LowerFIRRTLToHW : Pass<"lower-firrtl-to-hw", "mlir::ModuleOp"> { "sim::SimDialect", "sv::SVDialect", "verif::VerifDialect", + "mlir::scf::SCFDialect", ]; let options = [ Option<"enableAnnotationWarning", "warn-on-unprocessed-annotations", diff --git a/lib/Conversion/FIRRTLToHW/CMakeLists.txt b/lib/Conversion/FIRRTLToHW/CMakeLists.txt index c35080f70cd5..697806ab5e40 100644 --- a/lib/Conversion/FIRRTLToHW/CMakeLists.txt +++ b/lib/Conversion/FIRRTLToHW/CMakeLists.txt @@ -15,6 +15,7 @@ add_circt_conversion_library(CIRCTFIRRTLToHW CIRCTLTL CIRCTSeq CIRCTSim + CIRCTSimTransforms CIRCTSV CIRCTSVLoweringUtils CIRCTVerif diff --git a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index 394fe4fe48d1..8c7b22a6b7ef 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -31,15 +31,18 @@ #include "circt/Dialect/SV/SVOps.h" #include "circt/Dialect/Seq/SeqOps.h" #include "circt/Dialect/Sim/SimOps.h" +#include "circt/Dialect/Sim/SimTransforms.h" #include "circt/Dialect/Verif/VerifOps.h" #include "circt/Support/BackedgeBuilder.h" #include "circt/Support/Namespace.h" +#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/ImplicitLocOpBuilder.h" #include "mlir/IR/Threading.h" #include "mlir/Pass/Pass.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/MapVector.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Mutex.h" #include "llvm/Support/Path.h" @@ -1755,6 +1758,7 @@ struct FIRRTLLowering : public FIRRTLVisitor { Backedge createBackedge(Location loc, Type type); Backedge createBackedge(Value orig, Type type); bool updateIfBackedge(Value dest, Value src); + FailureOr resolveBackedge(Value root); /// Returns true if the lowered operation requires an inner symbol on it. bool requiresInnerSymbol(hw::InnerSymbolOpInterface op) { @@ -2042,6 +2046,7 @@ struct FIRRTLLowering : public FIRRTLVisitor { LogicalResult visitPrintfLike(T op, const FileDescriptorInfo &fileDescriptorInfo, bool usePrintfCond); + LogicalResult flushProceduralizedPrints(); LogicalResult visitStmt(PrintFOp op); LogicalResult visitStmt(FPrintFOp op); LogicalResult visitStmt(FFlushOp op); @@ -2151,6 +2156,11 @@ struct FIRRTLLowering : public FIRRTLVisitor { /// parent operation has handled the region, blocks, and block arguments. SmallVector> worklist; + /// Lower-to-core print requests preserved in IR order until the end of the + /// lowering, where they are regrouped by their resolved clocks. + SmallVector, 2> + pendingPrintRequests; + void addToWorklist(Block &block) { worklist.push_back({block.begin(), block.end()}); } @@ -2236,41 +2246,26 @@ LogicalResult FIRRTLLowering::run() { } } + if (failed(flushProceduralizedPrints())) { + backedgeBuilder.abandon(); + return failure(); + } + // Replace all backedges with uses of their regular values. We process them // after the module body since the lowering table is too hard to keep up to // date. Multiple operations may be lowered to the same backedge when values // are folded, which means we would have to scan the entire lowering table to // safely replace a backedge. - for (auto &[backedge, value] : backedges) { - SmallVector driverLocs; - // In the case where we have backedges connected to other backedges, we have - // to find the value that actually drives the group. - while (true) { - // If we find the original backedge we have some undriven logic or - // a combinatorial loop. Bail out and provide information on the nodes. - if (backedge == value) { - Location edgeLoc = backedge.getLoc(); - if (driverLocs.empty()) { - mlir::emitError(edgeLoc, "sink does not have a driver"); - } else { - auto diag = mlir::emitError(edgeLoc, "sink in combinational loop"); - for (auto loc : driverLocs) - diag.attachNote(loc) << "through driver here"; - } - backedgeBuilder.abandon(); - return failure(); - } - // If the value is not another backedge, we have found the driver. - auto *it = backedges.find(value); - if (it == backedges.end()) - break; - // Find what is driving the next backedge. - driverLocs.push_back(value.getLoc()); - value = it->second; + for (auto &[backedge, ignored] : backedges) { + (void)ignored; + auto resolvedValue = resolveBackedge(backedge); + if (failed(resolvedValue)) { + backedgeBuilder.abandon(); + return failure(); } if (auto *defOp = backedge.getDefiningOp()) maybeUnusedValues.erase(defOp); - backedge.replaceAllUsesWith(value); + backedge.replaceAllUsesWith(*resolvedValue); } // Now that all of the operations that can be lowered are, remove th @@ -3146,6 +3141,38 @@ bool FIRRTLLowering::updateIfBackedge(Value dest, Value src) { return true; } +FailureOr FIRRTLLowering::resolveBackedge(Value root) { + if (!root) + return root; + + DenseSet visited; + SmallVector driverLocs; + Value current = root; + while (true) { + auto *it = backedges.find(current); + if (it == backedges.end()) + return current; + + Value next = it->second; + if (next == current) { + auto diag = mlir::emitError(root.getLoc(), "sink does not have a driver"); + for (auto loc : driverLocs) + diag.attachNote(loc) << "through driver here"; + return failure(); + } + + if (next == root || !visited.insert(next).second) { + auto diag = mlir::emitError(root.getLoc(), "sink in combinational loop"); + for (auto loc : driverLocs) + diag.attachNote(loc) << "through driver here"; + return failure(); + } + + driverLocs.push_back(next.getLoc()); + current = next; + } +} + /// Switch the insertion point of the current builder to the end of the /// specified block and run the closure. This correctly handles the case /// where the closure is null, but the caller needs to make sure the block @@ -3940,6 +3967,25 @@ LogicalResult FIRRTLLowering::visitDecl(MemOp op) { return success(); } +LogicalResult FIRRTLLowering::flushProceduralizedPrints() { + SmallMapVector, 2> + printRequestMap; + for (auto &[clock, request] : pendingPrintRequests) { + auto resolvedClock = resolveBackedge(clock); + if (failed(resolvedClock)) + return failure(); + printRequestMap[*resolvedClock].push_back(request); + } + + for (auto &[clock, requests] : printRequestMap) { + OpBuilder printBuilder(requests.back().anchorOp); + if (failed(sim::proceduralizePrintsForClock(printBuilder, clock, requests))) + return failure(); + } + pendingPrintRequests.clear(); + return success(); +} + LogicalResult FIRRTLLowering::prepareInstanceOperands(ArrayRef portInfo, Operation *instanceOp, @@ -5457,7 +5503,8 @@ LogicalResult FIRRTLLowering::visitStmt(PrintFOp op) { if (failed(formatString)) return failure(); - sim::PrintFormattedOp::create(builder, *formatString, clock, cond); + pendingPrintRequests.push_back( + {clock, {op.getLoc(), *formatString, cond, Value(), op, nullptr}}); return success(); } diff --git a/test/Conversion/FIRRTLToHW/lower-to-core.mlir b/test/Conversion/FIRRTLToHW/lower-to-core.mlir index b4d4e3d9bf8e..8157dfebd3ea 100644 --- a/test/Conversion/FIRRTLToHW/lower-to-core.mlir +++ b/test/Conversion/FIRRTLToHW/lower-to-core.mlir @@ -15,14 +15,18 @@ firrtl.circuit "LowerToCore" { // CHECK: verif.clocked_assert %pred if %enable, posedge [[CLK]] : i1 firrtl.assert %clock, %pred, %enable, "assert failed: %d"(%x) : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.sint<4> - + // CHECK: [[PRINT_CLK:%.+]] = seq.from_clock %clock + // CHECK: hw.triggered posedge [[PRINT_CLK]](%enable, %x) : i1, i4 { + // CHECK-NEXT: ^bb0(%[[EN:.*]]: i1, %[[XARG:.*]]: i4): // CHECK: [[LIT0:%.+]] = sim.fmt.literal "value=" - // CHECK: [[FMTVAL:%.+]] = sim.fmt.dec %x signed : i4 + // CHECK: [[FMTVAL:%.+]] = sim.fmt.dec %[[XARG]] signed : i4 // CHECK: [[LIT1:%.+]] = sim.fmt.literal " @ " // CHECK: [[HIER:%.+]] = sim.fmt.hier_path // CHECK: [[NL:%.+]] = sim.fmt.literal "\0A" // CHECK: [[MSG:%.+]] = sim.fmt.concat ([[LIT0]], [[FMTVAL]], [[LIT1]], [[HIER]], [[NL]]) - // CHECK: sim.print [[MSG]] on %clock if %enable + // CHECK: scf.if %[[EN]] { + // CHECK-NEXT: sim.proc.print [[MSG]] + // CHECK-NOT: sim.print // CHECK-NOT: sv.assert // CHECK-NOT: sv.fwrite firrtl.printf %clock, %enable, "value=%d @ {{}}\0A"(%x, %hier) @@ -31,6 +35,61 @@ firrtl.circuit "LowerToCore" { firrtl.skip } + // CHECK-LABEL: hw.module @PrintOnMemPortBackedge( + firrtl.module @PrintOnMemPortBackedge( + in %clock: !firrtl.clock, + in %enable: !firrtl.uint<1>) { + %mem = firrtl.mem Undefined {depth = 8 : i64, name = "m", portNames = ["r"], readLatency = 1 : i32, writeLatency = 1 : i32} : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %addr = firrtl.subfield %mem[addr] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %en = firrtl.subfield %mem[en] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %clk = firrtl.subfield %mem[clk] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + // CHECK: [[PRINT_CLK:%.+]] = seq.from_clock %clock + // CHECK: hw.triggered posedge [[PRINT_CLK]](%enable) : i1 { + // CHECK-NEXT: ^bb0(%[[EN:.*]]: i1): + // CHECK: [[LIT:%.+]] = sim.fmt.literal "hit\0A" + // CHECK: scf.if %[[EN]] { + // CHECK-NEXT: sim.proc.print [[LIT]] + firrtl.printf %clk, %en, "hit\0A"() : !firrtl.clock, !firrtl.uint<1> + %c0_i3 = firrtl.constant 0 : !firrtl.uint<3> + firrtl.connect %addr, %c0_i3 : !firrtl.uint<3>, !firrtl.uint<3> + firrtl.connect %en, %enable : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %clk, %clock : !firrtl.clock, !firrtl.clock + firrtl.skip + } + + // CHECK-LABEL: hw.module @MergePrintsOnResolvedSharedClock( + firrtl.module @MergePrintsOnResolvedSharedClock( + in %clock: !firrtl.clock, + in %enable0: !firrtl.uint<1>, + in %enable1: !firrtl.uint<1>) { + %r0, %r1 = firrtl.mem Undefined {depth = 8 : i64, name = "m2", portNames = ["r0", "r1"], readLatency = 1 : i32, writeLatency = 1 : i32} : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>>, !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %r0_addr = firrtl.subfield %r0[addr] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %r0_en = firrtl.subfield %r0[en] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %r0_clk = firrtl.subfield %r0[clk] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %r1_addr = firrtl.subfield %r1[addr] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %r1_en = firrtl.subfield %r1[en] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + %r1_clk = firrtl.subfield %r1[clk] : !firrtl.bundle, en: uint<1>, clk: clock, data flip: uint<8>> + // CHECK: [[MERGED_CLK:%.+]] = seq.from_clock %clock + // CHECK-COUNT-1: hw.triggered posedge [[MERGED_CLK]](%enable0, %enable1) : i1, i1 { + // CHECK-NEXT: ^bb0(%[[EN0:.*]]: i1, %[[EN1:.*]]: i1): + // CHECK: [[LITA:%.+]] = sim.fmt.literal "A\0A" + // CHECK: [[LITB:%.+]] = sim.fmt.literal "B\0A" + // CHECK: scf.if %[[EN0]] { + // CHECK-NEXT: sim.proc.print [[LITA]] + // CHECK: scf.if %[[EN1]] { + // CHECK-NEXT: sim.proc.print [[LITB]] + firrtl.printf %r0_clk, %r0_en, "A\0A"() : !firrtl.clock, !firrtl.uint<1> + firrtl.printf %r1_clk, %r1_en, "B\0A"() : !firrtl.clock, !firrtl.uint<1> + %c0_i3 = firrtl.constant 0 : !firrtl.uint<3> + firrtl.connect %r0_addr, %c0_i3 : !firrtl.uint<3>, !firrtl.uint<3> + firrtl.connect %r0_en, %enable0 : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %r0_clk, %clock : !firrtl.clock, !firrtl.clock + firrtl.connect %r1_addr, %c0_i3 : !firrtl.uint<3>, !firrtl.uint<3> + firrtl.connect %r1_en, %enable1 : !firrtl.uint<1>, !firrtl.uint<1> + firrtl.connect %r1_clk, %clock : !firrtl.clock, !firrtl.clock + firrtl.skip + } + // CHECK-LABEL: hw.module @AttachToPort(inout %a : i8) // CHECK-NEXT: hw.instance "sink" @AnalogSink(a: %a: !hw.inout) -> () // CHECK-NEXT: hw.output diff --git a/test/firtool/lower-to-core.fir b/test/firtool/lower-to-core.fir index 59ebd99eb6e5..c123e3314f4f 100644 --- a/test/firtool/lower-to-core.fir +++ b/test/firtool/lower-to-core.fir @@ -12,12 +12,16 @@ circuit LowerToCore: printf(clock, enable, "value=%d\n", x) ; CHECK-LABEL: hw.module @LowerToCore -; CHECK-DAG: [[NL:%.+]] = sim.fmt.literal "\0A" -; CHECK-DAG: [[LIT:%.+]] = sim.fmt.literal "value=" ; CHECK-DAG: [[CLK:%.+]] = seq.from_clock %clock ; CHECK-DAG: verif.clocked_assert %pred if %enable, posedge [[CLK]] : i1 -; CHECK-DAG: [[FMT:%.+]] = sim.fmt.dec %x signed : i4 +; CHECK: hw.triggered posedge [[CLK]](%enable, %x) : i1, i4 { +; CHECK-NEXT: ^bb0(%[[EN:.*]]: i1, %[[XARG:.*]]: i4): +; CHECK: [[NL:%.+]] = sim.fmt.literal "\0A" +; CHECK: [[LIT:%.+]] = sim.fmt.literal "value=" +; CHECK: [[FMT:%.+]] = sim.fmt.dec %[[XARG]] signed : i4 ; CHECK: [[MSG:%.+]] = sim.fmt.concat ([[LIT]], [[FMT]], [[NL]]) -; CHECK: sim.print [[MSG]] on %clock if %enable +; CHECK: scf.if %[[EN]] { +; CHECK-NEXT: sim.proc.print [[MSG]] +; CHECK-NOT: sim.print ; CHECK-NOT: sv.assert ; CHECK-NOT: sv.fwrite