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