CIRCT  18.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 = hlmemHandle.getType().cast<seq::HLMemType>();
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 = addr.getType().dyn_cast<IntegerType>();
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 = memory.getType().cast<seq::HLMemType>();
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 symName, 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, symName);
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 
259 void printFIFOAEThreshold(OpAsmPrinter &p, Operation *op, IntegerAttr threshold,
260  Type outputFlagType) {
261  if (threshold) {
262  p << "almost_empty"
263  << " " << threshold.getInt();
264  }
265 }
266 
268  setNameFn(getOutput(), "out");
269  setNameFn(getEmpty(), "empty");
270  setNameFn(getFull(), "full");
271  if (auto ae = getAlmostEmpty())
272  setNameFn(ae, "almostEmpty");
273  if (auto af = getAlmostFull())
274  setNameFn(af, "almostFull");
275 }
276 
277 LogicalResult FIFOOp::verify() {
278  auto aet = getAlmostEmptyThreshold();
279  auto aft = getAlmostFullThreshold();
280  size_t depth = getDepth();
281  if (aft.has_value() && aft.value() > depth)
282  return emitOpError("almost full threshold must be <= FIFO depth");
283 
284  if (aet.has_value() && aet.value() > depth)
285  return emitOpError("almost empty threshold must be <= FIFO depth");
286 
287  return success();
288 }
289 
290 //===----------------------------------------------------------------------===//
291 // CompRegOp
292 //===----------------------------------------------------------------------===//
293 
294 /// Suggest a name for each result value based on the saved result names
295 /// attribute.
297  // If the wire has an optional 'name' attribute, use it.
298  if (auto name = getName())
299  setNameFn(getResult(), *name);
300 }
301 
302 LogicalResult CompRegOp::verify() {
303  if ((getReset() == nullptr) ^ (getResetValue() == nullptr))
304  return emitOpError(
305  "either reset and resetValue or neither must be specified");
306  return success();
307 }
308 
309 std::optional<size_t> CompRegOp::getTargetResultIndex() { return 0; }
310 
311 template <typename TOp>
312 LogicalResult verifyResets(TOp op) {
313  if ((op.getReset() == nullptr) ^ (op.getResetValue() == nullptr))
314  return op->emitOpError(
315  "either reset and resetValue or neither must be specified");
316  bool hasReset = op.getReset() != nullptr;
317  if (hasReset && op.getResetValue().getType() != op.getInput().getType())
318  return op->emitOpError("reset value must be the same type as the input");
319 
320  return success();
321 }
322 
323 /// Suggest a name for each result value based on the saved result names
324 /// attribute.
326  // If the wire has an optional 'name' attribute, use it.
327  if (auto name = getName())
328  setNameFn(getResult(), *name);
329 }
330 
331 std::optional<size_t> CompRegClockEnabledOp::getTargetResultIndex() {
332  return 0;
333 }
334 
335 LogicalResult CompRegClockEnabledOp::verify() {
336  if (failed(verifyResets(*this)))
337  return failure();
338  return success();
339 }
340 
341 //===----------------------------------------------------------------------===//
342 // ShiftRegOp
343 //===----------------------------------------------------------------------===//
344 
346  // If the wire has an optional 'name' attribute, use it.
347  if (auto name = getName())
348  setNameFn(getResult(), *name);
349 }
350 
351 std::optional<size_t> ShiftRegOp::getTargetResultIndex() { return 0; }
352 
353 LogicalResult ShiftRegOp::verify() {
354  if (failed(verifyResets(*this)))
355  return failure();
356  return success();
357 }
358 
359 //===----------------------------------------------------------------------===//
360 // FirRegOp
361 //===----------------------------------------------------------------------===//
362 
363 void FirRegOp::build(OpBuilder &builder, OperationState &result, Value input,
364  Value clk, StringAttr name, hw::InnerSymAttr innerSym) {
365 
366  OpBuilder::InsertionGuard guard(builder);
367 
368  result.addOperands(input);
369  result.addOperands(clk);
370 
371  result.addAttribute(getNameAttrName(result.name), name);
372 
373  if (innerSym)
374  result.addAttribute(getInnerSymAttrName(result.name), innerSym);
375 
376  result.addTypes(input.getType());
377 }
378 
379 void FirRegOp::build(OpBuilder &builder, OperationState &result, Value input,
380  Value clk, StringAttr name, Value reset, Value resetValue,
381  hw::InnerSymAttr innerSym, bool isAsync) {
382 
383  OpBuilder::InsertionGuard guard(builder);
384 
385  result.addOperands(input);
386  result.addOperands(clk);
387  result.addOperands(reset);
388  result.addOperands(resetValue);
389 
390  result.addAttribute(getNameAttrName(result.name), name);
391  if (isAsync)
392  result.addAttribute(getIsAsyncAttrName(result.name), builder.getUnitAttr());
393 
394  if (innerSym)
395  result.addAttribute(getInnerSymAttrName(result.name), innerSym);
396 
397  result.addTypes(input.getType());
398 }
399 
400 ParseResult FirRegOp::parse(OpAsmParser &parser, OperationState &result) {
401  auto &builder = parser.getBuilder();
402  llvm::SMLoc loc = parser.getCurrentLocation();
403 
404  using Op = OpAsmParser::UnresolvedOperand;
405 
406  Op next, clk;
407  if (parser.parseOperand(next) || parser.parseKeyword("clock") ||
408  parser.parseOperand(clk))
409  return failure();
410 
411  if (succeeded(parser.parseOptionalKeyword("sym"))) {
412  hw::InnerSymAttr innerSym;
413  if (parser.parseCustomAttributeWithFallback(innerSym, /*type=*/nullptr,
414  "inner_sym", result.attributes))
415  return failure();
416  }
417 
418  // Parse reset [sync|async] %reset, %value
419  std::optional<std::pair<Op, Op>> resetAndValue;
420  if (succeeded(parser.parseOptionalKeyword("reset"))) {
421  bool isAsync;
422  if (succeeded(parser.parseOptionalKeyword("async")))
423  isAsync = true;
424  else if (succeeded(parser.parseOptionalKeyword("sync")))
425  isAsync = false;
426  else
427  return parser.emitError(loc, "invalid reset, expected 'sync' or 'async'");
428  if (isAsync)
429  result.attributes.append("isAsync", builder.getUnitAttr());
430 
431  resetAndValue = {{}, {}};
432  if (parser.parseOperand(resetAndValue->first) || parser.parseComma() ||
433  parser.parseOperand(resetAndValue->second))
434  return failure();
435  }
436 
437  Type ty;
438  if (succeeded(parser.parseOptionalKeyword("preset"))) {
439  IntegerAttr preset;
440  if (parser.parseAttribute(preset, "preset", result.attributes) ||
441  parser.parseOptionalAttrDict(result.attributes))
442  return failure();
443  ty = preset.getType();
444  } else {
445  if (parser.parseOptionalAttrDict(result.attributes) ||
446  parser.parseColon() || parser.parseType(ty))
447  return failure();
448  }
449  result.addTypes({ty});
450 
451  setNameFromResult(parser, result);
452 
453  if (parser.resolveOperand(next, ty, result.operands))
454  return failure();
455 
456  Type clkTy = ClockType::get(result.getContext());
457  if (parser.resolveOperand(clk, clkTy, result.operands))
458  return failure();
459 
460  if (resetAndValue) {
461  Type i1 = IntegerType::get(result.getContext(), 1);
462  if (parser.resolveOperand(resetAndValue->first, i1, result.operands) ||
463  parser.resolveOperand(resetAndValue->second, ty, result.operands))
464  return failure();
465  }
466 
467  return success();
468 }
469 
470 void FirRegOp::print(::mlir::OpAsmPrinter &p) {
471  SmallVector<StringRef> elidedAttrs = {
472  getInnerSymAttrName(), getIsAsyncAttrName(), getPresetAttrName()};
473 
474  p << ' ' << getNext() << " clock " << getClk();
475 
476  if (auto sym = getInnerSymAttr()) {
477  p << " sym ";
478  sym.print(p);
479  }
480 
481  if (hasReset()) {
482  p << " reset " << (getIsAsync() ? "async" : "sync") << ' ';
483  p << getReset() << ", " << getResetValue();
484  }
485 
486  if (auto preset = getPresetAttr()) {
487  p << " preset " << preset.getValue();
488  }
489 
490  if (canElideName(p, *this))
491  elidedAttrs.push_back("name");
492 
493  p.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
494  p << " : " << getNext().getType();
495 }
496 
497 /// Verifier for the FIR register op.
498 LogicalResult FirRegOp::verify() {
499  if (getReset() || getResetValue() || getIsAsync()) {
500  if (!getReset() || !getResetValue())
501  return emitOpError("must specify reset and reset value");
502  } else {
503  if (getIsAsync())
504  return emitOpError("register with no reset cannot be async");
505  }
506  if (auto preset = getPresetAttr()) {
507  if (preset.getType() != getType())
508  return emitOpError("preset type must match register type");
509  }
510  return success();
511 }
512 
513 /// Suggest a name for each result value based on the saved result names
514 /// attribute.
516  // If the register has an optional 'name' attribute, use it.
517  if (!getName().empty())
518  setNameFn(getResult(), getName());
519 }
520 
521 std::optional<size_t> FirRegOp::getTargetResultIndex() { return 0; }
522 
523 LogicalResult FirRegOp::canonicalize(FirRegOp op, PatternRewriter &rewriter) {
524  // If the register has a constant zero reset, drop the reset and reset value
525  // altogether.
526  if (auto reset = op.getReset()) {
527  if (auto constOp = reset.getDefiningOp<hw::ConstantOp>()) {
528  if (constOp.getValue().isZero()) {
529  rewriter.replaceOpWithNewOp<FirRegOp>(op, op.getNext(), op.getClk(),
530  op.getNameAttr(),
531  op.getInnerSymAttr());
532  return success();
533  }
534  }
535  }
536 
537  // If the register has a symbol, we can't optimize it away.
538  if (op.getInnerSymAttr())
539  return failure();
540 
541  // Replace a register with a trivial feedback or constant clock with a
542  // constant zero.
543  // TODO: Once HW aggregate constant values are supported, move this
544  // canonicalization to the folder.
545  auto isConstant = [&]() -> bool {
546  if (op.getNext() == op.getResult())
547  return true;
548  if (auto clk = op.getClk().getDefiningOp<seq::ToClockOp>())
549  return clk.getInput().getDefiningOp<hw::ConstantOp>();
550  return false;
551  };
552 
553  if (isConstant()) {
554  if (auto resetValue = op.getResetValue()) {
555  // If the register has a reset value, we can replace it with that.
556  rewriter.replaceOp(op, resetValue);
557  } else {
558  if (op.getType().isa<seq::ClockType>()) {
559  rewriter.replaceOpWithNewOp<seq::ConstClockOp>(
560  op,
561  seq::ClockConstAttr::get(rewriter.getContext(), ClockConst::Low));
562  } else {
563  auto constant = rewriter.create<hw::ConstantOp>(
564  op.getLoc(), APInt::getZero(hw::getBitWidth(op.getType())));
565  rewriter.replaceOpWithNewOp<hw::BitcastOp>(op, op.getType(), constant);
566  }
567  }
568  return success();
569  }
570 
571  // For reset-less 1d array registers, replace an uninitialized element with
572  // constant zero. For example, let `r` be a 2xi1 register and its next value
573  // be `{foo, r[0]}`. `r[0]` is connected to itself so will never be
574  // initialized. If we don't enable aggregate preservation, `r_0` is replaced
575  // with `0`. Hence this canonicalization replaces 0th element of the next
576  // value with zero to match the behaviour.
577  if (!op.getReset()) {
578  if (auto arrayCreate = op.getNext().getDefiningOp<hw::ArrayCreateOp>()) {
579  // For now only support 1d arrays.
580  // TODO: Support nested arrays and bundles.
581  if (hw::type_cast<hw::ArrayType>(op.getResult().getType())
582  .getElementType()
583  .isa<IntegerType>()) {
584  SmallVector<Value> nextOperands;
585  bool changed = false;
586  for (const auto &[i, value] :
587  llvm::enumerate(arrayCreate.getOperands())) {
588  auto index = arrayCreate.getOperands().size() - i - 1;
589  APInt elementIndex;
590  // Check that the corresponding operand is op's element.
591  if (auto arrayGet = value.getDefiningOp<hw::ArrayGetOp>())
592  if (arrayGet.getInput() == op.getResult() &&
593  matchPattern(arrayGet.getIndex(),
594  m_ConstantInt(&elementIndex)) &&
595  elementIndex == index) {
596  nextOperands.push_back(rewriter.create<hw::ConstantOp>(
597  op.getLoc(),
598  APInt::getZero(hw::getBitWidth(arrayGet.getType()))));
599  changed = true;
600  continue;
601  }
602  nextOperands.push_back(value);
603  }
604  // If one of the operands is self loop, update the next value.
605  if (changed) {
606  auto newNextVal = rewriter.create<hw::ArrayCreateOp>(
607  arrayCreate.getLoc(), nextOperands);
608  if (arrayCreate->hasOneUse())
609  // If the original next value has a single use, we can replace the
610  // value directly.
611  rewriter.replaceOp(arrayCreate, newNextVal);
612  else {
613  // Otherwise, replace the entire firreg with a new one.
614  rewriter.replaceOpWithNewOp<FirRegOp>(op, newNextVal, op.getClk(),
615  op.getNameAttr(),
616  op.getInnerSymAttr());
617  }
618 
619  return success();
620  }
621  }
622  }
623  }
624 
625  return failure();
626 }
627 
628 OpFoldResult FirRegOp::fold(FoldAdaptor adaptor) {
629  // If the register has a symbol, we can't optimize it away.
630  if (getInnerSymAttr())
631  return {};
632 
633  // If the register is held in permanent reset, replace it with its reset
634  // value. This works trivially if the reset is asynchronous and therefore
635  // level-sensitive, in which case it will always immediately assume the reset
636  // value in silicon. If it is synchronous, the register value is undefined
637  // until the first clock edge at which point it becomes the reset value, in
638  // which case we simply define the initial value to already be the reset
639  // value.
640  if (auto reset = getReset())
641  if (auto constOp = reset.getDefiningOp<hw::ConstantOp>())
642  if (constOp.getValue().isOne())
643  return getResetValue();
644 
645  // If the register's next value is trivially it's current value, or the
646  // register is never clocked, we can replace the register with a constant
647  // value.
648  bool isTrivialFeedback = (getNext() == getResult());
649  bool isNeverClocked =
650  adaptor.getClk() != nullptr; // clock operand is constant
651  if (!isTrivialFeedback && !isNeverClocked)
652  return {};
653 
654  // If the register has a reset value, we can replace it with that.
655  if (auto resetValue = getResetValue())
656  return resetValue;
657 
658  // Otherwise we want to replace the register with a constant 0. For now this
659  // only works with integer types.
660  auto intType = getType().dyn_cast<IntegerType>();
661  if (!intType)
662  return {};
663  return IntegerAttr::get(intType, 0);
664 }
665 
666 //===----------------------------------------------------------------------===//
667 // ClockGateOp
668 //===----------------------------------------------------------------------===//
669 
670 OpFoldResult ClockGateOp::fold(FoldAdaptor adaptor) {
671  // Forward the clock if one of the enables is always true.
672  if (isConstantOne(adaptor.getEnable()) ||
673  isConstantOne(adaptor.getTestEnable()))
674  return getInput();
675 
676  // Fold to a constant zero clock if the enables are always false.
677  if (isConstantZero(adaptor.getEnable()) &&
678  (!getTestEnable() || isConstantZero(adaptor.getTestEnable())))
679  return ClockConstAttr::get(getContext(), ClockConst::Low);
680 
681  // Forward constant zero clocks.
682  if (auto clockAttr = dyn_cast_or_null<ClockConstAttr>(adaptor.getInput()))
683  if (clockAttr.getValue() == ClockConst::Low)
684  return ClockConstAttr::get(getContext(), ClockConst::Low);
685 
686  // Transitive clock gating - eliminate clock gates that are driven by an
687  // identical enable signal somewhere higher in the clock gate hierarchy.
688  auto clockGateInputOp = getInput().getDefiningOp<ClockGateOp>();
689  while (clockGateInputOp) {
690  if (clockGateInputOp.getEnable() == getEnable() &&
691  clockGateInputOp.getTestEnable() == getTestEnable())
692  return getInput();
693  clockGateInputOp = clockGateInputOp.getInput().getDefiningOp<ClockGateOp>();
694  }
695 
696  return {};
697 }
698 
699 LogicalResult ClockGateOp::canonicalize(ClockGateOp op,
700  PatternRewriter &rewriter) {
701  // Remove constant false test enable.
702  if (auto testEnable = op.getTestEnable()) {
703  if (auto constOp = testEnable.getDefiningOp<hw::ConstantOp>()) {
704  if (constOp.getValue().isZero()) {
705  rewriter.updateRootInPlace(op,
706  [&] { op.getTestEnableMutable().clear(); });
707  return success();
708  }
709  }
710  }
711 
712  return failure();
713 }
714 
715 std::optional<size_t> ClockGateOp::getTargetResultIndex() {
716  return std::nullopt;
717 }
718 
719 //===----------------------------------------------------------------------===//
720 // ClockMuxOp
721 //===----------------------------------------------------------------------===//
722 
723 OpFoldResult ClockMuxOp::fold(FoldAdaptor adaptor) {
724  if (isConstantOne(adaptor.getCond()))
725  return getTrueClock();
726  if (isConstantZero(adaptor.getCond()))
727  return getFalseClock();
728  return {};
729 }
730 
731 //===----------------------------------------------------------------------===//
732 // FirMemOp
733 //===----------------------------------------------------------------------===//
734 
736  auto nameAttr = (*this)->getAttrOfType<StringAttr>("name");
737  if (!nameAttr.getValue().empty())
738  setNameFn(getResult(), nameAttr.getValue());
739 }
740 
741 std::optional<size_t> FirMemOp::getTargetResultIndex() { return 0; }
742 
743 template <class Op>
744 static LogicalResult verifyFirMemMask(Op op) {
745  if (auto mask = op.getMask()) {
746  auto memType = op.getMemory().getType();
747  if (!memType.getMaskWidth())
748  return op.emitOpError("has mask operand but memory type '")
749  << memType << "' has no mask";
750  auto expected = IntegerType::get(op.getContext(), *memType.getMaskWidth());
751  if (mask.getType() != expected)
752  return op.emitOpError("has mask operand of type '")
753  << mask.getType() << "', but memory type requires '" << expected
754  << "'";
755  }
756  return success();
757 }
758 
759 LogicalResult FirMemWriteOp::verify() { return verifyFirMemMask(*this); }
760 LogicalResult FirMemReadWriteOp::verify() { return verifyFirMemMask(*this); }
761 
762 static bool isConstClock(Value value) {
763  if (!value)
764  return false;
765  return value.getDefiningOp<seq::ConstClockOp>();
766 }
767 
768 static bool isConstZero(Value value) {
769  if (value)
770  if (auto constOp = value.getDefiningOp<hw::ConstantOp>())
771  return constOp.getValue().isZero();
772  return false;
773 }
774 
775 static bool isConstAllOnes(Value value) {
776  if (value)
777  if (auto constOp = value.getDefiningOp<hw::ConstantOp>())
778  return constOp.getValue().isAllOnes();
779  return false;
780 }
781 
782 LogicalResult FirMemReadOp::canonicalize(FirMemReadOp op,
783  PatternRewriter &rewriter) {
784  // Remove the enable if it is constant true.
785  if (isConstAllOnes(op.getEnable())) {
786  rewriter.updateRootInPlace(op, [&] { op.getEnableMutable().erase(0); });
787  return success();
788  }
789  return failure();
790 }
791 
792 LogicalResult FirMemWriteOp::canonicalize(FirMemWriteOp op,
793  PatternRewriter &rewriter) {
794  // Remove the write port if it is trivially dead.
795  if (isConstZero(op.getEnable()) || isConstZero(op.getMask()) ||
796  isConstClock(op.getClk())) {
797  rewriter.eraseOp(op);
798  return success();
799  }
800  bool anyChanges = false;
801 
802  // Remove the enable if it is constant true.
803  if (auto enable = op.getEnable(); isConstAllOnes(enable)) {
804  rewriter.updateRootInPlace(op, [&] { op.getEnableMutable().erase(0); });
805  anyChanges = true;
806  }
807 
808  // Remove the mask if it is all ones.
809  if (auto mask = op.getMask(); isConstAllOnes(mask)) {
810  rewriter.updateRootInPlace(op, [&] { op.getMaskMutable().erase(0); });
811  anyChanges = true;
812  }
813 
814  return success(anyChanges);
815 }
816 
817 LogicalResult FirMemReadWriteOp::canonicalize(FirMemReadWriteOp op,
818  PatternRewriter &rewriter) {
819  // Replace the read-write port with a read port if the write behavior is
820  // trivially disabled.
821  if (isConstZero(op.getEnable()) || isConstZero(op.getMask()) ||
822  isConstClock(op.getClk()) || isConstZero(op.getMode())) {
823  auto opAttrs = op->getAttrs();
824  auto opAttrNames = op.getAttributeNames();
825  auto newOp = rewriter.replaceOpWithNewOp<FirMemReadOp>(
826  op, op.getMemory(), op.getAddress(), op.getClk(), op.getEnable());
827  for (auto namedAttr : opAttrs)
828  if (!llvm::is_contained(opAttrNames, namedAttr.getName()))
829  newOp->setAttr(namedAttr.getName(), namedAttr.getValue());
830  return success();
831  }
832  bool anyChanges = false;
833 
834  // Remove the enable if it is constant true.
835  if (auto enable = op.getEnable(); isConstAllOnes(enable)) {
836  rewriter.updateRootInPlace(op, [&] { op.getEnableMutable().erase(0); });
837  anyChanges = true;
838  }
839 
840  // Remove the mask if it is all ones.
841  if (auto mask = op.getMask(); isConstAllOnes(mask)) {
842  rewriter.updateRootInPlace(op, [&] { op.getMaskMutable().erase(0); });
843  anyChanges = true;
844  }
845 
846  return success(anyChanges);
847 }
848 
849 //===----------------------------------------------------------------------===//
850 // ConstClockOp
851 //===----------------------------------------------------------------------===//
852 
853 OpFoldResult ConstClockOp::fold(FoldAdaptor adaptor) {
854  return ClockConstAttr::get(getContext(), getValue());
855 }
856 
857 //===----------------------------------------------------------------------===//
858 // ToClockOp/FromClockOp
859 //===----------------------------------------------------------------------===//
860 
861 LogicalResult ToClockOp::canonicalize(ToClockOp op, PatternRewriter &rewriter) {
862  if (auto fromClock = op.getInput().getDefiningOp<FromClockOp>()) {
863  rewriter.replaceOp(op, fromClock.getInput());
864  return success();
865  }
866  return failure();
867 }
868 
869 OpFoldResult ToClockOp::fold(FoldAdaptor adaptor) {
870  if (auto fromClock = getInput().getDefiningOp<FromClockOp>())
871  return fromClock.getInput();
872  if (auto intAttr = dyn_cast_or_null<IntegerAttr>(adaptor.getInput())) {
873  auto value =
874  intAttr.getValue().isZero() ? ClockConst::Low : ClockConst::High;
875  return ClockConstAttr::get(getContext(), value);
876  }
877  return {};
878 }
879 
880 LogicalResult FromClockOp::canonicalize(FromClockOp op,
881  PatternRewriter &rewriter) {
882  if (auto toClock = op.getInput().getDefiningOp<ToClockOp>()) {
883  rewriter.replaceOp(op, toClock.getInput());
884  return success();
885  }
886  return failure();
887 }
888 
889 OpFoldResult FromClockOp::fold(FoldAdaptor adaptor) {
890  if (auto toClock = getInput().getDefiningOp<ToClockOp>())
891  return toClock.getInput();
892  if (auto clockAttr = dyn_cast_or_null<ClockConstAttr>(adaptor.getInput())) {
893  auto ty = IntegerType::get(getContext(), 1);
894  return IntegerAttr::get(ty, clockAttr.getValue() == ClockConst::High);
895  }
896  return {};
897 }
898 
899 //===----------------------------------------------------------------------===//
900 // FIR memory helper
901 //===----------------------------------------------------------------------===//
902 
903 FirMemory::FirMemory(hw::HWModuleGeneratedOp op) {
904  depth = op->getAttrOfType<IntegerAttr>("depth").getInt();
905  numReadPorts = op->getAttrOfType<IntegerAttr>("numReadPorts").getUInt();
906  numWritePorts = op->getAttrOfType<IntegerAttr>("numWritePorts").getUInt();
907  numReadWritePorts =
908  op->getAttrOfType<IntegerAttr>("numReadWritePorts").getUInt();
909  readLatency = op->getAttrOfType<IntegerAttr>("readLatency").getUInt();
910  writeLatency = op->getAttrOfType<IntegerAttr>("writeLatency").getUInt();
911  dataWidth = op->getAttrOfType<IntegerAttr>("width").getUInt();
912  if (op->hasAttrOfType<IntegerAttr>("maskGran"))
913  maskGran = op->getAttrOfType<IntegerAttr>("maskGran").getUInt();
914  else
915  maskGran = dataWidth;
916  readUnderWrite = op->getAttrOfType<seq::RUWAttr>("readUnderWrite").getValue();
917  writeUnderWrite =
918  op->getAttrOfType<seq::WUWAttr>("writeUnderWrite").getValue();
919  if (auto clockIDsAttr = op->getAttrOfType<ArrayAttr>("writeClockIDs"))
920  for (auto clockID : clockIDsAttr)
921  writeClockIDs.push_back(
922  clockID.cast<IntegerAttr>().getValue().getZExtValue());
923  initFilename = op->getAttrOfType<StringAttr>("initFilename").getValue();
924  initIsBinary = op->getAttrOfType<BoolAttr>("initIsBinary").getValue();
925  initIsInline = op->getAttrOfType<BoolAttr>("initIsInline").getValue();
926 }
927 
928 //===----------------------------------------------------------------------===//
929 // TableGen generated logic.
930 //===----------------------------------------------------------------------===//
931 
932 // Provide the autogenerated implementation guts for the Op classes.
933 #define GET_OP_CLASSES
934 #include "circt/Dialect/Seq/Seq.cpp.inc"
lowerAnnotationsNoRefTypePorts FirtoolPreserveValuesMode value
Definition: Firtool.cpp:95
MlirType elementType
Definition: CHIRRTL.cpp:25
#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
Builder builder
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:762
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:775
void printFIFOAEThreshold(OpAsmPrinter &p, Operation *op, IntegerAttr threshold, Type outputFlagType)
Definition: SeqOps.cpp:259
LogicalResult verifyResets(TOp op)
Definition: SeqOps.cpp:312
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:768
static LogicalResult verifyFirMemMask(Op op)
Definition: SeqOps.cpp:744
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:397
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
bool isConstant(Operation *op)
Return true if the specified operation has a constant value.
Definition: FIRRTLOps.cpp:4070
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
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
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:186
Definition: seq.py:1