Skip to content

Commit e314a09

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

7 files changed

Lines changed: 414 additions & 0 deletions

File tree

include/circt/Conversion/Passes.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,12 @@ def LowerFirMem : Pass<"lower-seq-firmem", "mlir::ModuleOp"> {
840840
// ConvertSimToSV
841841
//===----------------------------------------------------------------------===//
842842

843+
def LowerPrintFormattedProcToSV
844+
: Pass<"sim-lower-print-formatted-proc-to-sv", "hw::HWModuleOp"> {
845+
let summary = "Lower sim.proc.print formatting ops to sv.fwrite";
846+
let dependentDialects = ["circt::hw::HWDialect", "circt::sv::SVDialect"];
847+
}
848+
843849
def LowerSimToSV: Pass<"lower-sim-to-sv", "mlir::ModuleOp"> {
844850
let summary = "Lower simulator-specific `sim` ops to SV.";
845851
let constructor = "circt::createLowerSimToSVPass()";

include/circt/Conversion/SimToSV.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#ifndef CIRCT_CONVERSION_SIMTOSV_H
1414
#define CIRCT_CONVERSION_SIMTOSV_H
1515

16+
#include "circt/Dialect/HW/HWOps.h"
1617
#include "circt/Support/LLVM.h"
1718
#include <memory>
1819

@@ -21,6 +22,7 @@ namespace circt {
2122
#define GEN_PASS_DECL_LOWERSIMTOSV
2223
#include "circt/Conversion/Passes.h.inc"
2324

25+
LogicalResult lowerPrintFormattedProcToSV(hw::HWModuleOp module);
2426
std::unique_ptr<mlir::Pass> createLowerSimToSVPass();
2527

2628
} // namespace circt

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

lib/Conversion/SimToSV/SimToSV.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,10 @@ struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
477477

478478
std::atomic<bool> usedSynthesisMacro = false;
479479
auto lowerModule = [&](hw::HWModuleOp module) {
480+
if (failed(lowerPrintFormattedProcToSV(module))) {
481+
return failure();
482+
}
483+
480484
if (moveOpsIntoIfdefGuardsAndProcesses(module))
481485
usedSynthesisMacro = true;
482486

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)