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