From 7e0fbd7be99d840a2387319d37855418f48e3c96 Mon Sep 17 00:00:00 2001 From: woshiren Date: Tue, 7 Apr 2026 21:55:24 +0800 Subject: [PATCH 1/2] [Sim][SimToSV] Supplementing the infrastructure for Sim dialects --- include/circt/Dialect/Sim/SimOps.td | 53 ++- lib/Conversion/MooreToCore/MooreToCore.cpp | 4 +- lib/Conversion/SimToSV/CMakeLists.txt | 1 + lib/Conversion/SimToSV/SimToSV.cpp | 319 +++++++++++++++++- lib/Dialect/Sim/SimOps.cpp | 18 +- .../Sim/Transforms/ProceduralizeSim.cpp | 303 +++++++++++------ lib/Firtool/CMakeLists.txt | 1 + lib/Firtool/Firtool.cpp | 2 + test/Conversion/SimToSV/print-io.mlir | 33 ++ test/Conversion/SimToSV/scf-if.mlir | 17 + test/Dialect/Sim/proceduralize-sim.mlir | 38 +++ test/Dialect/Sim/round-trip.mlir | 2 + 12 files changed, 673 insertions(+), 118 deletions(-) create mode 100644 test/Conversion/SimToSV/print-io.mlir create mode 100644 test/Conversion/SimToSV/scf-if.mlir diff --git a/include/circt/Dialect/Sim/SimOps.td b/include/circt/Dialect/Sim/SimOps.td index 58cc29619553..d1e5c92b9d40 100644 --- a/include/circt/Dialect/Sim/SimOps.td +++ b/include/circt/Dialect/Sim/SimOps.td @@ -502,6 +502,16 @@ def FormatHierPathOp : SimOp<"fmt.hier_path", [Pure]> { let assemblyFormat = "(`escaped` $useEscapes^)? attr-dict"; } +def TimeOp : SimOp<"time", [Pure]> { + let summary = "Current simulation time"; + let description = [{ + Returns the current simulation time as an `i64` value. + }]; + + let results = (outs I64:$result); + let assemblyFormat = "attr-dict"; +} + def FormatStringConcatOp : SimOp<"fmt.concat", [Pure]> { let summary = "Concatenate format strings"; let description = [{ @@ -560,11 +570,13 @@ def PrintFormattedOp : SimOp<"print"> { }]; let arguments = (ins FormatStringType:$input, + Optional:$stream, ClockType:$clock, - I1:$condition + I1:$condition, + DefaultValuedAttr:$usePrintfCond ); let hasCanonicalizeMethod = true; - let assemblyFormat = "$input `on` $clock `if` $condition attr-dict"; + let assemblyFormat = "$input (`to` $stream^)? `on` $clock `if` $condition attr-dict"; } def PrintFormattedProcOp : SimOp<"proc.print"> { @@ -574,10 +586,43 @@ def PrintFormattedProcOp : SimOp<"proc.print"> { This operation must be within a procedural region. }]; - let arguments = (ins FormatStringType:$input); + let arguments = (ins + FormatStringType:$input, + Optional:$stream, + DefaultValuedAttr:$usePrintfCond + ); let hasVerifier = true; let hasCanonicalizeMethod = true; - let assemblyFormat = "$input attr-dict"; + let assemblyFormat = "$input (`to` $stream^)? attr-dict"; +} + +def GetFileOp : SimOp<"get_file"> { + let summary = "Get a file descriptor for a formatted file name"; + let description = [{ + Looks up the descriptor for a file name in a process-global mapping. + If no descriptor exists, opens the file and records the new descriptor. + }]; + + let arguments = (ins StrAttr:$fileNameFormat, + Variadic:$fileNameOperands); + let results = (outs I32:$descriptor); + let assemblyFormat = [{ + $fileNameFormat `(` $fileNameOperands `)` attr-dict + `:` functional-type($fileNameOperands, results) + }]; +} + +def FFlushOp : SimOp<"fflush"> { + let summary = "Flush a file stream on a given clock and condition"; + let arguments = (ins I32:$stream, ClockType:$clock, I1:$condition); + let assemblyFormat = "$stream `on` $clock `if` $condition attr-dict"; +} + +def FFlushProcOp : SimOp<"proc.fflush"> { + let summary = "Flush a file stream within a procedural region"; + let arguments = (ins I32:$stream); + let hasVerifier = 1; + let assemblyFormat = "$stream attr-dict"; } //===----------------------------------------------------------------------===// diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index 444b60638968..d5de4edba5f8 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -2745,7 +2745,7 @@ struct DisplayBIOpConversion : public OpConversionPattern { matchAndRewrite(DisplayBIOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { rewriter.replaceOpWithNewOp( - op, adaptor.getMessage()); + op, adaptor.getMessage(), Value{}); return success(); } }; @@ -2796,7 +2796,7 @@ static LogicalResult convert(SeverityBIOp op, SeverityBIOp::Adaptor adaptor, sim::FormatLiteralOp::create(rewriter, op.getLoc(), severityString); auto message = sim::FormatStringConcatOp::create( rewriter, op.getLoc(), ValueRange{prefix, adaptor.getMessage()}); - rewriter.replaceOpWithNewOp(op, message); + rewriter.replaceOpWithNewOp(op, message, Value{}); return success(); } diff --git a/lib/Conversion/SimToSV/CMakeLists.txt b/lib/Conversion/SimToSV/CMakeLists.txt index d7afea62d40e..b75cf3c1357e 100644 --- a/lib/Conversion/SimToSV/CMakeLists.txt +++ b/lib/Conversion/SimToSV/CMakeLists.txt @@ -11,4 +11,5 @@ add_circt_conversion_library(CIRCTSimToSV CIRCTEmit CIRCTSim CIRCTSeq + MLIRSCFDialect ) diff --git a/lib/Conversion/SimToSV/SimToSV.cpp b/lib/Conversion/SimToSV/SimToSV.cpp index 965ac5cd38b1..be44d284124a 100644 --- a/lib/Conversion/SimToSV/SimToSV.cpp +++ b/lib/Conversion/SimToSV/SimToSV.cpp @@ -19,12 +19,17 @@ #include "circt/Dialect/Sim/SimDialect.h" #include "circt/Dialect/Sim/SimOps.h" #include "circt/Support/Namespace.h" +#include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/IR/Builders.h" #include "mlir/IR/DialectImplementation.h" #include "mlir/IR/ImplicitLocOpBuilder.h" +#include "mlir/IR/Operation.h" +#include "mlir/IR/PatternMatch.h" #include "mlir/IR/Threading.h" #include "mlir/Pass/Pass.h" +#include "mlir/Support/WalkResult.h" #include "mlir/Transforms/DialectConversion.h" +#include "mlir/Transforms/RegionUtils.h" #define DEBUG_TYPE "lower-sim-to-sv" @@ -39,7 +44,8 @@ using namespace sim; /// Check whether an op should be placed inside an ifdef guard that prevents it /// from affecting synthesis runs. static bool needsIfdefGuard(Operation *op) { - return isa(op); + return isa(op); } /// Check whether an op should be placed inside an always process triggered on a @@ -53,12 +59,116 @@ static std::pair needsClockAndConditionWrapper(Operation *op) { .Default({}); } +static Value getDefaultStderrFD(OpBuilder &builder, Location loc) { + return hw::ConstantOp::create(builder, loc, APInt(32, 0x80000002)); +} + +struct LoweredFormatString { + SmallString<64> format; + SmallVector operands; +}; + +static void appendEscapedLiteral(SmallString<64> &result, StringRef literal) { + for (char c : literal) { + if (c == '%') + result += "%%"; + else + result.push_back(c); + } +} + +static void appendDecimalSpecifier(SmallString<64> &result, + std::optional width) { + result.push_back('%'); + if (width) + result += Twine(*width).str(); + result.push_back('d'); +} + +static LogicalResult lowerFormatString(Value input, LoweredFormatString &out, + ConversionPatternRewriter &rewriter) { + Operation *op = input.getDefiningOp(); + return TypeSwitch(op) + .Case([&](auto lit) { + appendEscapedLiteral(out.format, lit.getLiteral()); + return success(); + }) + .Case([&](auto concat) { + for (Value sub : concat.getInputs()) + if (failed(lowerFormatString(sub, out, rewriter))) + return failure(); + return success(); + }) + .Case([&](auto fmt) { + out.format.push_back('%'); + if (fmt.getSpecifierWidth()) + out.format += Twine(*fmt.getSpecifierWidth()).str(); + out.format.push_back('b'); + out.operands.push_back(fmt.getValue()); + return success(); + }) + .Case([&](auto fmt) { + if (fmt.getValue().template getDefiningOp()) { + out.format.push_back('%'); + if (fmt.getSpecifierWidth()) + out.format += Twine(*fmt.getSpecifierWidth()).str(); + else + out.format.push_back('0'); + out.format.push_back('t'); + out.operands.push_back(fmt.getValue()); + } else { + appendDecimalSpecifier(out.format, fmt.getSpecifierWidth()); + + Value operand = fmt.getValue(); + if (fmt.getIsSigned()) { + operand = sv::SystemFunctionOp::create( + rewriter, fmt.getLoc(), operand.getType(), "signed", operand); + } + out.operands.push_back(operand); + } + return success(); + }) + .Case([&](auto fmt) { + out.format.push_back('%'); + if (fmt.getSpecifierWidth()) + out.format += Twine(*fmt.getSpecifierWidth()).str(); + out.format.push_back(fmt.getIsHexUppercase() ? 'X' : 'x'); + out.operands.push_back(fmt.getValue()); + return success(); + }) + .Case([&](auto fmt) { + out.format.push_back('%'); + if (fmt.getSpecifierWidth()) + out.format += Twine(*fmt.getSpecifierWidth()).str(); + out.format.push_back('o'); + out.operands.push_back(fmt.getValue()); + return success(); + }) + .Case([&](auto fmt) { + out.format += "%c"; + out.operands.push_back(fmt.getValue()); + return success(); + }) + .Case([&](auto fmt) { + out.format += fmt.getUseEscapes() ? "%M" : "%m"; + return success(); + }) + .Default([](Operation *) { return failure(); }); +} + +static bool isFormatOp(Operation *op) { + return isa(op); +} + namespace { struct SimConversionState { hw::HWModuleOp module; bool usedSynthesisMacro = false; SetVector dpiCallees; + SmallVector formatCleanupRoots; }; template @@ -281,6 +391,144 @@ class DPICallLowering : public SimConversionPattern { } }; +class GetFileLowering : public SimConversionPattern { +public: + using SimConversionPattern::SimConversionPattern; + + LogicalResult + matchAndRewrite(GetFileOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + Value fileName; + if (adaptor.getFileNameOperands().empty()) { + fileName = sv::ConstantStrOp::create(rewriter, op.getLoc(), + op.getFileNameFormat()); + } else { + fileName = + sv::SFormatFOp::create(rewriter, op.getLoc(), op.getFileNameFormat(), + adaptor.getFileNameOperands()); + } + + Value fd; + auto callee = + rewriter.getStringAttr("__circt_lib_logging::FileDescriptor::get"); + if (op->getParentOp() && + op->getParentOp()->hasTrait()) { + fd = sv::FuncCallProceduralOp::create(rewriter, op.getLoc(), + TypeRange{rewriter.getI32Type()}, + callee, ValueRange{fileName}) + ->getResult(0); + } else { + fd = sv::FuncCallOp::create(rewriter, op.getLoc(), + TypeRange{rewriter.getI32Type()}, callee, + ValueRange{fileName}) + ->getResult(0); + } + rewriter.replaceOp(op, fd); + return success(); + } +}; + +class PrintFormattedProcLowering + : public SimConversionPattern { +public: + using SimConversionPattern::SimConversionPattern; + + LogicalResult + matchAndRewrite(PrintFormattedProcOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + Value originalInput = op.getInput(); + + LoweredFormatString lowered; + if (failed(lowerFormatString(adaptor.getInput(), lowered, rewriter))) + return rewriter.notifyMatchFailure(op, "unsupported format string"); + + Value stream = adaptor.getStream(); + if (!stream) + stream = getDefaultStderrFD(rewriter, op.getLoc()); + + auto buildFwrite = [&]() { + sv::FWriteOp::create(rewriter, op.getLoc(), stream, + rewriter.getStringAttr(lowered.format), + lowered.operands); + }; + + if (op.getUsePrintfCond()) { + Value printfCond = sv::MacroRefExprOp::create( + rewriter, op->getLoc(), rewriter.getI1Type(), "PRINTF_COND_"); + sv::IfOp::create(rewriter, op->getLoc(), printfCond, + [&]() { buildFwrite(); }); + } else { + buildFwrite(); + } + rewriter.eraseOp(op); + + state.formatCleanupRoots.push_back(originalInput); + + return success(); + } +}; + +class FFlushProcLowering : public SimConversionPattern { +public: + using SimConversionPattern::SimConversionPattern; + + LogicalResult + matchAndRewrite(FFlushProcOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + sv::FFlushOp::create(rewriter, op.getLoc(), adaptor.getStream()); + rewriter.eraseOp(op); + return success(); + } +}; + +class ScfIfLowering : public OpConversionPattern { +public: + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(mlir::scf::IfOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + if (op.getNumResults() != 0) + return rewriter.notifyMatchFailure(op, "resultful scf.if unsupported"); + + auto *parentOp = op->getParentOp(); + if (!parentOp || !parentOp->hasTrait()) + return rewriter.notifyMatchFailure( + op, "scf.if parent is not an SV procedural region"); + + auto cloneWithoutYield = [&](Block &from) { + for (auto &nested : from.without_terminator()) + rewriter.clone(nested); + }; + + if (op.getElseRegion().empty()) { + sv::IfOp::create(rewriter, op.getLoc(), adaptor.getCondition(), [&]() { + cloneWithoutYield(op.getThenRegion().front()); + }); + } else { + sv::IfOp::create( + rewriter, op.getLoc(), adaptor.getCondition(), + [&]() { cloneWithoutYield(op.getThenRegion().front()); }, + [&]() { cloneWithoutYield(op.getElseRegion().front()); }); + } + + rewriter.eraseOp(op); + return success(); + } +}; + +class TimeLowering : public SimConversionPattern { +public: + using SimConversionPattern::SimConversionPattern; + + LogicalResult + matchAndRewrite(sim::TimeOp op, OpAdaptor, + ConversionPatternRewriter &rewriter) const final { + rewriter.replaceOpWithNewOp(op); + return success(); + } +}; + // A helper struct to lower DPI function/call. struct LowerDPIFunc { llvm::DenseMap symbolToFragment; @@ -373,6 +621,14 @@ void LowerDPIFunc::addFragments(hw::HWModuleOp module, ArrayAttr::get(module.getContext(), fragments.takeVector())); } +static bool isInProceduralRegion(Operation *op) { + auto *parentOp = op->getParentOp(); + while (parentOp && !parentOp->hasTrait()) { + parentOp = parentOp->getParentOp(); + } + return parentOp; +} + static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp) { bool usedSynthesisMacro = false; @@ -382,11 +638,16 @@ static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp) { // Move the op into an ifdef guard if needed. if (needsIfdefGuard(op)) { // Try to reuse an ifdef guard immediately before the op. + bool isProcedural = isInProceduralRegion(op); Block *block = nullptr; if (op->getPrevNode()) block = TypeSwitch(op->getPrevNode()) .Case( [&](auto guardOp) -> Block * { + bool guardIsProcedural = isa( + guardOp.getOperation()); + if (guardIsProcedural != isProcedural) + return nullptr; if (guardOp.getCond().getIdent().getAttr() == "SYNTHESIS" && guardOp.hasElse()) @@ -398,7 +659,7 @@ static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp) { // If there was no pre-existing guard, create one. if (!block) { OpBuilder builder(op); - if (op->getParentOp()->hasTrait()) + if (isProcedural) block = sv::IfDefProceduralOp::create( builder, loc, "SYNTHESIS", [] {}, [] {}) .getElseBlock(); @@ -477,8 +738,22 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase { std::atomic usedSynthesisMacro = false; auto lowerModule = [&](hw::HWModuleOp module) { - if (moveOpsIntoIfdefGuardsAndProcesses(module)) - usedSynthesisMacro = true; + bool moduleNeeds = moveOpsIntoIfdefGuardsAndProcesses(module); + module.walk([&](Operation *op) { + StringRef condName; + if (auto ifdef = dyn_cast(op)) + condName = ifdef.getCond().getName(); + else if (auto ifdefP = dyn_cast(op)) + condName = ifdefP.getCond().getName(); + + if (condName == "SYNTHESIS") { + moduleNeeds = true; + return WalkResult::interrupt(); + } + return WalkResult::advance(); + }); + if (moduleNeeds) + usedSynthesisMacro.store(true); SimConversionState state; ConversionTarget target(*context); @@ -487,6 +762,10 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase { target.addLegalDialect(); target.addLegalDialect(); target.addLegalDialect(); + target.addLegalOp(); RewritePatternSet patterns(context); patterns.add(context, state); @@ -496,11 +775,41 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase { patterns.add(convert); patterns.add(convert); patterns.add(context, state); + patterns.add(context, state); + patterns.add(context, state); + patterns.add(context, state); + patterns.add(context); + patterns.add(context, state); auto result = applyPartialConversion(module, target, std::move(patterns)); if (failed(result)) return result; + SmallVector worklist; + llvm::SmallDenseSet queued; + llvm::SmallDenseSet erased; + auto enqueueFormatDef = [&](Value value) { + if (auto *defOp = value.getDefiningOp(); defOp && isFormatOp(defOp)) + if (queued.insert(defOp).second) + worklist.push_back(defOp); + }; + for (Value root : state.formatCleanupRoots) + enqueueFormatDef(root); + + while (!worklist.empty()) { + Operation *candidate = worklist.pop_back_val(); + queued.erase(candidate); + if (erased.contains(candidate)) + continue; + if (!candidate->use_empty()) + continue; + if (auto concat = dyn_cast(candidate)) + for (Value input : concat.getInputs()) + enqueueFormatDef(input); + erased.insert(candidate); + candidate->erase(); + } + // Set the emit fragment. lowerDPIFunc.addFragments(module, state.dpiCallees.takeVector()); @@ -513,7 +822,7 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase { context, circuit.getOps(), lowerModule))) return signalPassFailure(); - if (usedSynthesisMacro) { + if (usedSynthesisMacro.load()) { Operation *op = circuit.lookupSymbol("SYNTHESIS"); if (op) { if (!isa(op)) { diff --git a/lib/Dialect/Sim/SimOps.cpp b/lib/Dialect/Sim/SimOps.cpp index 4f3bc5423595..e9e56be59732 100644 --- a/lib/Dialect/Sim/SimOps.cpp +++ b/lib/Dialect/Sim/SimOps.cpp @@ -623,22 +623,22 @@ LogicalResult PrintFormattedOp::canonicalize(PrintFormattedOp op, return failure(); } -LogicalResult PrintFormattedProcOp::verify() { +static LogicalResult verifyInProceduralRegion(Operation *op) { // Check if we know for sure that the parent is not procedural. - auto *parentOp = getOperation()->getParentOp(); + auto *parentOp = op->getParentOp(); if (!parentOp) - return emitOpError("must be within a procedural region."); + return op->emitOpError("must be within a procedural region."); if (isa_and_nonnull(parentOp->getDialect())) { if (!isa(parentOp)) - return emitOpError("must be within a procedural region."); + return op->emitOpError("must be within a procedural region."); return success(); } if (isa_and_nonnull(parentOp->getDialect())) { if (!parentOp->hasTrait()) - return emitOpError("must be within a procedural region."); + return op->emitOpError("must be within a procedural region."); return success(); } @@ -646,6 +646,14 @@ LogicalResult PrintFormattedProcOp::verify() { return success(); } +LogicalResult PrintFormattedProcOp::verify() { + return verifyInProceduralRegion(getOperation()); +} + +LogicalResult FFlushProcOp::verify() { + return verifyInProceduralRegion(getOperation()); +} + LogicalResult PrintFormattedProcOp::canonicalize(PrintFormattedProcOp op, PatternRewriter &rewriter) { // Remove empty prints. diff --git a/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp b/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp index d6e21796a113..3fd15ce7e4e6 100644 --- a/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp +++ b/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp @@ -18,6 +18,7 @@ #include "circt/Support/Debug.h" #include "mlir/Dialect/SCF/IR/SCF.h" #include "mlir/Pass/Pass.h" +#include "mlir/Transforms/RegionUtils.h" #include "llvm/ADT/IndexedMap.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SetVector.h" @@ -42,21 +43,19 @@ struct ProceduralizeSimPass : impl::ProceduralizeSimBase { void runOnOperation() override; private: - LogicalResult proceduralizePrintOps(Value clock, - ArrayRef printOps); - SmallVector getPrintFragments(PrintFormattedOp op); + LogicalResult proceduralizeOps(Value clock, ArrayRef ops); void cleanup(); - // Mapping Clock -> List of printf ops - SmallMapVector, 2> printfOpMap; + // Mapping Clock -> List of simulation ops to proceduralize. + SmallMapVector, 2> simOpMap; // List of formatting ops to be pruned after proceduralization. SmallVector cleanupList; }; } // namespace -LogicalResult ProceduralizeSimPass::proceduralizePrintOps( - Value clock, ArrayRef printOps) { +LogicalResult +ProceduralizeSimPass::proceduralizeOps(Value clock, ArrayRef ops) { // List of uniqued values to become arguments of the TriggeredOp. SmallSetVector arguments; @@ -64,61 +63,116 @@ LogicalResult ProceduralizeSimPass::proceduralizePrintOps( SmallDenseMap, 4> fragmentMap; SmallVector locs; SmallDenseSet alwaysEnabledConditions; + SmallSetVector timeValues; + SmallSetVector getFileValues; + SmallVector activeOps; - locs.reserve(printOps.size()); + locs.reserve(ops.size()); - for (auto printOp : printOps) { - // Handle the print condition value. If it is not constant, it has to become - // a region argument. If it is constant false, skip the operation. - if (auto cstCond = printOp.getCondition().getDefiningOp()) { - if (cstCond.getValue().isAllOnes()) - alwaysEnabledConditions.insert(printOp.getCondition()); - else - continue; - } else { - arguments.insert(printOp.getCondition()); + auto collectValue = [&](auto &&self, Value value) -> void { + if (!value) + return; + if (value.getDefiningOp()) { + timeValues.insert(value); + return; } + if (auto getFile = value.getDefiningOp()) { + getFileValues.insert(value); + for (auto fileNameOperand : getFile.getFileNameOperands()) + self(self, fileNameOperand); + cleanupList.push_back(getFile); + return; + } + arguments.insert(value); + }; + + for (auto *op : ops) { + if (auto printOp = dyn_cast(op)) { + // Handle the print condition value. If it is not constant, it has to + // become a region argument. If it is constant false, skip the operation. + if (auto cstCond = + printOp.getCondition().getDefiningOp()) { + if (cstCond.getValue().isAllOnes()) + alwaysEnabledConditions.insert(printOp.getCondition()); + else { + printOp.erase(); + continue; + } + } else { + arguments.insert(printOp.getCondition()); + } - // Accumulate locations - locs.push_back(printOp.getLoc()); - - // Get the flat list of formatting fragments and collect leaf fragments - SmallVector flatString; - if (auto concatInput = - printOp.getInput().getDefiningOp()) { + // Accumulate locations. + locs.push_back(printOp.getLoc()); + + // Get the flat list of formatting fragments and collect leaf fragments. + SmallVector flatString; + if (auto concatInput = + printOp.getInput().getDefiningOp()) { + auto isAcyclic = concatInput.getFlattenedInputs(flatString); + if (failed(isAcyclic)) { + printOp.emitError("Cyclic format string cannot be proceduralized."); + return failure(); + } + } else { + flatString.push_back(printOp.getInput()); + } - auto isAcyclic = concatInput.getFlattenedInputs(flatString); - if (failed(isAcyclic)) { - printOp.emitError("Cyclic format string cannot be proceduralized."); + auto &fragmentList = fragmentMap[printOp]; + assert(fragmentList.empty() && "printf operation visited twice."); + + for (auto &fragment : flatString) { + auto *fmtOp = fragment.getDefiningOp(); + if (!fmtOp) { + printOp.emitError("Proceduralization of format strings passed as " + "block argument is unsupported."); + return failure(); + } + fragmentList.push_back(fmtOp); + // For value formatters, the value to be formatted has to become + // an argument. + if (auto fmtVal = getFormattedValue(fmtOp)) { + collectValue(collectValue, fmtVal); + continue; + } + // Some formatting fragments do not have value operands. + if (llvm::isa(fmtOp)) + continue; + printOp.emitError("Unsupported formatting fragment op in " + "proceduralization.") + .attachNote(fmtOp->getLoc()) + << "unexpected fragment op is here"; return failure(); } - } else { - flatString.push_back(printOp.getInput()); - } - auto &fragmentList = fragmentMap[printOp]; - assert(fragmentList.empty() && "printf operation visited twice."); + collectValue(collectValue, printOp.getStream()); + activeOps.push_back(op); + continue; + } - for (auto &fragment : flatString) { - auto *fmtOp = fragment.getDefiningOp(); - if (!fmtOp) { - printOp.emitError("Proceduralization of format strings passed as block " - "argument is unsupported."); - return failure(); - } - fragmentList.push_back(fmtOp); - // For non-literal fragments, the value to be formatted has to become an - // argument. - if (!llvm::isa(fmtOp)) { - auto fmtVal = getFormattedValue(fmtOp); - assert(!!fmtVal && "Unexpected foramtting fragment op."); - arguments.insert(fmtVal); + auto fflushOp = cast(op); + if (auto cstCond = + fflushOp.getCondition().getDefiningOp()) { + if (cstCond.getValue().isAllOnes()) + alwaysEnabledConditions.insert(fflushOp.getCondition()); + else { + fflushOp.erase(); + continue; } + } else { + arguments.insert(fflushOp.getCondition()); } + + locs.push_back(fflushOp.getLoc()); + collectValue(collectValue, fflushOp.getStream()); + activeOps.push_back(op); } + if (activeOps.empty()) + return success(); + // Build the hw::TriggeredOp - OpBuilder builder(printOps.back()); + OpBuilder builder(activeOps.back()); auto fusedLoc = builder.getFusedLoc(locs); SmallVector argVec = arguments.takeVector(); @@ -140,70 +194,106 @@ LogicalResult ProceduralizeSimPass::proceduralizePrintOps( // Materialize and map a 'true' constant within the TriggeredOp if required. builder.setInsertionPointToStart(trigOp.getBodyBlock()); + Operation *lastPreludeOp = nullptr; + auto setPreludeInsertionPoint = [&]() { + if (lastPreludeOp) + builder.setInsertionPointAfter(lastPreludeOp); + else + builder.setInsertionPointToStart(trigOp.getBodyBlock()); + }; + + for (auto timeValue : timeValues) { + auto timeOp = timeValue.getDefiningOp(); + assert(timeOp && "Expected sim.time defining op"); + auto *clonedTimeOp = builder.clone(*timeOp); + lastPreludeOp = clonedTimeOp; + argumentMapper.map(timeValue, clonedTimeOp->getResult(0)); + } + + for (auto getFileValue : getFileValues) { + auto getFileOp = getFileValue.getDefiningOp(); + assert(getFileOp && "Expected sim.get_file defining op"); + setPreludeInsertionPoint(); + auto *clonedGetFileOp = builder.clone(*getFileOp, argumentMapper); + lastPreludeOp = clonedGetFileOp; + argumentMapper.map(getFileValue, clonedGetFileOp->getResult(0)); + } + if (!alwaysEnabledConditions.empty()) { + setPreludeInsertionPoint(); auto cstTrue = builder.createOrFold( fusedLoc, IntegerAttr::get(builder.getI1Type(), 1)); + if (auto *cstTrueOp = cstTrue.getDefiningOp()) + lastPreludeOp = cstTrueOp; for (auto cstCond : alwaysEnabledConditions) argumentMapper.map(cstCond, cstTrue); } SmallDenseMap cloneMap; Value prevConditionValue; - Block *prevConditionBlock; - - for (auto printOp : printOps) { - - // Throw away disabled prints - if (auto cstCond = printOp.getCondition().getDefiningOp()) { - if (cstCond.getValue().isZero()) { - printOp.erase(); - continue; - } - } - - // Create a copy of the required fragment operations within the - // TriggeredOp's body. - auto fragments = fragmentMap[printOp]; - SmallVector clonedOperands; - builder.setInsertionPointToStart(trigOp.getBodyBlock()); - for (auto *fragment : fragments) { - auto &fmtCloned = cloneMap[fragment]; - if (!fmtCloned) - fmtCloned = builder.clone(*fragment, argumentMapper); - clonedOperands.push_back(fmtCloned->getResult(0)); - } - // Concatenate fragments to a single value if necessary. - Value procPrintInput; - if (clonedOperands.size() != 1) - procPrintInput = builder.createOrFold( - printOp.getLoc(), clonedOperands); - else - procPrintInput = clonedOperands.front(); - - // Check if we can reuse the previous conditional block. - auto condArg = argumentMapper.lookup(printOp.getCondition()); - if (condArg != prevConditionValue) + Block *prevConditionBlock = nullptr; + auto getOrCreateConditionBlock = [&](Value cond, Location loc) { + if (cond != prevConditionValue) prevConditionBlock = nullptr; - auto *condBlock = prevConditionBlock; - // If not, create a new scf::IfOp for the condition. - if (!condBlock) { + if (!prevConditionBlock) { builder.setInsertionPointToEnd(trigOp.getBodyBlock()); - auto ifOp = mlir::scf::IfOp::create(builder, printOp.getLoc(), - TypeRange{}, condArg, true, false); + auto ifOp = + mlir::scf::IfOp::create(builder, loc, TypeRange{}, cond, true, false); builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); - mlir::scf::YieldOp::create(builder, printOp.getLoc()); - condBlock = builder.getBlock(); - prevConditionValue = condArg; - prevConditionBlock = condBlock; + mlir::scf::YieldOp::create(builder, loc); + prevConditionBlock = builder.getBlock(); + prevConditionValue = cond; } + return prevConditionBlock; + }; + + for (auto *op : activeOps) { + if (auto printOp = dyn_cast(op)) { + // Create a copy of the required fragment operations within the + // TriggeredOp's body. + auto fragments = fragmentMap[printOp]; + SmallVector clonedOperands; + setPreludeInsertionPoint(); + for (auto *fragment : fragments) { + auto &fmtCloned = cloneMap[fragment]; + if (!fmtCloned) { + fmtCloned = builder.clone(*fragment, argumentMapper); + lastPreludeOp = fmtCloned; + } + clonedOperands.push_back(fmtCloned->getResult(0)); + } + // Concatenate fragments to a single value if necessary. + Value procPrintInput; + if (clonedOperands.size() != 1) + procPrintInput = builder.createOrFold( + printOp.getLoc(), clonedOperands); + else + procPrintInput = clonedOperands.front(); + + auto cond = argumentMapper.lookup(printOp.getCondition()); + auto *condBlock = getOrCreateConditionBlock(cond, printOp.getLoc()); + + // Create the procedural print operation and prune the operations outside + // of the TriggeredOp. + builder.setInsertionPoint(condBlock->getTerminator()); + Value stream = argumentMapper.lookupOrDefault(printOp.getStream()); + PrintFormattedProcOp::create(builder, printOp.getLoc(), procPrintInput, + stream, printOp.getUsePrintfCond()); + cleanupList.push_back(printOp.getInput().getDefiningOp()); + printOp.erase(); + continue; + } + + auto fflushOp = cast(op); + auto cond = argumentMapper.lookup(fflushOp.getCondition()); + auto *condBlock = getOrCreateConditionBlock(cond, fflushOp.getLoc()); - // Create the procedural print operation and prune the operations outside of - // the TriggeredOp. + // Create the procedural fflush operation. builder.setInsertionPoint(condBlock->getTerminator()); - PrintFormattedProcOp::create(builder, printOp.getLoc(), procPrintInput); - cleanupList.push_back(printOp.getInput().getDefiningOp()); - printOp.erase(); + Value stream = argumentMapper.lookupOrDefault(fflushOp.getStream()); + FFlushProcOp::create(builder, fflushOp.getLoc(), stream); + fflushOp.erase(); } return success(); } @@ -246,20 +336,29 @@ void ProceduralizeSimPass::cleanup() { void ProceduralizeSimPass::runOnOperation() { LLVM_DEBUG(debugPassHeader(this) << "\n"); - printfOpMap.clear(); + simOpMap.clear(); cleanupList.clear(); auto theModule = getOperation(); - // Collect printf operations grouped by their clock. - theModule.walk( - [&](PrintFormattedOp op) { printfOpMap[op.getClock()].push_back(op); }); + // Collect simulation operations grouped by their clock. + theModule.walk([&](Operation *op) { + if (auto printOp = dyn_cast(op)) { + simOpMap[printOp.getClock()].push_back(op); + return; + } + if (auto fflushOp = dyn_cast(op)) + simOpMap[fflushOp.getClock()].push_back(op); + }); // Create a hw::TriggeredOp for each clock - for (auto &[clock, printOps] : printfOpMap) - if (failed(proceduralizePrintOps(clock, printOps))) { + for (auto &[clock, ops] : simOpMap) + if (failed(proceduralizeOps(clock, ops))) { signalPassFailure(); return; } cleanup(); + + mlir::IRRewriter rewriter(theModule); + (void)mlir::runRegionDCE(rewriter, theModule->getRegions()); } diff --git a/lib/Firtool/CMakeLists.txt b/lib/Firtool/CMakeLists.txt index 25687341b5bb..1d2629299f2a 100644 --- a/lib/Firtool/CMakeLists.txt +++ b/lib/Firtool/CMakeLists.txt @@ -12,6 +12,7 @@ add_circt_library(CIRCTFirtool CIRCTSeqToSV CIRCTSimToSV CIRCTSeqTransforms + CIRCTSimTransforms CIRCTSVTransforms CIRCTTransforms CIRCTVerifToSV diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index 10dc119379c6..7a447bcbd5fe 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -14,6 +14,7 @@ #include "circt/Dialect/OM/OMPasses.h" #include "circt/Dialect/SV/SVPasses.h" #include "circt/Dialect/Seq/SeqPasses.h" +#include "circt/Dialect/Sim/SimPasses.h" #include "circt/Dialect/Verif/VerifPasses.h" #include "circt/Support/Passes.h" #include "circt/Transforms/Passes.h" @@ -334,6 +335,7 @@ LogicalResult firtool::populateHWToSV(mlir::PassManager &pm, verif::createLowerSymbolicValuesPass({opt.getSymbolicValueLowering()})); pm.addPass(seq::createExternalizeClockGate(opt.getClockGateOptions())); + pm.nest().addPass(circt::sim::createProceduralizeSim()); pm.addPass(circt::createLowerSimToSVPass()); pm.addPass(circt::createLowerSeqToSVPass( {/*disableRegRandomization=*/!opt.isRandomEnabled( diff --git a/test/Conversion/SimToSV/print-io.mlir b/test/Conversion/SimToSV/print-io.mlir new file mode 100644 index 000000000000..ac5c50848872 --- /dev/null +++ b/test/Conversion/SimToSV/print-io.mlir @@ -0,0 +1,33 @@ +// RUN: circt-opt --sim-proceduralize --lower-hw-to-sv --lower-sim-to-sv %s | FileCheck %s + +sv.macro.decl @SYNTHESIS +sv.macro.decl @PRINTF_COND_ +sv.func private @"__circt_lib_logging::FileDescriptor::get"(in %name : !hw.string, out fd : i32 {sv.func.explicitly_returned}) attributes {verilogName = "__circt_lib_logging::FileDescriptor::get"} + +// CHECK-LABEL: hw.module @print_io +// CHECK: [[CLK:%.+]] = seq.from_clock %clk +// CHECK-NEXT: sv.always posedge [[CLK]] { +// CHECK-NEXT: %[[TIME:.*]] = sv.system.time : i64 +// CHECK-NEXT: %[[NAME:.*]] = sv.sformatf "out_%0d.log"(%val) : i8 +// CHECK-NEXT: %[[FD:.*]] = sv.func.call.procedural @"__circt_lib_logging::FileDescriptor::get"(%[[NAME]]) : (!hw.string) -> i32 +// CHECK-NEXT: sv.if %cond { +// CHECK: sv.fwrite %{{.+}}, "%0t"(%[[TIME]]) : i64 +// CHECK: sv.fwrite %{{.+}}, "v=%x\0A"(%val) : i8 +// CHECK: sv.fwrite %[[FD]], "v=%x\0A"(%val) : i8 +// CHECK-NEXT: } +// CHECK-NEXT: } +hw.module @print_io(in %clk: !seq.clock, in %cond: i1, in %val: i8) { + %lit = sim.fmt.literal "v=" + %hex = sim.fmt.hex %val, isUpper false : i8 + %nl = sim.fmt.literal "\0A" + %msg = sim.fmt.concat (%lit, %hex, %nl) + %dec = sim.fmt.dec %val signed : i8 + %t = sim.time + %tfmt = sim.fmt.dec %t : i64 + sim.print %tfmt on %clk if %cond {usePrintfCond = true} + %tmsg = sim.fmt.concat (%dec, %nl) + sim.print %msg on %clk if %cond {usePrintfCond = true} + + %fd = sim.get_file "out_%0d.log"(%val) : (i8) -> i32 + sim.print %msg to %fd on %clk if %cond +} diff --git a/test/Conversion/SimToSV/scf-if.mlir b/test/Conversion/SimToSV/scf-if.mlir new file mode 100644 index 000000000000..9406e6577a7c --- /dev/null +++ b/test/Conversion/SimToSV/scf-if.mlir @@ -0,0 +1,17 @@ +// RUN: circt-opt --lower-sim-to-sv %s | FileCheck %s + +// CHECK-LABEL: hw.module @proc_if +// CHECK: sv.alwayscomb { +// CHECK-NEXT: sv.if %cond { +// CHECK: sv.fwrite %{{.+}}, "hello" +// CHECK-NEXT: } +// CHECK-NEXT: } +hw.module @proc_if(in %cond : i1) { + %msg = sim.fmt.literal "hello" + sv.alwayscomb { + scf.if %cond { + sim.proc.print %msg + } + } + hw.output +} diff --git a/test/Dialect/Sim/proceduralize-sim.mlir b/test/Dialect/Sim/proceduralize-sim.mlir index 2e46bf55222d..e1c43fb96911 100644 --- a/test/Dialect/Sim/proceduralize-sim.mlir +++ b/test/Dialect/Sim/proceduralize-sim.mlir @@ -262,3 +262,41 @@ hw.module @condition_as_val(in %clk: !seq.clock, in %condval: i1) { %bin = sim.fmt.bin %condval specifierWidth 1 : i1 sim.print %bin on %clk if %condval } + +// CHECK-LABEL: @print_time +// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk +// CHECK-NEXT: hw.triggered posedge %[[TRG]] { +// CHECK-NEXT: %[[TIME:.*]] = sim.time +// CHECK-NEXT: %[[FMT:.*]] = sim.fmt.dec %[[TIME]] : i64 +// CHECK-NEXT: sim.proc.print %[[FMT]] +// CHECK-NEXT: } + +hw.module @print_time(in %clk : !seq.clock) { + %true = hw.constant true + %time = sim.time + %fmt = sim.fmt.dec %time : i64 + sim.print %fmt on %clk if %true +} + +// CHECK-LABEL: @print_and_flush_file +// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk +// CHECK-NEXT: hw.triggered posedge %[[TRG]](%[[CARG:.*]], %[[VARG:.*]]) : i1, i8 { +// CHECK-NEXT: ^bb0(%[[COND:.*]]: i1, %[[VAL:.*]]: i8): +// CHECK-DAG: %[[LIT:.*]] = sim.fmt.literal "v=" +// CHECK-DAG: %[[NAME:.*]] = sim.get_file "out_%0d.log"(%[[VAL]]) : (i8) -> i32 +// CHECK-DAG: %[[HEX:.*]] = sim.fmt.hex %[[VAL]], isUpper false : i8 +// CHECK-DAG: %[[MSG:.*]] = sim.fmt.concat (%[[LIT]], %[[HEX]]) +// CHECK: scf.if %[[COND]] { +// CHECK-NEXT: sim.proc.print %[[MSG]] to %[[NAME]] +// CHECK-NEXT: sim.proc.fflush %[[NAME]] +// CHECK-NEXT: } +// CHECK-NEXT: } + +hw.module @print_and_flush_file(in %clk : !seq.clock, in %cond : i1, in %val : i8) { + %lit = sim.fmt.literal "v=" + %hex = sim.fmt.hex %val, isUpper false : i8 + %msg = sim.fmt.concat (%lit, %hex) + %fd = sim.get_file "out_%0d.log"(%val) : (i8) -> i32 + sim.print %msg to %fd on %clk if %cond + sim.fflush %fd on %clk if %cond +} diff --git a/test/Dialect/Sim/round-trip.mlir b/test/Dialect/Sim/round-trip.mlir index 65b34115dd89..37c975f31dc2 100644 --- a/test/Dialect/Sim/round-trip.mlir +++ b/test/Dialect/Sim/round-trip.mlir @@ -84,6 +84,8 @@ func.func @FormatStrings() { sim.fmt.hier_path // CHECK: sim.fmt.hier_path escaped sim.fmt.hier_path escaped + // CHECK: sim.time + %t = sim.time return } From 2f1d03e11df911702fb7ce14cd846470175cc5a2 Mon Sep 17 00:00:00 2001 From: woshiren Date: Wed, 8 Apr 2026 13:30:22 +0800 Subject: [PATCH 2/2] [FIRRTLToHW] Lower FIRRTL prints to Sim --- lib/Conversion/FIRRTLToHW/LowerToHW.cpp | 342 +++++++++++++----- lib/Firtool/CMakeLists.txt | 1 + lib/Firtool/Firtool.cpp | 3 + .../FIRRTLToHW/lower-to-hw-module.mlir | 11 +- test/Conversion/FIRRTLToHW/lower-to-hw.mlir | 85 +---- test/Dialect/Sim/proceduralize-sim.mlir | 21 ++ test/firtool/lower-layers.fir | 10 +- test/firtool/print.fir | 49 +-- 8 files changed, 347 insertions(+), 175 deletions(-) diff --git a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index 6133e7ed445e..aa969dbd3e29 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -182,8 +182,8 @@ class FileDescriptorInfo { FileDescriptorInfo(StringAttr outputFileName, mlir::ValueRange substitutions) : outputFileFormat(outputFileName), substitutions(substitutions) { assert(outputFileName || - substitutions.empty() && - "substitutions must be empty when output file name is empty"); + (substitutions.empty() && + "substitutions must be empty when output file name is empty")); } FileDescriptorInfo() = default; @@ -2079,15 +2079,20 @@ struct FIRRTLLowering : public FIRRTLVisitor { LogicalResult visitStmt(ForceOp op); std::optional getLoweredFmtOperand(Value operand); + std::optional getLoweredFmtOperandLegacy(Value operand); + FailureOr lowerSimFormatString(StringRef formatString, + ValueRange substitutions); LogicalResult loweredFmtOperands(ValueRange operands, SmallVectorImpl &loweredOperands); + LogicalResult + loweredFmtOperandsLegacy(ValueRange operands, + SmallVectorImpl &loweredOperands); FailureOr callFileDescriptorLib(const FileDescriptorInfo &info); - // Lower statemens that use file descriptors such as printf, fprintf and - // fflush. `fn` is a function that takes a file descriptor and build an always - // and if-procedural block. + // Lower statements that use file descriptors such as printf, fprintf and + // fflush. `fn` takes a file descriptor and effective condition. LogicalResult lowerStatementWithFd( const FileDescriptorInfo &fileDescriptorInfo, Value clock, Value cond, - const std::function &fn, bool usePrintfCond); + const std::function &fn, bool usePrintfCond); // Lower a printf-like operation. `fileDescriptorInfo` is a pair of the // file name and whether it requires format string substitution. template @@ -2153,9 +2158,6 @@ struct FIRRTLLowering : public FIRRTLVisitor { /// `getReadValue(v)`. DenseMap readInOutCreated; - /// This keeps track of the file descriptors for each file name. - DenseMap fileNameToFileDescriptor; - // We auto-unique graph-level blocks to reduce the amount of generated // code and ensure that side effects are properly ordered in FIRRTL. using AlwaysKeyType = std::tuple FIRRTLLowering::getLoweredFmtOperand(Value operand) { + // Handle special substitutions. + if (type_isa(operand.getType())) { + if (isa(operand.getDefiningOp())) + return sim::TimeOp::create(builder); + if (isa(operand.getDefiningOp())) + return {nullptr}; + } + + auto loweredValue = getLoweredValue(operand); + if (!loweredValue) { + // If this is a zero bit operand, just pass a one bit zero. + if (!isZeroBitFIRRTLType(operand.getType())) + return {}; + loweredValue = getOrCreateIntConstant(1, 0); + } + + return loweredValue; +} + +/// Reserved for verification path /// Zero bit operands are rewritten as one bit zeros and signed integers are /// wrapped in $signed(). -std::optional FIRRTLLowering::getLoweredFmtOperand(Value operand) { +std::optional FIRRTLLowering::getLoweredFmtOperandLegacy(Value operand) { // Handle special substitutions. if (type_isa(operand.getType())) { if (isa(operand.getDefiningOp())) - return sv::TimeOp::create(builder); + return sim::TimeOp::create(builder); if (isa(operand.getDefiningOp())) return {nullptr}; } @@ -2829,49 +2851,219 @@ FIRRTLLowering::loweredFmtOperands(mlir::ValueRange operands, return success(); } +// Reserved for verification path +LogicalResult FIRRTLLowering::loweredFmtOperandsLegacy( + mlir::ValueRange operands, SmallVectorImpl &loweredOperands) { + for (auto operand : operands) { + std::optional loweredValue = getLoweredFmtOperandLegacy(operand); + if (!loweredValue) + return failure(); + // Skip if the lowered value is null. + if (*loweredValue) + loweredOperands.push_back(*loweredValue); + } + return success(); +} + +FailureOr +FIRRTLLowering::lowerSimFormatString(StringRef formatString, + ValueRange substitutions) { + SmallVector fragments; + size_t subIdx = 0; + auto getWidthAttr = [&](StringRef width) -> IntegerAttr { + if (width.empty()) + return {}; + unsigned parsedWidth = 0; + if (width.getAsInteger(10, parsedWidth)) + return {}; + return builder.getI32IntegerAttr(parsedWidth); + }; + + auto appendLiteral = [&](StringRef text) { + if (text.empty()) + return; + fragments.push_back(sim::FormatLiteralOp::create( + builder, builder.getLoc(), builder.getStringAttr(text))); + }; + + SmallString<32> pendingLiteral; + for (size_t i = 0, e = formatString.size(); i != e; ++i) { + char c = formatString[i]; + if (c == '%') { + if (i + 1 >= e) { + pendingLiteral.push_back(c); + continue; + } + + // Flush pending literal before formatting fragment. + appendLiteral(pendingLiteral); + pendingLiteral.clear(); + + SmallString<6> width; + c = formatString[++i]; + while (isdigit(c)) { + width.push_back(c); + if (i + 1 >= e) + break; + c = formatString[++i]; + } + + if (c == '%') { + pendingLiteral.push_back('%'); + continue; + } + + if (c == 'm') { + UnitAttr useEscapes; + fragments.push_back(sim::FormatHierPathOp::create( + builder, builder.getLoc(), useEscapes)); + continue; + } + if (c == 'M') { + fragments.push_back(sim::FormatHierPathOp::create( + builder, builder.getLoc(), builder.getUnitAttr())); + continue; + } + + if (subIdx >= substitutions.size()) + return emitError(builder.getLoc(), + "format string has too few operands"); + + auto loweredValue = getLoweredFmtOperand(substitutions[subIdx++]); + if (!loweredValue || !*loweredValue) + return failure(); + + auto widthAttr = getWidthAttr(width); + switch (c) { + case 'b': + fragments.push_back(sim::FormatBinOp::create( + builder, *loweredValue, + /*isLeftAligned=*/builder.getBoolAttr(false), + /*paddingChar=*/builder.getI8IntegerAttr(48), widthAttr)); + break; + case 'd': { + UnitAttr isSigned; + if (auto intTy = dyn_cast(substitutions[subIdx - 1].getType())) + if (intTy.isSigned()) + isSigned = builder.getUnitAttr(); + fragments.push_back(sim::FormatDecOp::create( + builder, *loweredValue, + /*isLeftAligned=*/builder.getBoolAttr(false), + /*paddingChar=*/builder.getI8IntegerAttr(32), widthAttr, isSigned)); + break; + } + case 't': { + fragments.push_back(sim::FormatDecOp::create( + builder, *loweredValue, + /*isLeftAligned=*/builder.getBoolAttr(false), + /*paddingChar=*/builder.getI8IntegerAttr(32), widthAttr, + /*isSigned=*/nullptr)); + break; + } + case 'x': + case 'X': + fragments.push_back(sim::FormatHexOp::create( + builder, *loweredValue, + /*isHexUppercase=*/builder.getBoolAttr(c == 'X'), + /*isLeftAligned=*/builder.getBoolAttr(false), + /*paddingChar=*/builder.getI8IntegerAttr(48), widthAttr)); + break; + case 'c': + fragments.push_back(sim::FormatCharOp::create(builder, *loweredValue)); + break; + default: + return emitError(builder.getLoc(), "unsupported format specifier '%") + << c << "'"; + } + continue; + } + + if (c == '{' && i + 3 < e && formatString.slice(i, i + 4) == "{{}}") { + appendLiteral(pendingLiteral); + pendingLiteral.clear(); + + if (subIdx >= substitutions.size()) + return emitError(builder.getLoc(), + "format string has too few substitutions"); + + auto substitution = substitutions[subIdx++]; + if (!type_isa(substitution.getType())) + return emitError(builder.getLoc(), + "expected fstring operand for '{{}}' substitution"); + + auto result = + TypeSwitch(substitution.getDefiningOp()) + .Case([&](auto) { + auto time = sim::TimeOp::create(builder); + IntegerAttr specifierWidth; + UnitAttr isSigned; + fragments.push_back(sim::FormatDecOp::create( + builder, time, + /*isLeftAligned=*/builder.getBoolAttr(false), + /*paddingChar=*/builder.getI8IntegerAttr(32), + specifierWidth, isSigned)); + return success(); + }) + .Case([&](auto) { + UnitAttr useEscapes; + fragments.push_back(sim::FormatHierPathOp::create( + builder, builder.getLoc(), useEscapes)); + return success(); + }) + .Default([&](auto) { + emitError(builder.getLoc(), + "has a substitution with an unimplemented lowering") + .attachNote(substitution.getLoc()) + << "op with an unimplemented lowering is here"; + return failure(); + }); + if (failed(result)) + return failure(); + i += 3; + continue; + } + + pendingLiteral.push_back(c); + } + + appendLiteral(pendingLiteral); + + if (subIdx != substitutions.size()) + return emitError(builder.getLoc(), + "format string did not consume all operands"); + + if (fragments.empty()) + return sim::FormatLiteralOp::create(builder, builder.getLoc(), + builder.getStringAttr("")) + .getResult(); + if (fragments.size() == 1) + return fragments.front(); + return sim::FormatStringConcatOp::create(builder, fragments).getResult(); +} + LogicalResult FIRRTLLowering::lowerStatementWithFd( const FileDescriptorInfo &fileDescriptor, Value clock, Value cond, - const std::function &fn, bool usePrintfCond) { - // Emit an "#ifndef SYNTHESIS" guard into the always block. - bool failed = false; + const std::function &fn, bool usePrintfCond) { circuitState.addMacroDecl(builder.getStringAttr("SYNTHESIS")); - addToIfDefBlock("SYNTHESIS", std::function(), [&]() { - addToAlwaysBlock(clock, [&]() { - // TODO: This is not printf specific anymore. Replace "Printf" with "FD" - // or similar but be aware that changing macro name breaks existing uses. - circuitState.usedPrintf = true; - if (usePrintfCond) - circuitState.addFragment(theModule, "PRINTF_COND_FRAGMENT"); - - // Emit an "sv.if '`PRINTF_COND_ & cond' into the #ifndef. - Value ifCond = cond; - if (usePrintfCond) { - ifCond = - sv::MacroRefExprOp::create(builder, cond.getType(), "PRINTF_COND_"); - ifCond = builder.createOrFold(ifCond, cond, true); - } - addIfProceduralBlock(ifCond, [&]() { - // `fd`represents a file decriptor. Use the stdout or the one opened - // using $fopen. - Value fd; - if (fileDescriptor.isDefaultFd()) { - // Emit the sv.fwrite, writing to stderr by default. - fd = hw::ConstantOp::create(builder, APInt(32, 0x80000002)); - } else { - // Call the library function to get the FD. - auto fdOrError = callFileDescriptorLib(fileDescriptor); - if (llvm::failed(fdOrError)) { - failed = true; - return; - } - fd = *fdOrError; - } - failed = llvm::failed(fn(fd)); - }); - }); - }); - return failure(failed); + // TODO: This is not printf specific anymore. Replace "Printf" with "FD" + // or similar but be aware that changing macro name breaks existing uses. + circuitState.usedPrintf = true; + if (usePrintfCond) + circuitState.addFragment(theModule, "PRINTF_COND_FRAGMENT"); + + Value fd; + if (fileDescriptor.isDefaultFd()) { + // Default stream remains stderr. + fd = hw::ConstantOp::create(builder, APInt(32, 0x80000002)); + } else { + auto fdOrError = callFileDescriptorLib(fileDescriptor); + if (failed(fdOrError)) + return failure(); + fd = *fdOrError; + } + + return fn(fd, cond); } FailureOr @@ -2879,26 +3071,14 @@ FIRRTLLowering::callFileDescriptorLib(const FileDescriptorInfo &info) { circuitState.usedFileDescriptorLib = true; circuitState.addFragment(theModule, "CIRCT_LIB_LOGGING_FRAGMENT"); - Value fileName; + SmallVector fileNameOperands; if (info.isSubstitutionRequired()) { - SmallVector fileNameOperands; if (failed(loweredFmtOperands(info.getSubstitutions(), fileNameOperands))) return failure(); - - fileName = sv::SFormatFOp::create(builder, info.getOutputFileFormat(), - fileNameOperands) - .getResult(); - } else { - // If substitution is not required, just use the output file name. - fileName = sv::ConstantStrOp::create(builder, info.getOutputFileFormat()) - .getResult(); } - - return sv::FuncCallProceduralOp::create( - builder, mlir::TypeRange{builder.getIntegerType(32)}, - builder.getStringAttr("__circt_lib_logging::FileDescriptor::get"), - ValueRange{fileName}) - ->getResult(0); + return sim::GetFileOp::create(builder, builder.getLoc(), + info.getOutputFileFormat(), fileNameOperands) + .getDescriptor(); } /// Set the lowered value of 'orig' to 'result', remembering this in a map. @@ -5275,21 +5455,21 @@ static LogicalResult resolveFormatString(Location loc, template LogicalResult FIRRTLLowering::visitPrintfLike( T op, const FileDescriptorInfo &fileDescriptorInfo, bool usePrintfCond) { - auto clock = getLoweredNonClockValue(op.getClock()); + auto clock = getLoweredValue(op.getClock()); auto cond = getLoweredValue(op.getCond()); if (!clock || !cond) return failure(); - StringAttr formatString; - if (failed(resolveFormatString(op.getLoc(), op.getFormatString(), - op.getSubstitutions(), formatString))) + auto formatString = + lowerSimFormatString(op.getFormatString(), op.getSubstitutions()); + if (failed(formatString)) return failure(); - auto fn = [&](Value fd) { - SmallVector operands; - if (failed(loweredFmtOperands(op.getSubstitutions(), operands))) - return failure(); - sv::FWriteOp::create(builder, op.getLoc(), fd, formatString, operands); + auto fn = [&](Value fd, Value effectiveCond) { + auto printOp = sim::PrintFormattedOp::create(builder, *formatString, fd, + clock, effectiveCond); + if (usePrintfCond) + printOp.setUsePrintfCondAttr(builder.getBoolAttr(true)); return success(); }; @@ -5311,13 +5491,13 @@ LogicalResult FIRRTLLowering::visitStmt(FPrintFOp op) { // FFlush lowers into $fflush statement. LogicalResult FIRRTLLowering::visitStmt(FFlushOp op) { - auto clock = getLoweredNonClockValue(op.getClock()); + auto clock = getLoweredValue(op.getClock()); auto cond = getLoweredValue(op.getCond()); if (!clock || !cond) return failure(); - auto fn = [&](Value fd) { - sv::FFlushOp::create(builder, op.getLoc(), fd); + auto fn = [&](Value fd, Value effectiveCond) { + sim::FFlushOp::create(builder, fd, clock, effectiveCond); return success(); }; @@ -5465,7 +5645,7 @@ LogicalResult FIRRTLLowering::lowerVerificationStatement( opOperands, message))) return failure(); - if (failed(loweredFmtOperands(opOperands, messageOps))) + if (failed(loweredFmtOperandsLegacy(opOperands, messageOps))) return failure(); if (flavor == VerificationFlavor::SVA) { @@ -5537,7 +5717,7 @@ LogicalResult FIRRTLLowering::lowerVerificationStatement( } // Handle the regular SVA case. - sv::EventControl event; + sv::EventControl event = circt::sv::EventControl::AtPosEdge; switch (opEventControl) { case EventControl::AtPosEdge: event = circt::sv::EventControl::AtPosEdge; diff --git a/lib/Firtool/CMakeLists.txt b/lib/Firtool/CMakeLists.txt index 1d2629299f2a..165beb1d12a2 100644 --- a/lib/Firtool/CMakeLists.txt +++ b/lib/Firtool/CMakeLists.txt @@ -6,6 +6,7 @@ add_circt_library(CIRCTFirtool CIRCTFIRRTLToHW CIRCTFIRRTLTransforms CIRCTHWToBTOR2 + CIRCTHWToSV CIRCTHWTransforms CIRCTLTLToCore CIRCTOMTransforms diff --git a/lib/Firtool/Firtool.cpp b/lib/Firtool/Firtool.cpp index 7a447bcbd5fe..19362c23b795 100644 --- a/lib/Firtool/Firtool.cpp +++ b/lib/Firtool/Firtool.cpp @@ -7,9 +7,11 @@ //===----------------------------------------------------------------------===// #include "circt/Firtool/Firtool.h" +#include "circt/Conversion/HWToSV.h" #include "circt/Conversion/Passes.h" #include "circt/Dialect/FIRRTL/FIRRTLOps.h" #include "circt/Dialect/FIRRTL/Passes.h" +#include "circt/Dialect/HW/HWOps.h" #include "circt/Dialect/HW/HWPasses.h" #include "circt/Dialect/OM/OMPasses.h" #include "circt/Dialect/SV/SVPasses.h" @@ -336,6 +338,7 @@ LogicalResult firtool::populateHWToSV(mlir::PassManager &pm, pm.addPass(seq::createExternalizeClockGate(opt.getClockGateOptions())); pm.nest().addPass(circt::sim::createProceduralizeSim()); + pm.nest().addPass(circt::createLowerHWToSVPass()); pm.addPass(circt::createLowerSimToSVPass()); pm.addPass(circt::createLowerSeqToSVPass( {/*disableRegRandomization=*/!opt.isRandomEnabled( diff --git a/test/Conversion/FIRRTLToHW/lower-to-hw-module.mlir b/test/Conversion/FIRRTLToHW/lower-to-hw-module.mlir index eb519b0d2d26..1d96b437554d 100644 --- a/test/Conversion/FIRRTLToHW/lower-to-hw-module.mlir +++ b/test/Conversion/FIRRTLToHW/lower-to-hw-module.mlir @@ -50,7 +50,6 @@ firrtl.circuit "Simple" { firrtl.module private @TestInstance(in %u2: !firrtl.uint<2>, in %s8: !firrtl.sint<8>, in %clock: !firrtl.clock, in %reset: !firrtl.uint<1>) { - // CHECK-NEXT: [[CLK:%.+]] = seq.from_clock %clock // CHECK-NEXT: %c0_i2 = hw.constant // CHECK-NEXT: %xyz.out4 = hw.instance "xyz" @Simple(in1: [[ARG1:%.+]]: i4, in2: %u2: i2, in3: %s8: i8) -> (out4: i4) %xyz:4 = firrtl.instance xyz @Simple(in in1: !firrtl.uint<4>, in in2: !firrtl.uint<2>, in in3: !firrtl.sint<8>, out out4: !firrtl.uint<4>) @@ -68,13 +67,17 @@ firrtl.circuit "Simple" { // Parameterized module reference. // hw.instance carries the parameters, unlike at the FIRRTL layer. + // CHECK: %{{.+}} = sim.fmt.hex %xyz.out4, isUpper false : i4 + // CHECK: %{{.+}} = hw.constant -2147483646 : i32 + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset {usePrintfCond = true} // CHECK: %myext.out = hw.instance "myext" @MyParameterizedExtModule(in: %reset: i1) -> (out: i8) %myext:2 = firrtl.instance myext @MyParameterizedExtModule(in in: !firrtl.uint<1>, out out: !firrtl.uint<8>) - // CHECK: %[[STDERR:.+]] = hw.constant -2147483646 : i32 - // CHECK: sv.fwrite %[[STDERR]], "%x"(%xyz.out4) : i4 - // CHECK: sv.fwrite %[[STDERR]], "Something interesting! %x"(%myext.out) : i8 + // CHECK: %{{.+}} = sim.fmt.literal "Something interesting! " + // CHECK: %{{.+}} = sim.fmt.hex %myext.out, isUpper false : i8 + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset {usePrintfCond = true} + // CHECK-NOT: sv.fwrite firrtl.connect %myext#0, %reset : !firrtl.uint<1>, !firrtl.uint<1> firrtl.printf %clock, %reset, "Something interesting! %x"(%myext#1) : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<8> diff --git a/test/Conversion/FIRRTLToHW/lower-to-hw.mlir b/test/Conversion/FIRRTLToHW/lower-to-hw.mlir index 22a3955e665b..d91af95406f5 100644 --- a/test/Conversion/FIRRTLToHW/lower-to-hw.mlir +++ b/test/Conversion/FIRRTLToHW/lower-to-hw.mlir @@ -357,70 +357,25 @@ firrtl.circuit "Simple" attributes {annotations = [{class = firrtl.module private @Print(in %clock: !firrtl.clock, in %reset: !firrtl.uint<1>, in %a: !firrtl.uint<4>, in %b: !firrtl.uint<4>, in %c: !firrtl.sint<4>, in %d: !firrtl.sint<4>) { - // CHECK: [[CLOCK:%.+]] = seq.from_clock %clock - // CHECK: [[ADD:%.+]] = comb.add + // CHECK: sim.fmt.literal "No operands and literal: " + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset {usePrintfCond = true} + // CHECK: sim.fmt.literal "Binary: " + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset {usePrintfCond = true} + // CHECK: sim.fmt.literal "Decimal: " + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset {usePrintfCond = true} + // CHECK: sim.fmt.literal "Hexadecimal: " + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset {usePrintfCond = true} + // CHECK: sim.fmt.literal "ASCII Character: " + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset {usePrintfCond = true} + // CHECK: sim.fmt.literal "Hi signed " + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset {usePrintfCond = true} + // CHECK: %{{.+}} = sim.time + // CHECK: %{{.+}} = sim.get_file "%0t%d.txt"(%{{.+}}, %a) : (i64, i4) -> i32 + // CHECK: sim.print %{{.+}} to %{{.+}} on %clock if %reset + // CHECK: sim.fflush %{{.+}} on %clock if %reset + // CHECK-NOT: sv.fwrite + // CHECK-NOT: sv.fflush - // CHECK: [[ADDSIGNED:%.+]] = comb.add - - // CHECK: sv.ifdef @SYNTHESIS { - // CHECK-NEXT: } else { - // CHECK-NEXT: sv.always posedge [[CLOCK]] { - // CHECK-NEXT: %[[PRINTF_COND:.+]] = sv.macro.ref.expr @PRINTF_COND_() : () -> i1 - // CHECK-NEXT: [[AND:%.+]] = comb.and bin %[[PRINTF_COND]], %reset - // CHECK-NEXT: sv.if [[AND]] { - // CHECK-NEXT: %[[STDERR:.+]] = hw.constant -2147483646 : i32 - // CHECK-NEXT: sv.fwrite %[[STDERR]], "No operands and literal: %%\0A" - // CHECK-NEXT: } - // CHECK-NEXT: %[[PRINTF_COND_:.+]] = sv.macro.ref.expr @PRINTF_COND_() : () -> i1 - // CHECK-NEXT: [[AND:%.+]] = comb.and bin %[[PRINTF_COND_]], %reset : i1 - // CHECK-NEXT: sv.if [[AND]] { - // CHECK-NEXT: %[[STDERR:.+]] = hw.constant -2147483646 : i32 - // CHECK-NEXT: sv.fwrite %[[STDERR]], "Binary: %b %0b %4b\0A"([[ADD]], %b, [[ADD]]) : i5, i4, i5 - // CHECK-NEXT: } - // CHECK-NEXT: %[[PRINTF_COND_:.+]] = sv.macro.ref.expr @PRINTF_COND_() : () -> i1 - // CHECK-NEXT: [[AND:%.+]] = comb.and bin %[[PRINTF_COND_]], %reset : i1 - // CHECK-NEXT: sv.if [[AND]] { - // CHECK-NEXT: %[[STDERR:.+]] = hw.constant -2147483646 : i32 - // CHECK-NEXT: sv.fwrite %[[STDERR]], "Decimal: %d %0d %4d\0A"([[ADD]], %b, [[ADD]]) : i5, i4, i5 - // CHECK-NEXT: } - // CHECK-NEXT: %[[PRINTF_COND_:.+]] = sv.macro.ref.expr @PRINTF_COND_() : () -> i1 - // CHECK-NEXT: [[AND:%.+]] = comb.and bin %[[PRINTF_COND_]], %reset : i1 - // CHECK-NEXT: sv.if [[AND]] { - // CHECK-NEXT: %[[STDERR:.+]] = hw.constant -2147483646 : i32 - // CHECK-NEXT: sv.fwrite %[[STDERR]], "Hexadecimal: %x %0x %4x\0A"([[ADD]], %b, [[ADD]]) : i5, i4, i5 - // CHECK-NEXT: } - // CHECK-NEXT: %[[PRINTF_COND_:.+]] = sv.macro.ref.expr @PRINTF_COND_() : () -> i1 - // CHECK-NEXT: [[AND:%.+]] = comb.and bin %[[PRINTF_COND_]], %reset : i1 - // CHECK-NEXT: sv.if [[AND]] { - // CHECK-NEXT: %[[STDERR:.+]] = hw.constant -2147483646 : i32 - // CHECK-NEXT: sv.fwrite %[[STDERR]], "ASCII Character: %c\0A"([[ADD]]) : i5 - // CHECK-NEXT: } - // CHECK-NEXT: %[[PRINTF_COND_:.+]] = sv.macro.ref.expr @PRINTF_COND_() : () -> i1 - // CHECK-NEXT: [[AND:%.+]] = comb.and bin %[[PRINTF_COND_]], %reset : i1 - // CHECK-NEXT: sv.if [[AND]] { - // CHECK-NEXT: %[[STDERR:.+]] = hw.constant -2147483646 : i32 - // CHECK-NEXT: [[SUMSIGNED:%.+]] = sv.system "signed"([[ADDSIGNED]]) - // CHECK-NEXT: [[DSIGNED:%.+]] = sv.system "signed"(%d) - // CHECK-NEXT: sv.fwrite %[[STDERR]], "Hi signed %d %d\0A"([[SUMSIGNED]], [[DSIGNED]]) : i5, i4 - // CHECK-NEXT: } - // CHECK-NEXT: %[[PRINTF_COND_:.+]] = sv.macro.ref.expr @PRINTF_COND_() : () -> i1 - // CHECK-NEXT: [[AND:%.+]] = comb.and bin %[[PRINTF_COND_]], %reset : i1 - // CHECK-NEXT: sv.if [[AND]] { - // CHECK-NEXT: %[[STDERR:.+]] = hw.constant -2147483646 : i32 - // CHECK-NEXT: [[TIME:%.+]] = sv.system.time : i64 - // CHECK-NEXT: sv.fwrite %[[STDERR]], "[%0t]: %d %m"([[TIME]], %a) : i64, i4 - // CHECK-NEXT: } - // CHECK-NEXT: sv.if %reset { - // CHECK-NEXT: [[TIME:%.+]] = sv.system.time : i64 - // CHECK-NEXT: [[STR:%.+]] = sv.sformatf "%0t%d.txt"([[TIME]], %a) : i64, i4 - // CHECK-NEXT: [[FD:%.+]] = sv.func.call.procedural @"__circt_lib_logging::FileDescriptor::get"([[STR]]) : (!hw.string) -> i32 - // CHECK-NEXT: [[TIME:%.+]] = sv.system.time : i64 - // CHECK-NEXT: sv.fwrite [[FD]], "[%0t]: dynamic file name\0A"([[TIME]]) : i64 - // CHECK-NEXT: [[TIME:%.+]] = sv.system.time : i64 - // CHECK-NEXT: [[STR:%.+]] = sv.sformatf "%0t%d.txt"([[TIME]], %a) : i64, i4 - // CHECK-NEXT: [[FD:%.+]] = sv.func.call.procedural @"__circt_lib_logging::FileDescriptor::get"([[STR]]) : (!hw.string) -> i32 - // CHECK-NEXT: sv.fflush fd [[FD]] - // CHECK-NEXT: } firrtl.printf %clock, %reset, "No operands and literal: %%\0A" : !firrtl.clock, !firrtl.uint<1> %0 = firrtl.add %a, %a : (!firrtl.uint<4>, !firrtl.uint<4>) -> !firrtl.uint<5> @@ -694,7 +649,7 @@ firrtl.circuit "Simple" attributes {annotations = [{class = in %i0: !firrtl.uint<0> ) { // Test special substitutions - // CHECK: %[[TIME:.+]] = sv.system.time + // CHECK: %[[TIME:.+]] = sim.time // CHECK-NEXT: %[[TIME_SAMPLED:.+]] = sv.system.sampled %[[TIME]] // CHECK: sv.assert.concurrent posedge // CHECK-SAME: message "Time: %0t"(%[[TIME_SAMPLED]]) : i64 @@ -706,7 +661,7 @@ firrtl.circuit "Simple" attributes {annotations = [{class = firrtl.assume %clock, %cond, %enable, "Module: {{}}"(%hier) : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1>, !firrtl.fstring - // CHECK: %[[TIME:.+]] = sv.system.time + // CHECK: %[[TIME:.+]] = sim.time // CHECK: sv.if %ASSERT_VERBOSE_COND_ { // CHECK-NEXT: sv.error.procedural "In %m at %0t, value = %d"(%[[TIME]], %value) : i64, i42 %time2 = firrtl.fstring.time : !firrtl.fstring diff --git a/test/Dialect/Sim/proceduralize-sim.mlir b/test/Dialect/Sim/proceduralize-sim.mlir index e1c43fb96911..e2e7ac625dac 100644 --- a/test/Dialect/Sim/proceduralize-sim.mlir +++ b/test/Dialect/Sim/proceduralize-sim.mlir @@ -278,6 +278,27 @@ hw.module @print_time(in %clk : !seq.clock) { sim.print %fmt on %clk if %true } +// CHECK-LABEL: @print_hier_path +// CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk +// CHECK-NEXT: hw.triggered posedge %[[TRG]](%[[COND:.*]]) : i1 { +// CHECK-NEXT: ^bb0(%[[ARGCOND:.*]]: i1): +// CHECK-DAG: %[[LIT0:.*]] = sim.fmt.literal "[" +// CHECK-DAG: %[[PATH:.*]] = sim.fmt.hier_path +// CHECK-DAG: %[[LIT1:.*]] = sim.fmt.literal "]" +// CHECK-DAG: %[[CAT:.*]] = sim.fmt.concat (%[[LIT0]], %[[PATH]], %[[LIT1]]) +// CHECK: scf.if %[[ARGCOND]] { +// CHECK-NEXT: sim.proc.print %[[CAT]] +// CHECK-NEXT: } +// CHECK-NEXT: } + +hw.module @print_hier_path(in %clk : !seq.clock, in %cond : i1) { + %lb = sim.fmt.literal "[" + %hp = sim.fmt.hier_path + %rb = sim.fmt.literal "]" + %msg = sim.fmt.concat (%lb, %hp, %rb) + sim.print %msg on %clk if %cond +} + // CHECK-LABEL: @print_and_flush_file // CHECK-NEXT: %[[TRG:.*]] = seq.from_clock %clk // CHECK-NEXT: hw.triggered posedge %[[TRG]](%[[CARG:.*]], %[[VARG:.*]]) : i1, i8 { diff --git a/test/firtool/lower-layers.fir b/test/firtool/lower-layers.fir index e18a517bc661..f921cc169b4d 100644 --- a/test/firtool/lower-layers.fir +++ b/test/firtool/lower-layers.fir @@ -127,12 +127,14 @@ circuit TestHarness: define trace = x ; CHECK: module TestHarness_Verification() - ; CHECK: `ifndef SYNTHESIS ; CHECK: always @(posedge TestHarness.clock) begin - ; CHECK: if ((`PRINTF_COND_) & TestHarness.reset) - ; CHECK: $fwrite(32'h80000002, "The last PC was: %x", TestHarness.dut.verification.pc_d); + ; CHECK: if (TestHarness.reset) begin + ; CHECK: `ifndef SYNTHESIS + ; CHECK: if (`PRINTF_COND_) + ; CHECK: $fwrite(32'h80000002, "The last PC was: %x", TestHarness.dut.verification.pc_d); + ; CHECK: `endif // not def SYNTHESIS + ; CHECK: end ; CHECK: end // always @(posedge) - ; CHECK: `endif // not def SYNTHESIS ; CHECK: endmodule ; CHECK: module TestHarness( diff --git a/test/firtool/print.fir b/test/firtool/print.fir index 86c5d7ccd372..85dacc5379de 100644 --- a/test/firtool/print.fir +++ b/test/firtool/print.fir @@ -27,44 +27,51 @@ circuit PrintTest: input cond : UInt<1> input a : UInt<8> - ; CHECK: if ((`PRINTF_COND_) & cond) begin - ; CHECK: $fwrite(32'h80000002, "binary: %b %0b %8b\n", a, a, a); + ; CHECK: always @(posedge clock) begin + ; CHECK-NEXT: automatic logic [63:0] [[TIME_VAR:_.+]]; + ; CHECK-NEXT: [[TIME_VAR]] = $time; + ; CHECK-NEXT: [[FD_TEST:_.+]] = __circt_lib_logging::FileDescriptor::get("test.txt"); + ; CHECK-NEXT: [[FD_NAME:_.+]] = __circt_lib_logging::FileDescriptor::get($sformatf("%m%c.txt", + ; CHECK-NEXT: 8'h61)); + ; CHECK-NEXT: [[FD_DYN:_.+]] = __circt_lib_logging::FileDescriptor::get($sformatf("%0t%d.txt", + ; CHECK-NEXT: [[TIME_VAR]], + ; CHECK-NEXT: a)); + + ; CHECK: if (cond) begin + ; CHECK: `ifndef SYNTHESIS + ; CHECK-NEXT: if (`PRINTF_COND_) begin + ; CHECK: $fwrite(32'h80000002, "binary: %b %0b %8b\n", a, a, a); printf(clock, cond, "binary: %b %0b %8b\n", a, a, a) - ; CHECK-NEXT: $fwrite(32'h80000002, "decimal: %d %0d %3d\n", a, a, a); + ; CHECK-NEXT: $fwrite(32'h80000002, "decimal: %d %0d %3d\n", a, a, a); printf(clock, cond, "decimal: %d %0d %3d\n", a, a, a) - ; CHECK-NEXT: $fwrite(32'h80000002, "hexadecimal: %x %0x %2x\n", a, a, a); + ; CHECK-NEXT: $fwrite(32'h80000002, "hexadecimal: %x %0x %2x\n", a, a, a); printf(clock, cond, "hexadecimal: %x %0x %2x\n", a, a, a) - ; CHECK-NEXT: $fwrite(32'h80000002, "ASCII character: %c\n", a); + ; CHECK-NEXT: $fwrite(32'h80000002, "ASCII character: %c\n", a); printf(clock, cond, "ASCII character: %c\n", a) - ; CHECK-NEXT: $fwrite(32'h80000002, "literals: %%\n"); + ; CHECK-NEXT: $fwrite(32'h80000002, "literals: %%\n"); printf(clock, cond, "literals: %%\n") - ; CHECK-NEXT: $fwrite(32'h80000002, "[%0t]: %m\n", $time); - ; CHECK-NEXT: end + ; CHECK-NEXT: $fwrite(32'h80000002, "[%0t]: %m\n", [[TIME_VAR]]); + ; CHECK-NEXT: end printf(clock, cond, "[{{SimulationTime}}]: {{HierarchicalModuleName}}\n") - ; CHECK: if (cond) begin - ; CHECK-NEXT: ___circt_lib_logging3A3AFileDescriptor3A3Aget_0_1 = __circt_lib_logging::FileDescriptor::get("test.txt"); - ; CHECK-NEXT: $fwrite(___circt_lib_logging3A3AFileDescriptor3A3Aget_0_1, "hello"); + ; CHECK-NEXT: $fwrite([[FD_TEST]], "hello"); fprintf(clock, cond, "test.txt", "hello") - ; CHECK-NEXT: ___circt_lib_logging3A3AFileDescriptor3A3Aget_0_0 = __circt_lib_logging::FileDescriptor::get($sformatf("%m%c.txt", - ; CHECK-NEXT: 8'h61)); - ; CHECK-NEXT: $fwrite(___circt_lib_logging3A3AFileDescriptor3A3Aget_0_0, - ; CHECK-NEXT: "[%0t]: static file name (w/ substitution)\n", $time); + ; CHECK-NEXT: $fwrite([[FD_NAME]], + ; CHECK-NEXT: "[%0t]: static file name (w/ substitution)\n", [[TIME_VAR]]); node c = UInt<8>(97) fprintf(clock, cond, "{{HierarchicalModuleName}}%c.txt", c, "[{{SimulationTime}}]: static file name (w/ substitution)\n") - ; CHECK-NEXT: $fflush(32'h80000002); - ; CHECK-NEXT: ___circt_lib_logging3A3AFileDescriptor3A3Aget_0 = __circt_lib_logging::FileDescriptor::get($sformatf("%0t%d.txt", - ; CHECK-NEXT: $time, - ; CHECK-NEXT: a)); + ; CHECK-NEXT: $fflush(32'h80000002); + fflush(clock, cond) - ; CHECK-NEXT: $fflush(___circt_lib_logging3A3AFileDescriptor3A3Aget_0); - ; CHECK-NEXT: end + ; CHECK-NEXT: $fflush([[FD_DYN]]); + ; CHECK-NEXT: `endif // not def SYNTHESIS + ; CHECK-NEXT: end fflush(clock, cond, "{{SimulationTime}}%d.txt", a);