CIRCT  20.0.0git
SeqOps.cpp
Go to the documentation of this file.
1 //===- SeqOps.cpp - Implement the Seq operations ------------------------===//
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 file implements sequential ops.
10 //
11 //===----------------------------------------------------------------------===//
12 
14 #include "circt/Dialect/HW/HWOps.h"
18 #include "mlir/Analysis/TopologicalSortUtils.h"
19 #include "mlir/IR/Builders.h"
20 #include "mlir/IR/DialectImplementation.h"
21 #include "mlir/IR/Matchers.h"
22 #include "mlir/IR/PatternMatch.h"
23 
25 #include "llvm/ADT/SmallString.h"
26 
27 using namespace mlir;
28 using namespace circt;
29 using namespace seq;
30 
31 bool circt::seq::isValidIndexValues(Value hlmemHandle, ValueRange addresses) {
32  auto memType = cast<seq::HLMemType>(hlmemHandle.getType());
33  auto shape = memType.getShape();
34  if (shape.size() != addresses.size())
35  return false;
36 
37  for (auto [dim, addr] : llvm::zip(shape, addresses)) {
38  auto addrType = dyn_cast<IntegerType>(addr.getType());
39  if (!addrType)
40  return false;
41  if (addrType.getIntOrFloatBitWidth() != llvm::Log2_64_Ceil(dim))
42  return false;
43  }
44  return true;
45 }
46 
47 // If there was no name specified, check to see if there was a useful name
48 // specified in the asm file.
49 static void setNameFromResult(OpAsmParser &parser, OperationState &result) {
50  if (result.attributes.getNamed("name"))
51  return;
52  // If there is no explicit name attribute, get it from the SSA result name.
53  // If numeric, just use an empty name.
54  StringRef resultName = parser.getResultName(0).first;
55  if (!resultName.empty() && isdigit(resultName[0]))
56  resultName = "";
57  result.addAttribute("name", parser.getBuilder().getStringAttr(resultName));
58 }
59 
60 static bool canElideName(OpAsmPrinter &p, Operation *op) {
61  if (!op->hasAttr("name"))
62  return true;
63 
64  auto name = op->getAttrOfType<StringAttr>("name").getValue();
65  if (name.empty())
66  return true;
67 
68  SmallString<32> resultNameStr;
69  llvm::raw_svector_ostream tmpStream(resultNameStr);
70  p.printOperand(op->getResult(0), tmpStream);
71  auto actualName = tmpStream.str().drop_front();
72  return actualName == name;
73 }
74 
75 static ParseResult
76 parseOptionalTypeMatch(OpAsmParser &parser, Type refType,
77  std::optional<OpAsmParser::UnresolvedOperand> operand,
78  Type &type) {
79  if (operand)
80  type = refType;
81  return success();
82 }
83 
84 static void printOptionalTypeMatch(OpAsmPrinter &p, Operation *op, Type refType,
85  Value operand, Type type) {
86  // Nothing to do - this is strictly an implicit parsing helper.
87 }
88 
90  OpAsmParser &parser, Type refType,
91  std::optional<OpAsmParser::UnresolvedOperand> operand, Type &type) {
92  if (operand)
93  type = seq::ImmutableType::get(refType);
94  return success();
95 }
96 
97 static void printOptionalImmutableTypeMatch(OpAsmPrinter &p, Operation *op,
98  Type refType, Value operand,
99  Type type) {
100  // Nothing to do - this is strictly an implicit parsing helper.
101 }
102 
103 //===----------------------------------------------------------------------===//
104 // ReadPortOp
105 //===----------------------------------------------------------------------===//
106 
107 ParseResult ReadPortOp::parse(OpAsmParser &parser, OperationState &result) {
108  llvm::SMLoc loc = parser.getCurrentLocation();
109 
110  OpAsmParser::UnresolvedOperand memOperand, rdenOperand;
111  bool hasRdEn = false;
112  llvm::SmallVector<OpAsmParser::UnresolvedOperand, 2> addressOperands;
113  seq::HLMemType memType;
114 
115  if (parser.parseOperand(memOperand) ||
116  parser.parseOperandList(addressOperands, OpAsmParser::Delimiter::Square))
117  return failure();
118 
119  if (succeeded(parser.parseOptionalKeyword("rden"))) {
120  if (failed(parser.parseOperand(rdenOperand)))
121  return failure();
122  hasRdEn = true;
123  }
124 
125  if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() ||
126  parser.parseType(memType))
127  return failure();
128 
129  llvm::SmallVector<Type> operandTypes = memType.getAddressTypes();
130  operandTypes.insert(operandTypes.begin(), memType);
131 
132  llvm::SmallVector<OpAsmParser::UnresolvedOperand> allOperands = {memOperand};
133  llvm::copy(addressOperands, std::back_inserter(allOperands));
134  if (hasRdEn) {
135  operandTypes.push_back(parser.getBuilder().getI1Type());
136  allOperands.push_back(rdenOperand);
137  }
138 
139  if (parser.resolveOperands(allOperands, operandTypes, loc, result.operands))
140  return failure();
141 
142  result.addTypes(memType.getElementType());
143 
144  llvm::SmallVector<int32_t, 2> operandSizes;
145  operandSizes.push_back(1); // memory handle
146  operandSizes.push_back(addressOperands.size());
147  operandSizes.push_back(hasRdEn ? 1 : 0);
148  result.addAttribute("operandSegmentSizes",
149  parser.getBuilder().getDenseI32ArrayAttr(operandSizes));
150  return success();
151 }
152 
153 void ReadPortOp::print(OpAsmPrinter &p) {
154  p << " " << getMemory() << "[" << getAddresses() << "]";
155  if (getRdEn())
156  p << " rden " << getRdEn();
157  p.printOptionalAttrDict((*this)->getAttrs(), {"operandSegmentSizes"});
158  p << " : " << getMemory().getType();
159 }
160 
162  auto memName = getMemory().getDefiningOp<seq::HLMemOp>().getName();
163  setNameFn(getReadData(), (memName + "_rdata").str());
164 }
165 
166 void ReadPortOp::build(OpBuilder &builder, OperationState &result, Value memory,
167  ValueRange addresses, Value rdEn, unsigned latency) {
168  auto memType = cast<seq::HLMemType>(memory.getType());
169  ReadPortOp::build(builder, result, memType.getElementType(), memory,
170  addresses, rdEn, latency);
171 }
172 
173 //===----------------------------------------------------------------------===//
174 // WritePortOp
175 //===----------------------------------------------------------------------===//
176 
177 ParseResult WritePortOp::parse(OpAsmParser &parser, OperationState &result) {
178  llvm::SMLoc loc = parser.getCurrentLocation();
179  OpAsmParser::UnresolvedOperand memOperand, dataOperand, wrenOperand;
180  llvm::SmallVector<OpAsmParser::UnresolvedOperand, 2> addressOperands;
181  seq::HLMemType memType;
182 
183  if (parser.parseOperand(memOperand) ||
184  parser.parseOperandList(addressOperands,
185  OpAsmParser::Delimiter::Square) ||
186  parser.parseOperand(dataOperand) || parser.parseKeyword("wren") ||
187  parser.parseOperand(wrenOperand) ||
188  parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() ||
189  parser.parseType(memType))
190  return failure();
191 
192  llvm::SmallVector<Type> operandTypes = memType.getAddressTypes();
193  operandTypes.insert(operandTypes.begin(), memType);
194  operandTypes.push_back(memType.getElementType());
195  operandTypes.push_back(parser.getBuilder().getI1Type());
196 
197  llvm::SmallVector<OpAsmParser::UnresolvedOperand, 2> allOperands(
198  addressOperands);
199  allOperands.insert(allOperands.begin(), memOperand);
200  allOperands.push_back(dataOperand);
201  allOperands.push_back(wrenOperand);
202 
203  if (parser.resolveOperands(allOperands, operandTypes, loc, result.operands))
204  return failure();
205 
206  return success();
207 }
208 
209 void WritePortOp::print(OpAsmPrinter &p) {
210  p << " " << getMemory() << "[" << getAddresses() << "] " << getInData()
211  << " wren " << getWrEn();
212  p.printOptionalAttrDict((*this)->getAttrs());
213  p << " : " << getMemory().getType();
214 }
215 
216 //===----------------------------------------------------------------------===//
217 // HLMemOp
218 //===----------------------------------------------------------------------===//
219 
221  setNameFn(getHandle(), getName());
222 }
223 
224 void HLMemOp::build(OpBuilder &builder, OperationState &result, Value clk,
225  Value rst, StringRef name, llvm::ArrayRef<int64_t> shape,
226  Type elementType) {
227  HLMemType t = HLMemType::get(builder.getContext(), shape, elementType);
228  HLMemOp::build(builder, result, t, clk, rst, name);
229 }
230 
231 //===----------------------------------------------------------------------===//
232 // FIFOOp
233 //===----------------------------------------------------------------------===//
234 
235 // Flag threshold custom directive
236 static ParseResult parseFIFOFlagThreshold(OpAsmParser &parser,
237  IntegerAttr &threshold,
238  Type &outputFlagType,
239  StringRef directive) {
240  // look for an optional "almost_full $threshold" group.
241  if (succeeded(parser.parseOptionalKeyword(directive))) {
242  int64_t thresholdValue;
243  if (succeeded(parser.parseInteger(thresholdValue))) {
244  threshold = parser.getBuilder().getI64IntegerAttr(thresholdValue);
245  outputFlagType = parser.getBuilder().getI1Type();
246  return success();
247  }
248  return parser.emitError(parser.getNameLoc(),
249  "expected integer value after " + directive +
250  " directive");
251  }
252  return success();
253 }
254 
255 ParseResult parseFIFOAFThreshold(OpAsmParser &parser, IntegerAttr &threshold,
256  Type &outputFlagType) {
257  return parseFIFOFlagThreshold(parser, threshold, outputFlagType,
258  "almost_full");
259 }
260 
261 ParseResult parseFIFOAEThreshold(OpAsmParser &parser, IntegerAttr &threshold,
262  Type &outputFlagType) {
263  return parseFIFOFlagThreshold(parser, threshold, outputFlagType,
264  "almost_empty");
265 }
266 
267 void printFIFOAFThreshold(OpAsmPrinter &p, Operation *op, IntegerAttr threshold,
268  Type outputFlagType) {
269  if (threshold)
270  p << "almost_full"
271  << " " << threshold.getInt();
272 }
273 
274 void printFIFOAEThreshold(OpAsmPrinter &p, Operation *op, IntegerAttr threshold,
275  Type outputFlagType) {
276  if (threshold)
277  p << "almost_empty"
278  << " " << threshold.getInt();
279 }
280 
282  setNameFn(getOutput(), "out");
283  setNameFn(getEmpty(), "empty");
284  setNameFn(getFull(), "full");
285  if (auto ae = getAlmostEmpty())
286  setNameFn(ae, "almostEmpty");
287  if (auto af = getAlmostFull())
288  setNameFn(af, "almostFull");
289 }
290 
291 LogicalResult FIFOOp::verify() {
292  auto aet = getAlmostEmptyThreshold();
293  auto aft = getAlmostFullThreshold();
294  size_t depth = getDepth();
295  if (aft.has_value() && aft.value() > depth)
296  return emitOpError("almost full threshold must be <= FIFO depth");
297 
298  if (aet.has_value() && aet.value() > depth)
299  return emitOpError("almost empty threshold must be <= FIFO depth");
300 
301  return success();
302 }
303 
304 //===----------------------------------------------------------------------===//
305 // CompRegOp
306 //===----------------------------------------------------------------------===//
307 
308 /// Suggest a name for each result value based on the saved result names
309 /// attribute.
311  // If the wire has an optional 'name' attribute, use it.
312  if (auto name = getName())
313  setNameFn(getResult(), *name);
314 }
315 
316 LogicalResult CompRegOp::verify() {
317  if ((getReset() == nullptr) ^ (getResetValue() == nullptr))
318  return emitOpError(
319  "either reset and resetValue or neither must be specified");
320  return success();
321 }
322 
323 std::optional<size_t> CompRegOp::getTargetResultIndex() { return 0; }
324 
325 template <typename TOp>
326 LogicalResult verifyResets(TOp op) {
327  if ((op.getReset() == nullptr) ^ (op.getResetValue() == nullptr))
328  return op->emitOpError(
329  "either reset and resetValue or neither must be specified");
330  bool hasReset = op.getReset() != nullptr;
331  if (hasReset && op.getResetValue().getType() != op.getInput().getType())
332  return op->emitOpError("reset value must be the same type as the input");
333 
334  return success();
335 }
336 
337 /// Suggest a name for each result value based on the saved result names
338 /// attribute.
340  // If the wire has an optional 'name' attribute, use it.
341  if (auto name = getName())
342  setNameFn(getResult(), *name);
343 }
344 
345 std::optional<size_t> CompRegClockEnabledOp::getTargetResultIndex() {
346  return 0;
347 }
348 
349 LogicalResult CompRegClockEnabledOp::verify() {
350  if (failed(verifyResets(*this)))
351  return failure();
352  return success();
353 }
354 
355 //===----------------------------------------------------------------------===//
356 // ShiftRegOp
357 //===----------------------------------------------------------------------===//
358 
360  // If the wire has an optional 'name' attribute, use it.
361  if (auto name = getName())
362  setNameFn(getResult(), *name);
363 }
364 
365 std::optional<size_t> ShiftRegOp::getTargetResultIndex() { return 0; }
366 
367 LogicalResult ShiftRegOp::verify() {
368  if (failed(verifyResets(*this)))
369  return failure();
370  return success();
371 }
372 
373 //===----------------------------------------------------------------------===//
374 // FirRegOp
375 //===----------------------------------------------------------------------===//
376 
377 void FirRegOp::build(OpBuilder &builder, OperationState &result, Value input,
378  Value clk, StringAttr name, hw::InnerSymAttr innerSym,
379  Attribute preset) {
380 
381  OpBuilder::InsertionGuard guard(builder);
382 
383  result.addOperands(input);
384  result.addOperands(clk);
385 
386  result.addAttribute(getNameAttrName(result.name), name);
387 
388  if (innerSym)
389  result.addAttribute(getInnerSymAttrName(result.name), innerSym);
390 
391  if (preset)
392  result.addAttribute(getPresetAttrName(result.name), preset);
393 
394  result.addTypes(input.getType());
395 }
396 
397 void FirRegOp::build(OpBuilder &builder, OperationState &result, Value input,
398  Value clk, StringAttr name, Value reset, Value resetValue,
399  hw::InnerSymAttr innerSym, bool isAsync) {
400 
401  OpBuilder::InsertionGuard guard(builder);
402 
403  result.addOperands(input);
404  result.addOperands(clk);
405  result.addOperands(reset);
406  result.addOperands(resetValue);
407 
408  result.addAttribute(getNameAttrName(result.name), name);
409  if (isAsync)
410  result.addAttribute(getIsAsyncAttrName(result.name), builder.getUnitAttr());
411 
412  if (innerSym)
413  result.addAttribute(getInnerSymAttrName(result.name), innerSym);
414 
415  result.addTypes(input.getType());
416 }
417 
418 ParseResult FirRegOp::parse(OpAsmParser &parser, OperationState &result) {
419  auto &builder = parser.getBuilder();
420  llvm::SMLoc loc = parser.getCurrentLocation();
421 
422  using Op = OpAsmParser::UnresolvedOperand;
423 
424  Op next, clk;
425  if (parser.parseOperand(next) || parser.parseKeyword("clock") ||
426  parser.parseOperand(clk))
427  return failure();
428 
429  if (succeeded(parser.parseOptionalKeyword("sym"))) {
430  hw::InnerSymAttr innerSym;
431  if (parser.parseCustomAttributeWithFallback(innerSym, /*type=*/nullptr,
432  "inner_sym", result.attributes))
433  return failure();
434  }
435 
436  // Parse reset [sync|async] %reset, %value
437  std::optional<std::pair<Op, Op>> resetAndValue;
438  if (succeeded(parser.parseOptionalKeyword("reset"))) {
439  bool isAsync;
440  if (succeeded(parser.parseOptionalKeyword("async")))
441  isAsync = true;
442  else if (succeeded(parser.parseOptionalKeyword("sync")))
443  isAsync = false;
444  else
445  return parser.emitError(loc, "invalid reset, expected 'sync' or 'async'");
446  if (isAsync)
447  result.attributes.append("isAsync", builder.getUnitAttr());
448 
449  resetAndValue = {{}, {}};
450  if (parser.parseOperand(resetAndValue->first) || parser.parseComma() ||
451  parser.parseOperand(resetAndValue->second))
452  return failure();
453  }
454 
455  std::optional<APInt> presetValue;
456  llvm::SMLoc presetValueLoc;
457  if (succeeded(parser.parseOptionalKeyword("preset"))) {
458  presetValueLoc = parser.getCurrentLocation();
459  OptionalParseResult presetIntResult =
460  parser.parseOptionalInteger(presetValue.emplace());
461  if (!presetIntResult.has_value() || failed(*presetIntResult))
462  return parser.emitError(loc, "expected integer value");
463  }
464 
465  Type ty;
466  if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() ||
467  parser.parseType(ty))
468  return failure();
469  result.addTypes({ty});
470 
471  if (presetValue) {
472  uint64_t width = 0;
473  if (hw::type_isa<seq::ClockType>(ty)) {
474  width = 1;
475  } else {
476  int64_t maybeWidth = hw::getBitWidth(ty);
477  if (maybeWidth < 0)
478  return parser.emitError(presetValueLoc,
479  "cannot preset register of unknown width");
480  width = maybeWidth;
481  }
482 
483  APInt presetResult = presetValue->sextOrTrunc(width);
484  if (presetResult.zextOrTrunc(presetValue->getBitWidth()) != *presetValue)
485  return parser.emitError(loc, "preset value too large");
486 
487  auto builder = parser.getBuilder();
488  auto presetTy = builder.getIntegerType(width);
489  auto resultAttr = builder.getIntegerAttr(presetTy, presetResult);
490  result.addAttribute("preset", resultAttr);
491  }
492 
493  setNameFromResult(parser, result);
494 
495  if (parser.resolveOperand(next, ty, result.operands))
496  return failure();
497 
498  Type clkTy = ClockType::get(result.getContext());
499  if (parser.resolveOperand(clk, clkTy, result.operands))
500  return failure();
501 
502  if (resetAndValue) {
503  Type i1 = IntegerType::get(result.getContext(), 1);
504  if (parser.resolveOperand(resetAndValue->first, i1, result.operands) ||
505  parser.resolveOperand(resetAndValue->second, ty, result.operands))
506  return failure();
507  }
508 
509  return success();
510 }
511 
512 void FirRegOp::print(::mlir::OpAsmPrinter &p) {
513  SmallVector<StringRef> elidedAttrs = {
514  getInnerSymAttrName(), getIsAsyncAttrName(), getPresetAttrName()};
515 
516  p << ' ' << getNext() << " clock " << getClk();
517 
518  if (auto sym = getInnerSymAttr()) {
519  p << " sym ";
520  sym.print(p);
521  }
522 
523  if (hasReset()) {
524  p << " reset " << (getIsAsync() ? "async" : "sync") << ' ';
525  p << getReset() << ", " << getResetValue();
526  }
527 
528  if (auto preset = getPresetAttr()) {
529  p << " preset " << preset.getValue();
530  }
531 
532  if (canElideName(p, *this))
533  elidedAttrs.push_back("name");
534 
535  p.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
536  p << " : " << getNext().getType();
537 }
538 
539 /// Verifier for the FIR register op.
540 LogicalResult FirRegOp::verify() {
541  if (getReset() || getResetValue() || getIsAsync()) {
542  if (!getReset() || !getResetValue())
543  return emitOpError("must specify reset and reset value");
544  } else {
545  if (getIsAsync())
546  return emitOpError("register with no reset cannot be async");
547  }
548  if (auto preset = getPresetAttr()) {
549  int64_t presetWidth = hw::getBitWidth(preset.getType());
550  int64_t width = hw::getBitWidth(getType());
551  if (preset.getType() != getType() && presetWidth != width)
552  return emitOpError("preset type width must match register type");
553  }
554  return success();
555 }
556 
557 /// Suggest a name for each result value based on the saved result names
558 /// attribute.
560  // If the register has an optional 'name' attribute, use it.
561  if (!getName().empty())
562  setNameFn(getResult(), getName());
563 }
564 
565 std::optional<size_t> FirRegOp::getTargetResultIndex() { return 0; }
566 
567 LogicalResult FirRegOp::canonicalize(FirRegOp op, PatternRewriter &rewriter) {
568 
569  // If the register has a constant zero reset, drop the reset and reset value
570  // altogether (And preserve the PresetAttr).
571  if (auto reset = op.getReset()) {
572  if (auto constOp = reset.getDefiningOp<hw::ConstantOp>()) {
573  if (constOp.getValue().isZero()) {
574  rewriter.replaceOpWithNewOp<FirRegOp>(
575  op, op.getNext(), op.getClk(), op.getNameAttr(),
576  op.getInnerSymAttr(), op.getPresetAttr());
577  return success();
578  }
579  }
580  }
581 
582  // If the register has a symbol, we can't optimize it away.
583  if (op.getInnerSymAttr())
584  return failure();
585 
586  // Replace a register with a trivial feedback or constant clock with a
587  // constant zero.
588  // TODO: Once HW aggregate constant values are supported, move this
589  // canonicalization to the folder.
590  auto isConstant = [&]() -> bool {
591  if (op.getNext() == op.getResult())
592  return true;
593  if (auto clk = op.getClk().getDefiningOp<seq::ToClockOp>())
594  return clk.getInput().getDefiningOp<hw::ConstantOp>();
595  return false;
596  };
597 
598  // Preset can block canonicalization only if it is non-zero.
599  bool replaceWithConstZero = true;
600  if (auto preset = op.getPresetAttr())
601  if (!preset.getValue().isZero())
602  replaceWithConstZero = false;
603 
604  if (isConstant() && !op.getResetValue() && replaceWithConstZero) {
605  if (isa<seq::ClockType>(op.getType())) {
606  rewriter.replaceOpWithNewOp<seq::ConstClockOp>(
607  op, seq::ClockConstAttr::get(rewriter.getContext(), ClockConst::Low));
608  } else {
609  auto constant = rewriter.create<hw::ConstantOp>(
610  op.getLoc(), APInt::getZero(hw::getBitWidth(op.getType())));
611  rewriter.replaceOpWithNewOp<hw::BitcastOp>(op, op.getType(), constant);
612  }
613  return success();
614  }
615 
616  // For reset-less 1d array registers, replace an uninitialized element with
617  // constant zero. For example, let `r` be a 2xi1 register and its next value
618  // be `{foo, r[0]}`. `r[0]` is connected to itself so will never be
619  // initialized. If we don't enable aggregate preservation, `r_0` is replaced
620  // with `0`. Hence this canonicalization replaces 0th element of the next
621  // value with zero to match the behaviour.
622  if (!op.getReset() && !op.getPresetAttr()) {
623  if (auto arrayCreate = op.getNext().getDefiningOp<hw::ArrayCreateOp>()) {
624  // For now only support 1d arrays.
625  // TODO: Support nested arrays and bundles.
626  if (isa<IntegerType>(
627  hw::type_cast<hw::ArrayType>(op.getResult().getType())
628  .getElementType())) {
629  SmallVector<Value> nextOperands;
630  bool changed = false;
631  for (const auto &[i, value] :
632  llvm::enumerate(arrayCreate.getOperands())) {
633  auto index = arrayCreate.getOperands().size() - i - 1;
634  APInt elementIndex;
635  // Check that the corresponding operand is op's element.
636  if (auto arrayGet = value.getDefiningOp<hw::ArrayGetOp>())
637  if (arrayGet.getInput() == op.getResult() &&
638  matchPattern(arrayGet.getIndex(),
639  m_ConstantInt(&elementIndex)) &&
640  elementIndex == index) {
641  nextOperands.push_back(rewriter.create<hw::ConstantOp>(
642  op.getLoc(),
643  APInt::getZero(hw::getBitWidth(arrayGet.getType()))));
644  changed = true;
645  continue;
646  }
647  nextOperands.push_back(value);
648  }
649  // If one of the operands is self loop, update the next value.
650  if (changed) {
651  auto newNextVal = rewriter.create<hw::ArrayCreateOp>(
652  arrayCreate.getLoc(), nextOperands);
653  if (arrayCreate->hasOneUse())
654  // If the original next value has a single use, we can replace the
655  // value directly.
656  rewriter.replaceOp(arrayCreate, newNextVal);
657  else {
658  // Otherwise, replace the entire firreg with a new one.
659  rewriter.replaceOpWithNewOp<FirRegOp>(op, newNextVal, op.getClk(),
660  op.getNameAttr(),
661  op.getInnerSymAttr());
662  }
663 
664  return success();
665  }
666  }
667  }
668  }
669 
670  return failure();
671 }
672 
673 OpFoldResult FirRegOp::fold(FoldAdaptor adaptor) {
674  // If the register has a symbol or preset value, we can't optimize it away.
675  // TODO: Handle a preset value.
676  if (getInnerSymAttr())
677  return {};
678 
679  auto presetAttr = getPresetAttr();
680 
681  // If the register is held in permanent reset, replace it with its reset
682  // value. This works trivially if the reset is asynchronous and therefore
683  // level-sensitive, in which case it will always immediately assume the reset
684  // value in silicon. If it is synchronous, the register value is undefined
685  // until the first clock edge at which point it becomes the reset value, in
686  // which case we simply define the initial value to already be the reset
687  // value. Works only if no preset.
688  if (!presetAttr)
689  if (auto reset = getReset())
690  if (auto constOp = reset.getDefiningOp<hw::ConstantOp>())
691  if (constOp.getValue().isOne())
692  return getResetValue();
693 
694  // If the register's next value is trivially it's current value, or the
695  // register is never clocked, we can replace the register with a constant
696  // value.
697  bool isTrivialFeedback = (getNext() == getResult());
698  bool isNeverClocked =
699  adaptor.getClk() != nullptr; // clock operand is constant
700  if (!isTrivialFeedback && !isNeverClocked)
701  return {};
702 
703  // If the register has a const reset value, and no preset, we can replace it
704  // with the const reset. We cannot replace it with a non-constant reset value.
705  if (auto resetValue = getResetValue()) {
706  if (auto *op = resetValue.getDefiningOp()) {
707  if (op->hasTrait<OpTrait::ConstantLike>() && !presetAttr)
708  return resetValue;
709  if (auto constOp = dyn_cast<hw::ConstantOp>(op))
710  if (presetAttr.getValue() == constOp.getValue())
711  return resetValue;
712  }
713  return {};
714  }
715 
716  // Otherwise we want to replace the register with a constant 0. For now this
717  // only works with integer types.
718  auto intType = dyn_cast<IntegerType>(getType());
719  if (!intType)
720  return {};
721  // If preset present, then replace with preset.
722  if (presetAttr)
723  return presetAttr;
724  return IntegerAttr::get(intType, 0);
725 }
726 
727 //===----------------------------------------------------------------------===//
728 // ClockGateOp
729 //===----------------------------------------------------------------------===//
730 
731 OpFoldResult ClockGateOp::fold(FoldAdaptor adaptor) {
732  // Forward the clock if one of the enables is always true.
733  if (isConstantOne(adaptor.getEnable()) ||
734  isConstantOne(adaptor.getTestEnable()))
735  return getInput();
736 
737  // Fold to a constant zero clock if the enables are always false.
738  if (isConstantZero(adaptor.getEnable()) &&
739  (!getTestEnable() || isConstantZero(adaptor.getTestEnable())))
740  return ClockConstAttr::get(getContext(), ClockConst::Low);
741 
742  // Forward constant zero clocks.
743  if (auto clockAttr = dyn_cast_or_null<ClockConstAttr>(adaptor.getInput()))
744  if (clockAttr.getValue() == ClockConst::Low)
745  return ClockConstAttr::get(getContext(), ClockConst::Low);
746 
747  // Transitive clock gating - eliminate clock gates that are driven by an
748  // identical enable signal somewhere higher in the clock gate hierarchy.
749  auto clockGateInputOp = getInput().getDefiningOp<ClockGateOp>();
750  while (clockGateInputOp) {
751  if (clockGateInputOp.getEnable() == getEnable() &&
752  clockGateInputOp.getTestEnable() == getTestEnable())
753  return getInput();
754  clockGateInputOp = clockGateInputOp.getInput().getDefiningOp<ClockGateOp>();
755  }
756 
757  return {};
758 }
759 
760 LogicalResult ClockGateOp::canonicalize(ClockGateOp op,
761  PatternRewriter &rewriter) {
762  // Remove constant false test enable.
763  if (auto testEnable = op.getTestEnable()) {
764  if (auto constOp = testEnable.getDefiningOp<hw::ConstantOp>()) {
765  if (constOp.getValue().isZero()) {
766  rewriter.modifyOpInPlace(op,
767  [&] { op.getTestEnableMutable().clear(); });
768  return success();
769  }
770  }
771  }
772 
773  return failure();
774 }
775 
776 std::optional<size_t> ClockGateOp::getTargetResultIndex() {
777  return std::nullopt;
778 }
779 
780 //===----------------------------------------------------------------------===//
781 // ClockMuxOp
782 //===----------------------------------------------------------------------===//
783 
784 OpFoldResult ClockMuxOp::fold(FoldAdaptor adaptor) {
785  if (isConstantOne(adaptor.getCond()))
786  return getTrueClock();
787  if (isConstantZero(adaptor.getCond()))
788  return getFalseClock();
789  return {};
790 }
791 
792 //===----------------------------------------------------------------------===//
793 // FirMemOp
794 //===----------------------------------------------------------------------===//
795 
796 LogicalResult FirMemOp::canonicalize(FirMemOp op, PatternRewriter &rewriter) {
797  // Do not change memories if symbols point to them.
798  if (op.getInnerSymAttr())
799  return failure();
800 
801  // If the memory has no read ports, erase it.
802  for (auto *user : op->getUsers()) {
803  if (isa<FirMemReadOp, FirMemReadWriteOp>(user))
804  return failure();
805  assert(isa<FirMemWriteOp>(user) && "invalid seq.firmem user");
806  }
807 
808  for (auto *user : llvm::make_early_inc_range(op->getUsers()))
809  rewriter.eraseOp(user);
810 
811  rewriter.eraseOp(op);
812  return success();
813 }
814 
816  auto nameAttr = (*this)->getAttrOfType<StringAttr>("name");
817  if (!nameAttr.getValue().empty())
818  setNameFn(getResult(), nameAttr.getValue());
819 }
820 
821 std::optional<size_t> FirMemOp::getTargetResultIndex() { return 0; }
822 
823 template <class Op>
824 static LogicalResult verifyFirMemMask(Op op) {
825  if (auto mask = op.getMask()) {
826  auto memType = op.getMemory().getType();
827  if (!memType.getMaskWidth())
828  return op.emitOpError("has mask operand but memory type '")
829  << memType << "' has no mask";
830  auto expected = IntegerType::get(op.getContext(), *memType.getMaskWidth());
831  if (mask.getType() != expected)
832  return op.emitOpError("has mask operand of type '")
833  << mask.getType() << "', but memory type requires '" << expected
834  << "'";
835  }
836  return success();
837 }
838 
839 LogicalResult FirMemWriteOp::verify() { return verifyFirMemMask(*this); }
840 LogicalResult FirMemReadWriteOp::verify() { return verifyFirMemMask(*this); }
841 
842 static bool isConstClock(Value value) {
843  if (!value)
844  return false;
845  return value.getDefiningOp<seq::ConstClockOp>();
846 }
847 
848 static bool isConstZero(Value value) {
849  if (value)
850  if (auto constOp = value.getDefiningOp<hw::ConstantOp>())
851  return constOp.getValue().isZero();
852  return false;
853 }
854 
855 static bool isConstAllOnes(Value value) {
856  if (value)
857  if (auto constOp = value.getDefiningOp<hw::ConstantOp>())
858  return constOp.getValue().isAllOnes();
859  return false;
860 }
861 
862 LogicalResult FirMemReadOp::canonicalize(FirMemReadOp op,
863  PatternRewriter &rewriter) {
864  // Remove the enable if it is constant true.
865  if (isConstAllOnes(op.getEnable())) {
866  rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); });
867  return success();
868  }
869  return failure();
870 }
871 
872 LogicalResult FirMemWriteOp::canonicalize(FirMemWriteOp op,
873  PatternRewriter &rewriter) {
874  // Remove the write port if it is trivially dead.
875  if (isConstZero(op.getEnable()) || isConstZero(op.getMask()) ||
876  isConstClock(op.getClk())) {
877  rewriter.eraseOp(op);
878  return success();
879  }
880  bool anyChanges = false;
881 
882  // Remove the enable if it is constant true.
883  if (auto enable = op.getEnable(); isConstAllOnes(enable)) {
884  rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); });
885  anyChanges = true;
886  }
887 
888  // Remove the mask if it is all ones.
889  if (auto mask = op.getMask(); isConstAllOnes(mask)) {
890  rewriter.modifyOpInPlace(op, [&] { op.getMaskMutable().erase(0); });
891  anyChanges = true;
892  }
893 
894  return success(anyChanges);
895 }
896 
897 LogicalResult FirMemReadWriteOp::canonicalize(FirMemReadWriteOp op,
898  PatternRewriter &rewriter) {
899  // Replace the read-write port with a read port if the write behavior is
900  // trivially disabled.
901  if (isConstZero(op.getEnable()) || isConstZero(op.getMask()) ||
902  isConstClock(op.getClk()) || isConstZero(op.getMode())) {
903  auto opAttrs = op->getAttrs();
904  auto opAttrNames = op.getAttributeNames();
905  auto newOp = rewriter.replaceOpWithNewOp<FirMemReadOp>(
906  op, op.getMemory(), op.getAddress(), op.getClk(), op.getEnable());
907  for (auto namedAttr : opAttrs)
908  if (!llvm::is_contained(opAttrNames, namedAttr.getName()))
909  newOp->setAttr(namedAttr.getName(), namedAttr.getValue());
910  return success();
911  }
912  bool anyChanges = false;
913 
914  // Remove the enable if it is constant true.
915  if (auto enable = op.getEnable(); isConstAllOnes(enable)) {
916  rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); });
917  anyChanges = true;
918  }
919 
920  // Remove the mask if it is all ones.
921  if (auto mask = op.getMask(); isConstAllOnes(mask)) {
922  rewriter.modifyOpInPlace(op, [&] { op.getMaskMutable().erase(0); });
923  anyChanges = true;
924  }
925 
926  return success(anyChanges);
927 }
928 
929 //===----------------------------------------------------------------------===//
930 // ConstClockOp
931 //===----------------------------------------------------------------------===//
932 
933 OpFoldResult ConstClockOp::fold(FoldAdaptor adaptor) {
934  return ClockConstAttr::get(getContext(), getValue());
935 }
936 
937 //===----------------------------------------------------------------------===//
938 // ToClockOp/FromClockOp
939 //===----------------------------------------------------------------------===//
940 
941 LogicalResult ToClockOp::canonicalize(ToClockOp op, PatternRewriter &rewriter) {
942  if (auto fromClock = op.getInput().getDefiningOp<FromClockOp>()) {
943  rewriter.replaceOp(op, fromClock.getInput());
944  return success();
945  }
946  return failure();
947 }
948 
949 OpFoldResult ToClockOp::fold(FoldAdaptor adaptor) {
950  if (auto fromClock = getInput().getDefiningOp<FromClockOp>())
951  return fromClock.getInput();
952  if (auto intAttr = dyn_cast_or_null<IntegerAttr>(adaptor.getInput())) {
953  auto value =
954  intAttr.getValue().isZero() ? ClockConst::Low : ClockConst::High;
955  return ClockConstAttr::get(getContext(), value);
956  }
957  return {};
958 }
959 
960 LogicalResult FromClockOp::canonicalize(FromClockOp op,
961  PatternRewriter &rewriter) {
962  if (auto toClock = op.getInput().getDefiningOp<ToClockOp>()) {
963  rewriter.replaceOp(op, toClock.getInput());
964  return success();
965  }
966  return failure();
967 }
968 
969 OpFoldResult FromClockOp::fold(FoldAdaptor adaptor) {
970  if (auto toClock = getInput().getDefiningOp<ToClockOp>())
971  return toClock.getInput();
972  if (auto clockAttr = dyn_cast_or_null<ClockConstAttr>(adaptor.getInput())) {
973  auto ty = IntegerType::get(getContext(), 1);
974  return IntegerAttr::get(ty, clockAttr.getValue() == ClockConst::High);
975  }
976  return {};
977 }
978 
979 //===----------------------------------------------------------------------===//
980 // ClockInverterOp
981 //===----------------------------------------------------------------------===//
982 
983 OpFoldResult ClockInverterOp::fold(FoldAdaptor adaptor) {
984  if (auto chainedInv = getInput().getDefiningOp<ClockInverterOp>())
985  return chainedInv.getInput();
986  if (auto clockAttr = dyn_cast_or_null<ClockConstAttr>(adaptor.getInput())) {
987  auto clockIn = clockAttr.getValue() == ClockConst::High;
988  return ClockConstAttr::get(getContext(),
989  clockIn ? ClockConst::Low : ClockConst::High);
990  }
991  return {};
992 }
993 
994 //===----------------------------------------------------------------------===//
995 // FIR memory helper
996 //===----------------------------------------------------------------------===//
997 
998 FirMemory::FirMemory(hw::HWModuleGeneratedOp op) {
999  depth = op->getAttrOfType<IntegerAttr>("depth").getInt();
1000  numReadPorts = op->getAttrOfType<IntegerAttr>("numReadPorts").getUInt();
1001  numWritePorts = op->getAttrOfType<IntegerAttr>("numWritePorts").getUInt();
1002  numReadWritePorts =
1003  op->getAttrOfType<IntegerAttr>("numReadWritePorts").getUInt();
1004  readLatency = op->getAttrOfType<IntegerAttr>("readLatency").getUInt();
1005  writeLatency = op->getAttrOfType<IntegerAttr>("writeLatency").getUInt();
1006  dataWidth = op->getAttrOfType<IntegerAttr>("width").getUInt();
1007  if (op->hasAttrOfType<IntegerAttr>("maskGran"))
1008  maskGran = op->getAttrOfType<IntegerAttr>("maskGran").getUInt();
1009  else
1010  maskGran = dataWidth;
1011  readUnderWrite = op->getAttrOfType<seq::RUWAttr>("readUnderWrite").getValue();
1012  writeUnderWrite =
1013  op->getAttrOfType<seq::WUWAttr>("writeUnderWrite").getValue();
1014  if (auto clockIDsAttr = op->getAttrOfType<ArrayAttr>("writeClockIDs"))
1015  for (auto clockID : clockIDsAttr)
1016  writeClockIDs.push_back(
1017  cast<IntegerAttr>(clockID).getValue().getZExtValue());
1018  initFilename = op->getAttrOfType<StringAttr>("initFilename").getValue();
1019  initIsBinary = op->getAttrOfType<BoolAttr>("initIsBinary").getValue();
1020  initIsInline = op->getAttrOfType<BoolAttr>("initIsInline").getValue();
1021 }
1022 
1023 LogicalResult InitialOp::verify() {
1024  // Check outputs.
1025  auto *terminator = this->getBody().front().getTerminator();
1026  if (terminator->getOperands().size() != getNumResults())
1027  return emitError() << "result type doesn't match with the terminator";
1028  for (auto [lhs, rhs] :
1029  llvm::zip(terminator->getOperands().getTypes(), getResultTypes())) {
1030  if (cast<seq::ImmutableType>(rhs).getInnerType() != lhs)
1031  return emitError() << cast<seq::ImmutableType>(rhs).getInnerType()
1032  << " is expected but got " << lhs;
1033  }
1034 
1035  auto blockArgs = this->getBody().front().getArguments();
1036 
1037  if (blockArgs.size() != getNumOperands())
1038  return emitError() << "operand type doesn't match with the block arg";
1039 
1040  for (auto [blockArg, operand] : llvm::zip(blockArgs, getOperands())) {
1041  if (blockArg.getType() !=
1042  cast<ImmutableType>(operand.getType()).getInnerType())
1043  return emitError()
1044  << blockArg.getType() << " is expected but got "
1045  << cast<ImmutableType>(operand.getType()).getInnerType();
1046  }
1047  return success();
1048 }
1049 void InitialOp::build(OpBuilder &builder, OperationState &result,
1050  TypeRange resultTypes, std::function<void()> ctor) {
1051  OpBuilder::InsertionGuard guard(builder);
1052 
1053  builder.createBlock(result.addRegion());
1054  SmallVector<Type> types;
1055  for (auto t : resultTypes)
1056  types.push_back(seq::ImmutableType::get(t));
1057 
1058  result.addTypes(types);
1059 
1060  if (ctor)
1061  ctor();
1062 }
1063 
1064 TypedValue<seq::ImmutableType>
1065 circt::seq::createConstantInitialValue(OpBuilder builder, Location loc,
1066  mlir::IntegerAttr attr) {
1067  auto initial = builder.create<seq::InitialOp>(loc, attr.getType(), [&]() {
1068  auto constant = builder.create<hw::ConstantOp>(loc, attr);
1069  builder.create<seq::YieldOp>(loc, ArrayRef<Value>{constant});
1070  });
1071  return cast<TypedValue<seq::ImmutableType>>(initial->getResult(0));
1072 }
1073 
1074 mlir::TypedValue<seq::ImmutableType>
1075 circt::seq::createConstantInitialValue(OpBuilder builder, Operation *op) {
1076  assert(op->getNumResults() == 1 &&
1077  op->hasTrait<mlir::OpTrait::ConstantLike>());
1078  auto initial =
1079  builder.create<seq::InitialOp>(op->getLoc(), op->getResultTypes(), [&]() {
1080  auto clonedOp = builder.clone(*op);
1081  builder.create<seq::YieldOp>(op->getLoc(), clonedOp->getResults());
1082  });
1083  return cast<mlir::TypedValue<seq::ImmutableType>>(initial.getResult(0));
1084 }
1085 
1086 Value circt::seq::unwrapImmutableValue(TypedValue<seq::ImmutableType> value) {
1087  auto resultNum = cast<OpResult>(value).getResultNumber();
1088  auto initialOp = value.getDefiningOp<seq::InitialOp>();
1089  assert(initialOp);
1090  return initialOp.getBodyBlock()->getTerminator()->getOperand(resultNum);
1091 }
1092 
1093 FailureOr<seq::InitialOp> circt::seq::mergeInitialOps(Block *block) {
1094  SmallVector<Operation *> initialOps;
1095  for (auto &op : *block)
1096  if (isa<seq::InitialOp>(op))
1097  initialOps.push_back(&op);
1098 
1099  if (!mlir::computeTopologicalSorting(initialOps, {}))
1100  return block->getParentOp()->emitError() << "initial ops cannot be "
1101  << "topologically sorted";
1102 
1103  // No need to merge if there is only one initial op.
1104  if (initialOps.size() <= 1)
1105  return initialOps.empty() ? seq::InitialOp()
1106  : cast<seq::InitialOp>(initialOps[0]);
1107 
1108  auto initialOp = cast<seq::InitialOp>(initialOps.front());
1109  auto yieldOp = cast<seq::YieldOp>(initialOp.getBodyBlock()->getTerminator());
1110 
1111  llvm::MapVector<Value, Value>
1112  resultToYieldOperand; // seq.immutable value to operand.
1113 
1114  for (auto [result, operand] :
1115  llvm::zip(initialOp.getResults(), yieldOp->getOperands()))
1116  resultToYieldOperand.insert({result, operand});
1117 
1118  for (size_t i = 1; i < initialOps.size(); ++i) {
1119  auto currentInitialOp = cast<seq::InitialOp>(initialOps[i]);
1120  auto operands = currentInitialOp->getOperands();
1121  for (auto [blockArg, operand] :
1122  llvm::zip(currentInitialOp.getBodyBlock()->getArguments(), operands)) {
1123  if (auto initOp = operand.getDefiningOp<seq::InitialOp>()) {
1124  assert(resultToYieldOperand.count(operand) &&
1125  "it must be visited already");
1126  blockArg.replaceAllUsesWith(resultToYieldOperand.lookup(operand));
1127  } else {
1128  // Otherwise add the operand to the current block.
1129  initialOp.getBodyBlock()->addArgument(
1130  cast<seq::ImmutableType>(operand.getType()).getInnerType(),
1131  operand.getLoc());
1132  initialOp.getInputsMutable().append(operand);
1133  }
1134  }
1135 
1136  auto currentYieldOp =
1137  cast<seq::YieldOp>(currentInitialOp.getBodyBlock()->getTerminator());
1138 
1139  for (auto [result, operand] : llvm::zip(currentInitialOp.getResults(),
1140  currentYieldOp->getOperands()))
1141  resultToYieldOperand.insert({result, operand});
1142 
1143  // Append the operands of the current yield op to the original yield op.
1144  yieldOp.getOperandsMutable().append(currentYieldOp.getOperands());
1145  currentYieldOp->erase();
1146 
1147  // Append the operations of the current initial op to the original initial
1148  // op.
1149  initialOp.getBodyBlock()->getOperations().splice(
1150  initialOp.end(), currentInitialOp.getBodyBlock()->getOperations());
1151  }
1152 
1153  // Move the terminator to the end of the block.
1154  yieldOp->moveBefore(initialOp.getBodyBlock(),
1155  initialOp.getBodyBlock()->end());
1156 
1157  auto builder = OpBuilder::atBlockBegin(block);
1158  SmallVector<Type> types;
1159  for (auto [result, operand] : resultToYieldOperand)
1160  types.push_back(operand.getType());
1161 
1162  // Create a new initial op which accumulates the results of the merged initial
1163  // ops.
1164  auto newInitial = builder.create<seq::InitialOp>(initialOp.getLoc(), types);
1165  newInitial.getInputsMutable().append(initialOp.getInputs());
1166 
1167  for (auto [resultAndOperand, newResult] :
1168  llvm::zip(resultToYieldOperand, newInitial.getResults()))
1169  resultAndOperand.first.replaceAllUsesWith(newResult);
1170 
1171  // Update the block arguments of the new initial op.
1172  for (auto oldBlockArg : initialOp.getBodyBlock()->getArguments()) {
1173  auto blockArg = newInitial.getBodyBlock()->addArgument(
1174  oldBlockArg.getType(), oldBlockArg.getLoc());
1175  oldBlockArg.replaceAllUsesWith(blockArg);
1176  }
1177 
1178  newInitial.getBodyBlock()->getOperations().splice(
1179  newInitial.end(), initialOp.getBodyBlock()->getOperations());
1180 
1181  // Clean up.
1182  while (!initialOps.empty())
1183  initialOps.pop_back_val()->erase();
1184 
1185  return newInitial;
1186 }
1187 
1188 //===----------------------------------------------------------------------===//
1189 // TableGen generated logic.
1190 //===----------------------------------------------------------------------===//
1191 
1192 // Provide the autogenerated implementation guts for the Op classes.
1193 #define GET_OP_CLASSES
1194 #include "circt/Dialect/Seq/Seq.cpp.inc"
assert(baseType &&"element must be base type")
MlirType elementType
Definition: CHIRRTL.cpp:29
#define isdigit(x)
Definition: FIRLexer.cpp:26
static bool isConstantOne(Attribute operand)
Determine whether a constant operand is a one value for the sake of constant folding.
static InstancePath empty
static std::optional< APInt > getInt(Value value)
Helper to convert a value to a constant integer if it is one.
void printFIFOAFThreshold(OpAsmPrinter &p, Operation *op, IntegerAttr threshold, Type outputFlagType)
Definition: SeqOps.cpp:267
static bool isConstClock(Value value)
Definition: SeqOps.cpp:842
static ParseResult parseFIFOFlagThreshold(OpAsmParser &parser, IntegerAttr &threshold, Type &outputFlagType, StringRef directive)
Definition: SeqOps.cpp:236
static void printOptionalTypeMatch(OpAsmPrinter &p, Operation *op, Type refType, Value operand, Type type)
Definition: SeqOps.cpp:84
static bool isConstAllOnes(Value value)
Definition: SeqOps.cpp:855
static ParseResult parseOptionalImmutableTypeMatch(OpAsmParser &parser, Type refType, std::optional< OpAsmParser::UnresolvedOperand > operand, Type &type)
Definition: SeqOps.cpp:89
void printFIFOAEThreshold(OpAsmPrinter &p, Operation *op, IntegerAttr threshold, Type outputFlagType)
Definition: SeqOps.cpp:274
LogicalResult verifyResets(TOp op)
Definition: SeqOps.cpp:326
static bool canElideName(OpAsmPrinter &p, Operation *op)
Definition: SeqOps.cpp:60
ParseResult parseFIFOAEThreshold(OpAsmParser &parser, IntegerAttr &threshold, Type &outputFlagType)
Definition: SeqOps.cpp:261
static bool isConstZero(Value value)
Definition: SeqOps.cpp:848
static LogicalResult verifyFirMemMask(Op op)
Definition: SeqOps.cpp:824
static void printOptionalImmutableTypeMatch(OpAsmPrinter &p, Operation *op, Type refType, Value operand, Type type)
Definition: SeqOps.cpp:97
static ParseResult parseOptionalTypeMatch(OpAsmParser &parser, Type refType, std::optional< OpAsmParser::UnresolvedOperand > operand, Type &type)
Definition: SeqOps.cpp:76
static void setNameFromResult(OpAsmParser &parser, OperationState &result)
Definition: SeqOps.cpp:49
ParseResult parseFIFOAFThreshold(OpAsmParser &parser, IntegerAttr &threshold, Type &outputFlagType)
Definition: SeqOps.cpp:255
def create(data_type, value)
Definition: hw.py:433
static LogicalResult canonicalize(Op op, PatternRewriter &rewriter)
Definition: VerifOps.cpp:66
static LogicalResult verify(Value clock, bool eventExists, mlir::Location loc)
Definition: SVOps.cpp:2467
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
bool isConstant(Operation *op)
Return true if the specified operation has a constant value.
Definition: FIRRTLOps.cpp:4588
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
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.
FailureOr< seq::InitialOp > mergeInitialOps(Block *block)
Definition: SeqOps.cpp:1093
bool isValidIndexValues(Value hlmemHandle, ValueRange addresses)
Definition: SeqOps.cpp:31
mlir::TypedValue< seq::ImmutableType > createConstantInitialValue(OpBuilder builder, Location loc, mlir::IntegerAttr attr)
Definition: SeqOps.cpp:1065
Value unwrapImmutableValue(mlir::TypedValue< seq::ImmutableType > immutableVal)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
static bool isConstantZero(Attribute operand)
Determine whether a constant operand is a zero value.
Definition: FoldUtils.h:27
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
Definition: LLVM.h:182
Definition: seq.py:1