Skip to content

Commit 19f96ea

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 19f96ea

File tree

5 files changed

+404
-0
lines changed

5 files changed

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

0 commit comments

Comments
 (0)