CIRCT 23.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
23#include "mlir/IR/Builders.h"
24#include "mlir/IR/DialectImplementation.h"
25#include "mlir/IR/ImplicitLocOpBuilder.h"
26#include "mlir/IR/Threading.h"
27#include "mlir/Pass/Pass.h"
28#include "mlir/Transforms/DialectConversion.h"
29#include "mlir/Transforms/RegionUtils.h"
30#include "llvm/ADT/Twine.h"
31
32#define DEBUG_TYPE "lower-sim-to-sv"
33
34namespace circt {
35#define GEN_PASS_DEF_LOWERSIMTOSV
36#include "circt/Conversion/Passes.h.inc"
37} // namespace circt
38
39using namespace circt;
40using namespace sim;
41
42/// Check whether an op should be placed inside an ifdef guard that prevents it
43/// from affecting synthesis runs.
44static bool needsIfdefGuard(Operation *op) {
45 return isa<ClockedTerminateOp, ClockedPauseOp, TerminateOp, PauseOp>(op);
46}
47
48/// Check whether an op should be placed inside an always process triggered on a
49/// clock, and an if statement checking for a condition.
50static std::pair<Value, Value> needsClockAndConditionWrapper(Operation *op) {
51 return TypeSwitch<Operation *, std::pair<Value, Value>>(op)
52 .Case<ClockedTerminateOp, ClockedPauseOp>(
53 [](auto op) -> std::pair<Value, Value> {
54 return {op.getClock(), op.getCondition()};
55 })
56 .Default({});
57}
58
59namespace {
60
61struct SimConversionState {
62 hw::HWModuleOp module;
63 bool usedSynthesisMacro = false;
64 SetVector<StringAttr> dpiCallees;
65};
66
67template <typename T>
68struct SimConversionPattern : public OpConversionPattern<T> {
69 explicit SimConversionPattern(MLIRContext *context, SimConversionState &state)
70 : OpConversionPattern<T>(context), state(state) {}
71
72 SimConversionState &state;
73};
74
75hw::ModuleType
76DPIFunctionTypeToHWModuleType(const DPIFunctionType &dpiFuncType) {
77 SmallVector<hw::ModulePort> hwPorts;
78 for (auto &arg : dpiFuncType.getArguments()) {
80 switch (arg.dir) {
81 case DPIDirection::Input:
82 case DPIDirection::Ref:
83 hwDir = hw::ModulePort::Direction::Input;
84 break;
85 case DPIDirection::Output:
86 case DPIDirection::Return:
87 hwDir = hw::ModulePort::Direction::Output;
88 break;
89 case DPIDirection::InOut:
90 hwDir = hw::ModulePort::Direction::InOut;
91 break;
92 }
93 hwPorts.push_back({arg.name, arg.type, hwDir});
94 }
95 return hw::ModuleType::get(dpiFuncType.getContext(), hwPorts);
96}
97} // namespace
98
99// Lower `sim.plusargs.test` to a standard SV implementation.
100//
101class PlusArgsTestLowering : public SimConversionPattern<PlusArgsTestOp> {
102public:
103 using SimConversionPattern<PlusArgsTestOp>::SimConversionPattern;
104
105 LogicalResult
106 matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor,
107 ConversionPatternRewriter &rewriter) const final {
108 auto loc = op.getLoc();
109 auto resultType = rewriter.getIntegerType(1);
110 auto str = sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
111 auto reg = sv::RegOp::create(rewriter, loc, resultType,
112 rewriter.getStringAttr("_pargs"));
113 sv::InitialOp::create(rewriter, loc, [&] {
114 auto call = sv::SystemFunctionOp::create(
115 rewriter, loc, resultType, "test$plusargs", ArrayRef<Value>{str});
116 sv::BPAssignOp::create(rewriter, loc, reg, call);
117 });
118
119 rewriter.replaceOpWithNewOp<sv::ReadInOutOp>(op, reg);
120 return success();
121 }
122};
123
124// Lower `sim.plusargs.value` to a standard SV implementation.
125//
126class PlusArgsValueLowering : public SimConversionPattern<PlusArgsValueOp> {
127public:
128 using SimConversionPattern<PlusArgsValueOp>::SimConversionPattern;
129
130 LogicalResult
131 matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor,
132 ConversionPatternRewriter &rewriter) const final {
133 auto loc = op.getLoc();
134
135 auto i1ty = rewriter.getIntegerType(1);
136 auto type = op.getResult().getType();
137
138 auto wirev = sv::WireOp::create(rewriter, loc, type,
139 rewriter.getStringAttr("_pargs_v"));
140 auto wiref = sv::WireOp::create(rewriter, loc, i1ty,
141 rewriter.getStringAttr("_pargs_f"));
142
143 state.usedSynthesisMacro = true;
144 sv::IfDefOp::create(
145 rewriter, loc, "SYNTHESIS",
146 [&]() {
147 auto cstFalse = hw::ConstantOp::create(rewriter, loc, APInt(1, 0));
148 auto cstZ = sv::ConstantZOp::create(rewriter, loc, type);
149 auto assignZ = sv::AssignOp::create(rewriter, loc, wirev, cstZ);
151 assignZ,
152 sv::SVAttributeAttr::get(
153 rewriter.getContext(),
154 "This dummy assignment exists to avoid undriven lint "
155 "warnings (e.g., Verilator UNDRIVEN).",
156 /*emitAsComment=*/true));
157 sv::AssignOp::create(rewriter, loc, wiref, cstFalse);
158 },
159 [&]() {
160 auto i32ty = rewriter.getIntegerType(32);
161 auto regf = sv::RegOp::create(rewriter, loc, i32ty,
162 rewriter.getStringAttr("_found"));
163 auto regv = sv::RegOp::create(rewriter, loc, type,
164 rewriter.getStringAttr("_value"));
165 sv::InitialOp::create(rewriter, loc, [&] {
166 auto str =
167 sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
168 auto call = sv::SystemFunctionOp::create(
169 rewriter, loc, i32ty, "value$plusargs",
170 ArrayRef<Value>{str, regv});
171 sv::BPAssignOp::create(rewriter, loc, regf, call);
172 });
173 Value readRegF = sv::ReadInOutOp::create(rewriter, loc, regf);
174 Value readRegV = sv::ReadInOutOp::create(rewriter, loc, regv);
175 auto cstTrue = hw::ConstantOp::create(rewriter, loc, i32ty, 1);
176 // Squash any X coming from the regf to 0.
177 auto cmp = comb::ICmpOp::create(
178 rewriter, loc, comb::ICmpPredicate::ceq, readRegF, cstTrue);
179 sv::AssignOp::create(rewriter, loc, wiref, cmp);
180 sv::AssignOp::create(rewriter, loc, wirev, readRegV);
181 });
182
183 Value readf = sv::ReadInOutOp::create(rewriter, loc, wiref);
184 Value readv = sv::ReadInOutOp::create(rewriter, loc, wirev);
185
186 rewriter.replaceOp(op, {readf, readv});
187 return success();
188 }
189};
190
191static LogicalResult convert(ClockedTerminateOp op, PatternRewriter &rewriter) {
192 if (op.getSuccess())
193 rewriter.replaceOpWithNewOp<sv::FinishOp>(op, op.getVerbose());
194 else
195 rewriter.replaceOpWithNewOp<sv::FatalProceduralOp>(op, op.getVerbose());
196 return success();
197}
198
199static LogicalResult convert(ClockedPauseOp op, PatternRewriter &rewriter) {
200 rewriter.replaceOpWithNewOp<sv::StopOp>(op, op.getVerbose());
201 return success();
202}
203
204static LogicalResult convert(TerminateOp op, PatternRewriter &rewriter) {
205 if (op.getSuccess())
206 rewriter.replaceOpWithNewOp<sv::FinishOp>(op, op.getVerbose());
207 else
208 rewriter.replaceOpWithNewOp<sv::FatalProceduralOp>(op, op.getVerbose());
209 return success();
210}
211
212static LogicalResult convert(PauseOp op, PatternRewriter &rewriter) {
213 rewriter.replaceOpWithNewOp<sv::StopOp>(op, op.getVerbose());
214 return success();
215}
216
217class DPICallLowering : public SimConversionPattern<DPICallOp> {
218public:
219 using SimConversionPattern<DPICallOp>::SimConversionPattern;
220
221 LogicalResult
222 matchAndRewrite(DPICallOp op, OpAdaptor adaptor,
223 ConversionPatternRewriter &rewriter) const final {
224 auto loc = op.getLoc();
225 // Record the callee.
226 state.dpiCallees.insert(op.getCalleeAttr().getAttr());
227
228 bool isClockedCall = !!op.getClock();
229 bool hasEnable = !!op.getEnable();
230
231 SmallVector<sv::RegOp> temporaries;
232 SmallVector<Value> reads;
233 for (auto [type, result] :
234 llvm::zip(op.getResultTypes(), op.getResults())) {
235 temporaries.push_back(sv::RegOp::create(rewriter, op.getLoc(), type));
236 reads.push_back(
237 sv::ReadInOutOp::create(rewriter, op.getLoc(), temporaries.back()));
238 }
239
240 auto emitCall = [&]() {
241 auto call = sv::FuncCallProceduralOp::create(
242 rewriter, op.getLoc(), op.getResultTypes(), op.getCalleeAttr(),
243 adaptor.getInputs());
244 for (auto [lhs, rhs] : llvm::zip(temporaries, call.getResults())) {
245 if (isClockedCall)
246 sv::PAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
247 else
248 sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
249 }
250 };
251 if (isClockedCall) {
252 Value clockCast =
253 seq::FromClockOp::create(rewriter, loc, adaptor.getClock());
254 sv::AlwaysOp::create(
255 rewriter, loc,
256 ArrayRef<sv::EventControl>{sv::EventControl::AtPosEdge},
257 ArrayRef<Value>{clockCast}, [&]() {
258 if (!hasEnable)
259 return emitCall();
260 sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
261 emitCall);
262 });
263 } else {
264 // Unclocked call is lowered into always_comb.
265 // TODO: If there is a return value and no output argument, use an
266 // unclocked call op.
267 sv::AlwaysCombOp::create(rewriter, loc, [&]() {
268 if (!hasEnable)
269 return emitCall();
270 auto assignXToResults = [&] {
271 for (auto lhs : temporaries) {
272 auto xValue = sv::ConstantXOp::create(
273 rewriter, op.getLoc(), lhs.getType().getElementType());
274 sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, xValue);
275 }
276 };
277 sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(), emitCall,
278 assignXToResults);
279 });
280 }
281
282 rewriter.replaceOp(op, reads);
283 return success();
284 }
285};
286
287// A helper struct to lower DPI function/call.
289 llvm::DenseMap<StringAttr, StringAttr> symbolToFragment;
291 LowerDPIFunc(mlir::ModuleOp module) { nameSpace.add(module); }
292 void lower(sim::DPIFuncOp func);
293 void addFragments(hw::HWModuleOp module,
294 ArrayRef<StringAttr> dpiCallees) const;
295};
296
297static ArrayAttr buildSVPerArgumentAttrs(MLIRContext *context,
298 sim::DPIFuncOp func) {
299 Builder builder(context);
300 SmallVector<Attribute> convertedAttrs;
301 auto dpiType = func.getDpiFunctionType();
302 auto dpiArgs = dpiType.getArguments();
303 convertedAttrs.reserve(dpiArgs.size());
304 for (auto &arg : dpiArgs) {
305 NamedAttrList newAttrs;
306 if (arg.dir == sim::DPIDirection::Return)
307 newAttrs.append(
308 builder.getStringAttr(sv::FuncOp::getExplicitlyReturnedAttrName()),
309 builder.getUnitAttr());
310 convertedAttrs.push_back(newAttrs.getDictionary(context));
311 }
312 return ArrayAttr::get(context, convertedAttrs);
313}
314
315void LowerDPIFunc::lower(sim::DPIFuncOp func) {
316 ImplicitLocOpBuilder builder(func.getLoc(), func);
317 ArrayAttr inputLocsAttr, outputLocsAttr;
318
319 // Build ModuleType from DPI arguments for sv::FuncOp.
320 auto moduleType = DPIFunctionTypeToHWModuleType(func.getDpiFunctionType());
321
322 if (func.getArgumentLocs()) {
323 SmallVector<Attribute> inputLocs, outputLocs;
324 auto hwPorts = moduleType.getPorts();
325 for (auto [port, loc] : llvm::zip(
326 hwPorts, func.getArgumentLocsAttr().getAsRange<LocationAttr>())) {
327 (port.dir == hw::ModulePort::Output ? outputLocs : inputLocs)
328 .push_back(loc);
329 }
330 inputLocsAttr = builder.getArrayAttr(inputLocs);
331 outputLocsAttr = builder.getArrayAttr(outputLocs);
332 }
333
334 auto svFuncDecl = sv::FuncOp::create(
335 builder, func.getSymNameAttr(), moduleType,
336 buildSVPerArgumentAttrs(builder.getContext(), func), inputLocsAttr,
337 outputLocsAttr, func.getVerilogNameAttr());
338 // DPI function is a declaration so it must be a private function.
339 svFuncDecl.setPrivate();
340 auto name = builder.getStringAttr(nameSpace.newName(
341 func.getSymNameAttr().getValue(), "dpi_import_fragument"));
342
343 // Add include guards to avoid duplicate declarations. See Issue 7458.
344 auto macroDecl = sv::MacroDeclOp::create(
345 builder, nameSpace.newName("__CIRCT_DPI_IMPORT",
346 func.getSymNameAttr().getValue().upper()));
347 emit::FragmentOp::create(builder, name, [&]() {
348 sv::IfDefOp::create(
349 builder, macroDecl.getSymNameAttr(), []() {},
350 [&]() {
351 sv::FuncDPIImportOp::create(builder, func.getSymNameAttr(),
352 StringAttr());
353 sv::MacroDefOp::create(builder, macroDecl.getSymNameAttr(), "");
354 });
355 });
356
357 symbolToFragment.insert({func.getSymNameAttr(), name});
358 func.erase();
359}
360
362 ArrayRef<StringAttr> dpiCallees) const {
363 llvm::SetVector<Attribute> fragments;
364 // Add existing emit fragments.
365 if (auto exstingFragments =
366 module->getAttrOfType<ArrayAttr>(emit::getFragmentsAttrName()))
367 for (auto fragment : exstingFragments.getAsRange<FlatSymbolRefAttr>())
368 fragments.insert(fragment);
369 for (auto callee : dpiCallees) {
370 auto attr = symbolToFragment.at(callee);
371 fragments.insert(FlatSymbolRefAttr::get(attr));
372 }
373 if (!fragments.empty())
374 module->setAttr(
375 emit::getFragmentsAttrName(),
376 ArrayAttr::get(module.getContext(), fragments.takeVector()));
377}
378
379static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp) {
380 bool usedSynthesisMacro = false;
381
382 rootOp->walk([&](Operation *op) {
383 auto loc = op->getLoc();
384
385 // Move the op into an ifdef guard if needed.
386 if (needsIfdefGuard(op)) {
387 // Try to reuse an ifdef guard immediately before the op.
388 Block *block = nullptr;
389 if (op->getPrevNode())
390 block = TypeSwitch<Operation *, Block *>(op->getPrevNode())
391 .Case<sv::IfDefOp, sv::IfDefProceduralOp>(
392 [&](auto guardOp) -> Block * {
393 if (guardOp.getCond().getIdent().getAttr() ==
394 "SYNTHESIS" &&
395 guardOp.hasElse())
396 return guardOp.getElseBlock();
397 return nullptr;
398 })
399 .Default([](auto) { return nullptr; });
400
401 // If there was no pre-existing guard, create one.
402 if (!block) {
403 OpBuilder builder(op);
404 if (op->getParentOp()->hasTrait<ProceduralRegion>())
405 block = sv::IfDefProceduralOp::create(
406 builder, loc, "SYNTHESIS", [] {}, [] {})
407 .getElseBlock();
408 else
409 block = sv::IfDefOp::create(
410 builder, loc, "SYNTHESIS", [] {}, [] {})
411 .getElseBlock();
412 usedSynthesisMacro = true;
413 }
414
415 // Move the op into the guard block.
416 op->moveBefore(block, block->end());
417 }
418
419 // Check if the op requires an clock and condition wrapper.
420 auto [clock, condition] = needsClockAndConditionWrapper(op);
421
422 // Create an enclosing always process.
423 if (clock) {
424 // Try to reuse an always process immediately before the op.
425 Block *block = nullptr;
426 if (auto alwaysOp = dyn_cast_or_null<sv::AlwaysOp>(op->getPrevNode()))
427 if (alwaysOp.getNumConditions() == 1 &&
428 alwaysOp.getCondition(0).event == sv::EventControl::AtPosEdge)
429 if (auto clockOp = alwaysOp.getCondition(0)
430 .value.getDefiningOp<seq::FromClockOp>())
431 if (clockOp.getInput() == clock)
432 block = alwaysOp.getBodyBlock();
433
434 // If there was no pre-existing always process, create one.
435 if (!block) {
436 OpBuilder builder(op);
437 clock = seq::FromClockOp::create(builder, loc, clock);
438 block = sv::AlwaysOp::create(builder, loc, sv::EventControl::AtPosEdge,
439 clock, [] {})
440 .getBodyBlock();
441 }
442
443 // Move the op into the process.
444 op->moveBefore(block, block->end());
445 }
446
447 // Create an enclosing if condition.
448 if (condition) {
449 // Try to reuse an if statement immediately before the op.
450 Block *block = nullptr;
451 if (auto ifOp = dyn_cast_or_null<sv::IfOp>(op->getPrevNode()))
452 if (ifOp.getCond() == condition)
453 block = ifOp.getThenBlock();
454
455 // If there was no pre-existing if statement, create one.
456 if (!block) {
457 OpBuilder builder(op);
458 block = sv::IfOp::create(builder, loc, condition, [] {}).getThenBlock();
459 }
460
461 // Move the op into the if body.
462 op->moveBefore(block, block->end());
463 }
464 });
465
466 return usedSynthesisMacro;
467}
468
469namespace {
470
471LogicalResult emitLoweringError(Location loc, const Twine &reason) {
472 return mlir::emitError(loc)
473 << "cannot lower 'sim.proc.print' to sv.write: " << reason;
474}
475
476void appendLiteralToFWriteFormat(SmallString<128> &formatString,
477 StringRef literal) {
478 for (char ch : literal) {
479 if (ch == '%')
480 formatString += "%%";
481 else
482 formatString.push_back(ch);
483 }
484}
485
486LogicalResult appendIntegerSpecifier(SmallString<128> &formatString,
487 bool isLeftAligned, uint8_t paddingChar,
488 std::optional<int32_t> width, char spec) {
489 formatString.push_back('%');
490 if (isLeftAligned)
491 formatString.push_back('-');
492
493 // SystemVerilog formatting only has built-in support for '0' and ' '. Keep
494 // this lowering strict to avoid silently changing formatting semantics.
495 if (paddingChar == '0')
496 formatString.push_back('0');
497 else if (paddingChar != ' ')
498 return failure();
499
500 if (width.has_value())
501 llvm::Twine(width.value()).toVector(formatString);
502
503 formatString.push_back(spec);
504 return success();
505}
506
507void appendFloatSpecifier(SmallString<128> &formatString, bool isLeftAligned,
508 std::optional<int32_t> fieldWidth, int32_t fracDigits,
509 char spec) {
510 formatString.push_back('%');
511 if (isLeftAligned)
512 formatString.push_back('-');
513 if (fieldWidth.has_value())
514 llvm::Twine(fieldWidth.value()).toVector(formatString);
515 formatString.push_back('.');
516 llvm::Twine(fracDigits).toVector(formatString);
517 formatString.push_back(spec);
518}
519
520LogicalResult getFlattenedFormatFragments(Value input,
521 SmallVectorImpl<Value> &fragments) {
522 if (auto concat = input.getDefiningOp<FormatStringConcatOp>()) {
523 if (failed(concat.getFlattenedInputs(fragments)))
524 return emitLoweringError(input.getLoc(),
525 "cyclic sim.fmt.concat is unsupported");
526 return success();
527 }
528
529 fragments.push_back(input);
530 return success();
531}
532
533LogicalResult appendFormatFragmentToFWrite(Value fragment,
534 SmallString<128> &formatString,
535 SmallVectorImpl<Value> &args,
536 OpBuilder &builder) {
537 Operation *fragmentOp = fragment.getDefiningOp();
538 if (!fragmentOp)
539 return emitLoweringError(fragment.getLoc(),
540 "block argument format strings are unsupported as "
541 "sim.proc.print input");
542
543 return TypeSwitch<Operation *, LogicalResult>(fragmentOp)
544 .Case<FormatLiteralOp>([&](auto literal) -> LogicalResult {
545 appendLiteralToFWriteFormat(formatString, literal.getLiteral());
546 return success();
547 })
548 .Case<FormatHierPathOp>([&](auto hierPath) -> LogicalResult {
549 formatString += hierPath.getUseEscapes() ? "%M" : "%m";
550 return success();
551 })
552 .Case<FormatCharOp>([&](auto fmt) -> LogicalResult {
553 formatString += "%c";
554 args.push_back(fmt.getValue());
555 return success();
556 })
557 .Case<FormatDecOp>([&](auto fmt) -> LogicalResult {
558 if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
559 fmt.getPaddingChar(),
560 fmt.getSpecifierWidth(), 'd'))) {
561 return emitLoweringError(
562 fmt.getLoc(), "sim.fmt.dec only supports paddingChar 32 (' ') "
563 "or 48 ('0') for SystemVerilog lowering");
564 }
565 // Match sim.fmt.dec signedness semantics explicitly in SV.
566 if (fmt.getIsSigned()) {
567 auto signedValue = sv::SystemFunctionOp::create(
568 builder, fmt.getLoc(), fmt.getValue().getType(), "signed",
569 ValueRange{fmt.getValue()});
570 args.push_back(signedValue);
571 } else {
572 auto unsignedValue = sv::SystemFunctionOp::create(
573 builder, fmt.getLoc(), fmt.getValue().getType(), "unsigned",
574 ValueRange{fmt.getValue()});
575 args.push_back(unsignedValue);
576 }
577 return success();
578 })
579 .Case<FormatHexOp>([&](auto fmt) -> LogicalResult {
580 if (failed(appendIntegerSpecifier(
581 formatString, fmt.getIsLeftAligned(), fmt.getPaddingChar(),
582 fmt.getSpecifierWidth(),
583 fmt.getIsHexUppercase() ? 'X' : 'x'))) {
584 return emitLoweringError(
585 fmt.getLoc(), "sim.fmt.hex only supports paddingChar 32 (' ') "
586 "or 48 ('0') for SystemVerilog lowering");
587 }
588 args.push_back(fmt.getValue());
589 return success();
590 })
591 .Case<FormatOctOp>([&](auto fmt) -> LogicalResult {
592 if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
593 fmt.getPaddingChar(),
594 fmt.getSpecifierWidth(), 'o'))) {
595 return emitLoweringError(
596 fmt.getLoc(), "sim.fmt.oct only supports paddingChar 32 (' ') "
597 "or 48 ('0') for SystemVerilog lowering");
598 }
599 args.push_back(fmt.getValue());
600 return success();
601 })
602 .Case<FormatBinOp>([&](auto fmt) -> LogicalResult {
603 if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
604 fmt.getPaddingChar(),
605 fmt.getSpecifierWidth(), 'b'))) {
606 return emitLoweringError(
607 fmt.getLoc(), "sim.fmt.bin only supports paddingChar 32 (' ') "
608 "or 48 ('0') for SystemVerilog lowering");
609 }
610 args.push_back(fmt.getValue());
611 return success();
612 })
613 .Case<FormatScientificOp>([&](auto fmt) -> LogicalResult {
614 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
615 fmt.getFieldWidth(), fmt.getFracDigits(), 'e');
616 args.push_back(fmt.getValue());
617 return success();
618 })
619 .Case<FormatFloatOp>([&](auto fmt) -> LogicalResult {
620 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
621 fmt.getFieldWidth(), fmt.getFracDigits(), 'f');
622 args.push_back(fmt.getValue());
623 return success();
624 })
625 .Case<FormatGeneralOp>([&](auto fmt) -> LogicalResult {
626 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
627 fmt.getFieldWidth(), fmt.getFracDigits(), 'g');
628 args.push_back(fmt.getValue());
629 return success();
630 })
631 .Default([&](auto unsupportedOp) {
632 return emitLoweringError(unsupportedOp->getLoc(),
633 Twine("unsupported format fragment '") +
634 unsupportedOp->getName().getStringRef() +
635 "'");
636 });
637}
638
639LogicalResult foldFormatStringToFWrite(Value input,
640 SmallString<128> &formatString,
641 SmallVectorImpl<Value> &args,
642 OpBuilder &builder) {
643 SmallVector<Value, 8> fragments;
644 if (failed(getFlattenedFormatFragments(input, fragments)))
645 return failure();
646 for (auto fragment : fragments)
647 if (failed(appendFormatFragmentToFWrite(fragment, formatString, args,
648 builder)))
649 return failure();
650 return success();
651}
652
653LogicalResult lowerPrintFormattedProcToSV(hw::HWModuleOp module) {
654 SmallVector<PrintFormattedProcOp> printOps;
655 module.walk([&](PrintFormattedProcOp op) { printOps.push_back(op); });
656
657 llvm::SmallPtrSet<Operation *, 8> dceRoots;
658
659 for (auto printOp : printOps) {
660 OpBuilder builder(printOp);
661 SmallString<128> formatString;
662 SmallVector<Value> args;
663 if (failed(foldFormatStringToFWrite(printOp.getInput(), formatString, args,
664 builder))) {
665 return failure();
666 }
667 auto stream = printOp.getStream();
668 if (!stream) {
669 // no stream is specified, emit sv.write.
670 sv::WriteOp::create(builder, printOp.getLoc(), formatString, args);
671 } else {
672 // Stream-based printing is not supported yet.
673 return printOp->emitError(
674 "lowering 'sim.proc.print' with a stream is not supported yet");
675 }
676 auto *procRoot =
677 printOp->getParentWithTrait<mlir::OpTrait::IsIsolatedFromAbove>();
678 if (procRoot)
679 dceRoots.insert(procRoot);
680 printOp.erase();
681 }
682
683 mlir::IRRewriter rewriter(module);
684 for (Operation *dceRoot : dceRoots)
685 (void)mlir::runRegionDCE(rewriter, dceRoot->getRegions());
686
687 return success();
688}
689
690struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
691 void runOnOperation() override {
692 auto circuit = getOperation();
693 MLIRContext *context = &getContext();
694 LowerDPIFunc lowerDPIFunc(circuit);
695
696 // Lower DPI functions.
697 for (auto func :
698 llvm::make_early_inc_range(circuit.getOps<sim::DPIFuncOp>()))
699 lowerDPIFunc.lower(func);
700
701 std::atomic<bool> usedSynthesisMacro = false;
702 auto lowerModule = [&](hw::HWModuleOp module) {
703 if (failed(lowerPrintFormattedProcToSV(module)))
704 return failure();
705
707 usedSynthesisMacro = true;
708
709 SimConversionState state;
710 ConversionTarget target(*context);
711 target.addIllegalDialect<SimDialect>();
712 target.addLegalDialect<sv::SVDialect>();
713 target.addLegalDialect<hw::HWDialect>();
714 target.addLegalDialect<seq::SeqDialect>();
715 target.addLegalDialect<comb::CombDialect>();
716
717 RewritePatternSet patterns(context);
720 patterns.add<ClockedTerminateOp>(convert);
721 patterns.add<ClockedPauseOp>(convert);
722 patterns.add<TerminateOp>(convert);
723 patterns.add<PauseOp>(convert);
724 patterns.add<DPICallLowering>(context, state);
725 auto result = applyPartialConversion(module, target, std::move(patterns));
726
727 if (failed(result))
728 return result;
729
730 // Set the emit fragment.
731 lowerDPIFunc.addFragments(module, state.dpiCallees.takeVector());
732
733 if (state.usedSynthesisMacro)
734 usedSynthesisMacro = true;
735 return result;
736 };
737
738 if (failed(mlir::failableParallelForEach(
739 context, circuit.getOps<hw::HWModuleOp>(), lowerModule)))
740 return signalPassFailure();
741
742 if (usedSynthesisMacro) {
743 Operation *op = circuit.lookupSymbol("SYNTHESIS");
744 if (op) {
745 if (!isa<sv::MacroDeclOp>(op)) {
746 op->emitOpError("should be a macro declaration");
747 return signalPassFailure();
748 }
749 } else {
750 auto builder = ImplicitLocOpBuilder::atBlockBegin(
751 UnknownLoc::get(context), circuit.getBody());
752 sv::MacroDeclOp::create(builder, "SYNTHESIS");
753 }
754 }
755 }
756};
757} // anonymous namespace
758
759std::unique_ptr<Pass> circt::createLowerSimToSVPass() {
760 return std::make_unique<SimToSVPass>();
761}
static std::unique_ptr< Context > context
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:50
static ArrayAttr buildSVPerArgumentAttrs(MLIRContext *context, sim::DPIFuncOp func)
Definition SimToSV.cpp:297
static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp)
Definition SimToSV.cpp:379
static LogicalResult convert(ClockedTerminateOp op, PatternRewriter &rewriter)
Definition SimToSV.cpp:191
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:44
LogicalResult matchAndRewrite(DPICallOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:222
LogicalResult matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:106
LogicalResult matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:131
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 operation's regions are procedural.
create(data_type, value)
Definition hw.py:433
create(dest, src)
Definition sv.py:100
create(value)
Definition sv.py:108
create(data_type, name=None, sym_name=None)
Definition sv.py:63
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:759
Definition sim.py:1
circt::Namespace nameSpace
Definition SimToSV.cpp:290
void lower(sim::DPIFuncOp func)
Definition SimToSV.cpp:315
void addFragments(hw::HWModuleOp module, ArrayRef< StringAttr > dpiCallees) const
Definition SimToSV.cpp:361
llvm::DenseMap< StringAttr, StringAttr > symbolToFragment
Definition SimToSV.cpp:289
LowerDPIFunc(mlir::ModuleOp module)
Definition SimToSV.cpp:291