CIRCT 22.0.0git
Loading...
Searching...
No Matches
SimToSV.cpp
Go to the documentation of this file.
1//===- LowerSimToSV.cpp - Sim to SV lowering ------------------------------===//
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 transform translates Sim ops to SV.
10//
11//===----------------------------------------------------------------------===//
12
22#include "mlir/IR/Builders.h"
23#include "mlir/IR/DialectImplementation.h"
24#include "mlir/IR/ImplicitLocOpBuilder.h"
25#include "mlir/IR/Threading.h"
26#include "mlir/Pass/Pass.h"
27#include "mlir/Transforms/DialectConversion.h"
28
29#define DEBUG_TYPE "lower-sim-to-sv"
30
31namespace circt {
32#define GEN_PASS_DEF_LOWERSIMTOSV
33#include "circt/Conversion/Passes.h.inc"
34} // namespace circt
35
36using namespace circt;
37using namespace sim;
38
39/// Check whether an op should be placed inside an ifdef guard that prevents it
40/// from affecting synthesis runs.
41static bool needsIfdefGuard(Operation *op) {
42 return isa<ClockedTerminateOp, ClockedPauseOp, TerminateOp, PauseOp>(op);
43}
44
45/// Check whether an op should be placed inside an always process triggered on a
46/// clock, and an if statement checking for a condition.
47static std::pair<Value, Value> needsClockAndConditionWrapper(Operation *op) {
48 return TypeSwitch<Operation *, std::pair<Value, Value>>(op)
49 .Case<ClockedTerminateOp, ClockedPauseOp>(
50 [](auto op) -> std::pair<Value, Value> {
51 return {op.getClock(), op.getCondition()};
52 })
53 .Default({});
54}
55
56namespace {
57
58struct SimConversionState {
59 hw::HWModuleOp module;
60 bool usedSynthesisMacro = false;
61 SetVector<StringAttr> dpiCallees;
62};
63
64template <typename T>
65struct SimConversionPattern : public OpConversionPattern<T> {
66 explicit SimConversionPattern(MLIRContext *context, SimConversionState &state)
67 : OpConversionPattern<T>(context), state(state) {}
68
69 SimConversionState &state;
70};
71
72} // namespace
73
74// Lower `sim.plusargs.test` to a standard SV implementation.
75//
76class PlusArgsTestLowering : public SimConversionPattern<PlusArgsTestOp> {
77public:
78 using SimConversionPattern<PlusArgsTestOp>::SimConversionPattern;
79
80 LogicalResult
81 matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor,
82 ConversionPatternRewriter &rewriter) const final {
83 auto loc = op.getLoc();
84 auto resultType = rewriter.getIntegerType(1);
85 auto str = sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
86 auto reg = sv::RegOp::create(rewriter, loc, resultType,
87 rewriter.getStringAttr("_pargs"));
88 sv::InitialOp::create(rewriter, loc, [&] {
89 auto call = sv::SystemFunctionOp::create(
90 rewriter, loc, resultType, "test$plusargs", ArrayRef<Value>{str});
91 sv::BPAssignOp::create(rewriter, loc, reg, call);
92 });
93
94 rewriter.replaceOpWithNewOp<sv::ReadInOutOp>(op, reg);
95 return success();
96 }
97};
98
99// Lower `sim.plusargs.value` to a standard SV implementation.
100//
101class PlusArgsValueLowering : public SimConversionPattern<PlusArgsValueOp> {
102public:
103 using SimConversionPattern<PlusArgsValueOp>::SimConversionPattern;
104
105 LogicalResult
106 matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor,
107 ConversionPatternRewriter &rewriter) const final {
108 auto loc = op.getLoc();
109
110 auto i1ty = rewriter.getIntegerType(1);
111 auto type = op.getResult().getType();
112
113 auto wirev = sv::WireOp::create(rewriter, loc, type,
114 rewriter.getStringAttr("_pargs_v"));
115 auto wiref = sv::WireOp::create(rewriter, loc, i1ty,
116 rewriter.getStringAttr("_pargs_f"));
117
118 state.usedSynthesisMacro = true;
119 sv::IfDefOp::create(
120 rewriter, loc, "SYNTHESIS",
121 [&]() {
122 auto cstFalse = hw::ConstantOp::create(rewriter, loc, APInt(1, 0));
123 auto cstZ = sv::ConstantZOp::create(rewriter, loc, type);
124 auto assignZ = sv::AssignOp::create(rewriter, loc, wirev, cstZ);
126 assignZ,
127 sv::SVAttributeAttr::get(
128 rewriter.getContext(),
129 "This dummy assignment exists to avoid undriven lint "
130 "warnings (e.g., Verilator UNDRIVEN).",
131 /*emitAsComment=*/true));
132 sv::AssignOp::create(rewriter, loc, wiref, cstFalse);
133 },
134 [&]() {
135 auto i32ty = rewriter.getIntegerType(32);
136 auto regf = sv::RegOp::create(rewriter, loc, i32ty,
137 rewriter.getStringAttr("_found"));
138 auto regv = sv::RegOp::create(rewriter, loc, type,
139 rewriter.getStringAttr("_value"));
140 sv::InitialOp::create(rewriter, loc, [&] {
141 auto str =
142 sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
143 auto call = sv::SystemFunctionOp::create(
144 rewriter, loc, i32ty, "value$plusargs",
145 ArrayRef<Value>{str, regv});
146 sv::BPAssignOp::create(rewriter, loc, regf, call);
147 });
148 Value readRegF = sv::ReadInOutOp::create(rewriter, loc, regf);
149 Value readRegV = sv::ReadInOutOp::create(rewriter, loc, regv);
150 auto cstTrue = hw::ConstantOp::create(rewriter, loc, i32ty, 1);
151 // Squash any X coming from the regf to 0.
152 auto cmp = comb::ICmpOp::create(
153 rewriter, loc, comb::ICmpPredicate::ceq, readRegF, cstTrue);
154 sv::AssignOp::create(rewriter, loc, wiref, cmp);
155 sv::AssignOp::create(rewriter, loc, wirev, readRegV);
156 });
157
158 Value readf = sv::ReadInOutOp::create(rewriter, loc, wiref);
159 Value readv = sv::ReadInOutOp::create(rewriter, loc, wirev);
160
161 rewriter.replaceOp(op, {readf, readv});
162 return success();
163 }
164};
165
166static LogicalResult convert(ClockedTerminateOp op, PatternRewriter &rewriter) {
167 if (op.getSuccess())
168 rewriter.replaceOpWithNewOp<sv::FinishOp>(op, op.getVerbose());
169 else
170 rewriter.replaceOpWithNewOp<sv::FatalOp>(op, op.getVerbose());
171 return success();
172}
173
174static LogicalResult convert(ClockedPauseOp op, PatternRewriter &rewriter) {
175 rewriter.replaceOpWithNewOp<sv::StopOp>(op, op.getVerbose());
176 return success();
177}
178
179static LogicalResult convert(TerminateOp op, PatternRewriter &rewriter) {
180 if (op.getSuccess())
181 rewriter.replaceOpWithNewOp<sv::FinishOp>(op, op.getVerbose());
182 else
183 rewriter.replaceOpWithNewOp<sv::FatalOp>(op, op.getVerbose());
184 return success();
185}
186
187static LogicalResult convert(PauseOp op, PatternRewriter &rewriter) {
188 rewriter.replaceOpWithNewOp<sv::StopOp>(op, op.getVerbose());
189 return success();
190}
191
192class DPICallLowering : public SimConversionPattern<DPICallOp> {
193public:
194 using SimConversionPattern<DPICallOp>::SimConversionPattern;
195
196 LogicalResult
197 matchAndRewrite(DPICallOp op, OpAdaptor adaptor,
198 ConversionPatternRewriter &rewriter) const final {
199 auto loc = op.getLoc();
200 // Record the callee.
201 state.dpiCallees.insert(op.getCalleeAttr().getAttr());
202
203 bool isClockedCall = !!op.getClock();
204 bool hasEnable = !!op.getEnable();
205
206 SmallVector<sv::RegOp> temporaries;
207 SmallVector<Value> reads;
208 for (auto [type, result] :
209 llvm::zip(op.getResultTypes(), op.getResults())) {
210 temporaries.push_back(sv::RegOp::create(rewriter, op.getLoc(), type));
211 reads.push_back(
212 sv::ReadInOutOp::create(rewriter, op.getLoc(), temporaries.back()));
213 }
214
215 auto emitCall = [&]() {
216 auto call = sv::FuncCallProceduralOp::create(
217 rewriter, op.getLoc(), op.getResultTypes(), op.getCalleeAttr(),
218 adaptor.getInputs());
219 for (auto [lhs, rhs] : llvm::zip(temporaries, call.getResults())) {
220 if (isClockedCall)
221 sv::PAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
222 else
223 sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
224 }
225 };
226 if (isClockedCall) {
227 Value clockCast =
228 seq::FromClockOp::create(rewriter, loc, adaptor.getClock());
229 sv::AlwaysOp::create(
230 rewriter, loc,
231 ArrayRef<sv::EventControl>{sv::EventControl::AtPosEdge},
232 ArrayRef<Value>{clockCast}, [&]() {
233 if (!hasEnable)
234 return emitCall();
235 sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
236 emitCall);
237 });
238 } else {
239 // Unclocked call is lowered into always_comb.
240 // TODO: If there is a return value and no output argument, use an
241 // unclocked call op.
242 sv::AlwaysCombOp::create(rewriter, loc, [&]() {
243 if (!hasEnable)
244 return emitCall();
245 auto assignXToResults = [&] {
246 for (auto lhs : temporaries) {
247 auto xValue = sv::ConstantXOp::create(
248 rewriter, op.getLoc(), lhs.getType().getElementType());
249 sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, xValue);
250 }
251 };
252 sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(), emitCall,
253 assignXToResults);
254 });
255 }
256
257 rewriter.replaceOp(op, reads);
258 return success();
259 }
260};
261
262// A helper struct to lower DPI function/call.
264 llvm::DenseMap<StringAttr, StringAttr> symbolToFragment;
266 LowerDPIFunc(mlir::ModuleOp module) { nameSpace.add(module); }
267 void lower(sim::DPIFuncOp func);
268 void addFragments(hw::HWModuleOp module,
269 ArrayRef<StringAttr> dpiCallees) const;
270};
271
272void LowerDPIFunc::lower(sim::DPIFuncOp func) {
273 ImplicitLocOpBuilder builder(func.getLoc(), func);
274 ArrayAttr inputLocsAttr, outputLocsAttr;
275 if (func.getArgumentLocs()) {
276 SmallVector<Attribute> inputLocs, outputLocs;
277 for (auto [port, loc] :
278 llvm::zip(func.getModuleType().getPorts(),
279 func.getArgumentLocsAttr().getAsRange<LocationAttr>())) {
280 (port.dir == hw::ModulePort::Output ? outputLocs : inputLocs)
281 .push_back(loc);
282 }
283 inputLocsAttr = builder.getArrayAttr(inputLocs);
284 outputLocsAttr = builder.getArrayAttr(outputLocs);
285 }
286
287 auto svFuncDecl =
288 sv::FuncOp::create(builder, func.getSymNameAttr(), func.getModuleType(),
289 func.getPerArgumentAttrsAttr(), inputLocsAttr,
290 outputLocsAttr, func.getVerilogNameAttr());
291 // DPI function is a declaration so it must be a private function.
292 svFuncDecl.setPrivate();
293 auto name = builder.getStringAttr(nameSpace.newName(
294 func.getSymNameAttr().getValue(), "dpi_import_fragument"));
295
296 // Add include guards to avoid duplicate declarations. See Issue 7458.
297 auto macroDecl = sv::MacroDeclOp::create(
298 builder, nameSpace.newName("__CIRCT_DPI_IMPORT",
299 func.getSymNameAttr().getValue().upper()));
300 emit::FragmentOp::create(builder, name, [&]() {
301 sv::IfDefOp::create(
302 builder, macroDecl.getSymNameAttr(), []() {},
303 [&]() {
304 sv::FuncDPIImportOp::create(builder, func.getSymNameAttr(),
305 StringAttr());
306 sv::MacroDefOp::create(builder, macroDecl.getSymNameAttr(), "");
307 });
308 });
309
310 symbolToFragment.insert({func.getSymNameAttr(), name});
311 func.erase();
312}
313
315 ArrayRef<StringAttr> dpiCallees) const {
316 llvm::SetVector<Attribute> fragments;
317 // Add existing emit fragments.
318 if (auto exstingFragments =
319 module->getAttrOfType<ArrayAttr>(emit::getFragmentsAttrName()))
320 for (auto fragment : exstingFragments.getAsRange<FlatSymbolRefAttr>())
321 fragments.insert(fragment);
322 for (auto callee : dpiCallees) {
323 auto attr = symbolToFragment.at(callee);
324 fragments.insert(FlatSymbolRefAttr::get(attr));
325 }
326 if (!fragments.empty())
327 module->setAttr(
328 emit::getFragmentsAttrName(),
329 ArrayAttr::get(module.getContext(), fragments.takeVector()));
330}
331
332static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp) {
333 bool usedSynthesisMacro = false;
334
335 rootOp->walk([&](Operation *op) {
336 auto loc = op->getLoc();
337
338 // Move the op into an ifdef guard if needed.
339 if (needsIfdefGuard(op)) {
340 // Try to reuse an ifdef guard immediately before the op.
341 Block *block = nullptr;
342 if (op->getPrevNode())
343 block = TypeSwitch<Operation *, Block *>(op->getPrevNode())
344 .Case<sv::IfDefOp, sv::IfDefProceduralOp>(
345 [&](auto guardOp) -> Block * {
346 if (guardOp.getCond().getIdent().getAttr() ==
347 "SYNTHESIS" &&
348 guardOp.hasElse())
349 return guardOp.getElseBlock();
350 return nullptr;
351 })
352 .Default([](auto) { return nullptr; });
353
354 // If there was no pre-existing guard, create one.
355 if (!block) {
356 OpBuilder builder(op);
357 if (op->getParentOp()->hasTrait<sv::ProceduralRegion>())
358 block = sv::IfDefProceduralOp::create(
359 builder, loc, "SYNTHESIS", [] {}, [] {})
360 .getElseBlock();
361 else
362 block = sv::IfDefOp::create(
363 builder, loc, "SYNTHESIS", [] {}, [] {})
364 .getElseBlock();
365 usedSynthesisMacro = true;
366 }
367
368 // Move the op into the guard block.
369 op->moveBefore(block, block->end());
370 }
371
372 // Check if the op requires an clock and condition wrapper.
373 auto [clock, condition] = needsClockAndConditionWrapper(op);
374
375 // Create an enclosing always process.
376 if (clock) {
377 // Try to reuse an always process immediately before the op.
378 Block *block = nullptr;
379 if (auto alwaysOp = dyn_cast_or_null<sv::AlwaysOp>(op->getPrevNode()))
380 if (alwaysOp.getNumConditions() == 1 &&
381 alwaysOp.getCondition(0).event == sv::EventControl::AtPosEdge)
382 if (auto clockOp = alwaysOp.getCondition(0)
383 .value.getDefiningOp<seq::FromClockOp>())
384 if (clockOp.getInput() == clock)
385 block = alwaysOp.getBodyBlock();
386
387 // If there was no pre-existing always process, create one.
388 if (!block) {
389 OpBuilder builder(op);
390 clock = seq::FromClockOp::create(builder, loc, clock);
391 block = sv::AlwaysOp::create(builder, loc, sv::EventControl::AtPosEdge,
392 clock, [] {})
393 .getBodyBlock();
394 }
395
396 // Move the op into the process.
397 op->moveBefore(block, block->end());
398 }
399
400 // Create an enclosing if condition.
401 if (condition) {
402 // Try to reuse an if statement immediately before the op.
403 Block *block = nullptr;
404 if (auto ifOp = dyn_cast_or_null<sv::IfOp>(op->getPrevNode()))
405 if (ifOp.getCond() == condition)
406 block = ifOp.getThenBlock();
407
408 // If there was no pre-existing if statement, create one.
409 if (!block) {
410 OpBuilder builder(op);
411 block = sv::IfOp::create(builder, loc, condition, [] {}).getThenBlock();
412 }
413
414 // Move the op into the if body.
415 op->moveBefore(block, block->end());
416 }
417 });
418
419 return usedSynthesisMacro;
420}
421
422namespace {
423struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
424 void runOnOperation() override {
425 auto circuit = getOperation();
426 MLIRContext *context = &getContext();
427 LowerDPIFunc lowerDPIFunc(circuit);
428
429 // Lower DPI functions.
430 for (auto func :
431 llvm::make_early_inc_range(circuit.getOps<sim::DPIFuncOp>()))
432 lowerDPIFunc.lower(func);
433
434 std::atomic<bool> usedSynthesisMacro = false;
435 auto lowerModule = [&](hw::HWModuleOp module) {
437 usedSynthesisMacro = true;
438
439 SimConversionState state;
440 ConversionTarget target(*context);
441 target.addIllegalDialect<SimDialect>();
442 target.addLegalDialect<sv::SVDialect>();
443 target.addLegalDialect<hw::HWDialect>();
444 target.addLegalDialect<seq::SeqDialect>();
445 target.addLegalDialect<comb::CombDialect>();
446
447 RewritePatternSet patterns(context);
448 patterns.add<PlusArgsTestLowering>(context, state);
449 patterns.add<PlusArgsValueLowering>(context, state);
450 patterns.add<ClockedTerminateOp>(convert);
451 patterns.add<ClockedPauseOp>(convert);
452 patterns.add<TerminateOp>(convert);
453 patterns.add<PauseOp>(convert);
454 patterns.add<DPICallLowering>(context, state);
455 auto result = applyPartialConversion(module, target, std::move(patterns));
456
457 if (failed(result))
458 return result;
459
460 // Set the emit fragment.
461 lowerDPIFunc.addFragments(module, state.dpiCallees.takeVector());
462
463 if (state.usedSynthesisMacro)
464 usedSynthesisMacro = true;
465 return result;
466 };
467
468 if (failed(mlir::failableParallelForEach(
469 context, circuit.getOps<hw::HWModuleOp>(), lowerModule)))
470 return signalPassFailure();
471
472 if (usedSynthesisMacro) {
473 Operation *op = circuit.lookupSymbol("SYNTHESIS");
474 if (op) {
475 if (!isa<sv::MacroDeclOp>(op)) {
476 op->emitOpError("should be a macro declaration");
477 return signalPassFailure();
478 }
479 } else {
480 auto builder = ImplicitLocOpBuilder::atBlockBegin(
481 UnknownLoc::get(context), circuit.getBody());
482 sv::MacroDeclOp::create(builder, "SYNTHESIS");
483 }
484 }
485 }
486};
487} // anonymous namespace
488
489std::unique_ptr<Pass> circt::createLowerSimToSVPass() {
490 return std::make_unique<SimToSVPass>();
491}
static Block * getBodyBlock(FModuleLike mod)
static std::pair< Value, Value > needsClockAndConditionWrapper(Operation *op)
Check whether an op should be placed inside an always process triggered on a clock,...
Definition SimToSV.cpp:47
static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp)
Definition SimToSV.cpp:332
static LogicalResult convert(ClockedTerminateOp op, PatternRewriter &rewriter)
Definition SimToSV.cpp:166
static bool needsIfdefGuard(Operation *op)
Check whether an op should be placed inside an ifdef guard that prevents it from affecting synthesis ...
Definition SimToSV.cpp:41
LogicalResult matchAndRewrite(DPICallOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:197
LogicalResult matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:81
LogicalResult matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:106
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:87
Signals that an operations regions are procedural.
Definition SVOps.h:160
create(data_type, value)
Definition hw.py:433
create(dest, src)
Definition sv.py:98
create(value)
Definition sv.py:106
create(data_type, name=None, sym_name=None)
Definition sv.py:61
void setSVAttributes(mlir::Operation *op, mlir::ArrayAttr attrs)
Set the SV attributes of an operation.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
std::unique_ptr< mlir::Pass > createLowerSimToSVPass()
Definition SimToSV.cpp:489
circt::Namespace nameSpace
Definition SimToSV.cpp:265
void lower(sim::DPIFuncOp func)
Definition SimToSV.cpp:272
void addFragments(hw::HWModuleOp module, ArrayRef< StringAttr > dpiCallees) const
Definition SimToSV.cpp:314
llvm::DenseMap< StringAttr, StringAttr > symbolToFragment
Definition SimToSV.cpp:264
LowerDPIFunc(mlir::ModuleOp module)
Definition SimToSV.cpp:266