CIRCT 24.0.0git
Loading...
Searching...
No Matches
ArcOps.cpp
Go to the documentation of this file.
1//===- ArcOps.cpp ---------------------------------------------------------===//
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
12#include "mlir/Dialect/Func/IR/FuncOps.h"
13#include "mlir/IR/Builders.h"
14#include "mlir/IR/OpImplementation.h"
15#include "mlir/IR/PatternMatch.h"
16#include "mlir/IR/SymbolTable.h"
17#include "mlir/Interfaces/FunctionImplementation.h"
18#include "mlir/Interfaces/SideEffectInterfaces.h"
19#include "llvm/ADT/SmallPtrSet.h"
20#include "llvm/ADT/TypeSwitch.h"
21
22using namespace circt;
23using namespace arc;
24using namespace mlir;
25
26//===----------------------------------------------------------------------===//
27// Helpers
28//===----------------------------------------------------------------------===//
29
30static LogicalResult verifyTypeListEquivalence(Operation *op,
31 TypeRange expectedTypeList,
32 TypeRange actualTypeList,
33 StringRef elementName) {
34 if (expectedTypeList.size() != actualTypeList.size())
35 return op->emitOpError("incorrect number of ")
36 << elementName << "s: expected " << expectedTypeList.size()
37 << ", but got " << actualTypeList.size();
38
39 for (unsigned i = 0, e = expectedTypeList.size(); i != e; ++i) {
40 if (expectedTypeList[i] != actualTypeList[i]) {
41 auto diag = op->emitOpError(elementName)
42 << " type mismatch: " << elementName << " #" << i;
43 diag.attachNote() << "expected type: " << expectedTypeList[i];
44 diag.attachNote() << " actual type: " << actualTypeList[i];
45 return diag;
46 }
47 }
48
49 return success();
50}
51
52static LogicalResult verifyArcSymbolUse(Operation *op, TypeRange inputs,
53 TypeRange results,
54 SymbolTableCollection &symbolTable) {
55 // Check that the arc attribute was specified.
56 auto arcName = op->getAttrOfType<FlatSymbolRefAttr>("arc");
57 // The arc attribute is verified by the tablegen generated verifier as it is
58 // an ODS defined attribute.
59 assert(arcName && "FlatSymbolRefAttr called 'arc' missing");
60 DefineOp arc = symbolTable.lookupNearestSymbolFrom<DefineOp>(op, arcName);
61 if (!arc)
62 return op->emitOpError() << "`" << arcName.getValue()
63 << "` does not reference a valid `arc.define`";
64
65 // Verify that the operand and result types match the arc.
66 auto type = arc.getFunctionType();
67 if (failed(
68 verifyTypeListEquivalence(op, type.getInputs(), inputs, "operand")))
69 return failure();
70
71 if (failed(
72 verifyTypeListEquivalence(op, type.getResults(), results, "result")))
73 return failure();
74
75 return success();
76}
77
78static bool isSupportedModuleOp(Operation *moduleOp) {
79 return llvm::isa<arc::ModelOp, hw::HWModuleLike>(moduleOp);
80}
81
82/// Fetches the operation pointed to by `pointing` with name `symbol`, checking
83/// that it is a supported model operation for simulation.
84static Operation *getSupportedModuleOp(SymbolTableCollection &symbolTable,
85 Operation *pointing, StringAttr symbol) {
86 Operation *moduleOp = symbolTable.lookupNearestSymbolFrom(pointing, symbol);
87 if (!moduleOp) {
88 pointing->emitOpError("model not found");
89 return nullptr;
90 }
91
92 if (!isSupportedModuleOp(moduleOp)) {
93 pointing->emitOpError("model symbol does not point to a supported model "
94 "operation, points to ")
95 << moduleOp->getName() << " instead";
96 return nullptr;
97 }
98
99 return moduleOp;
100}
101
102static std::optional<hw::ModulePort> getModulePort(Operation *moduleOp,
103 StringRef portName) {
104 auto findRightPort = [&](auto ports) -> std::optional<hw::ModulePort> {
105 const hw::ModulePort *port = llvm::find_if(
106 ports, [&](hw::ModulePort port) { return port.name == portName; });
107 if (port == ports.end())
108 return std::nullopt;
109 return *port;
110 };
111
112 return TypeSwitch<Operation *, std::optional<hw::ModulePort>>(moduleOp)
113 .Case<arc::ModelOp>(
114 [&](arc::ModelOp modelOp) -> std::optional<hw::ModulePort> {
115 return findRightPort(modelOp.getIo().getPorts());
116 })
117 .Case<hw::HWModuleLike>(
118 [&](hw::HWModuleLike moduleLike) -> std::optional<hw::ModulePort> {
119 return findRightPort(moduleLike.getPortList());
120 })
121 .Default([](Operation *) { return std::nullopt; });
122}
123
124//===----------------------------------------------------------------------===//
125// DefineOp
126//===----------------------------------------------------------------------===//
127
128ParseResult DefineOp::parse(OpAsmParser &parser, OperationState &result) {
129 auto buildFuncType =
130 [](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
131 function_interface_impl::VariadicFlag,
132 std::string &) { return builder.getFunctionType(argTypes, results); };
133
134 return function_interface_impl::parseFunctionOp(
135 parser, result, /*allowVariadic=*/false,
136 getFunctionTypeAttrName(result.name), buildFuncType,
137 getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name));
138}
139
140void DefineOp::print(OpAsmPrinter &p) {
141 function_interface_impl::printFunctionOp(
142 p, *this, /*isVariadic=*/false, "function_type", getArgAttrsAttrName(),
143 getResAttrsAttrName());
144}
145
146LogicalResult DefineOp::verifyRegions() {
147 // Check that the body does not contain any side-effecting operations. We can
148 // simply iterate over the ops directly within the body; operations with
149 // regions, like scf::IfOp, implement the `HasRecursiveMemoryEffects` trait
150 // which causes the `isMemoryEffectFree` check to already recur into their
151 // regions.
152 for (auto &op : getBodyBlock()) {
153 if (isMemoryEffectFree(&op))
154 continue;
155
156 // We don't use a op-error here because that leads to the whole arc being
157 // printed. This can be switched of when creating the context, but one
158 // might not want to switch that off for other error messages. Here it's
159 // definitely not desirable as arcs can be very big and would fill up the
160 // error log, making it hard to read. Currently, only the signature (first
161 // line) of the arc is printed.
162 auto diag = mlir::emitError(getLoc(), "body contains non-pure operation");
163 diag.attachNote(op.getLoc()).append("first non-pure operation here: ");
164 return diag;
165 }
166 return success();
167}
168
169bool DefineOp::isPassthrough() {
170 if (getNumArguments() != getNumResults())
171 return false;
172
173 return llvm::all_of(
174 llvm::zip(getArguments(), getBodyBlock().getTerminator()->getOperands()),
175 [](const auto &argAndRes) {
176 return std::get<0>(argAndRes) == std::get<1>(argAndRes);
177 });
178}
179
180//===----------------------------------------------------------------------===//
181// OutputOp
182//===----------------------------------------------------------------------===//
183
184LogicalResult OutputOp::verify() {
185 auto *parent = (*this)->getParentOp();
186 TypeRange expectedTypes = parent->getResultTypes();
187 if (auto defOp = dyn_cast<DefineOp>(parent))
188 expectedTypes = defOp.getResultTypes();
189
190 TypeRange actualTypes = getOperands().getTypes();
191 return verifyTypeListEquivalence(*this, expectedTypes, actualTypes, "output");
192}
193
194//===----------------------------------------------------------------------===//
195// StateOp
196//===----------------------------------------------------------------------===//
197
198LogicalResult StateOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
199 return verifyArcSymbolUse(*this, getInputs().getTypes(),
200 getResults().getTypes(), symbolTable);
201}
202
203LogicalResult StateOp::verify() {
204 if (getLatency() < 1)
205 return emitOpError("latency must be a positive integer");
206
207 if (!getClock())
208 return emitOpError("requires a clock");
209
210 return success();
211}
212
213//===----------------------------------------------------------------------===//
214// StateWriteOp
215//===----------------------------------------------------------------------===//
216
217LogicalResult
218StateWriteOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
219 if (!getTraceTapModel().has_value())
220 return success();
221
222 auto modelOp = symbolTable.lookupNearestSymbolFrom<ModelOp>(
223 getOperation(), getTraceTapModelAttr());
224 if (!modelOp)
225 return emitOpError() << "`" << getTraceTapModelAttr()
226 << "` does not reference a valid `arc.model`";
227 if (!modelOp.getTraceTaps())
228 return emitOpError() << "referenced model has no trace metadata";
229 if (modelOp.getTraceTapsAttr().size() <= *getTraceTapIndex())
230 return emitOpError() << "tap index exceeds model's tap array";
231 auto tapAttr =
232 cast<TraceTapAttr>(modelOp.getTraceTapsAttr()[*getTraceTapIndex()]);
233 if (tapAttr.getSigType().getValue() != getValue().getType())
234 return emitOpError() << "incorrect signal type in referenced tap attribute";
235
236 return success();
237}
238
239LogicalResult StateWriteOp::verify() {
240 if (getTraceTapIndex().has_value() == getTraceTapModel().has_value())
241 return success();
242 return emitOpError() << "must specify both a trace tap model and index";
243}
244
245//===----------------------------------------------------------------------===//
246// CallOp
247//===----------------------------------------------------------------------===//
248
249LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
250 return verifyArcSymbolUse(*this, getInputs().getTypes(),
251 getResults().getTypes(), symbolTable);
252}
253
254bool CallOp::isClocked() { return false; }
255
256Value CallOp::getClock() { return Value{}; }
257
258void CallOp::eraseClock() {}
259
260uint32_t CallOp::getLatency() { return 0; }
261
262//===----------------------------------------------------------------------===//
263// MemoryWritePortOp
264//===----------------------------------------------------------------------===//
265
266SmallVector<Type> MemoryWritePortOp::getArcResultTypes() {
267 auto memType = cast<MemoryType>(getMemory().getType());
268 SmallVector<Type> resultTypes{memType.getAddressType(),
269 memType.getWordType()};
270 if (getEnable())
271 resultTypes.push_back(IntegerType::get(getContext(), 1));
272 if (getMask())
273 resultTypes.push_back(memType.getWordType());
274 return resultTypes;
275}
276
277LogicalResult
278MemoryWritePortOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
279 return verifyArcSymbolUse(*this, getInputs().getTypes(), getArcResultTypes(),
280 symbolTable);
281}
282
283LogicalResult MemoryWritePortOp::verify() {
284 if (getLatency() < 1)
285 return emitOpError("latency must be at least 1");
286
287 if (!getClock())
288 return emitOpError("requires a clock");
289
290 return success();
291}
292
293//===----------------------------------------------------------------------===//
294// RootInputOp
295//===----------------------------------------------------------------------===//
296
297void RootInputOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
298 SmallString<32> buf("in_");
299 buf += getName();
300 setNameFn(getState(), buf);
301}
302
303//===----------------------------------------------------------------------===//
304// RootOutputOp
305//===----------------------------------------------------------------------===//
306
307void RootOutputOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
308 SmallString<32> buf("out_");
309 buf += getName();
310 setNameFn(getState(), buf);
311}
312
313//===----------------------------------------------------------------------===//
314// ModelOp
315//===----------------------------------------------------------------------===//
316
317LogicalResult ModelOp::verify() {
318 if (getBodyBlock().getArguments().size() != 1)
319 return emitOpError("must have exactly one argument");
320 if (auto type = getBodyBlock().getArgument(0).getType();
321 !isa<StorageType>(type))
322 return emitOpError("argument must be of storage type");
323 for (const hw::ModulePort &port : getIo().getPorts())
324 if (port.dir == hw::ModulePort::Direction::InOut)
325 return emitOpError("inout ports are not supported");
326 return success();
327}
328
329LogicalResult ModelOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
330 auto fnAttrs = std::array{getInitialFnAttr(), getFinalFnAttr()};
331 auto nouns = std::array{"initializer", "finalizer"};
332 for (auto [fnAttr, noun] : llvm::zip(fnAttrs, nouns)) {
333 if (!fnAttr)
334 continue;
335 auto fn = symbolTable.lookupNearestSymbolFrom<func::FuncOp>(*this, fnAttr);
336 if (!fn)
337 return emitOpError() << noun << " '" << fnAttr.getValue()
338 << "' does not reference a valid function";
339 if (!llvm::equal(fn.getArgumentTypes(), getBody().getArgumentTypes())) {
340 auto diag = emitError() << noun << " '" << fnAttr.getValue()
341 << "' arguments must match arguments of model";
342 diag.attachNote(fn.getLoc()) << noun << " declared here:";
343 return diag;
344 }
345 }
346 return success();
347}
348
349//===----------------------------------------------------------------------===//
350// LutOp
351//===----------------------------------------------------------------------===//
352
353LogicalResult LutOp::verify() {
354 Location firstSideEffectOpLoc = UnknownLoc::get(getContext());
355 const WalkResult result = getBody().walk([&](Operation *op) {
356 if (auto memOp = dyn_cast<MemoryEffectOpInterface>(op)) {
357 SmallVector<SideEffects::EffectInstance<MemoryEffects::Effect>> effects;
358 memOp.getEffects(effects);
359
360 if (!effects.empty()) {
361 firstSideEffectOpLoc = memOp->getLoc();
362 return WalkResult::interrupt();
363 }
364 }
365
366 return WalkResult::advance();
367 });
368
369 if (result.wasInterrupted())
370 return emitOpError("no operations with side-effects allowed inside a LUT")
371 .attachNote(firstSideEffectOpLoc)
372 << "first operation with side-effects here";
373
374 return success();
375}
376
377//===----------------------------------------------------------------------===//
378// VectorizeOp
379//===----------------------------------------------------------------------===//
380
381LogicalResult VectorizeOp::verify() {
382 if (getInputs().empty())
383 return emitOpError("there has to be at least one input vector");
384
385 if (!llvm::all_equal(llvm::map_range(
386 getInputs(), [](OperandRange range) { return range.size(); })))
387 return emitOpError("all input vectors must have the same size");
388
389 for (OperandRange range : getInputs()) {
390 if (!llvm::all_equal(range.getTypes()))
391 return emitOpError("all input vector lane types must match");
392
393 if (range.empty())
394 return emitOpError("input vector must have at least one element");
395 }
396
397 if (getResults().empty())
398 return emitOpError("must have at least one result");
399
400 if (!llvm::all_equal(getResults().getTypes()))
401 return emitOpError("all result types must match");
402
403 if (getResults().size() != getInputs().front().size())
404 return emitOpError("number results must match input vector size");
405
406 return success();
407}
408
409static FailureOr<unsigned> getVectorWidth(Type base, Type vectorized) {
410 if (isa<VectorType>(base))
411 return failure();
412
413 if (auto vectorTy = dyn_cast<VectorType>(vectorized)) {
414 if (vectorTy.getElementType() != base)
415 return failure();
416
417 return vectorTy.getDimSize(0);
418 }
419
420 if (vectorized.getIntOrFloatBitWidth() < base.getIntOrFloatBitWidth())
421 return failure();
422
423 if (vectorized.getIntOrFloatBitWidth() % base.getIntOrFloatBitWidth() == 0)
424 return vectorized.getIntOrFloatBitWidth() / base.getIntOrFloatBitWidth();
425
426 return failure();
427}
428
429LogicalResult VectorizeOp::verifyRegions() {
430 auto returnOp = cast<VectorizeReturnOp>(getBody().front().getTerminator());
431 TypeRange bodyArgTypes = getBody().front().getArgumentTypes();
432
433 if (bodyArgTypes.size() != getInputs().size())
434 return emitOpError(
435 "number of block arguments must match number of input vectors");
436
437 // Boundary and body are vectorized, or both are not vectorized
438 if (returnOp.getValue().getType() == getResultTypes().front()) {
439 for (auto [i, argTy] : llvm::enumerate(bodyArgTypes))
440 if (argTy != getInputs()[i].getTypes().front())
441 return emitOpError("if terminator type matches result type the "
442 "argument types must match the input types");
443
444 return success();
445 }
446
447 // Boundary is vectorized, body is not
448 if (auto width = getVectorWidth(returnOp.getValue().getType(),
449 getResultTypes().front());
450 succeeded(width)) {
451 for (auto [i, argTy] : llvm::enumerate(bodyArgTypes)) {
452 Type inputTy = getInputs()[i].getTypes().front();
453 FailureOr<unsigned> argWidth = getVectorWidth(argTy, inputTy);
454 if (failed(argWidth))
455 return emitOpError("block argument must be a scalar variant of the "
456 "vectorized operand");
457
458 if (*argWidth != width)
459 return emitOpError("input and output vector width must match");
460 }
461
462 return success();
463 }
464
465 // Body is vectorized, boundary is not
466 if (auto width = getVectorWidth(getResultTypes().front(),
467 returnOp.getValue().getType());
468 succeeded(width)) {
469 for (auto [i, argTy] : llvm::enumerate(bodyArgTypes)) {
470 Type inputTy = getInputs()[i].getTypes().front();
471 FailureOr<unsigned> argWidth = getVectorWidth(inputTy, argTy);
472 if (failed(argWidth))
473 return emitOpError(
474 "block argument must be a vectorized variant of the operand");
475
476 if (*argWidth != width)
477 return emitOpError("input and output vector width must match");
478
479 if (getInputs()[i].size() > 1 && argWidth != getInputs()[i].size())
480 return emitOpError(
481 "when boundary not vectorized the number of vector element "
482 "operands must match the width of the vectorized body");
483 }
484
485 return success();
486 }
487
488 return returnOp.emitOpError(
489 "operand type must match parent op's result value or be a vectorized or "
490 "non-vectorized variant of it");
491}
492
493bool VectorizeOp::isBoundaryVectorized() {
494 return getInputs().front().size() == 1;
495}
496bool VectorizeOp::isBodyVectorized() {
497 auto returnOp = cast<VectorizeReturnOp>(getBody().front().getTerminator());
498 if (isBoundaryVectorized() &&
499 returnOp.getValue().getType() == getResultTypes().front())
500 return true;
501
502 if (auto width = getVectorWidth(getResultTypes().front(),
503 returnOp.getValue().getType());
504 succeeded(width))
505 return *width > 1;
506
507 return false;
508}
509
510//===----------------------------------------------------------------------===//
511// SimInstantiateOp
512//===----------------------------------------------------------------------===//
513
514void SimInstantiateOp::print(OpAsmPrinter &p) {
515 BlockArgument modelArg = getBody().getArgument(0);
516 auto modelType = cast<SimModelInstanceType>(modelArg.getType());
517
518 p << " " << modelType.getModel() << " as ";
519 p.printRegionArgument(modelArg, {}, true);
520
521 if (getRuntimeModel() || getRuntimeArgs()) {
522 p << " runtime ";
523 if (getRuntimeModel())
524 p << getRuntimeModelAttr();
525 p << "(";
526 if (getRuntimeArgs())
527 p << getRuntimeArgsAttr();
528 p << ")";
529 }
530
531 p.printOptionalAttrDictWithKeyword(
532 getOperation()->getAttrs(),
533 {getRuntimeModelAttrName(), getRuntimeArgsAttrName()});
534
535 p << " ";
536
537 p.printRegion(getBody(), false);
538}
539
540ParseResult SimInstantiateOp::parse(OpAsmParser &parser,
541 OperationState &result) {
542 StringAttr modelName;
543 if (failed(parser.parseSymbolName(modelName)))
544 return failure();
545
546 if (failed(parser.parseKeyword("as")))
547 return failure();
548
549 OpAsmParser::Argument modelArg;
550 if (failed(parser.parseArgument(modelArg, false, false)))
551 return failure();
552
553 if (succeeded(parser.parseOptionalKeyword("runtime"))) {
554 StringAttr runtimeSym;
555 StringAttr runtimeArgs;
556 auto symOpt = parser.parseOptionalSymbolName(runtimeSym);
557 if (parser.parseLParen())
558 return failure();
559 auto nameOpt = parser.parseOptionalAttribute(runtimeArgs);
560 if (parser.parseRParen())
561 return failure();
562 if (succeeded(symOpt))
563 result.addAttribute(
564 SimInstantiateOp::getRuntimeModelAttrName(result.name),
565 FlatSymbolRefAttr::get(runtimeSym));
566 if (nameOpt.has_value())
567 result.addAttribute(SimInstantiateOp::getRuntimeArgsAttrName(result.name),
568 runtimeArgs);
569 }
570
571 if (failed(parser.parseOptionalAttrDictWithKeyword(result.attributes)))
572 return failure();
573
574 MLIRContext *ctxt = result.getContext();
575 modelArg.type =
576 SimModelInstanceType::get(ctxt, FlatSymbolRefAttr::get(ctxt, modelName));
577
578 std::unique_ptr<Region> body = std::make_unique<Region>();
579 if (failed(parser.parseRegion(*body, {modelArg})))
580 return failure();
581
582 result.addRegion(std::move(body));
583 return success();
584}
585
586LogicalResult SimInstantiateOp::verifyRegions() {
587 Region &body = getBody();
588 if (body.getNumArguments() != 1)
589 return emitError("entry block of body region must have the model instance "
590 "as a single argument");
591 if (!llvm::isa<SimModelInstanceType>(body.getArgument(0).getType()))
592 return emitError("entry block argument type is not a model instance");
593 return success();
594}
595
596LogicalResult
597SimInstantiateOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
598 bool failed = false;
599 Operation *moduleOp = getSupportedModuleOp(
600 symbolTable, getOperation(),
601 llvm::cast<SimModelInstanceType>(getBody().getArgument(0).getType())
602 .getModel()
603 .getAttr());
604 if (!moduleOp)
605 failed = true;
606
607 if (getRuntimeModel().has_value()) {
608 Operation *runtimeModelOp = symbolTable.lookupNearestSymbolFrom(
609 getOperation(), getRuntimeModelAttr());
610 if (!runtimeModelOp) {
611 emitOpError("runtime model not found");
612 failed = true;
613 } else if (!isa<RuntimeModelOp>(runtimeModelOp)) {
614 emitOpError("referenced runtime model is not a RuntimeModelOp");
615 failed = true;
616 }
617 }
618
619 return success(!failed);
620}
621
622//===----------------------------------------------------------------------===//
623// SimSetInputOp
624//===----------------------------------------------------------------------===//
625
626LogicalResult
627SimSetInputOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
628 Operation *moduleOp = getSupportedModuleOp(
629 symbolTable, getOperation(),
630 llvm::cast<SimModelInstanceType>(getInstance().getType())
631 .getModel()
632 .getAttr());
633 if (!moduleOp)
634 return failure();
635
636 std::optional<hw::ModulePort> port = getModulePort(moduleOp, getInput());
637 if (!port)
638 return emitOpError("port not found on model");
639
640 if (port->dir != hw::ModulePort::Direction::Input &&
641 port->dir != hw::ModulePort::Direction::InOut)
642 return emitOpError("port is not an input port");
643
644 if (port->type != getValue().getType())
645 return emitOpError(
646 "mismatched types between value and model port, port expects ")
647 << port->type;
648
649 return success();
650}
651
652//===----------------------------------------------------------------------===//
653// SimGetPortOp
654//===----------------------------------------------------------------------===//
655
656LogicalResult
657SimGetPortOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
658 Operation *moduleOp = getSupportedModuleOp(
659 symbolTable, getOperation(),
660 llvm::cast<SimModelInstanceType>(getInstance().getType())
661 .getModel()
662 .getAttr());
663 if (!moduleOp)
664 return failure();
665
666 std::optional<hw::ModulePort> port = getModulePort(moduleOp, getPort());
667 if (!port)
668 return emitOpError("port not found on model");
669
670 if (port->type != getValue().getType())
671 return emitOpError(
672 "mismatched types between value and model port, port expects ")
673 << port->type;
674
675 return success();
676}
677
678//===----------------------------------------------------------------------===//
679// SimStepOp
680//===----------------------------------------------------------------------===//
681
682LogicalResult SimStepOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
683 Operation *moduleOp = getSupportedModuleOp(
684 symbolTable, getOperation(),
685 llvm::cast<SimModelInstanceType>(getInstance().getType())
686 .getModel()
687 .getAttr());
688 if (!moduleOp)
689 return failure();
690
691 return success();
692}
693
694//===----------------------------------------------------------------------===//
695// SimSetTimeOp
696//===----------------------------------------------------------------------===//
697
698LogicalResult
699SimSetTimeOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
700 Operation *moduleOp = getSupportedModuleOp(
701 symbolTable, getOperation(),
702 llvm::cast<SimModelInstanceType>(getInstance().getType())
703 .getModel()
704 .getAttr());
705 if (!moduleOp)
706 return failure();
707
708 return success();
709}
710
711//===----------------------------------------------------------------------===//
712// CoroutineDefineOp
713//===----------------------------------------------------------------------===//
714
715/// Resolve the callee symbol to a `CoroutineDefineOp` and verify that the
716/// given operand and result types match its function type.
717static LogicalResult verifyCoroutineCallTypes(Operation *op,
718 FlatSymbolRefAttr callee,
719 TypeRange operands,
720 TypeRange results,
721 SymbolTableCollection &symTable) {
722 auto defineOp =
723 symTable.lookupNearestSymbolFrom<CoroutineDefineOp>(op, callee);
724 if (!defineOp)
725 return op->emitOpError() << "`" << callee.getValue()
726 << "` does not reference a valid "
727 "`arc.coroutine.define`";
728
729 auto fnType = defineOp.getFunctionType();
730 if (failed(verifyTypeListEquivalence(op, fnType.getInputs(), operands,
731 "operand")))
732 return failure();
733 if (failed(verifyTypeListEquivalence(op, fnType.getResults(), results,
734 "result")))
735 return failure();
736 return success();
737}
738
739ParseResult CoroutineDefineOp::parse(OpAsmParser &parser,
740 OperationState &result) {
741 auto buildFuncType =
742 [](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
743 function_interface_impl::VariadicFlag,
744 std::string &) { return builder.getFunctionType(argTypes, results); };
745
746 return function_interface_impl::parseFunctionOp(
747 parser, result, /*allowVariadic=*/false,
748 getFunctionTypeAttrName(result.name), buildFuncType,
749 getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name));
750}
751
752void CoroutineDefineOp::print(OpAsmPrinter &p) {
753 function_interface_impl::printFunctionOp(
754 p, *this, /*isVariadic=*/false, "function_type", getArgAttrsAttrName(),
755 getResAttrsAttrName());
756}
757
758//===----------------------------------------------------------------------===//
759// CoroutineCallOp
760//===----------------------------------------------------------------------===//
761
762LogicalResult
763CoroutineCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
764 // The `state`/`pc` and `resumeState`/`resumePC` types are constrained to
765 // wrap the callee symbol by the `CoroutineCalleeWrappedType` traits on the
766 // op. All that remains is to resolve the callee and check that the trailing
767 // arg/result types match its signature.
768 auto callee = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
769 return verifyCoroutineCallTypes(*this, callee, getArgs().getTypes(),
770 getResults().getTypes(), symbolTable);
771}
772
773//===----------------------------------------------------------------------===//
774// CoroutineInstanceOp
775//===----------------------------------------------------------------------===//
776
777// An instance hides the coroutine's trailing observe bitmask and wakeup time.
778// Verify that the callee declares a bitmask and wakeup as its last two results
779// and that the instance's args and results match the callee's signature with
780// those two results removed.
781LogicalResult
782CoroutineInstanceOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
783 auto callee = (*this)->getAttrOfType<FlatSymbolRefAttr>("callee");
784 auto defineOp =
785 symbolTable.lookupNearestSymbolFrom<CoroutineDefineOp>(*this, callee);
786 if (!defineOp)
787 return emitOpError() << "`" << callee.getValue()
788 << "` does not reference a valid "
789 "`arc.coroutine.define`";
790
791 auto fnType = defineOp.getFunctionType();
792 auto fnResults = fnType.getResults();
793 if (fnResults.size() < 2 || !fnResults.back().isInteger(64))
794 return emitOpError() << "referenced coroutine `" << callee.getValue()
795 << "` must produce an `i64` wakeup time as its "
796 "last result";
797
798 // The observe bitmask carries one bit per coroutine argument.
799 auto maskType = dyn_cast<IntegerType>(fnResults[fnResults.size() - 2]);
800 if (!maskType || maskType.getWidth() != fnType.getNumInputs())
801 return emitOpError()
802 << "referenced coroutine `" << callee.getValue()
803 << "` must produce an observe bitmask with one bit per "
804 "argument (`i"
805 << fnType.getNumInputs() << "`) as its second-to-last result";
806
807 if (failed(verifyTypeListEquivalence(*this, fnType.getInputs(),
808 getArgs().getTypes(), "operand")))
809 return failure();
810 if (failed(verifyTypeListEquivalence(*this, fnResults.drop_back(2),
811 getResults().getTypes(), "result")))
812 return failure();
813 return success();
814}
815
816//===----------------------------------------------------------------------===//
817// Coroutine Terminators
818//===----------------------------------------------------------------------===//
819
820// The three terminators all yield values back through the enclosing
821// `arc.coroutine.define`'s result types. The helper below extracts the
822// expected types from the parent and checks them against the given operand
823// types.
824static LogicalResult verifyCoroutineTerminator(Operation *op,
825 TypeRange yieldOperands) {
826 auto parent = op->getParentOfType<CoroutineDefineOp>();
827 return verifyTypeListEquivalence(op, parent.getResultTypes(), yieldOperands,
828 "yielded value");
829}
830
831LogicalResult CoroutineYieldOp::verify() {
832 if (failed(verifyCoroutineTerminator(*this, getYieldOperands().getTypes())))
833 return failure();
834
835 // The `BranchOpInterface` already verifies that the destination block has
836 // the right number of arguments and that the trailing arguments match the
837 // yield's destination operands. Additionally verify that the leading
838 // arguments, which are supplied fresh by the caller upon resumption, match
839 // the coroutine's function type.
840 auto parent = (*this)->getParentOfType<CoroutineDefineOp>();
841 TypeRange coroutineArgTypes = parent.getArgumentTypes();
842 TypeRange destArgTypes = getDest()->getArgumentTypes();
843 if (destArgTypes.size() >= coroutineArgTypes.size())
844 if (failed(verifyTypeListEquivalence(
845 *this, coroutineArgTypes,
846 destArgTypes.take_front(coroutineArgTypes.size()),
847 "destination resume argument")))
848 return failure();
849
850 return success();
851}
852
853// The destination block's leading arguments match the coroutine's function
854// type and are supplied fresh by the caller upon resumption. They are
855// therefore "produced" operands from the branch's point of view. The
856// remaining destination block arguments map to the yield's destination
857// operands.
858SuccessorOperands CoroutineYieldOp::getSuccessorOperands(unsigned index) {
859 assert(index == 0 && "invalid successor index");
860 auto parent = (*this)->getParentOfType<CoroutineDefineOp>();
861 return SuccessorOperands(parent.getArgumentTypes().size(),
862 getDestOperandsMutable());
863}
864
865LogicalResult CoroutineReturnOp::verify() {
866 return verifyCoroutineTerminator(*this, getYieldOperands().getTypes());
867}
868
869LogicalResult CoroutineHaltOp::verify() {
870 return verifyCoroutineTerminator(*this, getYieldOperands().getTypes());
871}
872
873//===----------------------------------------------------------------------===//
874// ExecuteOp
875//===----------------------------------------------------------------------===//
876
877LogicalResult ExecuteOp::verifyRegions() {
878 return verifyTypeListEquivalence(*this, getInputs().getTypes(),
879 getBody().getArgumentTypes(), "input");
880}
881
882//===----------------------------------------------------------------------===//
883// ArrayRefAllocOp
884//===----------------------------------------------------------------------===//
885
886LogicalResult ArrayRefAllocOp::verify() {
887 if (auto init = getInit()) {
888 if (init->size() != getType().getNumElements()) {
889 return emitOpError("init size does not match array size; init had size ")
890 << init->size() << " but array has size "
891 << getType().getNumElements();
892 }
893
894 if (auto intTy = dyn_cast<IntegerType>(getType().getElementType())) {
895 unsigned elemBitwidth = intTy.getWidth();
896 for (Attribute attr : *init) {
897 auto intAttr = dyn_cast<IntegerAttr>(attr);
898 if (!intAttr || intAttr.getValue().getBitWidth() != elemBitwidth) {
899 return emitOpError("expected element to be of type ")
900 << getType().getElementType();
901 }
902 }
903 }
904 }
905 return success();
906}
907
908#include "circt/Dialect/Arc/ArcInterfaces.cpp.inc"
909
910#define GET_OP_CLASSES
911#include "circt/Dialect/Arc/Arc.cpp.inc"
static FailureOr< unsigned > getVectorWidth(Type base, Type vectorized)
Definition ArcOps.cpp:409
static std::optional< hw::ModulePort > getModulePort(Operation *moduleOp, StringRef portName)
Definition ArcOps.cpp:102
static bool isSupportedModuleOp(Operation *moduleOp)
Definition ArcOps.cpp:78
static LogicalResult verifyArcSymbolUse(Operation *op, TypeRange inputs, TypeRange results, SymbolTableCollection &symbolTable)
Definition ArcOps.cpp:52
static LogicalResult verifyTypeListEquivalence(Operation *op, TypeRange expectedTypeList, TypeRange actualTypeList, StringRef elementName)
Definition ArcOps.cpp:30
static LogicalResult verifyCoroutineCallTypes(Operation *op, FlatSymbolRefAttr callee, TypeRange operands, TypeRange results, SymbolTableCollection &symTable)
Resolve the callee symbol to a CoroutineDefineOp and verify that the given operand and result types m...
Definition ArcOps.cpp:717
static LogicalResult verifyCoroutineTerminator(Operation *op, TypeRange yieldOperands)
Definition ArcOps.cpp:824
static Operation * getSupportedModuleOp(SymbolTableCollection &symbolTable, Operation *pointing, StringAttr symbol)
Fetches the operation pointed to by pointing with name symbol, checking that it is a supported model ...
Definition ArcOps.cpp:84
assert(baseType &&"element must be base type")
static PortInfo getPort(ModuleTy &mod, size_t idx)
Definition HWOps.cpp:1475
@ InOut
Definition HW.h:42
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:222
static Block * getBodyBlock(FModuleLike mod)
static InstancePath empty
Definition arc.py:1
Direction
The direction of a Component or Cell port.
Definition CalyxOps.h:76
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
Definition LLVM.h:193
mlir::StringAttr name
Definition HWTypes.h:32