CIRCT  18.0.0git
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 
10 #include "mlir/IR/Builders.h"
11 #include "mlir/IR/OpImplementation.h"
12 #include "mlir/IR/PatternMatch.h"
13 #include "mlir/IR/SymbolTable.h"
14 #include "mlir/Interfaces/FunctionImplementation.h"
15 #include "mlir/Interfaces/SideEffectInterfaces.h"
16 
17 using namespace circt;
18 using namespace arc;
19 using namespace mlir;
20 
21 //===----------------------------------------------------------------------===//
22 // Helpers
23 //===----------------------------------------------------------------------===//
24 
25 static LogicalResult verifyTypeListEquivalence(Operation *op,
26  TypeRange expectedTypeList,
27  TypeRange actualTypeList,
28  StringRef elementName) {
29  if (expectedTypeList.size() != actualTypeList.size())
30  return op->emitOpError("incorrect number of ")
31  << elementName << "s: expected " << expectedTypeList.size()
32  << ", but got " << actualTypeList.size();
33 
34  for (unsigned i = 0, e = expectedTypeList.size(); i != e; ++i) {
35  if (expectedTypeList[i] != actualTypeList[i]) {
36  auto diag = op->emitOpError(elementName)
37  << " type mismatch: " << elementName << " #" << i;
38  diag.attachNote() << "expected type: " << expectedTypeList[i];
39  diag.attachNote() << " actual type: " << actualTypeList[i];
40  return diag;
41  }
42  }
43 
44  return success();
45 }
46 
47 static LogicalResult verifyArcSymbolUse(Operation *op, TypeRange inputs,
48  TypeRange results,
49  SymbolTableCollection &symbolTable) {
50  // Check that the arc attribute was specified.
51  auto arcName = op->getAttrOfType<FlatSymbolRefAttr>("arc");
52  // The arc attribute is verified by the tablegen generated verifier as it is
53  // an ODS defined attribute.
54  assert(arcName && "FlatSymbolRefAttr called 'arc' missing");
55  DefineOp arc = symbolTable.lookupNearestSymbolFrom<DefineOp>(op, arcName);
56  if (!arc)
57  return op->emitOpError() << "`" << arcName.getValue()
58  << "` does not reference a valid `arc.define`";
59 
60  // Verify that the operand and result types match the arc.
61  auto type = arc.getFunctionType();
62  if (failed(
63  verifyTypeListEquivalence(op, type.getInputs(), inputs, "operand")))
64  return failure();
65 
66  if (failed(
67  verifyTypeListEquivalence(op, type.getResults(), results, "result")))
68  return failure();
69 
70  return success();
71 }
72 
73 //===----------------------------------------------------------------------===//
74 // DefineOp
75 //===----------------------------------------------------------------------===//
76 
77 ParseResult DefineOp::parse(OpAsmParser &parser, OperationState &result) {
78  auto buildFuncType =
79  [](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
80  function_interface_impl::VariadicFlag,
81  std::string &) { return builder.getFunctionType(argTypes, results); };
82 
83  return function_interface_impl::parseFunctionOp(
84  parser, result, /*allowVariadic=*/false,
85  getFunctionTypeAttrName(result.name), buildFuncType,
86  getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name));
87 }
88 
89 void DefineOp::print(OpAsmPrinter &p) {
90  function_interface_impl::printFunctionOp(
91  p, *this, /*isVariadic=*/false, "function_type", getArgAttrsAttrName(),
92  getResAttrsAttrName());
93 }
94 
95 LogicalResult DefineOp::verifyRegions() {
96  // Check that the body does not contain any side-effecting operations. We can
97  // simply iterate over the ops directly within the body; operations with
98  // regions, like scf::IfOp, implement the `HasRecursiveMemoryEffects` trait
99  // which causes the `isMemoryEffectFree` check to already recur into their
100  // regions.
101  for (auto &op : getBodyBlock()) {
102  if (isMemoryEffectFree(&op))
103  continue;
104 
105  // We don't use a op-error here because that leads to the whole arc being
106  // printed. This can be switched of when creating the context, but one
107  // might not want to switch that off for other error messages. Here it's
108  // definitely not desirable as arcs can be very big and would fill up the
109  // error log, making it hard to read. Currently, only the signature (first
110  // line) of the arc is printed.
111  auto diag = mlir::emitError(getLoc(), "body contains non-pure operation");
112  diag.attachNote(op.getLoc()).append("first non-pure operation here: ");
113  return diag;
114  }
115  return success();
116 }
117 
118 bool DefineOp::isPassthrough() {
119  if (getNumArguments() != getNumResults())
120  return false;
121 
122  return llvm::all_of(
123  llvm::zip(getArguments(), getBodyBlock().getTerminator()->getOperands()),
124  [](const auto &argAndRes) {
125  return std::get<0>(argAndRes) == std::get<1>(argAndRes);
126  });
127 }
128 
129 //===----------------------------------------------------------------------===//
130 // OutputOp
131 //===----------------------------------------------------------------------===//
132 
133 LogicalResult OutputOp::verify() {
134  auto *parent = (*this)->getParentOp();
135  TypeRange expectedTypes = parent->getResultTypes();
136  if (auto defOp = dyn_cast<DefineOp>(parent))
137  expectedTypes = defOp.getResultTypes();
138 
139  TypeRange actualTypes = getOperands().getTypes();
140  return verifyTypeListEquivalence(*this, expectedTypes, actualTypes, "output");
141 }
142 
143 //===----------------------------------------------------------------------===//
144 // StateOp
145 //===----------------------------------------------------------------------===//
146 
147 LogicalResult StateOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
148  return verifyArcSymbolUse(*this, getInputs().getTypes(),
149  getResults().getTypes(), symbolTable);
150 }
151 
152 LogicalResult StateOp::verify() {
153  if (getLatency() > 0 && !getOperation()->getParentOfType<ClockDomainOp>() &&
154  !getClock())
155  return emitOpError(
156  "with non-zero latency outside a clock domain requires a clock");
157 
158  if (getLatency() == 0) {
159  if (getClock())
160  return emitOpError("with zero latency cannot have a clock");
161  if (getEnable())
162  return emitOpError("with zero latency cannot have an enable");
163  if (getReset())
164  return emitOpError("with zero latency cannot have a reset");
165  }
166 
167  if (getOperation()->getParentOfType<ClockDomainOp>() && getClock())
168  return emitOpError("inside a clock domain cannot have a clock");
169 
170  return success();
171 }
172 
173 bool StateOp::isClocked() { return getLatency() > 0; }
174 
175 //===----------------------------------------------------------------------===//
176 // CallOp
177 //===----------------------------------------------------------------------===//
178 
179 LogicalResult CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
180  return verifyArcSymbolUse(*this, getInputs().getTypes(),
181  getResults().getTypes(), symbolTable);
182 }
183 
184 //===----------------------------------------------------------------------===//
185 // MemoryWritePortOp
186 //===----------------------------------------------------------------------===//
187 
188 SmallVector<Type> MemoryWritePortOp::getArcResultTypes() {
189  auto memType = cast<MemoryType>(getMemory().getType());
190  SmallVector<Type> resultTypes{memType.getAddressType(),
191  memType.getWordType()};
192  if (getEnable())
193  resultTypes.push_back(IntegerType::get(getContext(), 1));
194  if (getMask())
195  resultTypes.push_back(memType.getWordType());
196  return resultTypes;
197 }
198 
199 LogicalResult
200 MemoryWritePortOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
201  return verifyArcSymbolUse(*this, getInputs().getTypes(), getArcResultTypes(),
202  symbolTable);
203 }
204 
205 LogicalResult MemoryWritePortOp::verify() {
206  if (getLatency() < 1)
207  return emitOpError("latency must be at least 1");
208 
209  if (!getOperation()->getParentOfType<ClockDomainOp>() && !getClock())
210  return emitOpError("outside a clock domain requires a clock");
211 
212  if (getOperation()->getParentOfType<ClockDomainOp>() && getClock())
213  return emitOpError("inside a clock domain cannot have a clock");
214 
215  return success();
216 }
217 
218 //===----------------------------------------------------------------------===//
219 // ClockDomainOp
220 //===----------------------------------------------------------------------===//
221 
222 LogicalResult ClockDomainOp::verifyRegions() {
223  return verifyTypeListEquivalence(*this, getBodyBlock().getArgumentTypes(),
224  getInputs().getTypes(), "input");
225 }
226 
227 //===----------------------------------------------------------------------===//
228 // RootInputOp
229 //===----------------------------------------------------------------------===//
230 
232  SmallString<32> buf("in_");
233  buf += getName();
234  setNameFn(getState(), buf);
235 }
236 
237 //===----------------------------------------------------------------------===//
238 // RootOutputOp
239 //===----------------------------------------------------------------------===//
240 
242  SmallString<32> buf("out_");
243  buf += getName();
244  setNameFn(getState(), buf);
245 }
246 
247 //===----------------------------------------------------------------------===//
248 // ModelOp
249 //===----------------------------------------------------------------------===//
250 
251 LogicalResult ModelOp::verify() {
252  if (getBodyBlock().getArguments().size() != 1)
253  return emitOpError("must have exactly one argument");
254  if (auto type = getBodyBlock().getArgument(0).getType();
255  !isa<StorageType>(type))
256  return emitOpError("argument must be of storage type");
257  return success();
258 }
259 
260 //===----------------------------------------------------------------------===//
261 // LutOp
262 //===----------------------------------------------------------------------===//
263 
264 LogicalResult LutOp::verify() {
265  Location firstSideEffectOpLoc = UnknownLoc::get(getContext());
266  const WalkResult result = getBody().walk([&](Operation *op) {
267  if (auto memOp = dyn_cast<MemoryEffectOpInterface>(op)) {
268  SmallVector<SideEffects::EffectInstance<MemoryEffects::Effect>> effects;
269  memOp.getEffects(effects);
270 
271  if (!effects.empty()) {
272  firstSideEffectOpLoc = memOp->getLoc();
273  return WalkResult::interrupt();
274  }
275  }
276 
277  return WalkResult::advance();
278  });
279 
280  if (result.wasInterrupted())
281  return emitOpError("no operations with side-effects allowed inside a LUT")
282  .attachNote(firstSideEffectOpLoc)
283  << "first operation with side-effects here";
284 
285  return success();
286 }
287 
288 //===----------------------------------------------------------------------===//
289 // VectorizeOp
290 //===----------------------------------------------------------------------===//
291 
292 LogicalResult VectorizeOp::verify() {
293  if (getInputs().empty())
294  return emitOpError("there has to be at least one input vector");
295 
296  if (!llvm::all_equal(llvm::map_range(
297  getInputs(), [](OperandRange range) { return range.size(); })))
298  return emitOpError("all input vectors must have the same size");
299 
300  for (OperandRange range : getInputs()) {
301  if (!llvm::all_equal(range.getTypes()))
302  return emitOpError("all input vector lane types must match");
303 
304  if (range.empty())
305  return emitOpError("input vector must have at least one element");
306  }
307 
308  if (getInputs().front().size() > 1 &&
309  !isa<IntegerType>(getInputs().front().front().getType()))
310  return emitOpError("input vector element type must be a signless integer");
311 
312  if (getResults().empty())
313  return emitOpError("must have at least one result");
314 
315  if (!llvm::all_equal(getResults().getTypes()))
316  return emitOpError("all result types must match");
317 
318  if (getResults().size() != getInputs().front().size())
319  return emitOpError("number results must match input vector size");
320 
321  if (getResults().size() > 1 &&
322  !isa<IntegerType>(getResults().front().getType()))
323  return emitError(
324  "may only return a vector type if boundary is already vectorized");
325 
326  return success();
327 }
328 
329 static FailureOr<unsigned> getVectorWidth(Type base, Type vectorized) {
330  if (isa<VectorType>(base))
331  return failure();
332 
333  if (auto vectorTy = dyn_cast<VectorType>(vectorized)) {
334  if (vectorTy.getElementType() != base)
335  return failure();
336 
337  return vectorTy.getDimSize(0);
338  }
339 
340  if (vectorized.getIntOrFloatBitWidth() < base.getIntOrFloatBitWidth())
341  return failure();
342 
343  if (vectorized.getIntOrFloatBitWidth() % base.getIntOrFloatBitWidth() == 0)
344  return vectorized.getIntOrFloatBitWidth() / base.getIntOrFloatBitWidth();
345 
346  return failure();
347 }
348 
349 LogicalResult VectorizeOp::verifyRegions() {
350  auto returnOp = cast<VectorizeReturnOp>(getBody().front().getTerminator());
351  TypeRange bodyArgTypes = getBody().front().getArgumentTypes();
352 
353  if (bodyArgTypes.size() != getInputs().size())
354  return emitOpError(
355  "number of block arguments must match number of input vectors");
356 
357  // Boundary and body are vectorized, or both are not vectorized
358  if (returnOp.getValue().getType() == getResultTypes().front()) {
359  for (auto [i, argTy] : llvm::enumerate(bodyArgTypes))
360  if (argTy != getInputs()[i].getTypes().front())
361  return emitOpError("if terminator type matches result type the "
362  "argument types must match the input types");
363 
364  return success();
365  }
366 
367  // Boundary is vectorized, body is not
368  if (auto width = getVectorWidth(returnOp.getValue().getType(),
369  getResultTypes().front());
370  succeeded(width)) {
371  for (auto [i, argTy] : llvm::enumerate(bodyArgTypes)) {
372  Type inputTy = getInputs()[i].getTypes().front();
373  FailureOr<unsigned> argWidth = getVectorWidth(argTy, inputTy);
374  if (failed(argWidth))
375  return emitOpError("block argument must be a scalar variant of the "
376  "vectorized operand");
377 
378  if (*argWidth != width)
379  return emitOpError("input and output vector width must match");
380  }
381 
382  return success();
383  }
384 
385  // Body is vectorized, boundary is not
386  if (auto width = getVectorWidth(getResultTypes().front(),
387  returnOp.getValue().getType());
388  succeeded(width)) {
389  for (auto [i, argTy] : llvm::enumerate(bodyArgTypes)) {
390  Type inputTy = getInputs()[i].getTypes().front();
391  FailureOr<unsigned> argWidth = getVectorWidth(inputTy, argTy);
392  if (failed(argWidth))
393  return emitOpError(
394  "block argument must be a vectorized variant of the operand");
395 
396  if (*argWidth != width)
397  return emitOpError("input and output vector width must match");
398 
399  if (getInputs()[i].size() > 1 && argWidth != getInputs()[i].size())
400  return emitOpError(
401  "when boundary not vectorized the number of vector element "
402  "operands must match the width of the vectorized body");
403  }
404 
405  return success();
406  }
407 
408  return returnOp.emitOpError(
409  "operand type must match parent op's result value or be a vectorized or "
410  "non-vectorized variant of it");
411 }
412 
413 bool VectorizeOp::isBoundaryVectorized() {
414  return getInputs().front().size() == 1;
415 }
416 bool VectorizeOp::isBodyVectorized() {
417  auto returnOp = cast<VectorizeReturnOp>(getBody().front().getTerminator());
418  if (isBoundaryVectorized() &&
419  returnOp.getValue().getType() == getResultTypes().front())
420  return true;
421 
422  if (auto width = getVectorWidth(getResultTypes().front(),
423  returnOp.getValue().getType());
424  succeeded(width))
425  return *width > 1;
426 
427  return false;
428 }
429 
430 #include "circt/Dialect/Arc/ArcInterfaces.cpp.inc"
431 
432 #define GET_OP_CLASSES
433 #include "circt/Dialect/Arc/Arc.cpp.inc"
static LogicalResult verifyArcSymbolUse(Operation *op, TypeRange inputs, TypeRange results, SymbolTableCollection &symbolTable)
Definition: ArcOps.cpp:47
static LogicalResult verifyTypeListEquivalence(Operation *op, TypeRange expectedTypeList, TypeRange actualTypeList, StringRef elementName)
Definition: ArcOps.cpp:25
static FailureOr< unsigned > getVectorWidth(Type base, Type vectorized)
Definition: ArcOps.cpp:329
assert(baseType &&"element must be base type")
int32_t width
Definition: FIRRTL.cpp:27
static InstancePath empty
llvm::SmallVector< StringAttr > inputs
Builder builder
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
void getAsmResultNames(OpAsmSetValueNameFn setNameFn, StringRef instanceName, ArrayAttr resultNames, ValueRange results)
Suggest a name for each result value based on the saved result names attribute.
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
Definition: LLVM.h:186