Skip to content

Commit 5358f98

Browse files
committed
[SimToSV] Implement sim.proc.print lowering logic
AI-assisted-by: OpenAI ChatGPT
1 parent f5d48db commit 5358f98

File tree

3 files changed

+322
-0
lines changed

3 files changed

+322
-0
lines changed

lib/Conversion/SimToSV/SimToSV.cpp

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include "mlir/IR/Threading.h"
2626
#include "mlir/Pass/Pass.h"
2727
#include "mlir/Transforms/DialectConversion.h"
28+
#include "mlir/Transforms/RegionUtils.h"
29+
#include "llvm/ADT/SmallPtrSet.h"
2830

2931
#define DEBUG_TYPE "lower-sim-to-sv"
3032

@@ -55,6 +57,162 @@ static std::pair<Value, Value> needsClockAndConditionWrapper(Operation *op) {
5557

5658
namespace {
5759

60+
static void appendLiteralToFWriteFormat(SmallString<128> &formatString,
61+
StringRef literal) {
62+
for (char ch : literal) {
63+
if (ch == '%')
64+
formatString += "%%";
65+
else
66+
formatString.push_back(ch);
67+
}
68+
}
69+
70+
static LogicalResult appendIntegerSpecifier(SmallString<128> &formatString,
71+
bool isLeftAligned,
72+
uint8_t paddingChar,
73+
std::optional<int32_t> width,
74+
char spec) {
75+
formatString.push_back('%');
76+
if (isLeftAligned)
77+
formatString.push_back('-');
78+
79+
// SystemVerilog formatting only has built-in support for '0' and ' '. Keep
80+
// this lowering strict to avoid silently changing formatting semantics.
81+
if (paddingChar == '0') {
82+
formatString.push_back('0');
83+
} else if (paddingChar != ' ') {
84+
return failure();
85+
}
86+
87+
if (width.has_value())
88+
formatString += std::to_string(width.value());
89+
90+
formatString.push_back(spec);
91+
return success();
92+
}
93+
94+
static void appendFloatSpecifier(SmallString<128> &formatString,
95+
bool isLeftAligned,
96+
std::optional<int32_t> fieldWidth,
97+
int32_t fracDigits, char spec) {
98+
formatString.push_back('%');
99+
if (isLeftAligned)
100+
formatString.push_back('-');
101+
if (fieldWidth.has_value())
102+
formatString += std::to_string(fieldWidth.value());
103+
formatString.push_back('.');
104+
formatString += std::to_string(fracDigits);
105+
formatString.push_back(spec);
106+
}
107+
108+
static LogicalResult foldFormatStringToFWrite(
109+
Value input, SmallString<128> &formatString, SmallVectorImpl<Value> &args,
110+
llvm::SmallPtrSetImpl<Operation *> &visited, std::string &failureReason) {
111+
Operation *op = input.getDefiningOp();
112+
if (!op) {
113+
failureReason =
114+
"block argument format strings are unsupported as sim.proc.print input";
115+
return failure();
116+
}
117+
118+
if (auto concat = dyn_cast<FormatStringConcatOp>(op)) {
119+
if (!visited.insert(op).second) {
120+
failureReason = "cyclic sim.fmt.concat is unsupported";
121+
return failure();
122+
}
123+
for (auto operand : concat.getInputs())
124+
if (failed(foldFormatStringToFWrite(operand, formatString, args, visited,
125+
failureReason)))
126+
return failure();
127+
visited.erase(op);
128+
return success();
129+
}
130+
131+
return TypeSwitch<Operation *, LogicalResult>(op)
132+
.Case<FormatLiteralOp>([&](auto literal) -> LogicalResult {
133+
appendLiteralToFWriteFormat(formatString, literal.getLiteral());
134+
return success();
135+
})
136+
.Case<FormatHierPathOp>([&](auto hierPath) -> LogicalResult {
137+
formatString += hierPath.getUseEscapes() ? "%M" : "%m";
138+
return success();
139+
})
140+
.Case<FormatCharOp>([&](auto fmt) -> LogicalResult {
141+
formatString += "%c";
142+
args.push_back(fmt.getValue());
143+
return success();
144+
})
145+
.Case<FormatDecOp>([&](auto fmt) -> LogicalResult {
146+
if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
147+
fmt.getPaddingChar(),
148+
fmt.getSpecifierWidth(), 'd'))) {
149+
failureReason = "sim.fmt.dec only supports paddingChar 32 (' ') or "
150+
"48 ('0') for SystemVerilog lowering";
151+
return failure();
152+
}
153+
args.push_back(fmt.getValue());
154+
return success();
155+
})
156+
.Case<FormatHexOp>([&](auto fmt) -> LogicalResult {
157+
if (failed(appendIntegerSpecifier(
158+
formatString, fmt.getIsLeftAligned(), fmt.getPaddingChar(),
159+
fmt.getSpecifierWidth(),
160+
fmt.getIsHexUppercase() ? 'X' : 'x'))) {
161+
failureReason = "sim.fmt.hex only supports paddingChar 32 (' ') or "
162+
"48 ('0') for SystemVerilog lowering";
163+
return failure();
164+
}
165+
args.push_back(fmt.getValue());
166+
return success();
167+
})
168+
.Case<FormatOctOp>([&](auto fmt) -> LogicalResult {
169+
if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
170+
fmt.getPaddingChar(),
171+
fmt.getSpecifierWidth(), 'o'))) {
172+
failureReason = "sim.fmt.oct only supports paddingChar 32 (' ') or "
173+
"48 ('0') for SystemVerilog lowering";
174+
return failure();
175+
}
176+
args.push_back(fmt.getValue());
177+
return success();
178+
})
179+
.Case<FormatBinOp>([&](auto fmt) -> LogicalResult {
180+
if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
181+
fmt.getPaddingChar(),
182+
fmt.getSpecifierWidth(), 'b'))) {
183+
failureReason = "sim.fmt.bin only supports paddingChar 32 (' ') or "
184+
"48 ('0') for SystemVerilog lowering";
185+
return failure();
186+
}
187+
args.push_back(fmt.getValue());
188+
return success();
189+
})
190+
.Case<FormatScientificOp>([&](auto fmt) -> LogicalResult {
191+
appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
192+
fmt.getFieldWidth(), fmt.getFracDigits(), 'e');
193+
args.push_back(fmt.getValue());
194+
return success();
195+
})
196+
.Case<FormatFloatOp>([&](auto fmt) -> LogicalResult {
197+
appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
198+
fmt.getFieldWidth(), fmt.getFracDigits(), 'f');
199+
args.push_back(fmt.getValue());
200+
return success();
201+
})
202+
.Case<FormatGeneralOp>([&](auto fmt) -> LogicalResult {
203+
appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
204+
fmt.getFieldWidth(), fmt.getFracDigits(), 'g');
205+
args.push_back(fmt.getValue());
206+
return success();
207+
})
208+
.Default([&](auto unsupportedOp) {
209+
failureReason = (Twine("unsupported format fragment '") +
210+
unsupportedOp->getName().getStringRef() + "'")
211+
.str();
212+
return failure();
213+
});
214+
}
215+
58216
struct SimConversionState {
59217
hw::HWModuleOp module;
60218
bool usedSynthesisMacro = false;
@@ -281,6 +439,62 @@ class DPICallLowering : public SimConversionPattern<DPICallOp> {
281439
}
282440
};
283441

