Skip to content

Commit 712394b

Browse files
committed
[Sim] Implement the lowering logic from sim.proc.print to the SV dialect.
AI-assisted-by: OpenAI ChatGPT
1 parent 9ed5229 commit 712394b

3 files changed

Lines changed: 373 additions & 0 deletions

File tree

lib/Conversion/SimToSV/SimToSV.cpp

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
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

467468
namespace {
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+
468692
struct 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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// RUN: circt-opt --lower-sim-to-sv --split-input-file --verify-diagnostics %s
2+
3+
hw.module @unsupported_padding_char(in %clk : i1, in %arg : i8) {
4+
hw.triggered posedge %clk (%arg) : i8 {
5+
^bb0(%arg_in : i8):
6+
%lit = sim.fmt.literal "bad="
7+
// expected-error @below {{cannot lower 'sim.proc.print' to sv.write: sim.fmt.dec only supports paddingChar 32 (' ') or 48 ('0') for SystemVerilog lowering}}
8+
%bad = sim.fmt.dec %arg_in paddingChar 42 specifierWidth 2 : i8
9+
%msg = sim.fmt.concat (%lit, %bad)
10+
sim.proc.print %msg
11+
}
12+
}
13+
14+
// -----
15+
16+
hw.module @unsupported_input_block_argument(in %clk : i1, in %arg : !sim.fstring) {
17+
hw.triggered posedge %clk (%arg) : !sim.fstring {
18+
// expected-error @below {{cannot lower 'sim.proc.print' to sv.write: block argument format strings are unsupported as sim.proc.print input}}
19+
^bb0(%arg_in : !sim.fstring):
20+
sim.proc.print %arg_in
21+
}
22+
}
23+
24+
// -----
25+
26+
hw.module @unsupported_stream_block_argument(
27+
in %clk : i1, in %stream : !sim.output_stream) {
28+
hw.triggered posedge %clk (%stream) : !sim.output_stream {
29+
^bb0(%stream_in : !sim.output_stream):
30+
%fmt = sim.fmt.literal "x"
31+
// expected-error @below {{lowering 'sim.proc.print' with a stream is not supported yet}}
32+
sim.proc.print %fmt to %stream_in
33+
}
34+
}
35+
36+
// -----
37+
38+
hw.module @unsupported_stream_from_get_file() {
39+
%literal = sim.fmt.literal "stream.log"
40+
// expected-error @below {{'sim.proc.print' op must not be in a non-procedural region}}
41+
sim.proc.print %literal
42+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// RUN: circt-opt --lower-sim-to-sv %s | FileCheck %s
2+
3+
// This pass assumes sim.proc.print is already in some procedural region.
4+
// It lowers both SV and non-SV procedural containers (e.g. hw.triggered).
5+
6+
// CHECK-LABEL: hw.module @proc_print
7+
hw.module @proc_print(in %clk : i1, in %arg: i8) {
8+
// CHECK: hw.triggered posedge %clk
9+
hw.triggered posedge %clk (%arg) : i8 {
10+
^bb0(%arg_in : i8):
11+
%l0 = sim.fmt.literal "err: "
12+
%f0 = sim.fmt.hex %arg_in, isUpper true specifierWidth 2 : i8
13+
%l1 = sim.fmt.literal " 100%"
14+
%msg = sim.fmt.concat (%l0, %f0, %l1)
15+
16+
// CHECK: sv.write "err: %02X 100%%"
17+
sim.proc.print %msg
18+
}
19+
}
20+
21+
22+
// CHECK-LABEL: hw.module @all_format_fragments
23+
hw.module @all_format_fragments(
24+
in %clk : i1, in %ival : i16, in %ch : i8, in %fval : f64) {
25+
hw.triggered posedge %clk (%ival, %ch, %fval) : i16, i8, f64 {
26+
^bb0(%ival_in : i16, %ch_in : i8, %fval_in : f64):
27+
%i0 = sim.fmt.literal "dec="
28+
%f0 = sim.fmt.dec %ival_in specifierWidth 6 signed : i16
29+
%i1 = sim.fmt.literal " hex="
30+
%f1 = sim.fmt.hex %ival_in, isUpper true paddingChar 48 specifierWidth 4 : i16
31+
%i2 = sim.fmt.literal " oct="
32+
%f2 = sim.fmt.oct %ival_in isLeftAligned true specifierWidth 6 : i16
33+
%i3 = sim.fmt.literal " bin="
34+
%f3 = sim.fmt.bin %ival_in paddingChar 32 specifierWidth 8 : i16
35+
%i4 = sim.fmt.literal " char="
36+
%f4 = sim.fmt.char %ch_in : i8
37+
%i5 = sim.fmt.literal " exp="
38+
%f5 = sim.fmt.exp %fval_in fieldWidth 10 fracDigits 3 : f64
39+
%i6 = sim.fmt.literal " flt="
40+
%f6 = sim.fmt.flt %fval_in isLeftAligned true fieldWidth 8 fracDigits 2 : f64
41+
%i7 = sim.fmt.literal " gen="
42+
%f7 = sim.fmt.gen %fval_in fracDigits 4 : f64
43+
%i8 = sim.fmt.literal " path="
44+
%f8 = sim.fmt.hier_path
45+
%i9 = sim.fmt.literal " esc="
46+
%f9 = sim.fmt.hier_path escaped
47+
%i10 = sim.fmt.literal " pct=%"
48+
%msg = sim.fmt.concat (%i0, %f0, %i1, %f1, %i2, %f2, %i3, %f3, %i4, %f4, %i5, %f5, %i6, %f6, %i7, %f7, %i8, %f8, %i9, %f9, %i10)
49+
50+
// CHECK: ^bb0(%[[IVAL:.+]]: i16, %[[CH:.+]]: i8, %[[FVAL:.+]]: f64):
51+
// CHECK-NEXT: %[[SIGNED:.+]] = sv.system "signed"(%[[IVAL]]) : (i16) -> i16
52+
// CHECK-NEXT: sv.write "dec=%6d hex=%04X oct=%-06o bin=%8b char=%c exp=%10.3e flt=%-8.2f gen=%.4g path=%m esc=%M pct=%%"(%[[SIGNED]], %[[IVAL]], %[[IVAL]], %[[IVAL]], %[[CH]], %[[FVAL]], %[[FVAL]], %[[FVAL]]) : i16, i16, i16, i16, i8, f64, f64, f64
53+
sim.proc.print %msg
54+
}
55+
}
56+
57+
// CHECK-LABEL: hw.module @nested_concat_order
58+
hw.module @nested_concat_order(in %clk : i1, in %lhs : i8, in %rhs : i8) {
59+
hw.triggered posedge %clk (%lhs, %rhs) : i8, i8 {
60+
^bb0(%lhs_in : i8, %rhs_in : i8):
61+
%l0 = sim.fmt.literal "L="
62+
%l1 = sim.fmt.literal ", R="
63+
%d0 = sim.fmt.dec %lhs_in specifierWidth 3 : i8
64+
%h0 = sim.fmt.hex %rhs_in, isUpper false specifierWidth 2 : i8
65+
66+
%c0 = sim.fmt.concat (%l0, %d0)
67+
%c1 = sim.fmt.concat (%c0, %l1)
68+
%c2 = sim.fmt.concat (%c1, %h0)
69+
70+
// CHECK: ^bb0(%[[LHS:.+]]: i8, %[[RHS:.+]]: i8):
71+
// CHECK-NEXT: %[[UNSIGNED:.+]] = sv.system "unsigned"(%[[LHS]]) : (i8) -> i8
72+
// CHECK-NEXT: sv.write "L=%3d, R=%02x"(%[[UNSIGNED]], %[[RHS]]) : i8, i8
73+
sim.proc.print %c2
74+
}
75+
}
76+
77+
// CHECK-LABEL: hw.module @dce_uses_outer_procedural_root
78+
hw.module @dce_uses_outer_procedural_root(
79+
in %clk : i1, in %cond : i1, in %val : i8) {
80+
// CHECK: hw.triggered posedge %clk
81+
hw.triggered posedge %clk (%cond, %val) : i1, i8 {
82+
^bb0(%cond_in : i1, %val_in : i8):
83+
%lit = sim.fmt.literal "v="
84+
%fmt = sim.fmt.dec %val_in : i8
85+
%msg = sim.fmt.concat (%lit, %fmt)
86+
// CHECK: ^bb0(%[[COND:.+]]: i1, %[[VAL:.+]]: i8):
87+
// CHECK: scf.if %[[COND]] {
88+
// CHECK: %[[UNSIGNED:.+]] = sv.system "unsigned"(%[[VAL]]) : (i8) -> i8
89+
// CHECK-NEXT: sv.write "v=%d"(%[[UNSIGNED]]) : i8
90+
scf.if %cond_in {
91+
sim.proc.print %msg
92+
}
93+
}
94+
}
95+
96+
hw.module @triggered_print(in %clk : i1) {
97+
// CHECK-LABEL: hw.module @triggered_print
98+
hw.triggered posedge %clk {
99+
%lit = sim.fmt.literal "hello"
100+
// CHECK: sv.write "hello"
101+
sim.proc.print %lit
102+
}
103+
}

0 commit comments

Comments
 (0)