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