442+
namespace {
443+
class PrintFormattedProcLowering
444+
: public SimConversionPattern<PrintFormattedProcOp> {
445+
public:
446+
using SimConversionPattern<PrintFormattedProcOp>::SimConversionPattern;
447+
448+
LogicalResult
449+
matchAndRewrite(PrintFormattedProcOp op, OpAdaptor adaptor,
450+
ConversionPatternRewriter &rewriter) const final {
451+
SmallString<128> formatString;
452+
SmallVector<Value> args;
453+
std::string failureReason;
454+
llvm::SmallPtrSet<Operation *, 8> visited;
455+
if (failed(foldFormatStringToFWrite(op.getInput(), formatString, args,
456+
visited, failureReason))) {
457+
auto diag = op.emitError("cannot lower 'sim.proc.print' to sv.fwrite: ");
458+
diag << failureReason;
459+
return failure();
460+
}
461+
462+
// Align with FIRRTLToHW: default to writing to stderr.
463+
// Specifying an output stream is not currently supported.
464+
auto fd =
465+
hw::ConstantOp::create(rewriter, op.getLoc(), APInt(32, 0x80000002));
466+
sv::FWriteOp::create(rewriter, op.getLoc(), fd, formatString, args);
467+
468+
rewriter.eraseOp(op);
469+
return success();
470+
}
471+
};
472+
473+
static LogicalResult validatePrintLowering(hw::HWModuleOp module) {
474+
LogicalResult result = success();
475+
module.walk([&](PrintFormattedOp op) {
476+
op.emitError("cannot lower 'sim.print' directly to SystemVerilog; run "
477+
"--sim-proceduralize first to convert it to "
478+
"'sim.proc.print'");
479+
result = failure();
480+
});
481+
482+
module.walk([&](PrintFormattedProcOp op) {
483+
SmallString<128> formatString;
484+
SmallVector<Value> args;
485+
std::string failureReason;
486+
llvm::SmallPtrSet<Operation *, 8> visited;
487+
if (failed(foldFormatStringToFWrite(op.getInput(), formatString, args,
488+
visited, failureReason))) {
489+
auto diag = op.emitError("cannot lower 'sim.proc.print' to sv.fwrite: ");
490+
diag << failureReason;
491+
result = failure();
492+
}
493+
});
494+
return result;
495+
}
496+
}; // namespace
497+
284498
// A helper struct to lower DPI function/call.
285499
struct LowerDPIFunc {
286500
llvm::DenseMap<StringAttr, StringAttr> symbolToFragment;
@@ -480,9 +694,16 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
480694
if (moveOpsIntoIfdefGuardsAndProcesses(module))
481695
usedSynthesisMacro = true;
482696

697+
if (failed(validatePrintLowering(module)))
698+
return failure();
699+
483700
SimConversionState state;
484701
ConversionTarget target(*context);
485702
target.addIllegalDialect<SimDialect>();
703+
target.addLegalOp<FormatLiteralOp, FormatHierPathOp, FormatCharOp,
704+
FormatDecOp, FormatHexOp, FormatOctOp, FormatBinOp,
705+
FormatScientificOp, FormatFloatOp, FormatGeneralOp,
706+
FormatStringConcatOp>();
486707
target.addLegalDialect<sv::SVDialect>();
487708
target.addLegalDialect<hw::HWDialect>();
488709
target.addLegalDialect<seq::SeqDialect>();
@@ -496,11 +717,15 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
496717
patterns.add<TerminateOp>(convert);
497718
patterns.add<PauseOp>(convert);
498719
patterns.add<DPICallLowering>(context, state);
720+
patterns.add<PrintFormattedProcLowering>(context, state);
499721
auto result = applyPartialConversion(module, target, std::move(patterns));
500722

501723
if (failed(result))
502724
return result;
503725

726+
mlir::IRRewriter rewriter(module);
727+
(void)mlir::runRegionDCE(rewriter, module->getRegions());
728+
504729
// Set the emit fragment.
505730
lowerDPIFunc.addFragments(module, state.dpiCallees.takeVector());
506731

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: circt-opt --lower-sim-to-sv --split-input-file --verify-diagnostics %s
2+
3+
hw.module @unsupported_padding_char(in %arg : i8) {
4+
sv.initial {
5+
%lit = sim.fmt.literal "bad="
6+
%bad = sim.fmt.dec %arg paddingChar 42 specifierWidth 2 : i8
7+
%msg = sim.fmt.concat (%lit, %bad)
8+
// expected-error @below {{cannot lower 'sim.proc.print' to sv.fwrite: sim.fmt.dec only supports paddingChar 32 (' ') or 48 ('0') for SystemVerilog lowering}}
9+
sim.proc.print %msg
10+
}
11+
}
12+
13+
// -----
14+
15+
hw.module @unsupported_input_block_argument(in %arg : !sim.fstring) {
16+
sv.initial {
17+
// expected-error @below {{cannot lower 'sim.proc.print' to sv.fwrite: block argument format strings are unsupported as sim.proc.print input}}
18+
sim.proc.print %arg
19+
}
20+
}
21+
22+
// -----
23+
24+
hw.module @sim_print_requires_proceduralize(in %clk : !seq.clock, in %cond : i1) {
25+
%lit = sim.fmt.literal "hello"
26+
// expected-error @below {{cannot lower 'sim.print' directly to SystemVerilog; run --sim-proceduralize first to convert it to 'sim.proc.print'}}
27+
sim.print %lit on %clk if %cond
28+
}

test/Conversion/SimToSV/print.mlir

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// RUN: circt-opt --lower-sim-to-sv %s | FileCheck %s
2+
3+
// CHECK-LABEL: hw.module @proc_print
4+
hw.module @proc_print(in %arg: i8) {
5+
// CHECK: sv.initial {
6+
sv.initial {
7+
%l0 = sim.fmt.literal "err: "
8+
%f0 = sim.fmt.hex %arg, isUpper true specifierWidth 2 : i8
9+
%l1 = sim.fmt.literal " 100%"
10+
%msg = sim.fmt.concat (%l0, %f0, %l1)
11+
12+
// CHECK-NEXT: %[[FD:.+]] = hw.constant -2147483646 : i32
13+
// CHECK-NEXT: sv.fwrite %[[FD]], "err: %02X 100%%"(%arg) : i8
14+
// CHECK-NOT: sim.fmt.
15+
sim.proc.print %msg
16+
}
17+
}
18+
19+
// CHECK-LABEL: hw.module @all_format_fragments
20+
hw.module @all_format_fragments(in %ival : i16, in %ch : i8, in %fval : f64) {
21+
sv.initial {
22+
%i0 = sim.fmt.literal "dec="
23+
%f0 = sim.fmt.dec %ival specifierWidth 6 signed : i16
24+
%i1 = sim.fmt.literal " hex="
25+
%f1 = sim.fmt.hex %ival, isUpper true paddingChar 48 specifierWidth 4 : i16
26+
%i2 = sim.fmt.literal " oct="
27+
%f2 = sim.fmt.oct %ival isLeftAligned true specifierWidth 6 : i16
28+
%i3 = sim.fmt.literal " bin="
29+
%f3 = sim.fmt.bin %ival paddingChar 32 specifierWidth 8 : i16
30+
%i4 = sim.fmt.literal " char="
31+
%f4 = sim.fmt.char %ch : i8
32+
%i5 = sim.fmt.literal " exp="
33+
%f5 = sim.fmt.exp %fval fieldWidth 10 fracDigits 3 : f64
34+
%i6 = sim.fmt.literal " flt="
35+
%f6 = sim.fmt.flt %fval isLeftAligned true fieldWidth 8 fracDigits 2 : f64
36+
%i7 = sim.fmt.literal " gen="
37+
%f7 = sim.fmt.gen %fval fracDigits 4 : f64
38+
%i8 = sim.fmt.literal " path="
39+
%f8 = sim.fmt.hier_path
40+
%i9 = sim.fmt.literal " esc="
41+
%f9 = sim.fmt.hier_path escaped
42+
%i10 = sim.fmt.literal " pct=%"
43+
%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)
44+
45+
// CHECK: %[[FD:.+]] = hw.constant -2147483646 : i32
46+
// CHECK-NEXT: sv.fwrite %[[FD]], "dec=%6d hex=%04X oct=%-06o bin=%8b char=%c exp=%10.3e flt=%-8.2f gen=%.4g path=%m esc=%M pct=%%"(%ival, %ival, %ival, %ival, %ch, %fval, %fval, %fval) : i16, i16, i16, i16, i8, f64, f64, f64
47+
// CHECK-NOT: sim.fmt.
48+
sim.proc.print %msg
49+
}
50+
}
51+
52+
// CHECK-LABEL: hw.module @nested_concat_order
53+
hw.module @nested_concat_order(in %lhs : i8, in %rhs : i8) {
54+
sv.initial {
55+
%l0 = sim.fmt.literal "L="
56+
%l1 = sim.fmt.literal ", R="
57+
%d0 = sim.fmt.dec %lhs specifierWidth 3 : i8
58+
%h0 = sim.fmt.hex %rhs, isUpper false specifierWidth 2 : i8
59+
60+
%c0 = sim.fmt.concat (%l0, %d0)
61+
%c1 = sim.fmt.concat (%c0, %l1)
62+
%c2 = sim.fmt.concat (%c1, %h0)
63+
64+
// CHECK: %[[FD:.+]] = hw.constant -2147483646 : i32
65+
// CHECK-NEXT: sv.fwrite %[[FD]], "L=%3d, R=%02x"(%lhs, %rhs) : i8, i8
66+
// CHECK-NOT: sim.fmt.
67+
sim.proc.print %c2
68+
}
69+
}

0 commit comments

Comments
 (0)