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