Skip to content

Commit ee209ff

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

File tree

5 files changed

+350
-0
lines changed

5 files changed

+350
-0
lines changed

include/circt/Dialect/Sim/SimPasses.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,10 @@ def LowerDPIFunc : Pass<"sim-lower-dpi-func", "mlir::ModuleOp"> {
3232
let dependentDialects = ["mlir::func::FuncDialect", "mlir::LLVM::LLVMDialect"];
3333
}
3434

35+
def LowerPrintFormattedProcToSV
36+
: Pass<"sim-lower-print-formatted-proc-to-sv", "hw::HWModuleOp"> {
37+
let summary = "Lower sim.proc.print formatting ops to sv.fwrite";
38+
let dependentDialects = ["circt::hw::HWDialect", "circt::sv::SVDialect"];
39+
}
40+
3541
#endif // CIRCT_DIALECT_SIM_SEQPASSES

lib/Dialect/Sim/Transforms/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
add_circt_dialect_library(CIRCTSimTransforms
22
FoldValueFormatters.cpp
33
LowerDPIFunc.cpp
4+
LowerPrintFormattedProcToSV.cpp
45
ProceduralizeSim.cpp
56

67

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
//===- LowerPrintFormattedProcToSV.cpp - Lower proc.print to sv.fwrite ---===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This pass lowers sim.proc.print to sv.fwrite.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "circt/Dialect/HW/HWOps.h"
14+
#include "circt/Dialect/SV/SVOps.h"
15+
#include "circt/Dialect/Sim/SimOps.h"
16+
#include "circt/Dialect/Sim/SimPasses.h"
17+
#include "mlir/IR/Builders.h"
18+
#include "mlir/IR/PatternMatch.h"
19+
#include "mlir/Pass/Pass.h"
20+
#include "mlir/Transforms/RegionUtils.h"
21+
#include "llvm/ADT/SmallPtrSet.h"
22+
23+
#define DEBUG_TYPE "sim-lower-print-formatted-proc-to-sv"
24+
25+
namespace circt {
26+
namespace sim {
27+
#define GEN_PASS_DEF_LOWERPRINTFORMATTEDPROCTOSV
28+
#include "circt/Dialect/Sim/SimPasses.h.inc"
29+
} // namespace sim
30+
} // namespace circt
31+
32+
using namespace circt;
33+
using namespace sim;
34+
35+
namespace {
36+
37+
static void appendLiteralToFWriteFormat(SmallString<128> &formatString,
38+
StringRef literal) {
39+
for (char ch : literal) {
40+
if (ch == '%')
41+
formatString += "%%";
42+
else
43+
formatString.push_back(ch);
44+
}
45+
}
46+
47+
static LogicalResult appendIntegerSpecifier(SmallString<128> &formatString,
48+
bool isLeftAligned,
49+
uint8_t paddingChar,
50+
std::optional<int32_t> width,
51+
char spec) {
52+
formatString.push_back('%');
53+
if (isLeftAligned)
54+
formatString.push_back('-');
55+
56+
// SystemVerilog formatting only has built-in support for '0' and ' '. Keep
57+
// this lowering strict to avoid silently changing formatting semantics.
58+
if (paddingChar == '0') {
59+
formatString.push_back('0');
60+
} else if (paddingChar != ' ') {
61+
return failure();
62+
}
63+
64+
if (width.has_value())
65+
formatString += std::to_string(width.value());
66+
67+
formatString.push_back(spec);
68+
return success();
69+
}
70+
71+
static void appendFloatSpecifier(SmallString<128> &formatString,
72+
bool isLeftAligned,
73+
std::optional<int32_t> fieldWidth,
74+
int32_t fracDigits, char spec) {
75+
formatString.push_back('%');
76+
if (isLeftAligned)
77+
formatString.push_back('-');
78+
if (fieldWidth.has_value())
79+
formatString += std::to_string(fieldWidth.value());
80+
formatString.push_back('.');
81+
formatString += std::to_string(fracDigits);
82+
formatString.push_back(spec);
83+
}
84+
85+
static LogicalResult
86+
getFlattenedFormatFragments(Value input, SmallVectorImpl<Value> &fragments,
87+
std::string &failureReason) {
88+
if (auto concat = input.getDefiningOp<FormatStringConcatOp>()) {
89+
if (failed(concat.getFlattenedInputs(fragments))) {
90+
failureReason = "cyclic sim.fmt.concat is unsupported";
91+
return failure();
92+
}
93+
return success();
94+
}
95+
96+
fragments.push_back(input);
97+
return success();
98+
}
99+
100+
static LogicalResult
101+
appendFormatFragmentToFWrite(Value fragment, SmallString<128> &formatString,
102+
SmallVectorImpl<Value> &args,
103+
std::string &failureReason) {
104+
Operation *fragmentOp = fragment.getDefiningOp();
105+
if (!fragmentOp) {
106+
failureReason =
107+
"block argument format strings are unsupported as sim.proc.print input";
108+
return failure();
109+
}
110+
111+
return TypeSwitch<Operation *, LogicalResult>(fragmentOp)
112+
.Case<FormatLiteralOp>([&](auto literal) -> LogicalResult {
113+
appendLiteralToFWriteFormat(formatString, literal.getLiteral());
114+
return success();
115+
})
116+
.Case<FormatHierPathOp>([&](auto hierPath) -> LogicalResult {
117+
formatString += hierPath.getUseEscapes() ? "%M" : "%m";
118+
return success();
119+
})
120+
.Case<FormatCharOp>([&](auto fmt) -> LogicalResult {
121+
formatString += "%c";
122+
args.push_back(fmt.getValue());
123+
return success();
124+
})
125+
.Case<FormatDecOp>([&](auto fmt) -> LogicalResult {
126+
if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
127+
fmt.getPaddingChar(),
128+
fmt.getSpecifierWidth(), 'd'))) {
129+
failureReason = "sim.fmt.dec only supports paddingChar 32 (' ') or "
130+
"48 ('0') for SystemVerilog lowering";
131+
return failure();
132+
}
133+
args.push_back(fmt.getValue());
134+
return success();
135+
})
136+
.Case<FormatHexOp>([&](auto fmt) -> LogicalResult {
137+
if (failed(appendIntegerSpecifier(
138+
formatString, fmt.getIsLeftAligned(), fmt.getPaddingChar(),
139+
fmt.getSpecifierWidth(),
140+
fmt.getIsHexUppercase() ? 'X' : 'x'))) {
141+
failureReason = "sim.fmt.hex only supports paddingChar 32 (' ') or "
142+
"48 ('0') for SystemVerilog lowering";
143+
return failure();
144+
}
145+
args.push_back(fmt.getValue());
146+
return success();
147+
})
148+
.Case<FormatOctOp>([&](auto fmt) -> LogicalResult {
149+
if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
150+
fmt.getPaddingChar(),
151+
fmt.getSpecifierWidth(), 'o'))) {
152+
failureReason = "sim.fmt.oct only supports paddingChar 32 (' ') or "
153+
"48 ('0') for SystemVerilog lowering";
154+
return failure();
155+
}
156+
args.push_back(fmt.getValue());
157+
return success();
158+
})
159+
.Case<FormatBinOp>([&](auto fmt) -> LogicalResult {
160+
if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
161+
fmt.getPaddingChar(),
162+
fmt.getSpecifierWidth(), 'b'))) {
163+
failureReason = "sim.fmt.bin only supports paddingChar 32 (' ') or "
164+
"48 ('0') for SystemVerilog lowering";
165+
return failure();
166+
}
167+
args.push_back(fmt.getValue());
168+
return success();
169+
})
170+
.Case<FormatScientificOp>([&](auto fmt) -> LogicalResult {
171+
appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
172+
fmt.getFieldWidth(), fmt.getFracDigits(), 'e');
173+
args.push_back(fmt.getValue());
174+
return success();
175+
})
176+
.Case<FormatFloatOp>([&](auto fmt) -> LogicalResult {
177+
appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
178+
fmt.getFieldWidth(), fmt.getFracDigits(), 'f');
179+
args.push_back(fmt.getValue());
180+
return success();
181+
})
182+
.Case<FormatGeneralOp>([&](auto fmt) -> LogicalResult {
183+
appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
184+
fmt.getFieldWidth(), fmt.getFracDigits(), 'g');
185+
args.push_back(fmt.getValue());
186+
return success();
187+
})
188+
.Default([&](auto unsupportedOp) {
189+
failureReason = (Twine("unsupported format fragment '") +
190+
unsupportedOp->getName().getStringRef() + "'")
191+
.str();
192+
return failure();
193+
});
194+
}
195+
196+
static LogicalResult foldFormatStringToFWrite(Value input,
197+
SmallString<128> &formatString,
198+
SmallVectorImpl<Value> &args,
199+
std::string &failureReason) {
200+
SmallVector<Value, 8> fragments;
201+
if (failed(getFlattenedFormatFragments(input, fragments, failureReason)))
202+
return failure();
203+
for (auto fragment : fragments)
204+
if (failed(appendFormatFragmentToFWrite(fragment, formatString, args,
205+
failureReason)))
206+
return failure();
207+
return success();
208+
}
209+
210+
struct LowerPrintFormattedProcToSVPass
211+
: public impl::LowerPrintFormattedProcToSVBase<
212+
LowerPrintFormattedProcToSVPass> {
213+
void runOnOperation() override {
214+
auto module = getOperation();
215+
216+
bool sawError = false;
217+
SmallVector<PrintFormattedProcOp> printOps;
218+
module.walk([&](PrintFormattedProcOp op) { printOps.push_back(op); });
219+
llvm::SmallPtrSet<Operation *, 8> dceRoots;
220+
221+
for (auto printOp : printOps) {
222+
SmallString<128> formatString;
223+
SmallVector<Value> args;
224+
std::string failureReason;
225+
if (failed(foldFormatStringToFWrite(printOp.getInput(), formatString,
226+
args, failureReason))) {
227+
auto diag =
228+
printOp.emitError("cannot lower 'sim.proc.print' to sv.fwrite: ");
229+
diag << failureReason;
230+
sawError = true;
231+
continue;
232+
}
233+
234+
OpBuilder builder(printOp);
235+
// Align with FIRRTLToHW: default to writing to stderr.
236+
// Specifying an output stream is not currently supported.
237+
auto fd = hw::ConstantOp::create(builder, printOp.getLoc(),
238+
APInt(32, 0x80000002));
239+
sv::FWriteOp::create(builder, printOp.getLoc(), fd, formatString, args);
240+
if (Operation *parent = printOp->getParentOp())
241+
dceRoots.insert(parent);
242+
printOp.erase();
243+
}
244+
245+
if (sawError)
246+
return signalPassFailure();
247+
248+
mlir::IRRewriter rewriter(module);
249+
for (Operation *dceRoot : dceRoots)
250+
(void)mlir::runRegionDCE(rewriter, dceRoot->getRegions());
251+
}
252+
};
253+
254+
} // namespace
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: circt-opt --sim-lower-print-formatted-proc-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+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// RUN: circt-opt --sim-lower-print-formatted-proc-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)