From 035ccca38ba435992c828337818a2e2df18c412f Mon Sep 17 00:00:00 2001 From: woshiren Date: Mon, 6 Apr 2026 22:51:41 +0800 Subject: [PATCH] [FIRRTLToHW] Lower FIRRTL prints to Sim and migrate SV lowering logic to SimToSV --- include/circt/Dialect/Sim/SimOps.td | 62 +++- lib/Conversion/FIRRTLToHW/LowerToHW.cpp | 295 +++++++++++----- lib/Conversion/MooreToCore/MooreToCore.cpp | 4 +- lib/Conversion/SimToSV/SimToSV.cpp | 319 +++++++++++++++++- lib/Dialect/Sim/SimOps.cpp | 18 +- .../Sim/Transforms/ProceduralizeSim.cpp | 6 +- .../FIRRTLToHW/lower-to-hw-module.mlir | 11 +- test/Conversion/FIRRTLToHW/lower-to-hw.mlir | 89 ++--- test/Conversion/SimToSV/print-io.mlir | 83 +++++ test/Dialect/Sim/round-trip.mlir | 5 + test/firtool/print.fir | 10 +- 11 files changed, 734 insertions(+), 168 deletions(-) create mode 100644 test/Conversion/SimToSV/print-io.mlir diff --git a/include/circt/Dialect/Sim/SimOps.td b/include/circt/Dialect/Sim/SimOps.td index 58cc29619553..0a902a2e2b69 100644 --- a/include/circt/Dialect/Sim/SimOps.td +++ b/include/circt/Dialect/Sim/SimOps.td @@ -502,6 +502,29 @@ 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 CastSignedOp : SimOp<"cast.signed", [Pure, SameOperandsAndResultType]> { + let summary = "Cast integer for signed interpretation"; + let description = [{ + Reinterprets an integer as signed while preserving the bit pattern. + Backends lower this to the native signed-cast mechanism. + }]; + + let arguments = (ins AnyInteger:$input); + let results = (outs AnyInteger:$result); + + let assemblyFormat = "$input attr-dict `:` qualified(type($input))"; +} + def FormatStringConcatOp : SimOp<"fmt.concat", [Pure]> { let summary = "Concatenate format strings"; let description = [{ @@ -560,11 +583,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 +599,39 @@ 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); 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/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index 6133e7ed445e..bf13d2be040d 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -2079,15 +2079,16 @@ struct FIRRTLLowering : public FIRRTLVisitor { LogicalResult visitStmt(ForceOp op); std::optional getLoweredFmtOperand(Value operand); + FailureOr lowerSimFormatString(StringRef formatString, + ValueRange substitutions); LogicalResult loweredFmtOperands(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 +2154,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 sv::TimeOp::create(builder); + return sim::TimeOp::create(builder); if (isa(operand.getDefiningOp())) return {nullptr}; } @@ -2809,8 +2807,7 @@ std::optional FIRRTLLowering::getLoweredFmtOperand(Value operand) { // it as signed decimal and have to wrap it in $signed(). if (auto intTy = firrtl::type_cast(operand.getType())) if (intTy.isSigned()) - loweredValue = sv::SystemFunctionOp::create( - builder, loweredValue.getType(), "signed", loweredValue); + loweredValue = sim::CastSignedOp::create(builder, loweredValue); return loweredValue; } @@ -2829,49 +2826,205 @@ FIRRTLLowering::loweredFmtOperands(mlir::ValueRange operands, 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 +3032,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 +5416,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 +5452,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(); }; 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/SimToSV.cpp b/lib/Conversion/SimToSV/SimToSV.cpp index 965ac5cd38b1..9327b36cafcd 100644 --- a/lib/Conversion/SimToSV/SimToSV.cpp +++ b/lib/Conversion/SimToSV/SimToSV.cpp @@ -53,6 +53,133 @@ 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) { + 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))) + 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'); + } else { + appendDecimalSpecifier(out.format, fmt.getSpecifierWidth()); + } + 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(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 void eraseFormatTreeIfDead(Value root, PatternRewriter &rewriter) { + auto *op = root.getDefiningOp(); + if (!op || !op->use_empty()) + return; + if (!isa(op)) + return; + + SmallVector operands(op->getOperands().begin(), + op->getOperands().end()); + rewriter.eraseOp(op); + for (auto operand : operands) + eraseFormatTreeIfDead(operand, rewriter); +} + +static bool isFormatOp(Operation *op) { + return isa(op); +} + +static void eraseDeadFormatOps(Operation *scope) { + bool changed = true; + while (changed) { + changed = false; + SmallVector toErase; + scope->walk([&](Operation *op) { + if (isFormatOp(op) && op->use_empty()) + toErase.push_back(op); + }); + if (toErase.empty()) + break; + for (auto *op : toErase) + op->erase(); + changed = true; + } +} + namespace { struct SimConversionState { @@ -281,6 +408,174 @@ 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()); + } + + auto call = sv::FuncCallOp::create( + rewriter, op.getLoc(), TypeRange{rewriter.getI32Type()}, + rewriter.getStringAttr("__circt_lib_logging::FileDescriptor::get"), + ValueRange{fileName}); + rewriter.replaceOp(op, call.getResults()); + return success(); + } +}; + +class PrintFormattedProcLowering + : public SimConversionPattern { +public: + using SimConversionPattern::SimConversionPattern; + + LogicalResult + matchAndRewrite(PrintFormattedProcOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto originalInput = op.getInput(); + LoweredFormatString lowered; + if (failed(lowerFormatString(adaptor.getInput(), lowered))) + return rewriter.notifyMatchFailure(op, "unsupported format string"); + + Value stream = adaptor.getStream(); + if (!stream) + stream = getDefaultStderrFD(rewriter, op.getLoc()); + sv::FWriteOp::create(rewriter, op.getLoc(), stream, + rewriter.getStringAttr(lowered.format), + lowered.operands); + rewriter.eraseOp(op); + eraseFormatTreeIfDead(originalInput, rewriter); + return success(); + } +}; + +class PrintFormattedLowering : public SimConversionPattern { +public: + using SimConversionPattern::SimConversionPattern; + + LogicalResult + matchAndRewrite(PrintFormattedOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto originalInput = op.getInput(); + LoweredFormatString lowered; + if (failed(lowerFormatString(adaptor.getInput(), lowered))) + return rewriter.notifyMatchFailure(op, "unsupported format string"); + + Value stream = adaptor.getStream(); + if (!stream) + stream = getDefaultStderrFD(rewriter, op.getLoc()); + + Value cond = adaptor.getCondition(); + if (op.getUsePrintfCond()) { + auto macro = sv::MacroRefExprOp::create(rewriter, op.getLoc(), + cond.getType(), "PRINTF_COND_"); + cond = comb::AndOp::create(rewriter, op.getLoc(), macro, cond, true); + } + + auto emitAlways = [&]() { + auto clock = + seq::FromClockOp::create(rewriter, op.getLoc(), adaptor.getClock()); + sv::AlwaysOp::create( + rewriter, op.getLoc(), + ArrayRef{sv::EventControl::AtPosEdge}, + ArrayRef{clock}, [&]() { + sv::IfOp::create(rewriter, op.getLoc(), cond, [&]() { + sv::FWriteOp::create(rewriter, op.getLoc(), stream, + rewriter.getStringAttr(lowered.format), + lowered.operands); + }); + }); + }; + + state.usedSynthesisMacro = true; + sv::IfDefOp::create( + rewriter, op.getLoc(), "SYNTHESIS", [] {}, emitAlways); + rewriter.eraseOp(op); + eraseFormatTreeIfDead(originalInput, rewriter); + 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 FFlushLowering : public SimConversionPattern { +public: + using SimConversionPattern::SimConversionPattern; + + LogicalResult + matchAndRewrite(FFlushOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto emitAlways = [&]() { + auto clock = + seq::FromClockOp::create(rewriter, op.getLoc(), adaptor.getClock()); + sv::AlwaysOp::create( + rewriter, op.getLoc(), + ArrayRef{sv::EventControl::AtPosEdge}, + ArrayRef{clock}, [&]() { + sv::IfOp::create(rewriter, op.getLoc(), adaptor.getCondition(), + [&]() { + sv::FFlushOp::create(rewriter, op.getLoc(), + adaptor.getStream()); + }); + }); + }; + + state.usedSynthesisMacro = true; + sv::IfDefOp::create( + rewriter, op.getLoc(), "SYNTHESIS", [] {}, emitAlways); + 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(); + } +}; + +class CastSignedLowering : public SimConversionPattern { +public: + using SimConversionPattern::SimConversionPattern; + + LogicalResult + matchAndRewrite(sim::CastSignedOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const final { + auto cast = sv::SystemFunctionOp::create(rewriter, op.getLoc(), + op.getResult().getType(), "signed", + adaptor.getInput()); + rewriter.replaceOp(op, cast); + return success(); + } +}; + // A helper struct to lower DPI function/call. struct LowerDPIFunc { llvm::DenseMap symbolToFragment; @@ -487,6 +782,10 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase { target.addLegalDialect(); target.addLegalDialect(); target.addLegalDialect(); + target.addLegalOp(); RewritePatternSet patterns(context); patterns.add(context, state); @@ -496,11 +795,20 @@ 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, state); + patterns.add(context, state); + patterns.add(context, state); + patterns.add(context, state); auto result = applyPartialConversion(module, target, std::move(patterns)); if (failed(result)) return result; + eraseDeadFormatOps(module); + // Set the emit fragment. lowerDPIFunc.addFragments(module, state.dpiCallees.takeVector()); @@ -513,7 +821,16 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase { context, circuit.getOps(), lowerModule))) return signalPassFailure(); - if (usedSynthesisMacro) { + bool needsSynthesisMacro = usedSynthesisMacro; + if (!needsSynthesisMacro) { + needsSynthesisMacro = false; + circuit.walk([&](sv::IfDefOp ifdefOp) { + if (ifdefOp.getCond().getName() == "SYNTHESIS") + needsSynthesisMacro = true; + }); + } + + if (needsSynthesisMacro) { 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..232f203a149b 100644 --- a/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp +++ b/lib/Dialect/Sim/Transforms/ProceduralizeSim.cpp @@ -201,7 +201,11 @@ LogicalResult ProceduralizeSimPass::proceduralizePrintOps( // Create the procedural print operation and prune the operations outside of // the TriggeredOp. builder.setInsertionPoint(condBlock->getTerminator()); - PrintFormattedProcOp::create(builder, printOp.getLoc(), procPrintInput); + Value stream{}; + if (printOp->getNumOperands() == 4) + stream = printOp->getOperand(1); + PrintFormattedProcOp::create(builder, printOp.getLoc(), procPrintInput, + stream); cleanupList.push_back(printOp.getInput().getDefiningOp()); printOp.erase(); } 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..751dc1c9357b 100644 --- a/test/Conversion/FIRRTLToHW/lower-to-hw.mlir +++ b/test/Conversion/FIRRTLToHW/lower-to-hw.mlir @@ -357,70 +357,27 @@ 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.cast.signed %{{.+}} : i5 + // CHECK: %{{.+}} = sim.cast.signed %d : i4 + // 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> @@ -653,7 +610,7 @@ firrtl.circuit "Simple" attributes {annotations = [{class = // CHECK-NEXT: [[TRUE:%.+]] = hw.constant true // CHECK-NEXT: [[TMP1:%.+]] = comb.xor bin %cond, [[TRUE]] // CHECK-NEXT: [[TMP2:%.+]] = comb.and bin %enable, [[TMP1]] - // CHECK-NEXT: [[SIGNEDVAL:%.+]] = sv.system "signed"(%value2) : (i24) -> i24 + // CHECK-NEXT: [[SIGNEDVAL:%.+]] = sim.cast.signed %value2 : i24 // CHECK-NEXT: [[TRUE2:%.+]] = hw.constant true // CHECK-NEXT: [[TMP3:%.+]] = comb.xor bin %cond, [[TRUE2]] // CHECK-NEXT: [[TMP4:%.+]] = comb.and bin %enable, [[TMP3]] @@ -694,7 +651,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 +663,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/Conversion/SimToSV/print-io.mlir b/test/Conversion/SimToSV/print-io.mlir new file mode 100644 index 000000000000..cb8c0516f499 --- /dev/null +++ b/test/Conversion/SimToSV/print-io.mlir @@ -0,0 +1,83 @@ +// RUN: circt-opt --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 +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) + %signedVal = sim.cast.signed %val : i8 + %dec = sim.fmt.dec %signedVal signed : i8 + %t = sim.time + %tmsg = sim.fmt.concat (%dec, %nl) + // CHECK-DAG: sv.system.time : i64 + + // CHECK: sv.ifdef @SYNTHESIS { + // CHECK-NEXT: } else { + // CHECK-NEXT: [[CLK0:%.+]] = seq.from_clock %clk + // CHECK-NEXT: sv.always posedge [[CLK0]] { + // CHECK: sv.if %{{.+}} { + // CHECK-NEXT: sv.fwrite %{{.+}}, "v=%x\0A"(%val) : i8 + // CHECK-NEXT: } + // CHECK-NEXT: } + sim.print %msg on %clk if %cond {usePrintfCond = true} + + %fd = sim.get_file "out_%0d.log"(%val) : (i8) -> i32 + // CHECK: [[NAME:%.+]] = sv.sformatf "out_%0d.log"(%val) : i8 + // CHECK-NEXT: [[FD:%.+]] = sv.func.call @"__circt_lib_logging::FileDescriptor::get"([[NAME]]) : (!hw.string) -> i32 + + // CHECK: sv.ifdef @SYNTHESIS { + // CHECK-NEXT: } else { + // CHECK-NEXT: [[CLK1:%.+]] = seq.from_clock %clk + // CHECK-NEXT: sv.always posedge [[CLK1]] { + // CHECK-NEXT: sv.if %cond { + // CHECK-NEXT: sv.fwrite [[FD]], "v=%x\0A"(%val) : i8 + // CHECK-NEXT: } + // CHECK-NEXT: } + // CHECK-NEXT: } + sim.print %msg to %fd on %clk if %cond + + // CHECK: sv.ifdef @SYNTHESIS { + // CHECK-NEXT: } else { + // CHECK-NEXT: %{{.+}} = seq.from_clock %clk + // CHECK-NEXT: sv.always posedge %{{.+}} { + // CHECK-NEXT: sv.if %cond { + // CHECK-NEXT: sv.fwrite [[FD]], "%d\0A"(%{{.+}}) : i8 + // CHECK-NEXT: } + // CHECK-NEXT: } + // CHECK-NEXT: } + sim.print %tmsg to %fd on %clk if %cond + + // CHECK: sv.ifdef @SYNTHESIS { + // CHECK-NEXT: } else { + // CHECK-NEXT: [[CLK2:%.+]] = seq.from_clock %clk + // CHECK-NEXT: sv.always posedge [[CLK2]] { + // CHECK-NEXT: sv.if %cond { + // CHECK-NEXT: sv.fflush fd [[FD]] + // CHECK-NEXT: } + // CHECK-NEXT: } + // CHECK-NEXT: } + sim.fflush %fd on %clk if %cond +} + +// CHECK-LABEL: hw.module @print_proc +hw.module @print_proc(in %val: i8) { + %lit = sim.fmt.literal "p=" + %hex = sim.fmt.hex %val, isUpper false : i8 + %nl = sim.fmt.literal "\0A" + %msg = sim.fmt.concat (%lit, %hex, %nl) + %fd = hw.constant 123 : i32 + sv.initial { + // CHECK: sv.initial { + // CHECK: sv.fwrite %{{.+}}, "p=%x\0A"(%val) : i8 + // CHECK: sv.fwrite %{{.+}}, "p=%x\0A"(%val) : i8 + // CHECK: sv.fflush fd %{{.+}} + sim.proc.print %msg + sim.proc.print %msg to %fd + sim.proc.fflush %fd + } +} diff --git a/test/Dialect/Sim/round-trip.mlir b/test/Dialect/Sim/round-trip.mlir index 65b34115dd89..e02a154daa81 100644 --- a/test/Dialect/Sim/round-trip.mlir +++ b/test/Dialect/Sim/round-trip.mlir @@ -84,6 +84,11 @@ func.func @FormatStrings() { sim.fmt.hier_path // CHECK: sim.fmt.hier_path escaped sim.fmt.hier_path escaped + // CHECK: sim.time + %t = sim.time + %x = hw.constant 0 : i8 + // CHECK: sim.cast.signed %{{.+}} : i8 + %s = sim.cast.signed %x : i8 return } diff --git a/test/firtool/print.fir b/test/firtool/print.fir index 86c5d7ccd372..5bc2ee7c6353 100644 --- a/test/firtool/print.fir +++ b/test/firtool/print.fir @@ -43,26 +43,20 @@ circuit PrintTest: ; CHECK-NEXT: $fwrite(32'h80000002, "literals: %%\n"); printf(clock, cond, "literals: %%\n") - ; CHECK-NEXT: $fwrite(32'h80000002, "[%0t]: %m\n", $time); + ; CHECK: $fwrite(32'h80000002, "[%0t]: %m\n", _GEN); ; 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"); 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: "[%0t]: static file name (w/ substitution)\n", _GEN); 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)); fflush(clock, cond) ; CHECK-NEXT: $fflush(___circt_lib_logging3A3AFileDescriptor3A3Aget_0);