Loading [MathJax]/extensions/tex2jax.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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 return failure();
362}
363
364//===----------------------------------------------------------------------===//
365// ShiftRegOp
366//===----------------------------------------------------------------------===//
367
368void ShiftRegOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
369 // If the wire has an optional 'name' attribute, use it.
370 if (auto name = getName())
371 setNameFn(getResult(), *name);
372}
373
374std::optional<size_t> ShiftRegOp::getTargetResultIndex() { return 0; }
375
376LogicalResult ShiftRegOp::verify() {
377 if (failed(verifyResets(*this)))
378 return failure();
379 return success();
380}
381
382//===----------------------------------------------------------------------===//
383// FirRegOp
384//===----------------------------------------------------------------------===//
385
386void FirRegOp::build(OpBuilder &builder, OperationState &result, Value input,
387 Value clk, StringAttr name, hw::InnerSymAttr innerSym,
388 Attribute preset) {
389
390 OpBuilder::InsertionGuard guard(builder);
391
392 result.addOperands(input);
393 result.addOperands(clk);
394
395 result.addAttribute(getNameAttrName(result.name), name);
396
397 if (innerSym)
398 result.addAttribute(getInnerSymAttrName(result.name), innerSym);
399
400 if (preset)
401 result.addAttribute(getPresetAttrName(result.name), preset);
402
403 result.addTypes(input.getType());
404}
405
406void FirRegOp::build(OpBuilder &builder, OperationState &result, Value input,
407 Value clk, StringAttr name, Value reset, Value resetValue,
408 hw::InnerSymAttr innerSym, bool isAsync) {
409
410 OpBuilder::InsertionGuard guard(builder);
411
412 result.addOperands(input);
413 result.addOperands(clk);
414 result.addOperands(reset);
415 result.addOperands(resetValue);
416
417 result.addAttribute(getNameAttrName(result.name), name);
418 if (isAsync)
419 result.addAttribute(getIsAsyncAttrName(result.name), builder.getUnitAttr());
420
421 if (innerSym)
422 result.addAttribute(getInnerSymAttrName(result.name), innerSym);
423
424 result.addTypes(input.getType());
425}
426
427ParseResult FirRegOp::parse(OpAsmParser &parser, OperationState &result) {
428 auto &builder = parser.getBuilder();
429 llvm::SMLoc loc = parser.getCurrentLocation();
430
431 using Op = OpAsmParser::UnresolvedOperand;
432
433 Op next, clk;
434 if (parser.parseOperand(next) || parser.parseKeyword("clock") ||
435 parser.parseOperand(clk))
436 return failure();
437
438 if (succeeded(parser.parseOptionalKeyword("sym"))) {
439 hw::InnerSymAttr innerSym;
440 if (parser.parseCustomAttributeWithFallback(innerSym, /*type=*/nullptr,
441 "inner_sym", result.attributes))
442 return failure();
443 }
444
445 // Parse reset [sync|async] %reset, %value
446 std::optional<std::pair<Op, Op>> resetAndValue;
447 if (succeeded(parser.parseOptionalKeyword("reset"))) {
448 bool isAsync;
449 if (succeeded(parser.parseOptionalKeyword("async")))
450 isAsync = true;
451 else if (succeeded(parser.parseOptionalKeyword("sync")))
452 isAsync = false;
453 else
454 return parser.emitError(loc, "invalid reset, expected 'sync' or 'async'");
455 if (isAsync)
456 result.attributes.append("isAsync", builder.getUnitAttr());
457
458 resetAndValue = {{}, {}};
459 if (parser.parseOperand(resetAndValue->first) || parser.parseComma() ||
460 parser.parseOperand(resetAndValue->second))
461 return failure();
462 }
463
464 std::optional<APInt> presetValue;
465 llvm::SMLoc presetValueLoc;
466 if (succeeded(parser.parseOptionalKeyword("preset"))) {
467 presetValueLoc = parser.getCurrentLocation();
468 OptionalParseResult presetIntResult =
469 parser.parseOptionalInteger(presetValue.emplace());
470 if (!presetIntResult.has_value() || failed(*presetIntResult))
471 return parser.emitError(loc, "expected integer value");
472 }
473
474 Type ty;
475 if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() ||
476 parser.parseType(ty))
477 return failure();
478 result.addTypes({ty});
479
480 if (presetValue) {
481 uint64_t width = 0;
482 if (hw::type_isa<seq::ClockType>(ty)) {
483 width = 1;
484 } else {
485 int64_t maybeWidth = hw::getBitWidth(ty);
486 if (maybeWidth < 0)
487 return parser.emitError(presetValueLoc,
488 "cannot preset register of unknown width");
489 width = maybeWidth;
490 }
491
492 APInt presetResult = presetValue->sextOrTrunc(width);
493 if (presetResult.zextOrTrunc(presetValue->getBitWidth()) != *presetValue)
494 return parser.emitError(loc, "preset value too large");
495
496 auto builder = parser.getBuilder();
497 auto presetTy = builder.getIntegerType(width);
498 auto resultAttr = builder.getIntegerAttr(presetTy, presetResult);
499 result.addAttribute("preset", resultAttr);
500 }
501
502 setNameFromResult(parser, result);
503
504 if (parser.resolveOperand(next, ty, result.operands))
505 return failure();
506
507 Type clkTy = ClockType::get(result.getContext());
508 if (parser.resolveOperand(clk, clkTy, result.operands))
509 return failure();
510
511 if (resetAndValue) {
512 Type i1 = IntegerType::get(result.getContext(), 1);
513 if (parser.resolveOperand(resetAndValue->first, i1, result.operands) ||
514 parser.resolveOperand(resetAndValue->second, ty, result.operands))
515 return failure();
516 }
517
518 return success();
519}
520
521void FirRegOp::print(::mlir::OpAsmPrinter &p) {
522 SmallVector<StringRef> elidedAttrs = {
523 getInnerSymAttrName(), getIsAsyncAttrName(), getPresetAttrName()};
524
525 p << ' ' << getNext() << " clock " << getClk();
526
527 if (auto sym = getInnerSymAttr()) {
528 p << " sym ";
529 sym.print(p);
530 }
531
532 if (hasReset()) {
533 p << " reset " << (getIsAsync() ? "async" : "sync") << ' ';
534 p << getReset() << ", " << getResetValue();
535 }
536
537 if (auto preset = getPresetAttr()) {
538 p << " preset " << preset.getValue();
539 }
540
541 if (canElideName(p, *this))
542 elidedAttrs.push_back("name");
543
544 p.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
545 p << " : " << getNext().getType();
546}
547
548/// Verifier for the FIR register op.
549LogicalResult FirRegOp::verify() {
550 if (getReset() || getResetValue() || getIsAsync()) {
551 if (!getReset() || !getResetValue())
552 return emitOpError("must specify reset and reset value");
553 } else {
554 if (getIsAsync())
555 return emitOpError("register with no reset cannot be async");
556 }
557 if (auto preset = getPresetAttr()) {
558 int64_t presetWidth = hw::getBitWidth(preset.getType());
559 int64_t width = hw::getBitWidth(getType());
560 if (preset.getType() != getType() && presetWidth != width)
561 return emitOpError("preset type width must match register type");
562 }
563 return success();
564}
565
566/// Suggest a name for each result value based on the saved result names
567/// attribute.
568void FirRegOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
569 // If the register has an optional 'name' attribute, use it.
570 if (!getName().empty())
571 setNameFn(getResult(), getName());
572}
573
574std::optional<size_t> FirRegOp::getTargetResultIndex() { return 0; }
575
576LogicalResult FirRegOp::canonicalize(FirRegOp op, PatternRewriter &rewriter) {
577
578 // If the register has a constant zero reset, drop the reset and reset value
579 // altogether (And preserve the PresetAttr).
580 if (auto reset = op.getReset()) {
581 if (auto constOp = reset.getDefiningOp<hw::ConstantOp>()) {
582 if (constOp.getValue().isZero()) {
583 rewriter.replaceOpWithNewOp<FirRegOp>(
584 op, op.getNext(), op.getClk(), op.getNameAttr(),
585 op.getInnerSymAttr(), op.getPresetAttr());
586 return success();
587 }
588 }
589 }
590
591 // If the register has a symbol, we can't optimize it away.
592 if (op.getInnerSymAttr())
593 return failure();
594
595 // Replace a register with a trivial feedback or constant clock with a
596 // constant zero.
597 // TODO: Once HW aggregate constant values are supported, move this
598 // canonicalization to the folder.
599 auto isConstant = [&]() -> bool {
600 if (op.getNext() == op.getResult())
601 return true;
602 if (auto clk = op.getClk().getDefiningOp<seq::ToClockOp>())
603 return clk.getInput().getDefiningOp<hw::ConstantOp>();
604 return false;
605 };
606
607 // Preset can block canonicalization only if it is non-zero.
608 bool replaceWithConstZero = true;
609 if (auto preset = op.getPresetAttr())
610 if (!preset.getValue().isZero())
611 replaceWithConstZero = false;
612
613 if (isConstant() && !op.getResetValue() && replaceWithConstZero) {
614 if (isa<seq::ClockType>(op.getType())) {
615 rewriter.replaceOpWithNewOp<seq::ConstClockOp>(
616 op, seq::ClockConstAttr::get(rewriter.getContext(), ClockConst::Low));
617 } else {
618 auto constant = rewriter.create<hw::ConstantOp>(
619 op.getLoc(), APInt::getZero(hw::getBitWidth(op.getType())));
620 rewriter.replaceOpWithNewOp<hw::BitcastOp>(op, op.getType(), constant);
621 }
622 return success();
623 }
624
625 // Canonicalize registers with mux-based constant drivers.
626 // This pattern matches registers where the next value is a mux with one
627 // branch being the register itself (creating a self-loop) and the other
628 // branch being a constant. In such cases, the register effectively holds a
629 // constant value and can be replaced with that constant.
630 if (auto nextMux = op.getNext().getDefiningOp<comb::MuxOp>()) {
631 // Reject optimization if register has preset attribute (for simplicity)
632 if (op.getPresetAttr())
633 return failure();
634
635 Attribute value;
636 Value replacedValue;
637
638 // Check if true branch is self-loop and false branch is constant
639 if (nextMux.getTrueValue() == op.getResult() &&
640 matchPattern(nextMux.getFalseValue(), m_Constant(&value))) {
641 replacedValue = nextMux.getFalseValue();
642 }
643 // Check if false branch is self-loop and true branch is constant
644 else if (nextMux.getFalseValue() == op.getResult() &&
645 matchPattern(nextMux.getTrueValue(), m_Constant(&value))) {
646 replacedValue = nextMux.getTrueValue();
647 }
648
649 if (!replacedValue)
650 return failure();
651
652 // Verify reset value compatibility: if register has reset, it must be
653 // a constant that matches the mux constant
654 if (op.getResetValue()) {
655 Attribute resetConst;
656 if (matchPattern(op.getResetValue(), m_Constant(&resetConst))) {
657 if (resetConst != value)
658 return failure();
659 } else {
660 // Non-constant reset value prevents optimization
661 return failure();
662 }
663 }
664
665 assert(replacedValue);
666 // Apply the optimization if all conditions are met
667 rewriter.replaceOp(op, replacedValue);
668 return success();
669 }
670
671 // For reset-less 1d array registers, replace an uninitialized element with
672 // constant zero. For example, let `r` be a 2xi1 register and its next value
673 // be `{foo, r[0]}`. `r[0]` is connected to itself so will never be
674 // initialized. If we don't enable aggregate preservation, `r_0` is replaced
675 // with `0`. Hence this canonicalization replaces 0th element of the next
676 // value with zero to match the behaviour.
677 if (!op.getReset() && !op.getPresetAttr()) {
678 if (auto arrayCreate = op.getNext().getDefiningOp<hw::ArrayCreateOp>()) {
679 // For now only support 1d arrays.
680 // TODO: Support nested arrays and bundles.
681 if (isa<IntegerType>(
682 hw::type_cast<hw::ArrayType>(op.getResult().getType())
683 .getElementType())) {
684 SmallVector<Value> nextOperands;
685 bool changed = false;
686 for (const auto &[i, value] :
687 llvm::enumerate(arrayCreate.getOperands())) {
688 auto index = arrayCreate.getOperands().size() - i - 1;
689 APInt elementIndex;
690 // Check that the corresponding operand is op's element.
691 if (auto arrayGet = value.getDefiningOp<hw::ArrayGetOp>())
692 if (arrayGet.getInput() == op.getResult() &&
693 matchPattern(arrayGet.getIndex(),
694 m_ConstantInt(&elementIndex)) &&
695 elementIndex == index) {
696 nextOperands.push_back(rewriter.create<hw::ConstantOp>(
697 op.getLoc(),
698 APInt::getZero(hw::getBitWidth(arrayGet.getType()))));
699 changed = true;
700 continue;
701 }
702 nextOperands.push_back(value);
703 }
704 // If one of the operands is self loop, update the next value.
705 if (changed) {
706 auto newNextVal = rewriter.create<hw::ArrayCreateOp>(
707 arrayCreate.getLoc(), nextOperands);
708 if (arrayCreate->hasOneUse())
709 // If the original next value has a single use, we can replace the
710 // value directly.
711 rewriter.replaceOp(arrayCreate, newNextVal);
712 else {
713 // Otherwise, replace the entire firreg with a new one.
714 rewriter.replaceOpWithNewOp<FirRegOp>(op, newNextVal, op.getClk(),
715 op.getNameAttr(),
716 op.getInnerSymAttr());
717 }
718
719 return success();
720 }
721 }
722 }
723 }
724
725 return failure();
726}
727
728OpFoldResult FirRegOp::fold(FoldAdaptor adaptor) {
729 // If the register has a symbol or preset value, we can't optimize it away.
730 // TODO: Handle a preset value.
731 if (getInnerSymAttr())
732 return {};
733
734 auto presetAttr = getPresetAttr();
735
736 // If the register is held in permanent reset, replace it with its reset
737 // value. This works trivially if the reset is asynchronous and therefore
738 // level-sensitive, in which case it will always immediately assume the reset
739 // value in silicon. If it is synchronous, the register value is undefined
740 // until the first clock edge at which point it becomes the reset value, in
741 // which case we simply define the initial value to already be the reset
742 // value. Works only if no preset.
743 if (!presetAttr)
744 if (auto reset = getReset())
745 if (auto constOp = reset.getDefiningOp<hw::ConstantOp>())
746 if (constOp.getValue().isOne())
747 return getResetValue();
748
749 // If the register's next value is trivially it's current value, or the
750 // register is never clocked, we can replace the register with a constant
751 // value.
752 bool isTrivialFeedback = (getNext() == getResult());
753 bool isNeverClocked =
754 adaptor.getClk() != nullptr; // clock operand is constant
755 if (!isTrivialFeedback && !isNeverClocked)
756 return {};
757
758 // If the register has a const reset value, and no preset, we can replace it
759 // with the const reset. We cannot replace it with a non-constant reset value.
760 if (auto resetValue = getResetValue()) {
761 if (auto *op = resetValue.getDefiningOp()) {
762 if (op->hasTrait<OpTrait::ConstantLike>() && !presetAttr)
763 return resetValue;
764 if (auto constOp = dyn_cast<hw::ConstantOp>(op))
765 if (presetAttr.getValue() == constOp.getValue())
766 return resetValue;
767 }
768 return {};
769 }
770
771 // Otherwise we want to replace the register with a constant 0. For now this
772 // only works with integer types.
773 auto intType = dyn_cast<IntegerType>(getType());
774 if (!intType)
775 return {};
776 // If preset present, then replace with preset.
777 if (presetAttr)
778 return presetAttr;
779 return IntegerAttr::get(intType, 0);
780}
781
782//===----------------------------------------------------------------------===//
783// ClockGateOp
784//===----------------------------------------------------------------------===//
785
786OpFoldResult ClockGateOp::fold(FoldAdaptor adaptor) {
787 // Forward the clock if one of the enables is always true.
788 if (isConstantOne(adaptor.getEnable()) ||
789 isConstantOne(adaptor.getTestEnable()))
790 return getInput();
791
792 // Fold to a constant zero clock if the enables are always false.
793 if (isConstantZero(adaptor.getEnable()) &&
794 (!getTestEnable() || isConstantZero(adaptor.getTestEnable())))
795 return ClockConstAttr::get(getContext(), ClockConst::Low);
796
797 // Forward constant zero clocks.
798 if (auto clockAttr = dyn_cast_or_null<ClockConstAttr>(adaptor.getInput()))
799 if (clockAttr.getValue() == ClockConst::Low)
800 return ClockConstAttr::get(getContext(), ClockConst::Low);
801
802 // Transitive clock gating - eliminate clock gates that are driven by an
803 // identical enable signal somewhere higher in the clock gate hierarchy.
804 auto clockGateInputOp = getInput().getDefiningOp<ClockGateOp>();
805 while (clockGateInputOp) {
806 if (clockGateInputOp.getEnable() == getEnable() &&
807 clockGateInputOp.getTestEnable() == getTestEnable())
808 return getInput();
809 clockGateInputOp = clockGateInputOp.getInput().getDefiningOp<ClockGateOp>();
810 }
811
812 return {};
813}
814
815LogicalResult ClockGateOp::canonicalize(ClockGateOp op,
816 PatternRewriter &rewriter) {
817 // Remove constant false test enable.
818 if (auto testEnable = op.getTestEnable()) {
819 if (auto constOp = testEnable.getDefiningOp<hw::ConstantOp>()) {
820 if (constOp.getValue().isZero()) {
821 rewriter.modifyOpInPlace(op,
822 [&] { op.getTestEnableMutable().clear(); });
823 return success();
824 }
825 }
826 }
827
828 return failure();
829}
830
831std::optional<size_t> ClockGateOp::getTargetResultIndex() {
832 return std::nullopt;
833}
834
835//===----------------------------------------------------------------------===//
836// ClockMuxOp
837//===----------------------------------------------------------------------===//
838
839OpFoldResult ClockMuxOp::fold(FoldAdaptor adaptor) {
840 if (isConstantOne(adaptor.getCond()))
841 return getTrueClock();
842 if (isConstantZero(adaptor.getCond()))
843 return getFalseClock();
844 return {};
845}
846
847//===----------------------------------------------------------------------===//
848// FirMemOp
849//===----------------------------------------------------------------------===//
850
851LogicalResult FirMemOp::canonicalize(FirMemOp op, PatternRewriter &rewriter) {
852 // Do not change memories if symbols point to them.
853 if (op.getInnerSymAttr())
854 return failure();
855
856 bool readOnly = true, writeOnly = true;
857
858 // If the memory has no read ports, erase it.
859 for (auto *user : op->getUsers()) {
860 if (isa<FirMemReadOp, FirMemReadWriteOp>(user)) {
861 writeOnly = false;
862 }
863 if (isa<FirMemWriteOp, FirMemReadWriteOp>(user)) {
864 readOnly = false;
865 }
866 assert((isa<FirMemReadOp, FirMemWriteOp, FirMemReadWriteOp>(user)) &&
867 "invalid seq.firmem user");
868 }
869 if (writeOnly) {
870 for (auto *user : llvm::make_early_inc_range(op->getUsers()))
871 rewriter.eraseOp(user);
872
873 rewriter.eraseOp(op);
874 return success();
875 }
876
877 if (readOnly && !op.getInit()) {
878 // Replace all read ports with a constant 0.
879 for (auto *user : llvm::make_early_inc_range(op->getUsers())) {
880 auto readOp = cast<FirMemReadOp>(user);
881 Value zero = rewriter.create<hw::ConstantOp>(
882 readOp.getLoc(), APInt::getZero(hw::getBitWidth(readOp.getType())));
883 if (readOp.getType() != zero.getType())
884 zero = rewriter.create<hw::BitcastOp>(readOp.getLoc(), readOp.getType(),
885 zero);
886 rewriter.replaceOp(readOp, zero);
887 }
888 rewriter.eraseOp(op);
889 return success();
890 }
891 return failure();
892}
893
894void FirMemOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
895 auto nameAttr = (*this)->getAttrOfType<StringAttr>("name");
896 if (!nameAttr.getValue().empty())
897 setNameFn(getResult(), nameAttr.getValue());
898}
899
900std::optional<size_t> FirMemOp::getTargetResultIndex() { return 0; }
901
902template <class Op>
903static LogicalResult verifyFirMemMask(Op op) {
904 if (auto mask = op.getMask()) {
905 auto memType = op.getMemory().getType();
906 if (!memType.getMaskWidth())
907 return op.emitOpError("has mask operand but memory type '")
908 << memType << "' has no mask";
909 auto expected = IntegerType::get(op.getContext(), *memType.getMaskWidth());
910 if (mask.getType() != expected)
911 return op.emitOpError("has mask operand of type '")
912 << mask.getType() << "', but memory type requires '" << expected
913 << "'";
914 }
915 return success();
916}
917
918LogicalResult FirMemWriteOp::verify() { return verifyFirMemMask(*this); }
919LogicalResult FirMemReadWriteOp::verify() { return verifyFirMemMask(*this); }
920
921static bool isConstClock(Value value) {
922 if (!value)
923 return false;
924 return value.getDefiningOp<seq::ConstClockOp>();
925}
926
927static bool isConstZero(Value value) {
928 if (value)
929 if (auto constOp = value.getDefiningOp<hw::ConstantOp>())
930 return constOp.getValue().isZero();
931 return false;
932}
933
934static bool isConstAllOnes(Value value) {
935 if (value)
936 if (auto constOp = value.getDefiningOp<hw::ConstantOp>())
937 return constOp.getValue().isAllOnes();
938 return false;
939}
940
941LogicalResult FirMemReadOp::canonicalize(FirMemReadOp op,
942 PatternRewriter &rewriter) {
943 // Remove the enable if it is constant true.
944 if (isConstAllOnes(op.getEnable())) {
945 rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); });
946 return success();
947 }
948 return failure();
949}
950
951LogicalResult FirMemWriteOp::canonicalize(FirMemWriteOp op,
952 PatternRewriter &rewriter) {
953 // Remove the write port if it is trivially dead.
954 if (isConstZero(op.getEnable()) || isConstZero(op.getMask()) ||
955 isConstClock(op.getClk())) {
956 auto memOp = op.getMemory().getDefiningOp<FirMemOp>();
957 if (memOp.getInnerSymAttr())
958 return failure();
959 rewriter.eraseOp(op);
960 return success();
961 }
962 bool anyChanges = false;
963
964 // Remove the enable if it is constant true.
965 if (auto enable = op.getEnable(); isConstAllOnes(enable)) {
966 rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); });
967 anyChanges = true;
968 }
969
970 // Remove the mask if it is all ones.
971 if (auto mask = op.getMask(); isConstAllOnes(mask)) {
972 rewriter.modifyOpInPlace(op, [&] { op.getMaskMutable().erase(0); });
973 anyChanges = true;
974 }
975
976 return success(anyChanges);
977}
978
979LogicalResult FirMemReadWriteOp::canonicalize(FirMemReadWriteOp op,
980 PatternRewriter &rewriter) {
981 // Replace the read-write port with a read port if the write behavior is
982 // trivially disabled.
983 if (isConstZero(op.getEnable()) || isConstZero(op.getMask()) ||
984 isConstClock(op.getClk()) || isConstZero(op.getMode())) {
985 auto opAttrs = op->getAttrs();
986 auto opAttrNames = op.getAttributeNames();
987 auto newOp = rewriter.replaceOpWithNewOp<FirMemReadOp>(
988 op, op.getMemory(), op.getAddress(), op.getClk(), op.getEnable());
989 for (auto namedAttr : opAttrs)
990 if (!llvm::is_contained(opAttrNames, namedAttr.getName()))
991 newOp->setAttr(namedAttr.getName(), namedAttr.getValue());
992 return success();
993 }
994 bool anyChanges = false;
995
996 // Remove the enable if it is constant true.
997 if (auto enable = op.getEnable(); isConstAllOnes(enable)) {
998 rewriter.modifyOpInPlace(op, [&] { op.getEnableMutable().erase(0); });
999 anyChanges = true;
1000 }
1001
1002 // Remove the mask if it is all ones.
1003 if (auto mask = op.getMask(); isConstAllOnes(mask)) {
1004 rewriter.modifyOpInPlace(op, [&] { op.getMaskMutable().erase(0); });
1005 anyChanges = true;
1006 }
1007
1008 return success(anyChanges);
1009}
1010
1011//===----------------------------------------------------------------------===//
1012// ConstClockOp
1013//===----------------------------------------------------------------------===//
1014
1015OpFoldResult ConstClockOp::fold(FoldAdaptor adaptor) {
1016 return ClockConstAttr::get(getContext(), getValue());
1017}
1018
1019//===----------------------------------------------------------------------===//
1020// ToClockOp/FromClockOp
1021//===----------------------------------------------------------------------===//
1022
1023LogicalResult ToClockOp::canonicalize(ToClockOp op, PatternRewriter &rewriter) {
1024 if (auto fromClock = op.getInput().getDefiningOp<FromClockOp>()) {
1025 rewriter.replaceOp(op, fromClock.getInput());
1026 return success();
1027 }
1028 return failure();
1029}
1030
1031OpFoldResult ToClockOp::fold(FoldAdaptor adaptor) {
1032 if (auto fromClock = getInput().getDefiningOp<FromClockOp>())
1033 return fromClock.getInput();
1034 if (auto intAttr = dyn_cast_or_null<IntegerAttr>(adaptor.getInput())) {
1035 auto value =
1036 intAttr.getValue().isZero() ? ClockConst::Low : ClockConst::High;
1037 return ClockConstAttr::get(getContext(), value);
1038 }
1039 return {};
1040}
1041
1042LogicalResult FromClockOp::canonicalize(FromClockOp op,
1043 PatternRewriter &rewriter) {
1044 if (auto toClock = op.getInput().getDefiningOp<ToClockOp>()) {
1045 rewriter.replaceOp(op, toClock.getInput());
1046 return success();
1047 }
1048 return failure();
1049}
1050
1051OpFoldResult FromClockOp::fold(FoldAdaptor adaptor) {
1052 if (auto toClock = getInput().getDefiningOp<ToClockOp>())
1053 return toClock.getInput();
1054 if (auto clockAttr = dyn_cast_or_null<ClockConstAttr>(adaptor.getInput())) {
1055 auto ty = IntegerType::get(getContext(), 1);
1056 return IntegerAttr::get(ty, clockAttr.getValue() == ClockConst::High);
1057 }
1058 return {};
1059}
1060
1061//===----------------------------------------------------------------------===//
1062// ClockInverterOp
1063//===----------------------------------------------------------------------===//
1064
1065OpFoldResult ClockInverterOp::fold(FoldAdaptor adaptor) {
1066 if (auto chainedInv = getInput().getDefiningOp<ClockInverterOp>())
1067 return chainedInv.getInput();
1068 if (auto clockAttr = dyn_cast_or_null<ClockConstAttr>(adaptor.getInput())) {
1069 auto clockIn = clockAttr.getValue() == ClockConst::High;
1070 return ClockConstAttr::get(getContext(),
1071 clockIn ? ClockConst::Low : ClockConst::High);
1072 }
1073 return {};
1074}
1075
1076//===----------------------------------------------------------------------===//
1077// FIR memory helper
1078//===----------------------------------------------------------------------===//
1079
1080FirMemory::FirMemory(hw::HWModuleGeneratedOp op) {
1081 depth = op->getAttrOfType<IntegerAttr>("depth").getInt();
1082 numReadPorts = op->getAttrOfType<IntegerAttr>("numReadPorts").getUInt();
1083 numWritePorts = op->getAttrOfType<IntegerAttr>("numWritePorts").getUInt();
1084 numReadWritePorts =
1085 op->getAttrOfType<IntegerAttr>("numReadWritePorts").getUInt();
1086 readLatency = op->getAttrOfType<IntegerAttr>("readLatency").getUInt();
1087 writeLatency = op->getAttrOfType<IntegerAttr>("writeLatency").getUInt();
1088 dataWidth = op->getAttrOfType<IntegerAttr>("width").getUInt();
1089 if (op->hasAttrOfType<IntegerAttr>("maskGran"))
1090 maskGran = op->getAttrOfType<IntegerAttr>("maskGran").getUInt();
1091 else
1092 maskGran = dataWidth;
1093 readUnderWrite = op->getAttrOfType<seq::RUWAttr>("readUnderWrite").getValue();
1094 writeUnderWrite =
1095 op->getAttrOfType<seq::WUWAttr>("writeUnderWrite").getValue();
1096 if (auto clockIDsAttr = op->getAttrOfType<ArrayAttr>("writeClockIDs"))
1097 for (auto clockID : clockIDsAttr)
1098 writeClockIDs.push_back(
1099 cast<IntegerAttr>(clockID).getValue().getZExtValue());
1100 initFilename = op->getAttrOfType<StringAttr>("initFilename").getValue();
1101 initIsBinary = op->getAttrOfType<BoolAttr>("initIsBinary").getValue();
1102 initIsInline = op->getAttrOfType<BoolAttr>("initIsInline").getValue();
1103}
1104
1105LogicalResult InitialOp::verify() {
1106 // Check outputs.
1107 auto *terminator = this->getBody().front().getTerminator();
1108 if (terminator->getOperands().size() != getNumResults())
1109 return emitError() << "result type doesn't match with the terminator";
1110 for (auto [lhs, rhs] :
1111 llvm::zip(terminator->getOperands().getTypes(), getResultTypes())) {
1112 if (cast<seq::ImmutableType>(rhs).getInnerType() != lhs)
1113 return emitError() << cast<seq::ImmutableType>(rhs).getInnerType()
1114 << " is expected but got " << lhs;
1115 }
1116
1117 auto blockArgs = this->getBody().front().getArguments();
1118
1119 if (blockArgs.size() != getNumOperands())
1120 return emitError() << "operand type doesn't match with the block arg";
1121
1122 for (auto [blockArg, operand] : llvm::zip(blockArgs, getOperands())) {
1123 if (blockArg.getType() !=
1124 cast<ImmutableType>(operand.getType()).getInnerType())
1125 return emitError()
1126 << blockArg.getType() << " is expected but got "
1127 << cast<ImmutableType>(operand.getType()).getInnerType();
1128 }
1129 return success();
1130}
1131void InitialOp::build(OpBuilder &builder, OperationState &result,
1132 TypeRange resultTypes, std::function<void()> ctor) {
1133 OpBuilder::InsertionGuard guard(builder);
1134
1135 builder.createBlock(result.addRegion());
1136 SmallVector<Type> types;
1137 for (auto t : resultTypes)
1138 types.push_back(seq::ImmutableType::get(t));
1139
1140 result.addTypes(types);
1141
1142 if (ctor)
1143 ctor();
1144}
1145
1146TypedValue<seq::ImmutableType>
1147circt::seq::createConstantInitialValue(OpBuilder builder, Location loc,
1148 mlir::IntegerAttr attr) {
1149 auto initial = builder.create<seq::InitialOp>(loc, attr.getType(), [&]() {
1150 auto constant = builder.create<hw::ConstantOp>(loc, attr);
1151 builder.create<seq::YieldOp>(loc, ArrayRef<Value>{constant});
1152 });
1153 return cast<TypedValue<seq::ImmutableType>>(initial->getResult(0));
1154}
1155
1156mlir::TypedValue<seq::ImmutableType>
1157circt::seq::createConstantInitialValue(OpBuilder builder, Operation *op) {
1158 assert(op->getNumResults() == 1 &&
1159 op->hasTrait<mlir::OpTrait::ConstantLike>());
1160 auto initial =
1161 builder.create<seq::InitialOp>(op->getLoc(), op->getResultTypes(), [&]() {
1162 auto clonedOp = builder.clone(*op);
1163 builder.create<seq::YieldOp>(op->getLoc(), clonedOp->getResults());
1164 });
1165 return cast<mlir::TypedValue<seq::ImmutableType>>(initial.getResult(0));
1166}
1167
1168Value circt::seq::unwrapImmutableValue(TypedValue<seq::ImmutableType> value) {
1169 auto resultNum = cast<OpResult>(value).getResultNumber();
1170 auto initialOp = value.getDefiningOp<seq::InitialOp>();
1171 assert(initialOp);
1172 return initialOp.getBodyBlock()->getTerminator()->getOperand(resultNum);
1173}
1174
1175FailureOr<seq::InitialOp> circt::seq::mergeInitialOps(Block *block) {
1176 SmallVector<Operation *> initialOps;
1177 for (auto &op : *block)
1178 if (isa<seq::InitialOp>(op))
1179 initialOps.push_back(&op);
1180
1181 if (!mlir::computeTopologicalSorting(initialOps, {}))
1182 return block->getParentOp()->emitError() << "initial ops cannot be "
1183 << "topologically sorted";
1184
1185 // No need to merge if there is only one initial op.
1186 if (initialOps.size() <= 1)
1187 return initialOps.empty() ? seq::InitialOp()
1188 : cast<seq::InitialOp>(initialOps[0]);
1189
1190 auto initialOp = cast<seq::InitialOp>(initialOps.front());
1191 auto yieldOp = cast<seq::YieldOp>(initialOp.getBodyBlock()->getTerminator());
1192
1193 llvm::MapVector<Value, Value>
1194 resultToYieldOperand; // seq.immutable value to operand.
1195
1196 for (auto [result, operand] :
1197 llvm::zip(initialOp.getResults(), yieldOp->getOperands()))
1198 resultToYieldOperand.insert({result, operand});
1199
1200 for (size_t i = 1; i < initialOps.size(); ++i) {
1201 auto currentInitialOp = cast<seq::InitialOp>(initialOps[i]);
1202 auto operands = currentInitialOp->getOperands();
1203 for (auto [blockArg, operand] :
1204 llvm::zip(currentInitialOp.getBodyBlock()->getArguments(), operands)) {
1205 if (auto initOp = operand.getDefiningOp<seq::InitialOp>()) {
1206 assert(resultToYieldOperand.count(operand) &&
1207 "it must be visited already");
1208 blockArg.replaceAllUsesWith(resultToYieldOperand.lookup(operand));
1209 } else {
1210 // Otherwise add the operand to the current block.
1211 initialOp.getBodyBlock()->addArgument(
1212 cast<seq::ImmutableType>(operand.getType()).getInnerType(),
1213 operand.getLoc());
1214 initialOp.getInputsMutable().append(operand);
1215 }
1216 }
1217
1218 auto currentYieldOp =
1219 cast<seq::YieldOp>(currentInitialOp.getBodyBlock()->getTerminator());
1220
1221 for (auto [result, operand] : llvm::zip(currentInitialOp.getResults(),
1222 currentYieldOp->getOperands()))
1223 resultToYieldOperand.insert({result, operand});
1224
1225 // Append the operands of the current yield op to the original yield op.
1226 yieldOp.getOperandsMutable().append(currentYieldOp.getOperands());
1227 currentYieldOp->erase();
1228
1229 // Append the operations of the current initial op to the original initial
1230 // op.
1231 initialOp.getBodyBlock()->getOperations().splice(
1232 initialOp.end(), currentInitialOp.getBodyBlock()->getOperations());
1233 }
1234
1235 // Move the terminator to the end of the block.
1236 yieldOp->moveBefore(initialOp.getBodyBlock(),
1237 initialOp.getBodyBlock()->end());
1238
1239 auto builder = OpBuilder::atBlockBegin(block);
1240 SmallVector<Type> types;
1241 for (auto [result, operand] : resultToYieldOperand)
1242 types.push_back(operand.getType());
1243
1244 // Create a new initial op which accumulates the results of the merged initial
1245 // ops.
1246 auto newInitial = builder.create<seq::InitialOp>(initialOp.getLoc(), types);
1247 newInitial.getInputsMutable().append(initialOp.getInputs());
1248
1249 for (auto [resultAndOperand, newResult] :
1250 llvm::zip(resultToYieldOperand, newInitial.getResults()))
1251 resultAndOperand.first.replaceAllUsesWith(newResult);
1252
1253 // Update the block arguments of the new initial op.
1254 for (auto oldBlockArg : initialOp.getBodyBlock()->getArguments()) {
1255 auto blockArg = newInitial.getBodyBlock()->addArgument(
1256 oldBlockArg.getType(), oldBlockArg.getLoc());
1257 oldBlockArg.replaceAllUsesWith(blockArg);
1258 }
1259
1260 newInitial.getBodyBlock()->getOperations().splice(
1261 newInitial.end(), initialOp.getBodyBlock()->getOperations());
1262
1263 // Clean up.
1264 while (!initialOps.empty())
1265 initialOps.pop_back_val()->erase();
1266
1267 return newInitial;
1268}
1269
1270//===----------------------------------------------------------------------===//
1271// TableGen generated logic.
1272//===----------------------------------------------------------------------===//
1273
1274// Provide the autogenerated implementation guts for the Op classes.
1275#define GET_OP_CLASSES
1276#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 InstancePath empty
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:921
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:934
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:903
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
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:55
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:110
FailureOr< seq::InitialOp > mergeInitialOps(Block *block)
Definition SeqOps.cpp:1175
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:1147
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:27
static bool isConstantOne(Attribute operand)
Determine whether a constant operand is a one value.
Definition FoldUtils.h:34
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
Definition LLVM.h:183
Definition seq.py:1
FirMemory(hw::HWModuleGeneratedOp op)
Definition SeqOps.cpp:1080