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
26#include "mlir/IR/Builders.h"
27#include "mlir/IR/BuiltinOps.h"
28#include "mlir/IR/DialectImplementation.h"
29#include "mlir/IR/ImplicitLocOpBuilder.h"
30#include "mlir/IR/Threading.h"
31#include "mlir/IR/Visitors.h"
32#include "mlir/Pass/Pass.h"
33#include "mlir/Transforms/DialectConversion.h"
34#include "llvm/ADT/Twine.h"
35
36#define DEBUG_TYPE "lower-sim-to-sv"
37
38namespace circt {
39#define GEN_PASS_DEF_LOWERSIMTOSV
40#include "circt/Conversion/Passes.h.inc"
41} // namespace circt
42
43using namespace circt;
44using namespace sim;
45using namespace mlir;
46
47/// Check whether an op should be placed inside an ifdef guard that prevents it
48/// from affecting synthesis runs.
49static bool needsIfdefGuard(Operation *op) {
50 return isa<ClockedTerminateOp, ClockedPauseOp, TerminateOp, PauseOp>(op);
51}
52
53/// Check whether an op should be placed inside an always process triggered on a
54/// clock, and an if statement checking for a condition.
55static std::pair<Value, Value> needsClockAndConditionWrapper(Operation *op) {
56 return TypeSwitch<Operation *, std::pair<Value, Value>>(op)
57 .Case<ClockedTerminateOp, ClockedPauseOp>(
58 [](auto op) -> std::pair<Value, Value> {
59 return {op.getClock(), op.getCondition()};
60 })
61 .Default({});
62}
63
64namespace {
65
66struct SimConversionState {
67 hw::HWModuleOp module;
68 bool usedSynthesisMacro = false;
69 bool usedFileDescriptorRuntime = false;
70 SetVector<StringAttr> dpiCallees;
71};
72
73struct SimTypeConverter : public TypeConverter {
74 explicit SimTypeConverter(MLIRContext *context) {
75 addConversion([](Type type) { return type; });
76 addConversion([&](OutputStreamType type) -> Type {
77 return IntegerType::get(type.getContext(), 32);
78 });
79 }
80};
81
82template <typename T>
83struct SimConversionPattern : public OpConversionPattern<T> {
84 explicit SimConversionPattern(MLIRContext *context, SimConversionState &state)
85 : OpConversionPattern<T>(context), state(state) {}
86
87 SimConversionState &state;
88};
89
90hw::ModuleType
91DPIFunctionTypeToHWModuleType(const DPIFunctionType &dpiFuncType) {
92 SmallVector<hw::ModulePort> hwPorts;
93 for (auto &arg : dpiFuncType.getArguments()) {
95 switch (arg.dir) {
96 case DPIDirection::Input:
97 case DPIDirection::Ref:
98 hwDir = hw::ModulePort::Direction::Input;
99 break;
100 case DPIDirection::Output:
101 case DPIDirection::Return:
102 hwDir = hw::ModulePort::Direction::Output;
103 break;
104 case DPIDirection::InOut:
105 hwDir = hw::ModulePort::Direction::InOut;
106 break;
107 }
108 hwPorts.push_back({arg.name, arg.type, hwDir});
109 }
110 return hw::ModuleType::get(dpiFuncType.getContext(), hwPorts);
111}
112} // namespace
113
114// Lower `sim.plusargs.test` to a standard SV implementation.
115//
116class PlusArgsTestLowering : public SimConversionPattern<PlusArgsTestOp> {
117public:
118 using SimConversionPattern<PlusArgsTestOp>::SimConversionPattern;
119
120 LogicalResult
121 matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor,
122 ConversionPatternRewriter &rewriter) const final {
123 auto loc = op.getLoc();
124 auto resultType = rewriter.getIntegerType(1);
125 auto str = sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
126 auto reg = sv::RegOp::create(rewriter, loc, resultType,
127 rewriter.getStringAttr("_pargs"));
128 sv::InitialOp::create(rewriter, loc, [&] {
129 auto call = sv::SystemFunctionOp::create(
130 rewriter, loc, resultType, "test$plusargs", ArrayRef<Value>{str});
131 sv::BPAssignOp::create(rewriter, loc, reg, call);
132 });
133
134 rewriter.replaceOpWithNewOp<sv::ReadInOutOp>(op, reg);
135 return success();
136 }
137};
138
139// Lower `sim.plusargs.value` to a standard SV implementation.
140//
141class PlusArgsValueLowering : public SimConversionPattern<PlusArgsValueOp> {
142public:
143 using SimConversionPattern<PlusArgsValueOp>::SimConversionPattern;
144
145 LogicalResult
146 matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor,
147 ConversionPatternRewriter &rewriter) const final {
148 auto loc = op.getLoc();
149
150 auto i1ty = rewriter.getIntegerType(1);
151 auto type = op.getResult().getType();
152
153 auto wirev = sv::WireOp::create(rewriter, loc, type,
154 rewriter.getStringAttr("_pargs_v"));
155 auto wiref = sv::WireOp::create(rewriter, loc, i1ty,
156 rewriter.getStringAttr("_pargs_f"));
157
158 state.usedSynthesisMacro = true;
159 sv::IfDefOp::create(
160 rewriter, loc, "SYNTHESIS",
161 [&]() {
162 auto cstFalse = hw::ConstantOp::create(rewriter, loc, APInt(1, 0));
163 auto cstZ = sv::ConstantZOp::create(rewriter, loc, type);
164 auto assignZ = sv::AssignOp::create(rewriter, loc, wirev, cstZ);
166 assignZ,
167 sv::SVAttributeAttr::get(
168 rewriter.getContext(),
169 "This dummy assignment exists to avoid undriven lint "
170 "warnings (e.g., Verilator UNDRIVEN).",
171 /*emitAsComment=*/true));
172 sv::AssignOp::create(rewriter, loc, wiref, cstFalse);
173 },
174 [&]() {
175 auto i32ty = rewriter.getIntegerType(32);
176 auto regf = sv::RegOp::create(rewriter, loc, i32ty,
177 rewriter.getStringAttr("_found"));
178 auto regv = sv::RegOp::create(rewriter, loc, type,
179 rewriter.getStringAttr("_value"));
180 sv::InitialOp::create(rewriter, loc, [&] {
181 auto str =
182 sv::ConstantStrOp::create(rewriter, loc, op.getFormatString());
183 auto call = sv::SystemFunctionOp::create(
184 rewriter, loc, i32ty, "value$plusargs",
185 ArrayRef<Value>{str, regv});
186 sv::BPAssignOp::create(rewriter, loc, regf, call);
187 });
188 Value readRegF = sv::ReadInOutOp::create(rewriter, loc, regf);
189 Value readRegV = sv::ReadInOutOp::create(rewriter, loc, regv);
190 auto cstTrue = hw::ConstantOp::create(rewriter, loc, i32ty, 1);
191 // Squash any X coming from the regf to 0.
192 auto cmp = comb::ICmpOp::create(
193 rewriter, loc, comb::ICmpPredicate::ceq, readRegF, cstTrue);
194 sv::AssignOp::create(rewriter, loc, wiref, cmp);
195 sv::AssignOp::create(rewriter, loc, wirev, readRegV);
196 });
197
198 Value readf = sv::ReadInOutOp::create(rewriter, loc, wiref);
199 Value readv = sv::ReadInOutOp::create(rewriter, loc, wirev);
200
201 rewriter.replaceOp(op, {readf, readv});
202 return success();
203 }
204};
205
206template <typename OpTy, unsigned StreamValue>
208public:
211
212 LogicalResult
213 matchAndRewrite(OpTy op, OpAdaptor adaptor,
214 ConversionPatternRewriter &rewriter) const final {
215 auto streamValue =
216 hw::ConstantOp::create(rewriter, op.getLoc(), APInt(32, StreamValue));
217 rewriter.replaceOp(op, streamValue);
218 return success();
219 }
220};
221
224
226 : public OpConversionPattern<mlir::UnrealizedConversionCastOp> {
227public:
229 mlir::UnrealizedConversionCastOp>::OpConversionPattern;
230
231 LogicalResult
232 matchAndRewrite(mlir::UnrealizedConversionCastOp op, OpAdaptor adaptor,
233 ConversionPatternRewriter &rewriter) const final {
234 SmallVector<Type> convertedResultTypes;
235 if (failed(typeConverter->convertTypes(op.getResultTypes(),
236 convertedResultTypes)))
237 return failure();
238
239 if (!llvm::equal(convertedResultTypes, adaptor.getOperands().getTypes()))
240 return failure();
241
242 rewriter.replaceOp(op, adaptor.getOperands());
243 return success();
244 }
245};
246
247static LogicalResult convert(ClockedTerminateOp op, PatternRewriter &rewriter) {
248 if (op.getSuccess())
249 rewriter.replaceOpWithNewOp<sv::FinishOp>(op, op.getVerbose());
250 else
251 rewriter.replaceOpWithNewOp<sv::FatalProceduralOp>(op, op.getVerbose());
252 return success();
253}
254
255static LogicalResult convert(ClockedPauseOp op, PatternRewriter &rewriter) {
256 rewriter.replaceOpWithNewOp<sv::StopOp>(op, op.getVerbose());
257 return success();
258}
259
260static LogicalResult convert(TerminateOp op, PatternRewriter &rewriter) {
261 if (op.getSuccess())
262 rewriter.replaceOpWithNewOp<sv::FinishOp>(op, op.getVerbose());
263 else
264 rewriter.replaceOpWithNewOp<sv::FatalProceduralOp>(op, op.getVerbose());
265 return success();
266}
267
268static LogicalResult convert(PauseOp op, PatternRewriter &rewriter) {
269 rewriter.replaceOpWithNewOp<sv::StopOp>(op, op.getVerbose());
270 return success();
271}
272
273class TriggeredLowering : public SimConversionPattern<TriggeredOp> {
274public:
275 using SimConversionPattern<TriggeredOp>::SimConversionPattern;
276
277 LogicalResult
278 matchAndRewrite(TriggeredOp op, OpAdaptor adaptor,
279 ConversionPatternRewriter &rewriter) const final {
280 auto loc = op.getLoc();
281 state.usedSynthesisMacro = true;
282
283 sv::IfDefOp::create(
284 rewriter, loc, "SYNTHESIS", [] {},
285 [&] {
286 auto trigger =
287 seq::FromClockOp::create(rewriter, loc, adaptor.getClock());
288 auto alwaysOp = sv::AlwaysOp::create(
289 rewriter, loc,
290 ArrayRef<sv::EventControl>{sv::EventControl::AtPosEdge},
291 ArrayRef<Value>{trigger});
292
293 Block *destination = alwaysOp.getBodyBlock();
294 if (auto condition = adaptor.getCondition()) {
295 rewriter.setInsertionPointToStart(destination);
296 destination = sv::IfOp::create(rewriter, loc, condition, [] {
297 }).getThenBlock();
298 }
299
300 rewriter.mergeBlocks(op.getBodyBlock(), destination);
301 });
302 rewriter.eraseOp(op);
303 return success();
304 }
305};
306
307class DPICallLowering : public SimConversionPattern<DPICallOp> {
308public:
309 using SimConversionPattern<DPICallOp>::SimConversionPattern;
310
311 LogicalResult
312 matchAndRewrite(DPICallOp op, OpAdaptor adaptor,
313 ConversionPatternRewriter &rewriter) const final {
314 auto loc = op.getLoc();
315 // Record the callee.
316 state.dpiCallees.insert(op.getCalleeAttr().getAttr());
317
318 bool isClockedCall = !!op.getClock();
319 bool hasEnable = !!op.getEnable();
320
321 SmallVector<sv::RegOp> temporaries;
322 SmallVector<Value> reads;
323 for (auto [type, result] :
324 llvm::zip(op.getResultTypes(), op.getResults())) {
325 temporaries.push_back(sv::RegOp::create(rewriter, op.getLoc(), type));
326 reads.push_back(
327 sv::ReadInOutOp::create(rewriter, op.getLoc(), temporaries.back()));
328 }
329
330 auto emitCall = [&]() {
331 auto call = sv::FuncCallProceduralOp::create(
332 rewriter, op.getLoc(), op.getResultTypes(), op.getCalleeAttr(),
333 adaptor.getInputs());
334 for (auto [lhs, rhs] : llvm::zip(temporaries, call.getResults())) {
335 if (isClockedCall)
336 sv::PAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
337 else
338 sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, rhs);
339 }
340 };
341 if (isClockedCall) {
342 Value clockCast =
343 seq::FromClockOp::create(rewriter, loc, adaptor.getClock());
344 sv::AlwaysOp::create(
345 rewriter, loc,
346 ArrayRef<sv::EventControl>{sv::EventControl::AtPosEdge},
347 ArrayRef<Value>{clockCast}, [&]() {
348 if (!hasEnable)
349 return emitCall();
350 sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(),
351 emitCall);
352 });
353 } else {
354 // Unclocked call is lowered into always_comb.
355 // TODO: If there is a return value and no output argument, use an
356 // unclocked call op.
357 sv::AlwaysCombOp::create(rewriter, loc, [&]() {
358 if (!hasEnable)
359 return emitCall();
360 auto assignXToResults = [&] {
361 for (auto lhs : temporaries) {
362 auto xValue = sv::ConstantXOp::create(
363 rewriter, op.getLoc(), lhs.getType().getElementType());
364 sv::BPAssignOp::create(rewriter, op.getLoc(), lhs, xValue);
365 }
366 };
367 sv::IfOp::create(rewriter, op.getLoc(), adaptor.getEnable(), emitCall,
368 assignXToResults);
369 });
370 }
371
372 rewriter.replaceOp(op, reads);
373 return success();
374 }
375};
376
377// A helper struct to lower DPI function/call.
379 llvm::DenseMap<StringAttr, StringAttr> symbolToFragment;
381 LowerDPIFunc(mlir::ModuleOp module) { nameSpace.add(module); }
382 void lower(sim::DPIFuncOp func);
383};
384
385static ArrayAttr buildSVPerArgumentAttrs(MLIRContext *context,
386 sim::DPIFuncOp func) {
387 Builder builder(context);
388 SmallVector<Attribute> convertedAttrs;
389 auto dpiType = func.getDpiFunctionType();
390 auto dpiArgs = dpiType.getArguments();
391 convertedAttrs.reserve(dpiArgs.size());
392 for (auto &arg : dpiArgs) {
393 NamedAttrList newAttrs;
394 if (arg.dir == sim::DPIDirection::Return)
395 newAttrs.append(
396 builder.getStringAttr(sv::FuncOp::getExplicitlyReturnedAttrName()),
397 builder.getUnitAttr());
398 convertedAttrs.push_back(newAttrs.getDictionary(context));
399 }
400 return ArrayAttr::get(context, convertedAttrs);
401}
402
403void LowerDPIFunc::lower(sim::DPIFuncOp func) {
404 ImplicitLocOpBuilder builder(func.getLoc(), func);
405 ArrayAttr inputLocsAttr, outputLocsAttr;
406
407 // Build ModuleType from DPI arguments for sv::FuncOp.
408 auto moduleType = DPIFunctionTypeToHWModuleType(func.getDpiFunctionType());
409
410 if (func.getArgumentLocs()) {
411 SmallVector<Attribute> inputLocs, outputLocs;
412 auto hwPorts = moduleType.getPorts();
413 for (auto [port, loc] : llvm::zip(
414 hwPorts, func.getArgumentLocsAttr().getAsRange<LocationAttr>())) {
415 (port.dir == hw::ModulePort::Output ? outputLocs : inputLocs)
416 .push_back(loc);
417 }
418 inputLocsAttr = builder.getArrayAttr(inputLocs);
419 outputLocsAttr = builder.getArrayAttr(outputLocs);
420 }
421
422 auto svFuncDecl = sv::FuncOp::create(
423 builder, func.getSymNameAttr(), moduleType,
424 buildSVPerArgumentAttrs(builder.getContext(), func), inputLocsAttr,
425 outputLocsAttr, func.getVerilogNameAttr());
426 // DPI function is a declaration so it must be a private function.
427 svFuncDecl.setPrivate();
428 auto name = builder.getStringAttr(nameSpace.newName(
429 func.getSymNameAttr().getValue(), "dpi_import_fragument"));
430
431 // Add include guards to avoid duplicate declarations. See Issue 7458.
432 auto macroDecl = sv::MacroDeclOp::create(
433 builder, nameSpace.newName("__CIRCT_DPI_IMPORT",
434 func.getSymNameAttr().getValue().upper()));
435 emit::FragmentOp::create(builder, name, [&]() {
436 sv::IfDefOp::create(
437 builder, macroDecl.getSymNameAttr(), []() {},
438 [&]() {
439 sv::FuncDPIImportOp::create(builder, func.getSymNameAttr(),
440 StringAttr());
441 sv::MacroDefOp::create(builder, macroDecl.getSymNameAttr(), "");
442 });
443 });
444
445 symbolToFragment.insert({func.getSymNameAttr(), name});
446 func.erase();
447}
448
449static void
451 const llvm::DenseMap<StringAttr, StringAttr> &symbolToFragment,
452 const SimConversionState &state) {
453 llvm::SetVector<Attribute> fragments;
454 // Add existing emit fragments.
455 if (auto exstingFragments =
456 module->getAttrOfType<ArrayAttr>(emit::getFragmentsAttrName()))
457 for (auto fragment : exstingFragments.getAsRange<FlatSymbolRefAttr>())
458 fragments.insert(fragment);
459 for (auto callee : state.dpiCallees) {
460 auto attr = symbolToFragment.at(callee);
461 fragments.insert(FlatSymbolRefAttr::get(attr));
462 }
463 if (state.usedFileDescriptorRuntime)
464 fragments.insert(sv::getFileDescriptorFragmentRef(module.getContext()));
465 if (!fragments.empty())
466 module->setAttr(
467 emit::getFragmentsAttrName(),
468 ArrayAttr::get(module.getContext(), fragments.takeVector()));
469}
470
471static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp) {
472 bool usedSynthesisMacro = false;
473
474 rootOp->walk<WalkOrder::PreOrder>([&](Operation *op) {
475 // `sim.triggered` is lowered as a whole later on. Do not pre-wrap the
476 // ops nested inside it here, or we may create redundant/invalid
477 // structure.
478 if (isa<TriggeredOp>(op))
479 return WalkResult::skip();
480
481 auto loc = op->getLoc();
482
483 // Move the op into an ifdef guard if needed.
484 if (needsIfdefGuard(op)) {
485 // Try to reuse an ifdef guard immediately before the op.
486 Block *block = nullptr;
487 if (op->getPrevNode())
488 block = TypeSwitch<Operation *, Block *>(op->getPrevNode())
489 .Case<sv::IfDefOp, sv::IfDefProceduralOp>(
490 [&](auto guardOp) -> Block * {
491 if (guardOp.getCond().getIdent().getAttr() ==
492 "SYNTHESIS" &&
493 guardOp.hasElse())
494 return guardOp.getElseBlock();
495 return nullptr;
496 })
497 .Default([](auto) { return nullptr; });
498
499 // If there was no pre-existing guard, create one.
500 if (!block) {
501 OpBuilder builder(op);
502 if (op->getParentOp()->hasTrait<ProceduralRegion>())
503 block = sv::IfDefProceduralOp::create(
504 builder, loc, "SYNTHESIS", [] {}, [] {})
505 .getElseBlock();
506 else
507 block = sv::IfDefOp::create(
508 builder, loc, "SYNTHESIS", [] {}, [] {})
509 .getElseBlock();
510 usedSynthesisMacro = true;
511 }
512
513 // Move the op into the guard block.
514 op->moveBefore(block, block->end());
515 }
516
517 // Check if the op requires an clock and condition wrapper.
518 auto [clock, condition] = needsClockAndConditionWrapper(op);
519
520 // Create an enclosing always process.
521 if (clock) {
522 // Try to reuse an always process immediately before the op.
523 Block *block = nullptr;
524 if (auto alwaysOp = dyn_cast_or_null<sv::AlwaysOp>(op->getPrevNode()))
525 if (alwaysOp.getNumConditions() == 1 &&
526 alwaysOp.getCondition(0).event == sv::EventControl::AtPosEdge)
527 if (auto clockOp = alwaysOp.getCondition(0)
528 .value.getDefiningOp<seq::FromClockOp>())
529 if (clockOp.getInput() == clock)
530 block = alwaysOp.getBodyBlock();
531
532 // If there was no pre-existing always process, create one.
533 if (!block) {
534 OpBuilder builder(op);
535 clock = seq::FromClockOp::create(builder, loc, clock);
536 block = sv::AlwaysOp::create(builder, loc, sv::EventControl::AtPosEdge,
537 clock, [] {})
538 .getBodyBlock();
539 }
540
541 // Move the op into the process.
542 op->moveBefore(block, block->end());
543 }
544
545 // Create an enclosing if condition.
546 if (condition) {
547 // Try to reuse an if statement immediately before the op.
548 Block *block = nullptr;
549 if (auto ifOp = dyn_cast_or_null<sv::IfOp>(op->getPrevNode()))
550 if (ifOp.getCond() == condition)
551 block = ifOp.getThenBlock();
552
553 // If there was no pre-existing if statement, create one.
554 if (!block) {
555 OpBuilder builder(op);
556 block = sv::IfOp::create(builder, loc, condition, [] {}).getThenBlock();
557 }
558
559 // Move the op into the if body.
560 op->moveBefore(block, block->end());
561 }
562 return WalkResult::advance();
563 });
564
565 return usedSynthesisMacro;
566}
567
568namespace {
569
570void appendLiteralToSVFormat(SmallString<128> &formatString,
571 StringRef literal) {
572 for (char ch : literal) {
573 if (ch == '%')
574 formatString += "%%";
575 else
576 formatString.push_back(ch);
577 }
578}
579
580LogicalResult appendIntegerSpecifier(SmallString<128> &formatString,
581 bool isLeftAligned, uint8_t paddingChar,
582 std::optional<int32_t> width, char spec) {
583 formatString.push_back('%');
584 if (isLeftAligned)
585 formatString.push_back('-');
586
587 // SystemVerilog formatting only has built-in support for '0' and ' '. Keep
588 // this lowering strict to avoid silently changing formatting semantics.
589 if (paddingChar == '0')
590 formatString.push_back('0');
591 else if (paddingChar != ' ')
592 return failure();
593
594 if (width.has_value())
595 llvm::Twine(width.value()).toVector(formatString);
596
597 formatString.push_back(spec);
598 return success();
599}
600
601void appendFloatSpecifier(SmallString<128> &formatString, bool isLeftAligned,
602 std::optional<int32_t> fieldWidth, int32_t fracDigits,
603 char spec) {
604 formatString.push_back('%');
605 if (isLeftAligned)
606 formatString.push_back('-');
607 if (fieldWidth.has_value())
608 llvm::Twine(fieldWidth.value()).toVector(formatString);
609 formatString.push_back('.');
610 llvm::Twine(fracDigits).toVector(formatString);
611 formatString.push_back(spec);
612}
613
614LogicalResult getFlattenedFormatFragments(Value input,
615 SmallVectorImpl<Value> &fragments) {
616 if (auto concat = input.getDefiningOp<FormatStringConcatOp>()) {
617 if (failed(concat.getFlattenedInputs(fragments)))
618 return mlir::emitError(input.getLoc(),
619 "cyclic sim.fmt.concat is unsupported");
620 return success();
621 }
622
623 fragments.push_back(input);
624 return success();
625}
626
627LogicalResult appendFormatFragmentToSVFormat(Value fragment,
628 SmallString<128> &formatString,
629 SmallVectorImpl<Value> &args,
630 OpBuilder &builder) {
631 Operation *fragmentOp = fragment.getDefiningOp();
632 if (!fragmentOp)
633 return mlir::emitError(fragment.getLoc(),
634 "block argument format strings are unsupported");
635
636 return TypeSwitch<Operation *, LogicalResult>(fragmentOp)
637 .Case<FormatLiteralOp>([&](auto literal) -> LogicalResult {
638 appendLiteralToSVFormat(formatString, literal.getLiteral());
639 return success();
640 })
641 .Case<FormatCurrentTimeOp>([&](auto fmt) -> LogicalResult {
642 formatString += "%0t";
643 args.push_back(sv::TimeOp::create(builder, fmt.getLoc()));
644 return success();
645 })
646 .Case<FormatHierPathOp>([&](auto hierPath) -> LogicalResult {
647 formatString += hierPath.getUseEscapes() ? "%M" : "%m";
648 return success();
649 })
650 .Case<FormatCharOp>([&](auto fmt) -> LogicalResult {
651 formatString += "%c";
652 args.push_back(fmt.getValue());
653 return success();
654 })
655 .Case<FormatDecOp>([&](auto fmt) -> LogicalResult {
656 if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
657 fmt.getPaddingChar(),
658 fmt.getSpecifierWidth(), 'd'))) {
659 return mlir::emitError(fmt.getLoc())
660 << "sim.fmt.dec only supports paddingChar 32 (' ') or 48 "
661 "('0')";
662 }
663 // Match sim.fmt.dec signedness semantics explicitly in SV.
664 if (fmt.getIsSigned()) {
665 auto signedValue = sv::SystemFunctionOp::create(
666 builder, fmt.getLoc(), fmt.getValue().getType(), "signed",
667 ValueRange{fmt.getValue()});
668 args.push_back(signedValue);
669 } else {
670 auto unsignedValue = sv::SystemFunctionOp::create(
671 builder, fmt.getLoc(), fmt.getValue().getType(), "unsigned",
672 ValueRange{fmt.getValue()});
673 args.push_back(unsignedValue);
674 }
675 return success();
676 })
677 .Case<FormatHexOp>([&](auto fmt) -> LogicalResult {
678 if (failed(appendIntegerSpecifier(
679 formatString, fmt.getIsLeftAligned(), fmt.getPaddingChar(),
680 fmt.getSpecifierWidth(),
681 fmt.getIsHexUppercase() ? 'X' : 'x'))) {
682 return mlir::emitError(fmt.getLoc())
683 << "sim.fmt.hex only supports paddingChar 32 (' ') or 48 "
684 "('0')";
685 }
686 args.push_back(fmt.getValue());
687 return success();
688 })
689 .Case<FormatOctOp>([&](auto fmt) -> LogicalResult {
690 if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
691 fmt.getPaddingChar(),
692 fmt.getSpecifierWidth(), 'o'))) {
693 return mlir::emitError(fmt.getLoc())
694 << "sim.fmt.oct only supports paddingChar 32 (' ') or 48 "
695 "('0')";
696 }
697 args.push_back(fmt.getValue());
698 return success();
699 })
700 .Case<FormatBinOp>([&](auto fmt) -> LogicalResult {
701 if (failed(appendIntegerSpecifier(formatString, fmt.getIsLeftAligned(),
702 fmt.getPaddingChar(),
703 fmt.getSpecifierWidth(), 'b'))) {
704 return mlir::emitError(fmt.getLoc())
705 << "sim.fmt.bin only supports paddingChar 32 (' ') or 48 "
706 "('0')";
707 }
708 args.push_back(fmt.getValue());
709 return success();
710 })
711 .Case<FormatScientificOp>([&](auto fmt) -> LogicalResult {
712 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
713 fmt.getFieldWidth(), fmt.getFracDigits(), 'e');
714 args.push_back(fmt.getValue());
715 return success();
716 })
717 .Case<FormatFloatOp>([&](auto fmt) -> LogicalResult {
718 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
719 fmt.getFieldWidth(), fmt.getFracDigits(), 'f');
720 args.push_back(fmt.getValue());
721 return success();
722 })
723 .Case<FormatGeneralOp>([&](auto fmt) -> LogicalResult {
724 appendFloatSpecifier(formatString, fmt.getIsLeftAligned(),
725 fmt.getFieldWidth(), fmt.getFracDigits(), 'g');
726 args.push_back(fmt.getValue());
727 return success();
728 })
729 .Default([&](auto unsupportedOp) {
730 return mlir::emitError(unsupportedOp->getLoc())
731 << "unsupported format fragment '"
732 << unsupportedOp->getName().getStringRef() << "'";
733 });
734}
735
736LogicalResult lowerFormatStringToSVFormat(Value input,
737 SmallString<128> &formatString,
738 SmallVectorImpl<Value> &args,
739 OpBuilder &builder) {
740 SmallVector<Value, 8> fragments;
741 if (failed(getFlattenedFormatFragments(input, fragments)))
742 return failure();
743 for (auto fragment : fragments)
744 if (failed(appendFormatFragmentToSVFormat(fragment, formatString, args,
745 builder)))
746 return failure();
747 return success();
748}
749
750FailureOr<Value> createFileDescriptorGetterForGetFile(GetFileOp getFileOp,
751 OpBuilder &builder) {
752 SmallString<128> formatString;
753 SmallVector<Value> args;
754 if (failed(lowerFormatStringToSVFormat(getFileOp.getFileName(), formatString,
755 args, builder))) {
756 getFileOp.emitError("cannot lower 'sim.get_file' to SystemVerilog")
757 .attachNote(getFileOp.getFileName().getLoc())
758 << "while lowering file name";
759 return failure();
760 }
761
762 Value fileName =
763 args.empty()
764 ? sv::ConstantStrOp::create(builder, getFileOp.getLoc(),
765 builder.getStringAttr(formatString))
766 .getResult()
767 : sv::SFormatFOp::create(builder, getFileOp.getLoc(),
768 builder.getStringAttr(formatString), args)
769 .getResult();
770
771 return sv::createProceduralFileDescriptorGetterCall(
772 builder, getFileOp.getLoc(), fileName);
773}
774
775static void cleanupDeadSimFmtOps(ArrayRef<Operation *> seedOps) {
776 auto filter = [](Operation *op, OpOperand &operand) {
777 return isa<FormatStringType>(operand.get().getType()) &&
778 isa_and_present<SimDialect>(op->getDialect());
779 };
780
782 sccs.visit(seedOps);
783 assert(sccs.getNumCyclicSCCs() == 0 &&
784 "Cyclic graph should have been rejected");
785
786 for (OpSCC entry : sccs.reverseTopological()) {
787 auto *op = cast<Operation *>(entry);
788 if (op->use_empty())
789 op->erase();
790 else
791 op->emitWarning("sim format/stream op still has users after lowering; "
792 "dialect conversion will fail");
793 }
794}
795
796LogicalResult lowerPrintFormattedProcToSV(hw::HWModuleOp module,
797 const TypeConverter &typeConverter,
798 SimConversionState &state) {
799 SmallVector<GetFileOp> getFileOps;
800 SmallVector<PrintFormattedProcOp> printOps;
801 SmallVector<Operation *, 8> cleanupSeeds;
802 module.walk([&](Operation *op) {
803 if (auto getFileOp = dyn_cast<GetFileOp>(op))
804 getFileOps.push_back(getFileOp);
805 if (auto printOp = dyn_cast<PrintFormattedProcOp>(op))
806 printOps.push_back(printOp);
807 });
808
809 for (auto getFileOp : getFileOps) {
810 OpBuilder builder(getFileOp);
811 auto fdOrFailure = createFileDescriptorGetterForGetFile(getFileOp, builder);
812 if (failed(fdOrFailure))
813 return failure();
814
815 auto stream = mlir::UnrealizedConversionCastOp::create(
816 builder, getFileOp.getLoc(), getFileOp.getResult().getType(),
817 *fdOrFailure);
818 getFileOp.replaceAllUsesWith(stream.getResult(0));
819 state.usedFileDescriptorRuntime = true;
820 state.usedSynthesisMacro = true;
821 cleanupSeeds.push_back(getFileOp);
822 }
823
824 for (auto printOp : printOps) {
825 OpBuilder builder(printOp);
826 SmallString<128> formatString;
827 SmallVector<Value> args;
828 if (failed(lowerFormatStringToSVFormat(printOp.getInput(), formatString,
829 args, builder))) {
830 printOp.emitError("cannot lower 'sim.proc.print' to SystemVerilog")
831 .attachNote(printOp.getInput().getLoc())
832 << "while lowering format string";
833 return failure();
834 }
835 auto stream = printOp.getStream();
836 if (!stream) {
837 // no stream is specified, emit sv.write.
838 sv::WriteOp::create(builder, printOp.getLoc(), formatString, args);
839 } else {
840 auto fdType = typeConverter.convertType(stream.getType());
841 assert(fdType && "expected output stream type conversion");
842 Value fd = mlir::UnrealizedConversionCastOp::create(
843 builder, printOp.getLoc(), fdType, stream)
844 ->getResult(0);
845 sv::FWriteOp::create(builder, printOp.getLoc(), fd, formatString, args);
846 }
847 cleanupSeeds.push_back(printOp);
848 }
849
850 cleanupDeadSimFmtOps(cleanupSeeds);
851
852 return success();
853}
854
855struct SimToSVPass : public circt::impl::LowerSimToSVBase<SimToSVPass> {
856 void runOnOperation() override {
857 auto circuit = getOperation();
858 MLIRContext *context = &getContext();
859 LowerDPIFunc lowerDPIFunc(circuit);
860
861 // Lower DPI functions.
862 for (auto func :
863 llvm::make_early_inc_range(circuit.getOps<sim::DPIFuncOp>()))
864 lowerDPIFunc.lower(func);
865
866 std::atomic<bool> usedSynthesisMacro = false;
867 std::atomic<bool> usedFileDescriptorRuntime = false;
868 auto lowerModule = [&](hw::HWModuleOp module) {
869 SimTypeConverter typeConverter(context);
870 SimConversionState state;
871
872 if (failed(lowerPrintFormattedProcToSV(module, typeConverter, state)))
873 return failure();
874
876 usedSynthesisMacro = true;
877
878 ConversionTarget target(*context);
879 target.addIllegalDialect<SimDialect>();
880 target.addLegalDialect<sv::SVDialect>();
881 target.addLegalDialect<hw::HWDialect>();
882 target.addLegalDialect<seq::SeqDialect>();
883 target.addLegalDialect<comb::CombDialect>();
884 target.addDynamicallyLegalOp<mlir::UnrealizedConversionCastOp>(
885 [&](mlir::UnrealizedConversionCastOp op) {
886 return typeConverter.isLegal(op);
887 });
888
889 RewritePatternSet patterns(context);
892 patterns.add<StdoutStreamLowering>(typeConverter, context);
893 patterns.add<StderrStreamLowering>(typeConverter, context);
895 patterns.add<ClockedTerminateOp>(convert);
896 patterns.add<ClockedPauseOp>(convert);
897 patterns.add<TerminateOp>(convert);
898 patterns.add<PauseOp>(convert);
900 patterns.add<DPICallLowering>(context, state);
901 auto result = applyPartialConversion(module, target, std::move(patterns));
902
903 if (failed(result))
904 return result;
905
906 // Set the emit fragments required by this module.
907 addFragments(module, lowerDPIFunc.symbolToFragment, state);
908
909 if (state.usedSynthesisMacro)
910 usedSynthesisMacro = true;
911 if (state.usedFileDescriptorRuntime)
912 usedFileDescriptorRuntime = true;
913 return result;
914 };
915
916 if (failed(mlir::failableParallelForEach(
917 context, circuit.getOps<hw::HWModuleOp>(), lowerModule)))
918 return signalPassFailure();
919
920 if (usedSynthesisMacro) {
921 Operation *op = circuit.lookupSymbol("SYNTHESIS");
922 if (op) {
923 if (!isa<sv::MacroDeclOp>(op)) {
924 op->emitOpError("should be a macro declaration");
925 return signalPassFailure();
926 }
927 } else {
928 auto builder = ImplicitLocOpBuilder::atBlockBegin(
929 UnknownLoc::get(context), circuit.getBody());
930 sv::MacroDeclOp::create(builder, "SYNTHESIS");
931 }
932 }
933
934 if (usedFileDescriptorRuntime) {
935 auto builder = ImplicitLocOpBuilder::atBlockBegin(
936 UnknownLoc::get(context), circuit.getBody());
937 sv::emitFileDescriptorRuntime(circuit, builder);
938 }
939 }
940};
941} // anonymous namespace
942
943std::unique_ptr<Pass> circt::createLowerSimToSVPass() {
944 return std::make_unique<SimToSVPass>();
945}
assert(baseType &&"element must be base type")
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:55
static ArrayAttr buildSVPerArgumentAttrs(MLIRContext *context, sim::DPIFuncOp func)
Definition SimToSV.cpp:385
static bool moveOpsIntoIfdefGuardsAndProcesses(Operation *rootOp)
Definition SimToSV.cpp:471
static LogicalResult convert(ClockedTerminateOp op, PatternRewriter &rewriter)
Definition SimToSV.cpp:247
StreamLowering< StdoutStreamOp, 0x80000001 > StdoutStreamLowering
Definition SimToSV.cpp:222
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:49
static void addFragments(hw::HWModuleOp module, const llvm::DenseMap< StringAttr, StringAttr > &symbolToFragment, const SimConversionState &state)
Definition SimToSV.cpp:450
StreamLowering< StderrStreamOp, 0x80000002 > StderrStreamLowering
Definition SimToSV.cpp:223
LogicalResult matchAndRewrite(DPICallOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:312
LogicalResult matchAndRewrite(PlusArgsTestOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:121
LogicalResult matchAndRewrite(PlusArgsValueOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:146
typename OpConversionPattern< OpTy >::OpAdaptor OpAdaptor
Definition SimToSV.cpp:210
LogicalResult matchAndRewrite(OpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:213
LogicalResult matchAndRewrite(TriggeredOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:278
LogicalResult matchAndRewrite(mlir::UnrealizedConversionCastOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const final
Definition SimToSV.cpp:232
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.
Iterative Tarjan SCC analysis on a sparse subgraph of MLIR operations.
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:943
llvm::PointerUnion< void *, mlir::Operation *, CyclicOpSCC > OpSCC
One entry in the SCC output: a null sentinel, a trivial (non-cyclic) operation, or a cyclic group.
Definition sim.py:1
circt::Namespace nameSpace
Definition SimToSV.cpp:380
void lower(sim::DPIFuncOp func)
Definition SimToSV.cpp:403
llvm::DenseMap< StringAttr, StringAttr > symbolToFragment
Definition SimToSV.cpp:379
LowerDPIFunc(mlir::ModuleOp module)
Definition SimToSV.cpp:381