2626#include " mlir/IR/Threading.h"
2727#include " mlir/Pass/Pass.h"
2828#include " mlir/Transforms/DialectConversion.h"
29+ #include " mlir/Transforms/RegionUtils.h"
2930
3031#define DEBUG_TYPE " lower-sim-to-sv"
3132
@@ -465,6 +466,229 @@ static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp) {
465466}
466467
467468namespace {
469+
470+ static LogicalResult emitLoweringError (Location loc, const Twine &reason) {
471+ mlir::emitError (loc) << " cannot lower 'sim.proc.print' to sv.write: "
472+ << reason;
473+ return failure ();
474+ }
475+
476+ static void appendLiteralToFWriteFormat (SmallString<128 > &formatString,
477+ StringRef literal) {
478+ for (char ch : literal) {
479+ if (ch == ' %' )
480+ formatString += " %%" ;
481+ else
482+ formatString.push_back (ch);
483+ }
484+ }
485+
486+ static LogicalResult appendIntegerSpecifier (SmallString<128 > &formatString,
487+ bool isLeftAligned,
488+ uint8_t paddingChar,
489+ std::optional<int32_t > width,
490+ char spec) {
491+ formatString.push_back (' %' );
492+ if (isLeftAligned)
493+ formatString.push_back (' -' );
494+
495+ // SystemVerilog formatting only has built-in support for '0' and ' '. Keep
496+ // this lowering strict to avoid silently changing formatting semantics.
497+ if (paddingChar == ' 0' )
498+ formatString.push_back (' 0' );
499+ else if (paddingChar != ' ' )
500+ return failure ();
501+
502+ if (width.has_value ())
503+ formatString += std::to_string (width.value ());
504+
505+ formatString.push_back (spec);
506+ return success ();
507+ }
508+
509+ static void appendFloatSpecifier (SmallString<128 > &formatString,
510+ bool isLeftAligned,
511+ std::optional<int32_t > fieldWidth,
512+ int32_t fracDigits, char spec) {
513+ formatString.push_back (' %' );
514+ if (isLeftAligned)
515+ formatString.push_back (' -' );
516+ if (fieldWidth.has_value ())
517+ formatString += std::to_string (fieldWidth.value ());
518+ formatString.push_back (' .' );
519+ formatString += std::to_string (fracDigits);
520+ formatString.push_back (spec);
521+ }
522+
523+ static LogicalResult
524+ getFlattenedFormatFragments (Value input, SmallVectorImpl<Value> &fragments) {
525+ if (auto concat = input.getDefiningOp <FormatStringConcatOp>()) {
526+ if (failed (concat.getFlattenedInputs (fragments)))
527+ return emitLoweringError (input.getLoc (),
528+ " cyclic sim.fmt.concat is unsupported" );
529+ return success ();
530+ }
531+
532+ fragments.push_back (input);
533+ return success ();
534+ }
535+
536+ static LogicalResult
537+ appendFormatFragmentToFWrite (Value fragment, SmallString<128 > &formatString,
538+ SmallVectorImpl<Value> &args, OpBuilder &builder) {
539+ Operation *fragmentOp = fragment.getDefiningOp ();
540+ if (!fragmentOp)
541+ return emitLoweringError (fragment.getLoc (),
542+ " block argument format strings are unsupported as "
543+ " sim.proc.print input" );
544+
545+ return TypeSwitch<Operation *, LogicalResult>(fragmentOp)
546+ .Case <FormatLiteralOp>([&](auto literal) -> LogicalResult {
547+ appendLiteralToFWriteFormat (formatString, literal.getLiteral ());
548+ return success ();
549+ })
550+ .Case <FormatHierPathOp>([&](auto hierPath) -> LogicalResult {
551+ formatString += hierPath.getUseEscapes () ? " %M" : " %m" ;
552+ return success ();
553+ })
554+ .Case <FormatCharOp>([&](auto fmt) -> LogicalResult {
555+ formatString += " %c" ;
556+ args.push_back (fmt.getValue ());
557+ return success ();
558+ })
559+ .Case <FormatDecOp>([&](auto fmt) -> LogicalResult {
560+ if (failed (appendIntegerSpecifier (formatString, fmt.getIsLeftAligned (),
561+ fmt.getPaddingChar (),
562+ fmt.getSpecifierWidth (), ' d' ))) {
563+ return emitLoweringError (
564+ fmt.getLoc (), " sim.fmt.dec only supports paddingChar 32 (' ') "
565+ " or 48 ('0') for SystemVerilog lowering" );
566+ }
567+ // Match sim.fmt.dec signedness semantics explicitly in SV.
568+ if (fmt.getIsSigned ()) {
569+ auto signedValue = sv::SystemFunctionOp::create (
570+ builder, fmt.getLoc (), fmt.getValue ().getType (), " signed" ,
571+ ValueRange{fmt.getValue ()});
572+ args.push_back (signedValue);
573+ } else {
574+ auto unsignedValue = sv::SystemFunctionOp::create (
575+ builder, fmt.getLoc (), fmt.getValue ().getType (), " unsigned" ,
576+ ValueRange{fmt.getValue ()});
577+ args.push_back (unsignedValue);
578+ }
579+ return success ();
580+ })
581+ .Case <FormatHexOp>([&](auto fmt) -> LogicalResult {
582+ if (failed (appendIntegerSpecifier (
583+ formatString, fmt.getIsLeftAligned (), fmt.getPaddingChar (),
584+ fmt.getSpecifierWidth (),
585+ fmt.getIsHexUppercase () ? ' X' : ' x' ))) {
586+ return emitLoweringError (
587+ fmt.getLoc (), " sim.fmt.hex only supports paddingChar 32 (' ') "
588+ " or 48 ('0') for SystemVerilog lowering" );
589+ }
590+ args.push_back (fmt.getValue ());
591+ return success ();
592+ })
593+ .Case <FormatOctOp>([&](auto fmt) -> LogicalResult {
594+ if (failed (appendIntegerSpecifier (formatString, fmt.getIsLeftAligned (),
595+ fmt.getPaddingChar (),
596+ fmt.getSpecifierWidth (), ' o' ))) {
597+ return emitLoweringError (
598+ fmt.getLoc (), " sim.fmt.oct only supports paddingChar 32 (' ') "
599+ " or 48 ('0') for SystemVerilog lowering" );
600+ }
601+ args.push_back (fmt.getValue ());
602+ return success ();
603+ })
604+ .Case <FormatBinOp>([&](auto fmt) -> LogicalResult {
605+ if (failed (appendIntegerSpecifier (formatString, fmt.getIsLeftAligned (),
606+ fmt.getPaddingChar (),
607+ fmt.getSpecifierWidth (), ' b' ))) {
608+ return emitLoweringError (
609+ fmt.getLoc (), " sim.fmt.bin only supports paddingChar 32 (' ') "
610+ " or 48 ('0') for SystemVerilog lowering" );
611+ }
612+ args.push_back (fmt.getValue ());
613+ return success ();
614+ })
615+ .Case <FormatScientificOp>([&](auto fmt) -> LogicalResult {
616+ appendFloatSpecifier (formatString, fmt.getIsLeftAligned (),
617+ fmt.getFieldWidth (), fmt.getFracDigits (), ' e' );
618+ args.push_back (fmt.getValue ());
619+ return success ();
620+ })
621+ .Case <FormatFloatOp>([&](auto fmt) -> LogicalResult {
622+ appendFloatSpecifier (formatString, fmt.getIsLeftAligned (),
623+ fmt.getFieldWidth (), fmt.getFracDigits (), ' f' );
624+ args.push_back (fmt.getValue ());
625+ return success ();
626+ })
627+ .Case <FormatGeneralOp>([&](auto fmt) -> LogicalResult {
628+ appendFloatSpecifier (formatString, fmt.getIsLeftAligned (),
629+ fmt.getFieldWidth (), fmt.getFracDigits (), ' g' );
630+ args.push_back (fmt.getValue ());
631+ return success ();
632+ })
633+ .Default ([&](auto unsupportedOp) {
634+ return emitLoweringError (unsupportedOp->getLoc (),
635+ Twine (" unsupported format fragment '" ) +
636+ unsupportedOp->getName ().getStringRef () +
637+ " '" );
638+ });
639+ }
640+
641+ static LogicalResult foldFormatStringToFWrite (Value input,
642+ SmallString<128 > &formatString,
643+ SmallVectorImpl<Value> &args,
644+ OpBuilder &builder) {
645+ SmallVector<Value, 8 > fragments;
646+ if (failed (getFlattenedFormatFragments (input, fragments)))
647+ return failure ();
648+ for (auto fragment : fragments)
649+ if (failed (appendFormatFragmentToFWrite (fragment, formatString, args,
650+ builder)))
651+ return failure ();
652+ return success ();
653+ }
654+
655+ LogicalResult lowerPrintFormattedProcToSV (hw::HWModuleOp module ) {
656+ SmallVector<PrintFormattedProcOp> printOps;
657+ module .walk ([&](PrintFormattedProcOp op) { printOps.push_back (op); });
658+
659+ llvm::SmallPtrSet<Operation *, 8 > dceRoots;
660+
661+ for (auto printOp : printOps) {
662+ OpBuilder builder (printOp);
663+ SmallString<128 > formatString;
664+ SmallVector<Value> args;
665+ if (failed (foldFormatStringToFWrite (printOp.getInput (), formatString, args,
666+ builder))) {
667+ return failure ();
668+ }
669+ auto stream = printOp.getStream ();
670+ if (!stream) {
671+ // no stream is specified, emit sv.write.
672+ sv::WriteOp::create (builder, printOp.getLoc (), formatString, args);
673+ } else {
674+ // Stream-based printing is not supported yet.
675+ return printOp->emitError (
676+ " lowering 'sim.proc.print' with a stream is not supported yet" );
677+ }
678+ auto *procRoot =
679+ printOp->getParentWithTrait <mlir::OpTrait::IsIsolatedFromAbove>();
680+ if (procRoot)
681+ dceRoots.insert (procRoot);
682+ printOp.erase ();
683+ }
684+
685+ mlir::IRRewriter rewriter (module );
686+ for (Operation *dceRoot : dceRoots)
687+ (void )mlir::runRegionDCE (rewriter, dceRoot->getRegions ());
688+
689+ return success ();
690+ }
691+
468692struct SimToSVPass : public circt ::impl::LowerSimToSVBase<SimToSVPass> {
469693 void runOnOperation () override {
470694 auto circuit = getOperation ();
@@ -478,6 +702,10 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
478702
479703 std::atomic<bool > usedSynthesisMacro = false ;
480704 auto lowerModule = [&](hw::HWModuleOp module ) {
705+ if (failed (lowerPrintFormattedProcToSV (module ))) {
706+ return failure ();
707+ }
708+
481709 if (moveOpsIntoIfdefGuardsAndProcesses (module ))
482710 usedSynthesisMacro = true ;
483711
0 commit comments