diff --git a/include/circt/Dialect/Moore/MooreOps.td b/include/circt/Dialect/Moore/MooreOps.td index c31a2a127550..490994538bc5 100644 --- a/include/circt/Dialect/Moore/MooreOps.td +++ b/include/circt/Dialect/Moore/MooreOps.td @@ -3039,6 +3039,23 @@ def DisplayBIOp : Builtin<"display"> { let assemblyFormat = "$message attr-dict"; } +def StrobeBIOp : Builtin<"strobe"> { + let summary = "Display formatted values at end of simulation timestep"; + let description = [{ + Schedules a display of the formatted message in the Postponed region of the + current simulation time, after all Active and NBA updates have settled. + The format string is evaluated at the time the body execute, + not at the time `$strobe` is called. + Corresponds to `$strobe[boh]` system task. + Message formatting is handled by `moore.fmt.*` ops. + + See IEEE 1800-2017 § 21.2.2 "Strobed monitoring". + }]; + let arguments = (ins FormatStringType:$message); + let results = (outs); + let assemblyFormat = "$message attr-dict"; +} + def SeverityInfo : I32EnumAttrCase<"Info", 0, "info">; def SeverityWarning : I32EnumAttrCase<"Warning", 1, "warning">; def SeverityError : I32EnumAttrCase<"Error", 2, "error">; diff --git a/include/circt/Dialect/SV/SVStatements.td b/include/circt/Dialect/SV/SVStatements.td index 961b970fcc79..81be2d3c87d9 100644 --- a/include/circt/Dialect/SV/SVStatements.td +++ b/include/circt/Dialect/SV/SVStatements.td @@ -528,6 +528,17 @@ def WriteOp : SVOp<"write", [ProceduralOp]> { }]; } +def StrobeOp : SVOp<"strobe", [ProceduralOp]> { + let summary = "'$strobe' statement"; + let arguments = (ins StrAttr:$format_string, + Variadic:$substitutions); + let results = (outs); + let assemblyFormat = [{ + $format_string attr-dict (`(` $substitutions^ `)` `:` + qualified(type($substitutions)))? + }]; +} + def FWriteOp : SVOp<"fwrite", [ProceduralOp]> { let summary = "'$fwrite' statement"; diff --git a/include/circt/Dialect/SV/SVVisitors.h b/include/circt/Dialect/SV/SVVisitors.h index 9f2915d88df5..62706fb08eac 100644 --- a/include/circt/Dialect/SV/SVVisitors.h +++ b/include/circt/Dialect/SV/SVVisitors.h @@ -40,7 +40,7 @@ class Visitor { AlwaysCombOp, AlwaysFFOp, InitialOp, CaseOp, // Other Statements. AssignOp, BPAssignOp, PAssignOp, ForceOp, ReleaseOp, AliasOp, - WriteOp, FWriteOp, FFlushOp, SystemFunctionOp, VerbatimOp, + WriteOp, StrobeOp, FWriteOp, FFlushOp, SystemFunctionOp, VerbatimOp, MacroRefOp, FuncCallOp, FuncCallProceduralOp, ReturnOp, IncludeOp, MacroErrorOp, // Type declarations. @@ -137,6 +137,7 @@ class Visitor { HANDLE(ReleaseOp, Unhandled); HANDLE(AliasOp, Unhandled); HANDLE(WriteOp, Unhandled); + HANDLE(StrobeOp, Unhandled); HANDLE(FWriteOp, Unhandled); HANDLE(FFlushOp, Unhandled); HANDLE(SystemFunctionOp, Unhandled); diff --git a/include/circt/Dialect/Sim/SimOps.td b/include/circt/Dialect/Sim/SimOps.td index 265ee5732c8a..4c8cd109052f 100644 --- a/include/circt/Dialect/Sim/SimOps.td +++ b/include/circt/Dialect/Sim/SimOps.td @@ -1212,6 +1212,20 @@ def TriggeredOp : SimOp<"triggered", [ ]; } +def DeferOp : SimOp<"defer", [ + SingleBlock, NoTerminator, ProceduralOp, ProceduralRegion]> { + let summary = "Execute a region at the end of the current simulation time"; + let description = [{ + Schedules its body for execution in the Postponed region of the current + simulation time. + }]; + let regions = (region SizedRegion<1>:$body); + let assemblyFormat = "$body attr-dict"; + let extraClassDeclaration = [{ + Block *getBodyBlock() { return &getBody().front(); } + }]; +} + //===----------------------------------------------------------------------===// // File I/O //===----------------------------------------------------------------------===// diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index e79666961ef1..2fe82b0c736b 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -4113,6 +4113,7 @@ class StmtEmitter : public EmitterBase, emitFormattedWriteLikeOp(OpTy op, StringRef callee, StringRef formatString, ValueRange substitutions, EmitPrefixFn emitPrefix); LogicalResult visitSV(WriteOp op); + LogicalResult visitSV(StrobeOp op); LogicalResult visitSV(FWriteOp op); LogicalResult visitSV(FFlushOp op); LogicalResult visitSV(VerbatimOp op); @@ -4682,6 +4683,12 @@ LogicalResult StmtEmitter::visitSV(WriteOp op) { [&](SmallPtrSetImpl &) {}); } +LogicalResult StmtEmitter::visitSV(StrobeOp op) { + return emitFormattedWriteLikeOp(op, "$strobe(", op.getFormatString(), + op.getSubstitutions(), + [&](SmallPtrSetImpl &) {}); +} + LogicalResult StmtEmitter::visitSV(FWriteOp op) { return emitFormattedWriteLikeOp(op, "$fwrite(", op.getFormatString(), op.getSubstitutions(), diff --git a/lib/Conversion/ImportVerilog/Statements.cpp b/lib/Conversion/ImportVerilog/Statements.cpp index 06b01251b933..8e2954a2f094 100644 --- a/lib/Conversion/ImportVerilog/Statements.cpp +++ b/lib/Conversion/ImportVerilog/Statements.cpp @@ -1006,11 +1006,12 @@ struct StmtVisitor { } // Display and Write Tasks (`$display[boh]?` or `$write[boh]?` or - // `$fdisplay[boh]?` or `$fwrite[boh]?`) + // `$fdisplay[boh]?` or `$fwrite[boh]? or `$strobe[boh]?`) using moore::IntFormat; bool isDisplay = false; bool isFDisplay = false; + bool isStrobe = false; bool appendNewline = false; IntFormat defaultFormat = IntFormat::Decimal; switch (nameId) { @@ -1082,6 +1083,25 @@ struct StmtVisitor { isFDisplay = true; defaultFormat = IntFormat::HexLower; break; + case ksn::Strobe: + isStrobe = true; + appendNewline = true; + break; + case ksn::StrobeB: + isStrobe = true; + appendNewline = true; + defaultFormat = IntFormat::Binary; + break; + case ksn::StrobeO: + isStrobe = true; + appendNewline = true; + defaultFormat = IntFormat::Octal; + break; + case ksn::StrobeH: + isStrobe = true; + appendNewline = true; + defaultFormat = IntFormat::HexLower; + break; default: break; } @@ -1116,6 +1136,17 @@ struct StmtVisitor { return true; } + if (isStrobe) { + auto message = + context.convertFormatString(args, loc, defaultFormat, appendNewline); + if (failed(message)) + return failure(); + if (*message == Value{}) + return true; + moore::StrobeBIOp::create(builder, loc, *message); + return true; + } + // Severity Tasks using moore::Severity; std::optional severity; diff --git a/lib/Conversion/MooreToCore/MooreToCore.cpp b/lib/Conversion/MooreToCore/MooreToCore.cpp index f7cbe2fb9999..90928bdd7567 100644 --- a/lib/Conversion/MooreToCore/MooreToCore.cpp +++ b/lib/Conversion/MooreToCore/MooreToCore.cpp @@ -2966,6 +2966,21 @@ struct FDisplayBIOpConversion : public OpConversionPattern { } }; +struct StrobeBIOpConversion : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult + matchAndRewrite(StrobeBIOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto loc = op.getLoc(); + auto deferOp = sim::DeferOp::create(rewriter, loc); + rewriter.createBlock(&deferOp.getBody()); + sim::PrintFormattedProcOp::create(rewriter, loc, adaptor.getMessage()); + rewriter.eraseOp(op); + return success(); + } +}; + struct FOpenBIOpConversion : public OpConversionPattern { using OpConversionPattern::OpConversionPattern; LogicalResult @@ -3575,6 +3590,7 @@ static void populateOpConversion(ConversionPatternSet &patterns, FormatRealOpConversion, DisplayBIOpConversion, FDisplayBIOpConversion, + StrobeBIOpConversion, // File I/O operations FOpenBIOpConversion, diff --git a/lib/Conversion/SimToSV/SimToSV.cpp b/lib/Conversion/SimToSV/SimToSV.cpp index f5cafb67a747..1e5671044d6a 100644 --- a/lib/Conversion/SimToSV/SimToSV.cpp +++ b/lib/Conversion/SimToSV/SimToSV.cpp @@ -815,6 +815,7 @@ LogicalResult lowerPrintFormattedProcToSV(hw::HWModuleOp module, SmallVector getFileOps; SmallVector printOps; SmallVector cleanupSeeds; + llvm::SetVector deferOpsToInline; module.walk([&](Operation *op) { if (auto getFileOp = dyn_cast(op)) getFileOps.push_back(getFileOp); @@ -848,8 +849,12 @@ LogicalResult lowerPrintFormattedProcToSV(hw::HWModuleOp module, << "while lowering format string"; return failure(); } + bool isDeferred = isa(printOp->getParentOp()); auto stream = printOp.getStream(); - if (!stream) { + if (isDeferred) { + sv::StrobeOp::create(builder, printOp.getLoc(), formatString, args); + deferOpsToInline.insert(cast(printOp->getParentOp())); + } else if (!stream) { // no stream is specified, emit sv.write. sv::WriteOp::create(builder, printOp.getLoc(), formatString, args); } else { @@ -865,6 +870,14 @@ LogicalResult lowerPrintFormattedProcToSV(hw::HWModuleOp module, cleanupDeadSimFmtOps(cleanupSeeds); + for (auto deferOp : deferOpsToInline) { + auto *body = deferOp.getBodyBlock(); + auto *parentBlock = deferOp->getBlock(); + parentBlock->getOperations().splice(Block::iterator(deferOp), + body->getOperations()); + deferOp.erase(); + } + return success(); } diff --git a/test/Conversion/ExportVerilog/sv-dialect.mlir b/test/Conversion/ExportVerilog/sv-dialect.mlir index 5c81ab042da5..012ab9ef3546 100644 --- a/test/Conversion/ExportVerilog/sv-dialect.mlir +++ b/test/Conversion/ExportVerilog/sv-dialect.mlir @@ -1998,6 +1998,23 @@ hw.module @write_task_test(in %clock : i1) { } } +// CHECK-LABEL: module strobe_task_test( +// CHECK: always_ff @(posedge clock) begin +// CHECK-NEXT: $strobe("stdout"); +// CHECK-NEXT: $strobe("%d", 32'h2A); +// CHECK-NEXT: $strobe("%d %d", 32'h80000001, 32'h80000002); +// CHECK-NEXT: end +hw.module @strobe_task_test(in %clock : i1) { + sv.alwaysff(posedge %clock) { + %c0 = hw.constant 42 : i32 + %c1 = hw.constant 0x80000001 : i32 + %c2 = hw.constant 0x80000002 : i32 + sv.strobe "stdout" + sv.strobe "%d"(%c0) : i32 + sv.strobe "%d %d"(%c1, %c2) : i32, i32 + } +} + // CHECK-LABEL: module fwrite_task_test( // CHECK: always_ff @(posedge clock) begin // CHECK-NEXT: $fwrite(32'h80000001, "stdout"); diff --git a/test/Conversion/ImportVerilog/builtins.sv b/test/Conversion/ImportVerilog/builtins.sv index c5554c6ab001..5f3bf7e5efdc 100644 --- a/test/Conversion/ImportVerilog/builtins.sv +++ b/test/Conversion/ImportVerilog/builtins.sv @@ -846,3 +846,42 @@ function void FileDisplayBuiltins(int fd, int x); $fdisplayh(fd, x); endfunction + +// IEEE 1800-2017 § 21.2.2 "Strobed monitoring" +// CHECK-LABEL: func.func private @StrobeBuiltins( +// CHECK-SAME: [[X:%.+]]: !moore.i32 +function void StrobeBuiltins(int x); + // CHECK: [[TMP:%.+]] = moore.fmt.literal "\0A" + // CHECK: moore.builtin.strobe [[TMP]] + $strobe; + + // CHECK: [[TMP1:%.+]] = moore.fmt.literal "hello" + // CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A" + // CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]]) + // CHECK: moore.builtin.strobe [[TMP3]] + $strobe("hello"); + + // CHECK: [[TMP1:%.+]] = moore.fmt.int decimal [[X]], align right, pad space signed : i32 + // CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A" + // CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]]) + // CHECK: moore.builtin.strobe [[TMP3]] + $strobe(x); + + // CHECK: [[TMP1:%.+]] = moore.fmt.int binary [[X]], align right, pad zero : i32 + // CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A" + // CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]]) + // CHECK: moore.builtin.strobe [[TMP3]] + $strobeb(x); + + // CHECK: [[TMP1:%.+]] = moore.fmt.int octal [[X]], align right, pad zero : i32 + // CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A" + // CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]]) + // CHECK: moore.builtin.strobe [[TMP3]] + $strobeo(x); + + // CHECK: [[TMP1:%.+]] = moore.fmt.int hex_lower [[X]], align right, pad zero : i32 + // CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A" + // CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]]) + // CHECK: moore.builtin.strobe [[TMP3]] + $strobeh(x); +endfunction diff --git a/test/Conversion/MooreToCore/basic.mlir b/test/Conversion/MooreToCore/basic.mlir index 12ef93971263..1308330e4f18 100644 --- a/test/Conversion/MooreToCore/basic.mlir +++ b/test/Conversion/MooreToCore/basic.mlir @@ -443,6 +443,18 @@ func.func @FormatStrings(%arg0: !moore.i42, %arg1: !moore.f32, %arg2: !moore.f64 return } +// CHECK-LABEL: func @StrobeToPrint +func.func @StrobeToPrint() { + // CHECK: [[TMP:%.+]] = sim.fmt.literal "hello\0A" + %0 = moore.fmt.literal "hello\0A" + + // CHECK-NEXT: sim.defer { + // CHECK-NEXT: sim.proc.print [[TMP]] + // CHECK-NEXT: } + moore.builtin.strobe %0 + return +} + // CHECK-LABEL: hw.module @InstanceNull() { moore.module @InstanceNull() { diff --git a/test/Conversion/SimToSV/lower-print-formatted-proc-to-sv.mlir b/test/Conversion/SimToSV/lower-print-formatted-proc-to-sv.mlir index 7453cb467515..38e841fed9eb 100644 --- a/test/Conversion/SimToSV/lower-print-formatted-proc-to-sv.mlir +++ b/test/Conversion/SimToSV/lower-print-formatted-proc-to-sv.mlir @@ -192,3 +192,21 @@ hw.module @print_to_file_under_condition(in %clk : i1, in %idx : i8, in %en : i1 } } } + +// CHECK-LABEL: hw.module @deferred_print +hw.module @deferred_print(in %clk : i1, in %arg : i8) { + hw.triggered posedge %clk (%arg) : i8 { + ^bb0(%arg_in : i8): + %l0 = sim.fmt.literal "val=" + %f0 = sim.fmt.dec %arg_in specifierWidth 3 : i8 + %msg = sim.fmt.concat (%l0, %f0) + + // CHECK: ^bb0(%[[ARG:.+]]: i8): + // CHECK-NEXT: %[[UNSIGNED:.+]] = sv.system "unsigned"(%[[ARG]]) : (i8) -> i8 + // CHECK-NEXT: sv.strobe "val=%3d"(%[[UNSIGNED]]) : i8 + // CHECK-NOT: sim.defer + sim.defer { + sim.proc.print %msg + } + } +}