26#include "mlir/IR/Builders.h"
27#include "mlir/IR/BuiltinOps.h"
28#include "mlir/IR/DialectImplementation.h"
29#include "mlir/IR/ImplicitLocOpBuilder.h"
30#include "mlir/IR/Threading.h"
31#include "mlir/IR/Visitors.h"
32#include "mlir/Pass/Pass.h"
33#include "mlir/Transforms/DialectConversion.h"
34#include "llvm/ADT/Twine.h"
35#include "llvm/Support/LogicalResult.h"
37#define DEBUG_TYPE "lower-sim-to-sv"
40#define GEN_PASS_DEF_LOWERSIMTOSV
41#include "circt/Conversion/Passes.h.inc"
51 return isa<ClockedTerminateOp, ClockedPauseOp, TerminateOp, PauseOp>(op);
57 return TypeSwitch<Operation *, std::pair<Value, Value>>(op)
58 .Case<ClockedTerminateOp, ClockedPauseOp>(
59 [](
auto op) -> std::pair<Value, Value> {
60 return {op.getClock(), op.getCondition()};
67struct SimConversionState {
68 hw::HWModuleOp module;
69 bool usedSynthesisMacro =
false;
70 bool usedFileDescriptorRuntime =
false;
71 SetVector<StringAttr> dpiCallees;
74struct SimTypeConverter :
public TypeConverter {
75 explicit SimTypeConverter(MLIRContext *
context) {
76 addConversion([](Type type) {
return type; });
77 addConversion([&](OutputStreamType type) -> Type {
78 return IntegerType::get(type.getContext(), 32);
80 addConversion([&](DynamicStringType type) -> Type {
81 return hw::StringType::get(type.getContext());
88 explicit SimConversionPattern(MLIRContext *
context, SimConversionState &state)
89 : OpConversionPattern<T>(
context), state(state) {}
91 SimConversionState &state;
95DPIFunctionTypeToHWModuleType(
const DPIFunctionType &dpiFuncType) {
96 SmallVector<hw::ModulePort> hwPorts;
97 for (
auto &arg : dpiFuncType.getArguments()) {
101 case DPIDirection::Input:
102 case DPIDirection::Ref:
105 case DPIDirection::Output:
106 case DPIDirection::Return:
109 case DPIDirection::InOut:
111 type = hw::InOutType::get(type);
114 hwPorts.push_back({arg.name, type, hwDir});
116 return hw::ModuleType::get(dpiFuncType.getContext(), hwPorts);
124 using SimConversionPattern<PlusArgsTestOp>::SimConversionPattern;
128 ConversionPatternRewriter &rewriter)
const final {
129 auto loc = op.getLoc();
130 auto resultType = rewriter.getIntegerType(1);
131 auto str = sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
133 rewriter.getStringAttr(
"_pargs"));
134 sv::InitialOp::create(rewriter, loc, [&] {
135 auto call = sv::SystemFunctionOp::create(
136 rewriter, loc, resultType,
"test$plusargs", ArrayRef<Value>{str});
137 sv::BPAssignOp::create(rewriter, loc, reg, call);
149 using SimConversionPattern<PlusArgsValueOp>::SimConversionPattern;
153 ConversionPatternRewriter &rewriter)
const final {
154 auto loc = op.getLoc();
156 auto i1ty = rewriter.getIntegerType(1);
157 auto type = op.getResult().getType();
160 rewriter.getStringAttr(
"_pargs_v"));
162 rewriter.getStringAttr(
"_pargs_f"));
164 state.usedSynthesisMacro =
true;
166 rewriter, loc,
"SYNTHESIS",
169 auto cstZ = sv::ConstantZOp::create(rewriter, loc, type);
173 sv::SVAttributeAttr::get(
174 rewriter.getContext(),
175 "This dummy assignment exists to avoid undriven lint "
176 "warnings (e.g., Verilator UNDRIVEN).",
181 auto i32ty = rewriter.getIntegerType(32);
183 rewriter.getStringAttr(
"_found"));
185 rewriter.getStringAttr(
"_value"));
186 sv::InitialOp::create(rewriter, loc, [&] {
188 sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
189 auto call = sv::SystemFunctionOp::create(
190 rewriter, loc, i32ty,
"value$plusargs",
191 ArrayRef<Value>{str, regv});
192 sv::BPAssignOp::create(rewriter, loc, regf, call);
198 auto cmp = comb::ICmpOp::create(
199 rewriter, loc, comb::ICmpPredicate::ceq, readRegF, cstTrue);
207 rewriter.replaceOp(op, {readf, readv});
212template <
typename OpTy,
unsigned StreamValue>
220 ConversionPatternRewriter &rewriter)
const final {
223 rewriter.replaceOp(op, streamValue);
239 ConversionPatternRewriter &rewriter)
const final {
240 SmallVector<Type> convertedResultTypes;
241 if (failed(typeConverter->convertTypes(op.getResultTypes(),
242 convertedResultTypes)))
245 if (!llvm::equal(convertedResultTypes, adaptor.getOperands().getTypes()))
248 rewriter.replaceOp(op, adaptor.getOperands());
253static LogicalResult
convert(ClockedTerminateOp op, PatternRewriter &rewriter) {
255 rewriter.replaceOpWithNewOp<sv::FinishOp>(op, op.getVerbose());
257 rewriter.replaceOpWithNewOp<sv::FatalProceduralOp>(op, op.getVerbose());
261static LogicalResult
convert(ClockedPauseOp op, PatternRewriter &rewriter) {
262 rewriter.replaceOpWithNewOp<sv::StopOp>(op, op.getVerbose());
266static LogicalResult
convert(TerminateOp op, PatternRewriter &rewriter) {
268 rewriter.replaceOpWithNewOp<sv::FinishOp>(op, op.getVerbose());
270 rewriter.replaceOpWithNewOp<sv::FatalProceduralOp>(op, op.getVerbose());
274static LogicalResult
convert(PauseOp op, PatternRewriter &rewriter) {
275 rewriter.replaceOpWithNewOp<sv::StopOp>(op, op.getVerbose());
281 using SimConversionPattern<TriggeredOp>::SimConversionPattern;
285 ConversionPatternRewriter &rewriter)
const final {
286 auto loc = op.getLoc();
287 state.usedSynthesisMacro =
true;
290 rewriter, loc,
"SYNTHESIS", [] {},
293 seq::FromClockOp::create(rewriter, loc, adaptor.getClock());
294 auto alwaysOp = sv::AlwaysOp::create(
296 ArrayRef<sv::EventControl>{sv::EventControl::AtPosEdge},
297 ArrayRef<Value>{trigger});
299 Block *destination = alwaysOp.getBodyBlock();
300 if (
auto condition = adaptor.getCondition()) {
301 rewriter.setInsertionPointToStart(destination);
302 destination = sv::IfOp::create(rewriter, loc, condition, [] {
306 rewriter.mergeBlocks(op.getBodyBlock(), destination);
308 rewriter.eraseOp(op);
319 ConversionPatternRewriter &rewriter)
const final {
320 Value fd = adaptor.getStream();
321 if (!fd.getType().isInteger(32))
322 return rewriter.notifyMatchFailure(op,
"expected converted i32 stream");
323 rewriter.replaceOpWithNewOp<sv::FFlushOp>(op, fd);
330 using SimConversionPattern<DPICallOp>::SimConversionPattern;
334 ConversionPatternRewriter &rewriter)
const final {
335 auto loc = op.getLoc();
337 state.dpiCallees.insert(op.getCalleeAttr().getAttr());
339 bool isClockedCall = !!op.getClock();
340 bool hasEnable = !!op.getEnable();
342 SmallVector<sv::VarOp> temporaries;
343 SmallVector<Value> reads;
344 for (
auto [type, result] :
345 llvm::zip(op.getResultTypes(), op.getResults())) {
351 auto emitCall = [&]() {
352 auto call = sv::FuncCallProceduralOp::create(
353 rewriter, op.getLoc(), op.getResultTypes(), op.getCalleeAttr(),
354 adaptor.getInputs());
355 for (
auto [lhs, rhs] : llvm::zip(temporaries, call.getResults())) {
357 sv::PAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
359 sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
364 seq::FromClockOp::create(rewriter, loc, adaptor.getClock());
365 sv::AlwaysOp::create(
367 ArrayRef<sv::EventControl>{sv::EventControl::AtPosEdge},
368 ArrayRef<Value>{clockCast}, [&]() {
371 sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
378 sv::AlwaysCombOp::create(rewriter, loc, [&]() {
381 auto assignXToResults = [&] {
382 for (
auto lhs : temporaries) {
383 auto xValue = sv::ConstantXOp::create(
384 rewriter, op.getLoc(), lhs.getType().getElementType());
385 sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, xValue);
388 sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(), emitCall,
393 rewriter.replaceOp(op, reads);
404 ConversionPatternRewriter &rewriter)
const final {
405 rewriter.replaceOpWithNewOp<sv::ConstantStrOp>(op, op.getLiteralAttr());
416 ConversionPatternRewriter &rewriter)
const final {
417 auto inputs = adaptor.getInputs();
418 if (inputs.empty()) {
419 rewriter.replaceOpWithNewOp<sv::ConstantStrOp>(
420 op, rewriter.getStringAttr(
""));
424 rewriter.replaceOpWithNewOp<sv::ConcatStrOp>(op, inputs);
438 sim::DPIFuncOp
func) {
440 SmallVector<Attribute> convertedAttrs;
441 auto dpiType =
func.getDpiFunctionType();
442 auto dpiArgs = dpiType.getArguments();
443 convertedAttrs.reserve(dpiArgs.size());
444 for (
auto &arg : dpiArgs) {
445 NamedAttrList newAttrs;
446 if (arg.dir == sim::DPIDirection::Return)
448 builder.getStringAttr(sv::FuncOp::getExplicitlyReturnedAttrName()),
449 builder.getUnitAttr());
450 convertedAttrs.push_back(newAttrs.getDictionary(
context));
452 return ArrayAttr::get(
context, convertedAttrs);
456 ImplicitLocOpBuilder builder(
func.getLoc(),
func);
457 ArrayAttr inputLocsAttr, outputLocsAttr;
460 auto moduleType = DPIFunctionTypeToHWModuleType(
func.getDpiFunctionType());
462 if (
func.getArgumentLocs()) {
463 SmallVector<Attribute> inputLocs, outputLocs;
464 auto hwPorts = moduleType.getPorts();
465 for (
auto [port, loc] : llvm::zip(
466 hwPorts,
func.getArgumentLocsAttr().getAsRange<LocationAttr>())) {
470 inputLocsAttr = builder.getArrayAttr(inputLocs);
471 outputLocsAttr = builder.getArrayAttr(outputLocs);
474 auto svFuncDecl = sv::FuncOp::create(
475 builder,
func.getSymNameAttr(), {}, moduleType,
477 outputLocsAttr,
func.getVerilogNameAttr());
479 svFuncDecl.setPrivate();
480 auto name = builder.getStringAttr(
nameSpace.newName(
481 func.getSymNameAttr().getValue(),
"dpi_import_fragument"));
484 auto macroDecl = sv::MacroDeclOp::create(
485 builder,
nameSpace.newName(
"__CIRCT_DPI_IMPORT",
486 func.getSymNameAttr().getValue().upper()));
487 emit::FragmentOp::create(builder, name, [&]() {
489 builder, macroDecl.getSymNameAttr(), []() {},
491 sv::FuncDPIImportOp::create(builder, func.getSymNameAttr(),
493 sv::MacroDefOp::create(builder, macroDecl.getSymNameAttr(),
"");
503 const llvm::DenseMap<StringAttr, StringAttr> &symbolToFragment,
504 const SimConversionState &state) {
505 llvm::SetVector<Attribute> fragments;
507 if (
auto exstingFragments =
509 for (
auto fragment : exstingFragments.getAsRange<FlatSymbolRefAttr>())
510 fragments.insert(fragment);
511 for (
auto callee : state.dpiCallees) {
512 auto attr = symbolToFragment.at(callee);
513 fragments.insert(FlatSymbolRefAttr::get(attr));
515 if (state.usedFileDescriptorRuntime)
517 if (!fragments.empty())
519 emit::getFragmentsAttrName(),
520 ArrayAttr::get(module.getContext(), fragments.takeVector()));
524 bool usedSynthesisMacro =
false;
526 rootOp->walk<WalkOrder::PreOrder>([&](Operation *op) {
530 if (isa<TriggeredOp>(op))
531 return WalkResult::skip();
533 auto loc = op->getLoc();
538 Block *block =
nullptr;
539 if (op->getPrevNode())
540 block = TypeSwitch<Operation *, Block *>(op->getPrevNode())
542 [&](
auto guardOp) -> Block * {
543 if (guardOp.getCond().getIdent().getAttr() ==
546 return guardOp.getElseBlock();
549 .Default([](
auto) {
return nullptr; });
553 OpBuilder builder(op);
555 block = sv::IfDefProceduralOp::create(
556 builder, loc,
"SYNTHESIS", [] {}, [] {})
559 block = sv::IfDefOp::create(
560 builder, loc,
"SYNTHESIS", [] {}, [] {})
562 usedSynthesisMacro =
true;
566 op->moveBefore(block, block->end());
575 Block *block =
nullptr;
576 if (
auto alwaysOp = dyn_cast_or_null<sv::AlwaysOp>(op->getPrevNode()))
577 if (alwaysOp.getNumConditions() == 1 &&
578 alwaysOp.getCondition(0).event == sv::EventControl::AtPosEdge)
579 if (
auto clockOp = alwaysOp.getCondition(0)
580 .value.getDefiningOp<seq::FromClockOp>())
581 if (clockOp.getInput() == clock)
582 block = alwaysOp.getBodyBlock();
586 OpBuilder builder(op);
587 clock = seq::FromClockOp::create(builder, loc, clock);
588 block = sv::AlwaysOp::create(builder, loc, sv::EventControl::AtPosEdge,
594 op->moveBefore(block, block->end());
600 Block *block =
nullptr;
601 if (
auto ifOp = dyn_cast_or_null<sv::IfOp>(op->getPrevNode()))
602 if (ifOp.getCond() == condition)
603 block = ifOp.getThenBlock();
607 OpBuilder builder(op);
608 block = sv::IfOp::create(builder, loc, condition, [] {}).getThenBlock();
612 op->moveBefore(block, block->end());
614 return WalkResult::advance();
617 return usedSynthesisMacro;
622void appendLiteralToSVFormat(SmallString<128> &formatString,
624 for (
char ch : literal) {
626 formatString +=
"%%";
628 formatString.push_back(ch);
632LogicalResult appendPaddedSpecifier(SmallString<128> &formatString,
633 bool isLeftAligned, uint8_t paddingChar,
634 std::optional<int32_t> width,
char spec) {
635 formatString.push_back(
'%');
637 formatString.push_back(
'-');
641 if (paddingChar ==
'0')
642 formatString.push_back(
'0');
643 else if (paddingChar !=
' ')
646 if (width.has_value())
647 llvm::Twine(width.value()).toVector(formatString);
649 formatString.push_back(spec);
653void appendFloatSpecifier(SmallString<128> &formatString,
bool isLeftAligned,
654 std::optional<int32_t> fieldWidth, int32_t fracDigits,
656 formatString.push_back(
'%');
658 formatString.push_back(
'-');
659 if (fieldWidth.has_value())
660 llvm::Twine(fieldWidth.value()).toVector(formatString);
661 formatString.push_back(
'.');
662 llvm::Twine(fracDigits).toVector(formatString);
663 formatString.push_back(spec);
666LogicalResult getFlattenedFormatFragments(Value input,
667 SmallVectorImpl<Value> &fragments) {
668 if (
auto concat = input.getDefiningOp<FormatStringConcatOp>()) {
669 if (failed(
concat.getFlattenedInputs(fragments)))
670 return mlir::emitError(input.getLoc(),
671 "cyclic sim.fmt.concat is unsupported");
675 fragments.push_back(input);
679LogicalResult appendFormatFragmentToSVFormat(Value fragment,
680 SmallString<128> &formatString,
681 SmallVectorImpl<Value> &args,
682 OpBuilder &builder) {
683 Operation *fragmentOp = fragment.getDefiningOp();
685 return mlir::emitError(fragment.getLoc(),
686 "block argument format strings are unsupported");
688 return TypeSwitch<Operation *, LogicalResult>(fragmentOp)
689 .Case<FormatLiteralOp>([&](
auto literal) -> LogicalResult {
690 appendLiteralToSVFormat(formatString, literal.getLiteral());
693 .Case<FormatStringOp>([&](
auto fmt) -> LogicalResult {
694 if (failed(appendPaddedSpecifier(formatString, fmt.getIsLeftAligned(),
695 fmt.getPaddingChar(),
696 fmt.getSpecifierWidth(),
's'))) {
697 return mlir::emitError(fmt.getLoc())
698 <<
"sim.fmt.string only supports paddingChar 32 (' ') or 48 "
701 args.push_back(mlir::UnrealizedConversionCastOp::create(
702 builder, fmt.getLoc(),
703 hw::StringType::get(builder.getContext()),
708 .Case<FormatCurrentTimeOp>([&](
auto fmt) -> LogicalResult {
709 formatString +=
"%0t";
710 args.push_back(sv::TimeOp::create(builder, fmt.getLoc()));
713 .Case<FormatHierPathOp>([&](
auto hierPath) -> LogicalResult {
714 formatString += hierPath.getUseEscapes() ?
"%M" :
"%m";
717 .Case<FormatCharOp>([&](
auto fmt) -> LogicalResult {
718 formatString +=
"%c";
719 args.push_back(fmt.getValue());
722 .Case<FormatDecOp>([&](
auto fmt) -> LogicalResult {
723 if (failed(appendPaddedSpecifier(formatString, fmt.getIsLeftAligned(),
724 fmt.getPaddingChar(),
725 fmt.getSpecifierWidth(),
'd'))) {
726 return mlir::emitError(fmt.getLoc())
727 <<
"sim.fmt.dec only supports paddingChar 32 (' ') or 48 "
731 if (fmt.getIsSigned()) {
732 auto signedValue = sv::SystemFunctionOp::create(
733 builder, fmt.getLoc(), fmt.getValue().getType(),
"signed",
734 ValueRange{fmt.getValue()});
735 args.push_back(signedValue);
737 auto unsignedValue = sv::SystemFunctionOp::create(
738 builder, fmt.getLoc(), fmt.getValue().getType(),
"unsigned",
739 ValueRange{fmt.getValue()});
740 args.push_back(unsignedValue);
744 .Case<FormatHexOp>([&](
auto fmt) -> LogicalResult {
745 if (failed(appendPaddedSpecifier(
746 formatString, fmt.getIsLeftAligned(), fmt.getPaddingChar(),
747 fmt.getSpecifierWidth(),
748 fmt.getIsHexUppercase() ?
'X' :
'x'))) {
749 return mlir::emitError(fmt.getLoc())
750 <<
"sim.fmt.hex only supports paddingChar 32 (' ') or 48 "
753 args.push_back(fmt.getValue());
756 .Case<FormatOctOp>([&](
auto fmt) -> LogicalResult {
757 if (failed(appendPaddedSpecifier(formatString, fmt.getIsLeftAligned(),
758 fmt.getPaddingChar(),
759 fmt.getSpecifierWidth(),
'o'))) {
760 return mlir::emitError(fmt.getLoc())
761 <<
"sim.fmt.oct only supports paddingChar 32 (' ') or 48 "
764 args.push_back(fmt.getValue());
767 .Case<FormatBinOp>([&](
auto fmt) -> LogicalResult {
768 if (failed(appendPaddedSpecifier(formatString, fmt.getIsLeftAligned(),
769 fmt.getPaddingChar(),
770 fmt.getSpecifierWidth(),
'b'))) {
771 return mlir::emitError(fmt.getLoc())
772 <<
"sim.fmt.bin only supports paddingChar 32 (' ') or 48 "
775 args.push_back(fmt.getValue());
778 .Case<FormatScientificOp>([&](
auto fmt) -> LogicalResult {
779 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
780 fmt.getFieldWidth(), fmt.getFracDigits(),
'e');
781 args.push_back(fmt.getValue());
784 .Case<FormatFloatOp>([&](
auto fmt) -> LogicalResult {
785 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
786 fmt.getFieldWidth(), fmt.getFracDigits(),
'f');
787 args.push_back(fmt.getValue());
790 .Case<FormatGeneralOp>([&](
auto fmt) -> LogicalResult {
791 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
792 fmt.getFieldWidth(), fmt.getFracDigits(),
'g');
793 args.push_back(fmt.getValue());
796 .Default([&](
auto unsupportedOp) {
797 return mlir::emitError(unsupportedOp->getLoc())
798 <<
"unsupported format fragment '"
799 << unsupportedOp->getName().getStringRef() <<
"'";
803LogicalResult lowerFormatStringToSVFormat(Value input,
804 SmallString<128> &formatString,
805 SmallVectorImpl<Value> &args,
807 bool &requiresFormatting) {
808 SmallVector<Value, 8> fragments;
809 if (failed(getFlattenedFormatFragments(input, fragments)))
811 requiresFormatting =
false;
812 for (
auto fragment : fragments) {
813 if (failed(appendFormatFragmentToSVFormat(fragment, formatString, args,
818 if (!isa<FormatLiteralOp>(fragment.getDefiningOp()))
819 requiresFormatting =
true;
824FailureOr<Value> createFileDescriptorGetterForGetFile(GetFileOp getFileOp,
825 OpBuilder &builder) {
826 SmallString<128> formatString;
827 SmallVector<Value> args;
828 bool requiresFormatting =
false;
829 if (failed(lowerFormatStringToSVFormat(getFileOp.getFileName(), formatString,
830 args, builder, requiresFormatting))) {
831 getFileOp.emitError(
"cannot lower 'sim.get_file' to SystemVerilog")
832 .attachNote(getFileOp.getFileName().getLoc())
833 <<
"while lowering file name";
839 ? sv::SFormatFOp::create(builder, getFileOp.getLoc(),
840 builder.getStringAttr(formatString), args)
842 : sv::ConstantStrOp::create(builder, getFileOp.getLoc(),
843 builder.getStringAttr(formatString))
847 builder, getFileOp.getLoc(), fileName);
850static void cleanupDeadSimFmtOps(ArrayRef<Operation *> seedOps) {
851 auto filter = [](Operation *op, OpOperand &operand) {
852 return isa<FormatStringType>(operand.get().getType()) &&
853 isa_and_present<SimDialect>(op->getDialect());
858 assert(sccs.getNumCyclicSCCs() == 0 &&
859 "Cyclic graph should have been rejected");
861 for (
OpSCC entry : sccs.reverseTopological()) {
862 auto *op = cast<Operation *>(entry);
866 op->emitWarning(
"sim format/stream op still has users after lowering; "
867 "dialect conversion will fail");
872 const TypeConverter &typeConverter,
873 SimConversionState &state) {
874 SmallVector<GetFileOp> getFileOps;
875 SmallVector<PrintFormattedProcOp> printOps;
876 SmallVector<FormatToStringOp> formatToStringOps;
877 SmallVector<Operation *, 8> cleanupSeeds;
878 module.walk([&](Operation *op) {
879 if (auto getFileOp = dyn_cast<GetFileOp>(op))
880 getFileOps.push_back(getFileOp);
881 if (
auto printOp = dyn_cast<PrintFormattedProcOp>(op))
882 printOps.push_back(printOp);
883 if (
auto formatToStringOp = dyn_cast<FormatToStringOp>(op))
884 formatToStringOps.push_back(formatToStringOp);
887 for (
auto getFileOp : getFileOps) {
888 OpBuilder builder(getFileOp);
889 auto fdOrFailure = createFileDescriptorGetterForGetFile(getFileOp, builder);
890 if (failed(fdOrFailure))
893 auto stream = mlir::UnrealizedConversionCastOp::create(
894 builder, getFileOp.getLoc(), getFileOp.getResult().getType(),
896 getFileOp.replaceAllUsesWith(stream.getResult(0));
897 state.usedFileDescriptorRuntime =
true;
898 state.usedSynthesisMacro =
true;
899 cleanupSeeds.push_back(getFileOp);
902 for (
auto printOp : printOps) {
903 OpBuilder builder(printOp);
904 SmallString<128> formatString;
905 SmallVector<Value> args;
906 bool requiresFormatting =
false;
907 if (failed(lowerFormatStringToSVFormat(printOp.getInput(), formatString,
909 requiresFormatting))) {
910 printOp.emitError(
"cannot lower 'sim.proc.print' to SystemVerilog")
911 .attachNote(printOp.getInput().getLoc())
912 <<
"while lowering format string";
915 auto stream = printOp.getStream();
918 sv::WriteOp::create(builder, printOp.getLoc(), formatString, args);
920 auto fdType = typeConverter.convertType(stream.getType());
921 assert(fdType &&
"expected output stream type conversion");
922 Value fd = mlir::UnrealizedConversionCastOp::create(
923 builder, printOp.getLoc(), fdType, stream)
925 sv::FWriteOp::create(builder, printOp.getLoc(), fd, formatString, args);
927 cleanupSeeds.push_back(printOp);
930 for (
auto op : formatToStringOps) {
931 OpBuilder builder(op);
932 SmallString<128> formatStr;
933 SmallVector<Value> args;
934 bool requiresFormatting =
false;
935 if (failed(lowerFormatStringToSVFormat(op.getFmtstring(), formatStr, args,
936 builder, requiresFormatting)))
938 "cannot lower 'sim.string.format_to_string' to SystemVerilog");
941 if (requiresFormatting)
942 svResult = sv::SFormatFOp::create(builder, op.getLoc(),
943 builder.getStringAttr(formatStr), args);
945 svResult = sv::ConstantStrOp::create(builder, op.getLoc(),
946 builder.getStringAttr(formatStr));
948 auto cast = mlir::UnrealizedConversionCastOp::create(
949 builder, op.getLoc(), op.getType(), svResult);
950 op.replaceAllUsesWith(cast.getResult(0));
951 cleanupSeeds.push_back(op);
954 cleanupDeadSimFmtOps(cleanupSeeds);
959struct SimToSVPass :
public circt::impl::LowerSimToSVBase<SimToSVPass> {
960 void runOnOperation()
override {
961 auto circuit = getOperation();
962 MLIRContext *
context = &getContext();
963 LowerDPIFunc lowerDPIFunc(circuit);
967 llvm::make_early_inc_range(circuit.getOps<sim::DPIFuncOp>()))
968 lowerDPIFunc.lower(func);
970 std::atomic<bool> usedSynthesisMacro =
false;
971 std::atomic<bool> usedFileDescriptorRuntime =
false;
972 auto lowerModule = [&](hw::HWModuleOp module) {
973 SimTypeConverter typeConverter(
context);
974 SimConversionState state;
976 if (failed(lowerPrintFormattedProcToSV(module, typeConverter, state)))
980 usedSynthesisMacro =
true;
982 ConversionTarget target(*
context);
983 target.addIllegalDialect<SimDialect>();
984 target.addLegalDialect<sv::SVDialect>();
985 target.addLegalDialect<hw::HWDialect>();
986 target.addLegalDialect<seq::SeqDialect>();
987 target.addLegalDialect<comb::CombDialect>();
988 target.addDynamicallyLegalOp<mlir::UnrealizedConversionCastOp>(
989 [&](mlir::UnrealizedConversionCastOp op) {
990 return typeConverter.isLegal(op);
993 RewritePatternSet patterns(
context);
994 patterns.add<PlusArgsTestLowering>(
context, state);
995 patterns.add<PlusArgsValueLowering>(
context, state);
998 patterns.add<FlushLowering>(typeConverter,
context);
999 patterns.add<UnrealizedConversionCastLowering>(typeConverter,
context);
1000 patterns.add<ClockedTerminateOp>(
convert);
1001 patterns.add<ClockedPauseOp>(
convert);
1002 patterns.add<TerminateOp>(
convert);
1003 patterns.add<PauseOp>(
convert);
1004 patterns.add<TriggeredLowering>(
context, state);
1005 patterns.add<DPICallLowering>(
context, state);
1006 patterns.add<StringConstantLowering, StringConcatLowering>(typeConverter,
1008 auto result = applyPartialConversion(module, target, std::move(patterns));
1014 addFragments(module, lowerDPIFunc.symbolToFragment, state);
1016 if (state.usedSynthesisMacro)
1017 usedSynthesisMacro =
true;
1018 if (state.usedFileDescriptorRuntime)
1019 usedFileDescriptorRuntime =
true;
1023 if (failed(mlir::failableParallelForEach(
1024 context, circuit.getOps<hw::HWModuleOp>(), lowerModule)))
1025 return signalPassFailure();
1027 if (usedSynthesisMacro) {
1028 Operation *op = circuit.lookupSymbol(
"SYNTHESIS");
1030 if (!isa<sv::MacroDeclOp>(op)) {
1031 op->emitOpError(
"should be a macro declaration");
1032 return signalPassFailure();
1035 auto builder = ImplicitLocOpBuilder::atBlockBegin(
1036 UnknownLoc::get(
context), circuit.getBody());
1037 sv::MacroDeclOp::create(builder,
"SYNTHESIS");
1041 if (usedFileDescriptorRuntime) {
1042 auto builder = ImplicitLocOpBuilder::atBlockBegin(
1043 UnknownLoc::get(
context), circuit.getBody());
1044 sv::emitFileDescriptorRuntime(circuit, builder);
1051 return std::make_unique<SimToSVPass>();
assert(baseType &&"element must be base type")
static SmallVector< T > concat(const SmallVectorImpl< T > &a, const SmallVectorImpl< T > &b)
Returns a new vector containing the concatenation of vectors a and b.
static std::unique_ptr< Context > context
false return failure()
Parse an optional keyword or string and set instance into 'result'.
static Block * getBodyBlock(FModuleLike mod)
static std::pair< Value, Value > needsClockAndConditionWrapper(Operation *op)
Check whether an op should be placed inside an always process triggered on a clock,...
static ArrayAttr buildSVPerArgumentAttrs(MLIRContext *context, sim::DPIFuncOp func)
static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp)
static LogicalResult convert(ClockedTerminateOp op, PatternRewriter &rewriter)
StreamLowering< StdoutStreamOp, 0x80000001 > StdoutStreamLowering
static bool needsIfdefGuard(Operation *op)
Check whether an op should be placed inside an ifdef guard that prevents it from affecting synthesis ...
static void addFragments(hw::HWModuleOp module, const llvm::DenseMap< StringAttr, StringAttr > &symbolToFragment, const SimConversionState &state)
StreamLowering< StderrStreamOp, 0x80000002 > StderrStreamLowering
LogicalResult matchAndRewrite(DPICallOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
LogicalResult matchAndRewrite(FlushOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
LogicalResult matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
LogicalResult matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
typename OpConversionPattern< OpTy >::OpAdaptor OpAdaptor
LogicalResult matchAndRewrite(OpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
LogicalResult matchAndRewrite(StringConcatOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
LogicalResult matchAndRewrite(StringConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
LogicalResult matchAndRewrite(TriggeredOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
LogicalResult matchAndRewrite(mlir::UnrealizedConversionCastOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
A namespace that is used to store existing names and generate new names in some scope within the IR.
Iterative Tarjan SCC analysis on a sparse subgraph of MLIR operations.
create(data_type, name=None, sym_name=None)
create(data_type, name=None, sym_name=None)
StringRef getFragmentsAttrName()
Return the name of the fragments array attribute.
FlatSymbolRefAttr getFileDescriptorFragmentRef(MLIRContext *context)
Return a reference to the shared file descriptor runtime fragment.
Value createProceduralFileDescriptorGetterCall(OpBuilder &builder, Location loc, Value fileName)
Create a call to the shared file descriptor getter from a procedural region.
void setSVAttributes(mlir::Operation *op, mlir::ArrayAttr attrs)
Set the SV attributes of an operation.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::PointerUnion< void *, mlir::Operation *, CyclicOpSCC > OpSCC
One entry in the SCC output: a null sentinel, a trivial (non-cyclic) operation, or a cyclic group.
bool isInProceduralRegion(Operation *op)
Returns true if op has a parent marked as a procedural region that is closer than any parent marked a...
std::unique_ptr< mlir::Pass > createLowerSimToSVPass()
circt::Namespace nameSpace
void lower(sim::DPIFuncOp func)
llvm::DenseMap< StringAttr, StringAttr > symbolToFragment
LowerDPIFunc(mlir::ModuleOp module)