Skip to content

Commit f18f480

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

6 files changed

Lines changed: 432 additions & 0 deletions

File tree

lib/Conversion/SimToSV/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_circt_conversion_library(CIRCTSimToSV
22
SimToSV.cpp
3+
LowerPrintFormattedProcToSV.cpp
34

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

lib/Conversion/SimToSV/SimToSV.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "circt/Conversion/SimToSV.h"
14+
#include "SimToSVInternal.h"
1415
#include "circt/Dialect/Comb/CombOps.h"
1516
#include "circt/Dialect/Emit/EmitOps.h"
1617
#include "circt/Dialect/HW/HWOps.h"
@@ -478,6 +479,10 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
478479

479480
std::atomic<bool> usedSynthesisMacro = false;
480481
auto lowerModule = [&](hw::HWModuleOp module) {
482+
if (failed(lowerPrintFormattedProcToSV(module))) {
483+
return failure();
484+
}
485+
481486
if (moveOpsIntoIfdefGuardsAndProcesses(module))
482487
usedSynthesisMacro = true;
483488

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//===- SimToSVInternal.h - Internal implementation details ----------------===//
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+
#ifndef CONVERSION_SIMTOSV_SIMTOSVINTERNALS_H
10+
#define CONVERSION_SIMTOSV_SIMTOSVINTERNALS_H
11+
12+
#include "circt/Dialect/HW/HWOps.h"
13+
#include "circt/Support/LLVM.h"
14+
15+
namespace circt {
16+
namespace sim {
17+
18+
LogicalResult lowerPrintFormattedProcToSV(hw::HWModuleOp module);
19+
20+
} // namespace sim
21+
} // namespace circt
22+
23+
#endif // CONVERSION_SIMTOSV_SIMTOSVINTERNALS_H
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
// expected-error @below {{cannot lower 'sim.proc.print' to sv.write: sim.fmt.dec only supports paddingChar 32 (' ') or 48 ('0') for SystemVerilog lowering}}
7+
%bad = sim.fmt.dec %arg paddingChar 42 specifierWidth 2 : i8
8+
%msg = sim.fmt.concat (%lit, %bad)
9+
sim.proc.print %msg
10+
}
11+
}
12+
13+
// -----
14+
15+
// expected-error @below {{cannot lower 'sim.proc.print' to sv.write: block argument format strings are unsupported as sim.proc.print input}}
16+
hw.module @unsupported_input_block_argument(in %arg : !sim.fstring) {
17+
sv.initial {
18+
sim.proc.print %arg
19+
}
20+
}
21+
22+
// -----
23+
24+
hw.module @unsupported_stream_block_argument(
25+
in %stream : !sim.output_stream) {
26+
sv.initial {
27+
%fmt = sim.fmt.literal "x"
28+
// expected-error @below {{lowering 'sim.proc.print' with a stream is not supported yet}}
29+
sim.proc.print %fmt to %stream
30+
}
31+
}
32+
33+
// -----
34+
35+
hw.module @unsupported_stream_from_get_file() {
36+
%literal = sim.fmt.literal "stream.log"
37+
// expected-error @below {{'sim.proc.print' op must not be in a non-procedural region}}
38+
sim.proc.print %literal
39+
}

0 commit comments

Comments
 (0)