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