CIRCT 23.0.0git
Loading...
Searching...
No Matches
LLHDOps.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
10
14#include "mlir/IR/Attributes.h"
15#include "mlir/IR/BuiltinDialect.h"
16#include "mlir/IR/BuiltinTypes.h"
17#include "mlir/IR/Matchers.h"
18#include "mlir/IR/PatternMatch.h"
19#include "mlir/IR/Region.h"
20#include "mlir/IR/Types.h"
21#include "mlir/IR/Value.h"
22#include "mlir/Interfaces/FunctionImplementation.h"
23#include "llvm/ADT/ArrayRef.h"
24#include "llvm/ADT/SmallVector.h"
25
26using namespace circt;
27using namespace mlir;
28using namespace llhd;
29
30unsigned circt::llhd::getLLHDTypeWidth(Type type) {
31 if (auto sig = dyn_cast<RefType>(type))
32 type = sig.getNestedType();
33 if (auto array = dyn_cast<hw::ArrayType>(type))
34 return array.getNumElements();
35 if (auto tup = dyn_cast<hw::StructType>(type))
36 return tup.getElements().size();
37 return type.getIntOrFloatBitWidth();
38}
39
41 if (auto sig = dyn_cast<RefType>(type))
42 type = sig.getNestedType();
43 if (auto array = dyn_cast<hw::ArrayType>(type))
44 return array.getElementType();
45 return type;
46}
47
48template <typename... OpTypes>
49static bool hasUserOfKind(Operation *op) {
50 for (auto *user : op->getUsers()) {
51 if (isa<OpTypes...>(user))
52 return true;
53 }
54 return false;
55}
56
57//===----------------------------------------------------------------------===//
58// ConstantTimeOp
59//===----------------------------------------------------------------------===//
60
61OpFoldResult llhd::ConstantTimeOp::fold(FoldAdaptor adaptor) {
62 assert(adaptor.getOperands().empty() && "const has no operands");
63 return getValueAttr();
64}
65
66void llhd::ConstantTimeOp::build(OpBuilder &builder, OperationState &result,
67 uint64_t time, const StringRef &timeUnit,
68 unsigned delta, unsigned epsilon) {
69 auto *ctx = builder.getContext();
70 auto attr = TimeAttr::get(ctx, time, timeUnit, delta, epsilon);
71 return build(builder, result, TimeType::get(ctx), attr);
72}
73
74//===----------------------------------------------------------------------===//
75// SignalOp
76//===----------------------------------------------------------------------===//
77
78static Value getValueAtIndex(OpBuilder &builder, Location loc, Value val,
79 unsigned index, Type resultType) {
80 return TypeSwitch<Type, Value>(val.getType())
81 .Case<hw::StructType>([&](hw::StructType ty) -> Value {
82 return hw::StructExtractOp::create(builder, loc, val,
83 ty.getElements()[index].name);
84 })
85 .Case<hw::ArrayType>([&](hw::ArrayType ty) -> Value {
86 Value idx = hw::ConstantOp::create(
87 builder, loc,
88 builder.getIntegerType(llvm::Log2_64_Ceil(ty.getNumElements())),
89 index);
90 return hw::ArrayGetOp::create(builder, loc, val, idx);
91 })
92 .Case<IntegerType>([&](IntegerType ty) -> Value {
93 return comb::ExtractOp::create(builder, loc, val, index,
94 resultType.getIntOrFloatBitWidth());
95 });
96}
97
98void SignalOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
99 if (getName() && !getName()->empty())
100 setNameFn(getResult(), *getName());
101}
102
103// Calculates the destructured subelements for an integer type.
104//
105// Typically, subelements are determined by only looking at the type. For an
106// IntegerType this would mean bit-blasting every bit of the type, so we instead
107// take the value that we're destructuring into account by looking at its users.
108static std::optional<DenseMap<Attribute, Type>>
109getIntegerSubelementIndexMap(Type type, Value value) {
110 int width = type.getIntOrFloatBitWidth();
111 if (width <= 1)
112 return {};
113
114 // Calculate the intervals of bits demanded by users. If a user's interval
115 // is unknown, we return nullopt.
116 SmallVector<bool> startIndices(width, false);
117 for (Operation *user : value.getUsers()) {
118 if (auto extract = dyn_cast<SigExtractOp>(user)) {
119 APInt lowBit;
120 if (matchPattern(extract.getLowBit(), m_ConstantInt(&lowBit))) {
121 startIndices[lowBit.getZExtValue()] = true;
122 int64_t highBit = lowBit.getZExtValue() + extract.getResultWidth();
123 if (highBit < width)
124 startIndices[highBit] = true;
125 continue;
126 }
127 }
128 if (isa<ProbeOp, DriveOp>(user)) {
129 // Probe and Drive require the entire interval but don't need to be
130 // bit-blasted.
131 continue;
132 }
133 // Potentially dynamic start index.
134 return {};
135 }
136
137 // Create subelements for each interval.
138 DenseMap<Attribute, Type> destructured;
139 for (int start = 0; start < width;) {
140 int end = start + 1;
141 while (end < width && !startIndices[end])
142 ++end;
143
144 int runLength = end - start;
145
146 destructured.insert(
147 {IntegerAttr::get(IndexType::get(type.getContext()), start),
148 IntegerType::get(type.getContext(), runLength)});
149 start = end;
150 }
151
152 return destructured;
153}
154
155static std::optional<DenseMap<Attribute, Type>>
156getSubelementIndexMap(Type type, Value value) {
157 // Handle IntegerType specially; destructuring integers into individual
158 // bits can create an explosion of ops, so instead we determine the subelement
159 // map dynamically.
160 if (auto intType = dyn_cast<IntegerType>(type)) {
161 return getIntegerSubelementIndexMap(intType, value);
162 } else {
163 auto destructurable = llvm::dyn_cast<DestructurableTypeInterface>(type);
164 if (!destructurable)
165 return {};
166 return destructurable.getSubelementIndexMap();
167 }
168}
169
170SmallVector<DestructurableMemorySlot> SignalOp::getDestructurableSlots() {
171 auto type = getType().getNestedType();
172
173 // It only makes sense to destructure a SignalOp if it has one or more users
174 // that access the destructured elements.
175 if (!hasUserOfKind<SigExtractOp, SigArrayGetOp, SigStructExtractOp,
176 SigArraySliceOp>(*this))
177 return {};
178
179 auto destructuredType = getSubelementIndexMap(type, getResult());
180 if (!destructuredType)
181 return {};
182 return {DestructurableMemorySlot{{getResult(), type}, *destructuredType}};
183}
184
185DenseMap<Attribute, MemorySlot> SignalOp::destructure(
186 const DestructurableMemorySlot &slot,
187 const SmallPtrSetImpl<Attribute> &usedIndices, OpBuilder &builder,
188 SmallVectorImpl<DestructurableAllocationOpInterface> &newAllocators) {
189 assert(slot.ptr == getResult());
190 builder.setInsertionPointAfter(*this);
191
192 DenseMap<Attribute, Type> subelementTypes = slot.subelementTypes;
193 DenseMap<Attribute, MemorySlot> slotMap;
194 SmallVector<std::pair<unsigned, Type>> indices;
195 for (auto attr : usedIndices) {
196 assert(isa<IntegerAttr>(attr));
197 auto elemType = subelementTypes.at(attr);
198 assert(elemType && "used index must exist");
199 indices.push_back({cast<IntegerAttr>(attr).getInt(), elemType});
200 }
201
202 llvm::sort(indices, [](auto a, auto b) { return a.first < b.first; });
203
204 for (auto [index, type] : indices) {
205 Value init = getValueAtIndex(builder, getLoc(), getInit(), index, type);
206 auto sigOp = SignalOp::create(builder, getLoc(), getNameAttr(), init);
207 newAllocators.push_back(sigOp);
208 slotMap.try_emplace<MemorySlot>(
209 IntegerAttr::get(IndexType::get(getContext()), index),
210 {sigOp.getResult(), type});
211 }
212
213 return slotMap;
214}
215
216std::optional<DestructurableAllocationOpInterface>
217SignalOp::handleDestructuringComplete(const DestructurableMemorySlot &slot,
218 OpBuilder &builder) {
219 assert(slot.ptr == getResult());
220 this->erase();
221 return std::nullopt;
222}
223
224//===----------------------------------------------------------------------===//
225// SigExtractOp
226//===----------------------------------------------------------------------===//
227
228template <class Op>
229static OpFoldResult foldSigPtrExtractOp(Op op, ArrayRef<Attribute> operands) {
230
231 if (!operands[1])
232 return nullptr;
233
234 // llhd.sig.extract(input, 0) with inputWidth == resultWidth => input
235 if (op.getResultWidth() == op.getInputWidth() &&
236 cast<IntegerAttr>(operands[1]).getValue().isZero())
237 return op.getInput();
238
239 return nullptr;
240}
241
242OpFoldResult llhd::SigExtractOp::fold(FoldAdaptor adaptor) {
243 return foldSigPtrExtractOp(*this, adaptor.getOperands());
244}
245
246// Returns the number of elements that overlap between [a1, a2) and [b1, b2).
247static int64_t intervalOverlap(int64_t a1, int64_t a2, int64_t b1, int64_t b2) {
248 return std::max<int64_t>(0, std::min(a2, b2) - std::max(a1, b1));
249}
250
251static void getSortedPtrs(DenseMap<Attribute, MemorySlot> &subslots,
252 SmallVectorImpl<std::pair<unsigned, Value>> &sorted) {
253 for (auto [attr, mem] : subslots) {
254 assert(isa<IntegerAttr>(attr));
255 sorted.push_back({cast<IntegerAttr>(attr).getInt(), mem.ptr});
256 }
257
258 llvm::sort(sorted, [](auto a, auto b) { return a.first < b.first; });
259}
260
261static void getSortedPtrs(const DenseMap<Attribute, Type> &subslots,
262 SmallVectorImpl<std::pair<unsigned, Type>> &sorted) {
263 for (auto [attr, mem] : subslots) {
264 assert(isa<IntegerAttr>(attr));
265 sorted.push_back({cast<IntegerAttr>(attr).getInt(), mem});
266 }
267
268 llvm::sort(sorted, [](auto a, auto b) { return a.first < b.first; });
269}
270
271bool SigExtractOp::canRewire(const DestructurableMemorySlot &slot,
272 SmallPtrSetImpl<Attribute> &usedIndices,
273 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
274 const DataLayout &dataLayout) {
275 if (slot.ptr != getInput())
276 return false;
277 APInt idx;
278 Type type = getLLHDElementType(getResult().getType());
279 if (!type.isSignlessInteger())
280 return false;
281 if (!matchPattern(getLowBit(), m_ConstantInt(&idx)))
282 return false;
283
284 // We rewire by pushing subslot values down to users, so we only support
285 // ProbeOp and DriveOp users as these can deal with the naked (non-ref) types
286 // produced by SROA.
287 for (Operation *user : getResult().getUsers()) {
288 if (!isa<ProbeOp, DriveOp>(user))
289 return false;
290 }
291
292 SmallVector<std::pair<unsigned, Type>> elements;
293 getSortedPtrs(slot.subelementTypes, elements);
294
295 int64_t index = idx.getZExtValue();
296 int64_t width = getLLHDTypeWidth(type);
297 int64_t coveredBits = 0;
298 for (auto [start, type] : elements) {
299 int64_t subslotWidth = type.getIntOrFloatBitWidth();
300 int64_t overlap =
301 intervalOverlap(index, index + width, start, start + subslotWidth);
302 if (overlap == 0)
303 continue;
304 usedIndices.insert(IntegerAttr::get(IndexType::get(getContext()), start));
305 coveredBits += overlap;
306 }
307
308 if (coveredBits != width)
309 return false;
310
311 mustBeSafelyUsed.emplace_back<MemorySlot>({getResult(), type});
312 return true;
313}
314
315DeletionKind SigExtractOp::rewire(const DestructurableMemorySlot &slot,
316 DenseMap<Attribute, MemorySlot> &subslots,
317 OpBuilder &builder,
318 const DataLayout &dataLayout) {
319 APInt idx;
320 [[maybe_unused]] bool result = matchPattern(getLowBit(), m_ConstantInt(&idx));
321 assert(result);
322 int64_t width = getLLHDTypeWidth(getResult().getType());
323 int64_t idxVal = idx.getZExtValue();
324
325 SmallVector<std::pair<unsigned, Value>> elements;
326 getSortedPtrs(subslots, elements);
327
328 for (Operation *user : llvm::make_early_inc_range(getResult().getUsers())) {
329 builder.setInsertionPoint(user);
330 // Decompose a ProbeOp into a concatenation of ProbeOps, one per subslot.
331 if (auto probeOp = dyn_cast<ProbeOp>(user)) {
332 SmallVector<Value> values;
333 for (auto [start, value] : elements) {
334 int64_t subslotWidth = cast<RefType>(value.getType())
335 .getNestedType()
336 .getIntOrFloatBitWidth();
337 int64_t overlap = intervalOverlap(idxVal, idxVal + width, start,
338 start + subslotWidth);
339 if (overlap == 0)
340 continue;
341 values.push_back(ProbeOp::create(builder, probeOp.getLoc(), value));
342 }
343 std::reverse(values.begin(), values.end());
344 Value value = comb::ConcatOp::create(builder, probeOp.getLoc(), values);
345 probeOp.replaceAllUsesWith(value);
346 probeOp.erase();
347 continue;
348 }
349
350 // Decompose a DriveOp into one DriveOp per subslot.
351 auto driveOp = cast<DriveOp>(user);
352 for (auto [start, sig] : elements) {
353 int64_t subslotWidth =
354 cast<RefType>(sig.getType()).getNestedType().getIntOrFloatBitWidth();
355 int64_t overlap =
356 intervalOverlap(idxVal, idxVal + width, start, start + subslotWidth);
357 if (overlap == 0)
358 continue;
359 Value val =
360 comb::ExtractOp::create(builder, driveOp.getLoc(), driveOp.getValue(),
361 start - idxVal, subslotWidth);
362 DriveOp::create(builder, driveOp.getLoc(), sig, val, driveOp.getTime(),
363 driveOp.getEnable());
364 }
365 driveOp.erase();
366 }
367
368 return DeletionKind::Delete;
369}
370
371LogicalResult SigExtractOp::ensureOnlySafeAccesses(
372 const MemorySlot &slot, SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
373 const DataLayout &dataLayout) {
374 return success();
375}
376
377//===----------------------------------------------------------------------===//
378// SigArraySliceOp
379//===----------------------------------------------------------------------===//
380
381OpFoldResult llhd::SigArraySliceOp::fold(FoldAdaptor adaptor) {
382 auto lowIndex = dyn_cast_or_null<IntegerAttr>(adaptor.getLowIndex());
383 if (!lowIndex)
384 return {};
385
386 // llhd.sig.array_slice(input, 0) with inputWidth == resultWidth => input
387 if (getResultWidth() == getInputWidth() && lowIndex.getValue().isZero())
388 return getInput();
389
390 return {};
391}
392
393template <class Op>
394static LogicalResult canonicalizeSigPtrArraySliceOp(Op op,
395 PatternRewriter &rewriter) {
396 IntegerAttr indexAttr;
397 if (!matchPattern(op.getLowIndex(), m_Constant(&indexAttr)))
398 return failure();
399
400 // llhd.sig.array_slice(llhd.sig.array_slice(target, a), b)
401 // => llhd.sig.array_slice(target, a+b)
402 IntegerAttr a;
403 if (matchPattern(op.getInput(),
404 m_Op<Op>(matchers::m_Any(), m_Constant(&a)))) {
405 auto sliceOp = op.getInput().template getDefiningOp<Op>();
406 rewriter.modifyOpInPlace(op, [&]() {
407 op.getInputMutable().assign(sliceOp.getInput());
408 Value newIndex = hw::ConstantOp::create(
409 rewriter, op->getLoc(), a.getValue() + indexAttr.getValue());
410 op.getLowIndexMutable().assign(newIndex);
411 });
412
413 return success();
414 }
415
416 return failure();
417}
418
419LogicalResult llhd::SigArraySliceOp::canonicalize(llhd::SigArraySliceOp op,
420 PatternRewriter &rewriter) {
421 return canonicalizeSigPtrArraySliceOp(op, rewriter);
422}
423
424//===----------------------------------------------------------------------===//
425// SigArrayGetOp
426//===----------------------------------------------------------------------===//
427
428bool SigArrayGetOp::canRewire(const DestructurableMemorySlot &slot,
429 SmallPtrSetImpl<Attribute> &usedIndices,
430 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
431 const DataLayout &dataLayout) {
432 if (slot.ptr != getInput())
433 return false;
434 APInt idx;
435 if (!matchPattern(getIndex(), m_ConstantInt(&idx)))
436 return false;
437 auto index =
438 IntegerAttr::get(IndexType::get(getContext()), idx.getZExtValue());
439 if (!slot.subelementTypes.contains(index))
440 return false;
441 usedIndices.insert(index);
442 mustBeSafelyUsed.emplace_back<MemorySlot>(
443 {getResult(), cast<RefType>(getResult().getType()).getNestedType()});
444 return true;
445}
446
447DeletionKind SigArrayGetOp::rewire(const DestructurableMemorySlot &slot,
448 DenseMap<Attribute, MemorySlot> &subslots,
449 OpBuilder &builder,
450 const DataLayout &dataLayout) {
451 APInt idx;
452 bool result = matchPattern(getIndex(), m_ConstantInt(&idx));
453 (void)result;
454 assert(result);
455 auto index =
456 IntegerAttr::get(IndexType::get(getContext()), idx.getZExtValue());
457 auto it = subslots.find(index);
458 assert(it != subslots.end());
459 replaceAllUsesWith(it->getSecond().ptr);
460 return DeletionKind::Delete;
461}
462
463LogicalResult SigArrayGetOp::ensureOnlySafeAccesses(
464 const MemorySlot &slot, SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
465 const DataLayout &dataLayout) {
466 return success();
467}
468
469//===----------------------------------------------------------------------===//
470// SigStructExtractOp
471//===----------------------------------------------------------------------===//
472
473LogicalResult llhd::SigStructExtractOp::inferReturnTypes(
474 MLIRContext *context, std::optional<Location> loc, ValueRange operands,
475 DictionaryAttr attrs, mlir::OpaqueProperties properties,
476 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
477 typename SigStructExtractOp::Adaptor adaptor(operands, attrs, properties,
478 regions);
479 auto nestedType = cast<RefType>(adaptor.getInput().getType()).getNestedType();
480 Type fieldType;
481
482 // Support both StructType and UnionType
483 if (auto structType = dyn_cast<hw::StructType>(nestedType)) {
484 fieldType = structType.getFieldType(adaptor.getField());
485 } else if (auto unionType = dyn_cast<hw::UnionType>(nestedType)) {
486 fieldType = unionType.getFieldType(adaptor.getField());
487 } else {
488 context->getDiagEngine().emit(loc.value_or(UnknownLoc()),
489 DiagnosticSeverity::Error)
490 << "expected struct or union type";
491 return failure();
492 }
493
494 if (!fieldType) {
495 context->getDiagEngine().emit(loc.value_or(UnknownLoc()),
496 DiagnosticSeverity::Error)
497 << "invalid field name specified";
498 return failure();
499 }
500 results.push_back(RefType::get(fieldType));
501 return success();
502}
503
504bool SigStructExtractOp::canRewire(
505 const DestructurableMemorySlot &slot,
506 SmallPtrSetImpl<Attribute> &usedIndices,
507 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
508 const DataLayout &dataLayout) {
509 if (slot.ptr != getInput())
510 return false;
511
512 auto nestedType = cast<RefType>(getInput().getType()).getNestedType();
513 std::optional<uint32_t> index;
514
515 // Support both StructType and UnionType
516 if (auto structType = dyn_cast<hw::StructType>(nestedType))
517 index = structType.getFieldIndex(getFieldAttr());
518 else if (auto unionType = dyn_cast<hw::UnionType>(nestedType))
519 index = unionType.getFieldIndex(getFieldAttr());
520 else
521 return false;
522
523 if (!index)
524 return false;
525 auto indexAttr = IntegerAttr::get(IndexType::get(getContext()), *index);
526 if (!slot.subelementTypes.contains(indexAttr))
527 return false;
528 usedIndices.insert(indexAttr);
529 mustBeSafelyUsed.emplace_back<MemorySlot>(
530 {getResult(), cast<RefType>(getResult().getType()).getNestedType()});
531 return true;
532}
533
534DeletionKind
535SigStructExtractOp::rewire(const DestructurableMemorySlot &slot,
536 DenseMap<Attribute, MemorySlot> &subslots,
537 OpBuilder &builder, const DataLayout &dataLayout) {
538 auto nestedType = cast<RefType>(getInput().getType()).getNestedType();
539 std::optional<unsigned> index;
540 if (auto structTy = dyn_cast<hw::StructType>(nestedType))
541 index = structTy.getFieldIndex(getFieldAttr());
542 else if (auto unionTy = dyn_cast<hw::UnionType>(nestedType))
543 index = unionTy.getFieldIndex(getFieldAttr());
544 assert(index.has_value());
545 auto indexAttr = IntegerAttr::get(IndexType::get(getContext()), *index);
546 auto it = subslots.find(indexAttr);
547 assert(it != subslots.end());
548 replaceAllUsesWith(it->getSecond().ptr);
549 return DeletionKind::Delete;
550}
551
552LogicalResult SigStructExtractOp::ensureOnlySafeAccesses(
553 const MemorySlot &slot, SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
554 const DataLayout &dataLayout) {
555 return success();
556}
557
558//===----------------------------------------------------------------------===//
559// ProbeOp
560//===----------------------------------------------------------------------===//
561
562bool ProbeOp::canRewire(const DestructurableMemorySlot &slot,
563 SmallPtrSetImpl<Attribute> &usedIndices,
564 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
565 const DataLayout &dataLayout) {
566 for (auto [key, _] : slot.subelementTypes)
567 usedIndices.insert(key);
568
569 return isa<hw::StructType, hw::ArrayType, IntegerType>(slot.elemType);
570}
571
572DeletionKind ProbeOp::rewire(const DestructurableMemorySlot &slot,
573 DenseMap<Attribute, MemorySlot> &subslots,
574 OpBuilder &builder, const DataLayout &dataLayout) {
575 SmallVector<std::pair<unsigned, Value>> elements;
576 SmallVector<Value> probed;
577 getSortedPtrs(subslots, elements);
578 for (auto [_, val] : elements)
579 probed.push_back(ProbeOp::create(builder, getLoc(), val));
580
581 Value repl =
582 TypeSwitch<Type, Value>(getType())
583 .Case<hw::StructType>([&](auto ty) {
584 return hw::StructCreateOp::create(builder, getLoc(), getType(),
585 probed);
586 })
587 .Case<hw::ArrayType>([&](auto ty) {
588 std::reverse(probed.begin(), probed.end());
589 return hw::ArrayCreateOp::create(builder, getLoc(), probed);
590 })
591 .Case<IntegerType>([&](auto ty) {
592 std::reverse(probed.begin(), probed.end());
593 return comb::ConcatOp::create(builder, getLoc(), probed);
594 });
595
596 replaceAllUsesWith(repl);
597 return DeletionKind::Delete;
598}
599
600LogicalResult
601ProbeOp::ensureOnlySafeAccesses(const MemorySlot &slot,
602 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
603 const DataLayout &dataLayout) {
604 return success();
605}
606
607void ProbeOp::getEffects(
608 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
609 &effects) {
610 if (mayHaveSSADominance(*getOperation()->getParentRegion()))
611 effects.emplace_back(MemoryEffects::Read::get(), &getSignalMutable());
612}
613
614//===----------------------------------------------------------------------===//
615// DriveOp
616//===----------------------------------------------------------------------===//
617
618LogicalResult llhd::DriveOp::fold(FoldAdaptor adaptor,
619 SmallVectorImpl<OpFoldResult> &result) {
620 if (!getEnable())
621 return failure();
622
623 if (matchPattern(getEnable(), m_One())) {
624 getEnableMutable().clear();
625 return success();
626 }
627
628 return failure();
629}
630
631LogicalResult llhd::DriveOp::canonicalize(llhd::DriveOp op,
632 PatternRewriter &rewriter) {
633 if (!op.getEnable())
634 return failure();
635
636 if (matchPattern(op.getEnable(), m_Zero())) {
637 rewriter.eraseOp(op);
638 return success();
639 }
640
641 return failure();
642}
643
644bool DriveOp::canRewire(const DestructurableMemorySlot &slot,
645 SmallPtrSetImpl<Attribute> &usedIndices,
646 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
647 const DataLayout &dataLayout) {
648 for (auto [key, _] : slot.subelementTypes)
649 usedIndices.insert(key);
650
651 return isa<hw::StructType, hw::ArrayType, IntegerType>(slot.elemType);
652}
653
654DeletionKind DriveOp::rewire(const DestructurableMemorySlot &slot,
655 DenseMap<Attribute, MemorySlot> &subslots,
656 OpBuilder &builder, const DataLayout &dataLayout) {
657 SmallVector<std::pair<unsigned, Value>> driven;
658 getSortedPtrs(subslots, driven);
659
660 for (auto [idx, sig] : driven) {
661 Type nestedType = cast<RefType>(sig.getType()).getNestedType();
662 DriveOp::create(
663 builder, getLoc(), sig,
664 getValueAtIndex(builder, getLoc(), getValue(), idx, nestedType),
665 getTime(), getEnable());
666 }
667
668 return DeletionKind::Delete;
669}
670
671LogicalResult
672DriveOp::ensureOnlySafeAccesses(const MemorySlot &slot,
673 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
674 const DataLayout &dataLayout) {
675 return success();
676}
677
678//===----------------------------------------------------------------------===//
679// ProcessOp
680//===----------------------------------------------------------------------===//
681
682LogicalResult ProcessOp::canonicalize(ProcessOp op, PatternRewriter &rewriter) {
683 if (!op.getBody().hasOneBlock())
684 return failure();
685
686 auto &block = op.getBody().front();
687 auto haltOp = dyn_cast<HaltOp>(block.getTerminator());
688 if (!haltOp)
689 return failure();
690
691 if (op.getNumResults() == 0 && block.getOperations().size() == 1) {
692 rewriter.eraseOp(op);
693 return success();
694 }
695
696 // Only constants and halt terminator are expected in a single block.
697 if (!llvm::all_of(block.without_terminator(), [](auto &bodyOp) {
698 return bodyOp.template hasTrait<OpTrait::ConstantLike>();
699 }))
700 return failure();
701
702 auto yieldOperands = haltOp.getYieldOperands();
705 llvm::BitVector operandsToErase(yieldOperands.size());
706
707 for (auto [operandNo, operand] : llvm::enumerate(yieldOperands)) {
708 auto *defOp = operand.getDefiningOp();
709 if (defOp && defOp->hasTrait<OpTrait::ConstantLike>()) {
710 // If the constant is available outside the process, use it directly;
711 // otherwise move it outside.
712 if (!defOp->getParentRegion()->isProperAncestor(&op.getBody())) {
713 defOp->moveBefore(op);
714 }
715 rewriter.replaceAllUsesWith(op.getResult(operandNo), operand);
716 operandsToErase.set(operandNo);
717 continue;
718 }
719
720 // Identify duplicate operands to merge and compute updated result
721 // positions for the process operation.
722 if (!uniqueOperands.contains(operand)) {
723 const auto newPos = uniqueOperands.size();
724 uniqueOperands.insert(std::make_pair(operand, newPos));
725 origToNewPos.insert(std::make_pair(operandNo, newPos));
726 } else {
727 auto firstOccurrencePos = uniqueOperands.lookup(operand);
728 origToNewPos.insert(std::make_pair(operandNo, firstOccurrencePos));
729 operandsToErase.set(operandNo);
730 }
731 }
732
733 const auto countOperandsToErase = operandsToErase.count();
734 if (countOperandsToErase == 0)
735 return failure();
736
737 // Remove the process operation if all its results have been replaced with
738 // constants.
739 if (countOperandsToErase == op.getNumResults()) {
740 rewriter.eraseOp(op);
741 return success();
742 }
743
744 rewriter.modifyOpInPlace(haltOp,
745 [&] { haltOp->eraseOperands(operandsToErase); });
746
747 SmallVector<Type> resultTypes = llvm::to_vector(haltOp->getOperandTypes());
748 auto newProcessOp = ProcessOp::create(rewriter, op.getLoc(), resultTypes,
749 op->getOperands(), op->getAttrs());
750 newProcessOp.getBody().takeBody(op.getBody());
751
752 // Update old results with new values, accounting for pruned halt operands.
753 for (auto oldResult : op.getResults()) {
754 auto newResultPos = origToNewPos.find(oldResult.getResultNumber());
755 if (newResultPos == origToNewPos.end())
756 continue;
757 auto newResult = newProcessOp.getResult(newResultPos->getSecond());
758 rewriter.replaceAllUsesWith(oldResult, newResult);
759 }
760
761 rewriter.eraseOp(op);
762 return success();
763}
764
765//===----------------------------------------------------------------------===//
766// CombinationalOp
767//===----------------------------------------------------------------------===//
768
769LogicalResult CombinationalOp::canonicalize(CombinationalOp op,
770 PatternRewriter &rewriter) {
771 // Inline the combinational region if it consists of a single block and
772 // contains no side-effecting operations.
773 if (op.getBody().hasOneBlock() && isMemoryEffectFree(op)) {
774 auto &block = op.getBody().front();
775 auto *terminator = block.getTerminator();
776 rewriter.inlineBlockBefore(&block, op, ValueRange{});
777 rewriter.replaceOp(op, terminator->getOperands());
778 rewriter.eraseOp(terminator);
779 return success();
780 }
781 return failure();
782}
783
784//===----------------------------------------------------------------------===//
785// WaitOp
786//===----------------------------------------------------------------------===//
787
788static LogicalResult verifyYieldResults(Operation *op,
789 ValueRange yieldOperands) {
790 // Determine the result values of the parent.
791 auto *parentOp = op->getParentOp();
792 SmallVector<Type> resultTypes;
793 TypeSwitch<Operation *>(parentOp)
794 .Case<ProcessOp, CombinationalOp>([&](auto op) {
795 resultTypes.append(op.getResultTypes().begin(),
796 op.getResultTypes().end());
797 })
798 .Case<FinalOp>([](auto) {})
799 .Case<GlobalSignalOp>(
800 [&](auto op) { resultTypes.push_back(op.getType()); });
801
802 // Check that the number of yield operands matches the process.
803 if (yieldOperands.size() != resultTypes.size())
804 return op->emitOpError()
805 << "has " << yieldOperands.size()
806 << " yield operands, but enclosing '" << parentOp->getName()
807 << "' returns " << resultTypes.size();
808
809 // Check that the types match.
810 for (unsigned i = 0; i < yieldOperands.size(); ++i)
811 if (yieldOperands[i].getType() != resultTypes[i])
812 return op->emitError()
813 << "type of yield operand " << i << " ("
814 << yieldOperands[i].getType() << ") does not match enclosing '"
815 << parentOp->getName() << "' result type (" << resultTypes[i]
816 << ")";
817
818 return success();
819}
820
821LogicalResult WaitOp::verify() {
822 return verifyYieldResults(*this, getYieldOperands());
823}
824
825//===----------------------------------------------------------------------===//
826// HaltOp
827//===----------------------------------------------------------------------===//
828
829LogicalResult HaltOp::verify() {
830 return verifyYieldResults(*this, getYieldOperands());
831}
832
833//===----------------------------------------------------------------------===//
834// YieldOp
835//===----------------------------------------------------------------------===//
836
837LogicalResult YieldOp::verify() {
838 return verifyYieldResults(*this, getYieldOperands());
839}
840
841namespace {
842struct IntegerTypeInterface
843 : public DestructurableTypeInterface::ExternalModel<IntegerTypeInterface,
844 IntegerType> {
845 std::optional<DenseMap<Attribute, Type>>
846 getSubelementIndexMap(Type type) const {
847 // We always return the empty map, indicating that IntegerType is not
848 // destructurable.
849 //
850 // It is not always profitable to SROA an integer, so an extra cost model
851 // is used by SignalOp::getDestructurableSlots() to determine the best
852 // slot configuration for a given integer SignalOp.
853 //
854 // SROA demands that any destructured type must implement
855 // DestructurableTypeInterface so we do nothing here.
856 return {};
857 }
858
859 Type getTypeAtIndex(Type type, Attribute index) const {
860 // As above, we never expect this to be called.
861 llvm_unreachable("Not implemented");
862 }
863};
864} // namespace
865
866void llhd::registerDestructableIntegerExternalModel(DialectRegistry &registry) {
867 registry.addExtension(+[](MLIRContext *ctx, BuiltinDialect *dialect) {
868 IntegerType::attachInterface<IntegerTypeInterface>(*ctx);
869 // SROA on the IntegerTypeInterface can cause comb::ExtractOps to be
870 // created.
871 ctx->loadDialect<comb::CombDialect>();
872 });
873}
874
875//===----------------------------------------------------------------------===//
876// GlobalSignalOp
877//===----------------------------------------------------------------------===//
878
879LogicalResult GlobalSignalOp::verifyRegions() {
880 if (auto *block = getInitBlock()) {
881 auto &terminator = block->back();
882 if (!isa<YieldOp>(terminator))
883 return emitOpError() << "must have a 'llhd.yield' terminator";
884 }
885 return success();
886}
887
888Block *GlobalSignalOp::getInitBlock() {
889 if (getInitRegion().empty())
890 return nullptr;
891 return &getInitRegion().front();
892}
893
894//===----------------------------------------------------------------------===//
895// GetGlobalSignalOp
896//===----------------------------------------------------------------------===//
897
898LogicalResult
899GetGlobalSignalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
900 // Resolve the target symbol.
901 auto *symbol =
902 symbolTable.lookupNearestSymbolFrom(*this, getGlobalNameAttr());
903 if (!symbol)
904 return emitOpError() << "references unknown symbol " << getGlobalNameAttr();
905
906 // Check that the symbol is a global signal.
907 auto signal = dyn_cast<GlobalSignalOp>(symbol);
908 if (!signal)
909 return emitOpError() << "must reference a 'llhd.global_signal', but "
910 << getGlobalNameAttr() << " is a '"
911 << symbol->getName() << "'";
912
913 // Check that the types match.
914 auto expType = signal.getType();
915 auto actType = getType().getNestedType();
916 if (expType != actType)
917 return emitOpError() << "returns a " << actType << " reference, but "
918 << getGlobalNameAttr() << " is of type " << expType;
919
920 return success();
921}
922
923//===----------------------------------------------------------------------===//
924// CoroutineOp
925//===----------------------------------------------------------------------===//
926
927ParseResult CoroutineOp::parse(OpAsmParser &parser, OperationState &result) {
928 auto buildFuncType =
929 [](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
930 function_interface_impl::VariadicFlag,
931 std::string &) { return builder.getFunctionType(argTypes, results); };
932
933 return function_interface_impl::parseFunctionOp(
934 parser, result, /*allowVariadic=*/false,
935 getFunctionTypeAttrName(result.name), buildFuncType,
936 getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name));
937}
938
939void CoroutineOp::print(OpAsmPrinter &p) {
940 function_interface_impl::printFunctionOp(
941 p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(),
942 getArgAttrsAttrName(), getResAttrsAttrName());
943}
944
945//===----------------------------------------------------------------------===//
946// CallCoroutineOp
947//===----------------------------------------------------------------------===//
948
949LogicalResult
950CallCoroutineOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
951 auto calleeName = getCalleeAttr();
952 auto coroutine =
953 symbolTable.lookupNearestSymbolFrom<CoroutineOp>(*this, calleeName);
954 if (!coroutine)
955 return emitOpError() << "'" << calleeName.getValue()
956 << "' does not reference a valid 'llhd.coroutine'";
957
958 auto type = coroutine.getFunctionType();
959 if (type.getNumInputs() != getNumOperands())
960 return emitOpError() << "has " << getNumOperands()
961 << " operands, but callee expects "
962 << type.getNumInputs();
963
964 for (unsigned i = 0, e = type.getNumInputs(); i != e; ++i)
965 if (getOperand(i).getType() != type.getInput(i))
966 return emitOpError() << "operand " << i << " type mismatch: expected "
967 << type.getInput(i) << ", got "
968 << getOperand(i).getType();
969
970 if (type.getNumResults() != getNumResults())
971 return emitOpError() << "has " << getNumResults()
972 << " results, but callee returns "
973 << type.getNumResults();
974
975 for (unsigned i = 0, e = type.getNumResults(); i != e; ++i)
976 if (getResult(i).getType() != type.getResult(i))
977 return emitOpError() << "result " << i << " type mismatch: expected "
978 << type.getResult(i) << ", got "
979 << getResult(i).getType();
980
981 return success();
982}
983
984//===----------------------------------------------------------------------===//
985// Auto-Generated Implementations
986//===----------------------------------------------------------------------===//
987
988#define GET_OP_CLASSES
989#include "circt/Dialect/LLHD/LLHD.cpp.inc"
990#include "circt/Dialect/LLHD/LLHDEnums.cpp.inc"
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static void getSortedPtrs(DenseMap< Attribute, MemorySlot > &subslots, SmallVectorImpl< std::pair< unsigned, Value > > &sorted)
Definition LLHDOps.cpp:251
static LogicalResult verifyYieldResults(Operation *op, ValueRange yieldOperands)
Definition LLHDOps.cpp:788
static bool hasUserOfKind(Operation *op)
Definition LLHDOps.cpp:49
static int64_t intervalOverlap(int64_t a1, int64_t a2, int64_t b1, int64_t b2)
Definition LLHDOps.cpp:247
static LogicalResult canonicalizeSigPtrArraySliceOp(Op op, PatternRewriter &rewriter)
Definition LLHDOps.cpp:394
static std::optional< DenseMap< Attribute, Type > > getIntegerSubelementIndexMap(Type type, Value value)
Definition LLHDOps.cpp:109
static std::optional< DenseMap< Attribute, Type > > getSubelementIndexMap(Type type, Value value)
Definition LLHDOps.cpp:156
static OpFoldResult foldSigPtrExtractOp(Op op, ArrayRef< Attribute > operands)
Definition LLHDOps.cpp:229
static Value getValueAtIndex(OpBuilder &builder, Location loc, Value val, unsigned index, Type resultType)
Definition LLHDOps.cpp:78
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
static InstancePath empty
create(low_bit, result_type, input=None)
Definition comb.py:187
create(elements, Type result_type=None)
Definition hw.py:483
create(array_value, idx)
Definition hw.py:450
create(data_type, value)
Definition hw.py:433
create(elements, Type result_type=None)
Definition hw.py:544
create(struct_value, str field_name)
Definition hw.py:568
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
unsigned getLLHDTypeWidth(Type type)
Definition LLHDOps.cpp:30
Type getLLHDElementType(Type type)
Definition LLHDOps.cpp:40
void registerDestructableIntegerExternalModel(mlir::DialectRegistry &registry)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
Definition LLVM.h:183