CIRCT 24.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 = hw::type_dyn_cast<hw::StructType>(type))
36 return tup.getElements().size();
37 return type.getIntOrFloatBitWidth();
38}
39
40Type circt::llhd::getLLHDElementType(Type type) {
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) {
69 auto *ctx = builder.getContext();
70 auto attr = TimeAttr::get(ctx, time, timeUnit, delta);
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, DriveLikeOp>(user)) {
129 // Probe, drive, and assign require the entire interval but don't need to
130 // be 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 =
206 getInit() ? getValueAtIndex(builder, getLoc(), getInit(), index, type)
207 : Value{};
208 auto sigOp = SignalOp::create(builder, getLoc(), RefType::get(type),
209 getNameAttr(), init);
210 newAllocators.push_back(sigOp);
211 slotMap.try_emplace<MemorySlot>(
212 IntegerAttr::get(IndexType::get(getContext()), index),
213 {sigOp.getResult(), type});
214 }
215
216 return slotMap;
217}
218
219std::optional<DestructurableAllocationOpInterface>
220SignalOp::handleDestructuringComplete(const DestructurableMemorySlot &slot,
221 OpBuilder &builder) {
222 assert(slot.ptr == getResult());
223 this->erase();
224 return std::nullopt;
225}
226
227//===----------------------------------------------------------------------===//
228// NetOp
229//===----------------------------------------------------------------------===//
230
231void NetOp::getAsmResultNames(OpAsmSetValueNameFn setNameFn) {
232 if (getName() && !getName()->empty())
233 setNameFn(getResult(), *getName());
234}
235
236//===----------------------------------------------------------------------===//
237// SigExtractOp
238//===----------------------------------------------------------------------===//
239
240template <class Op>
241static OpFoldResult foldSigPtrExtractOp(Op op, ArrayRef<Attribute> operands) {
242
243 if (!operands[1])
244 return nullptr;
245
246 // llhd.sig.extract(input, 0) with inputWidth == resultWidth => input
247 if (op.getResultWidth() == op.getInputWidth() &&
248 cast<IntegerAttr>(operands[1]).getValue().isZero())
249 return op.getInput();
250
251 return nullptr;
252}
253
254OpFoldResult llhd::SigExtractOp::fold(FoldAdaptor adaptor) {
255 return foldSigPtrExtractOp(*this, adaptor.getOperands());
256}
257
258// Returns the number of elements that overlap between [a1, a2) and [b1, b2).
259static int64_t intervalOverlap(int64_t a1, int64_t a2, int64_t b1, int64_t b2) {
260 return std::max<int64_t>(0, std::min(a2, b2) - std::max(a1, b1));
261}
262
263static void getSortedPtrs(DenseMap<Attribute, MemorySlot> &subslots,
264 SmallVectorImpl<std::pair<unsigned, Value>> &sorted) {
265 for (auto [attr, mem] : subslots) {
266 assert(isa<IntegerAttr>(attr));
267 sorted.push_back({cast<IntegerAttr>(attr).getInt(), mem.ptr});
268 }
269
270 llvm::sort(sorted, [](auto a, auto b) { return a.first < b.first; });
271}
272
273static void getSortedPtrs(const DenseMap<Attribute, Type> &subslots,
274 SmallVectorImpl<std::pair<unsigned, Type>> &sorted) {
275 for (auto [attr, mem] : subslots) {
276 assert(isa<IntegerAttr>(attr));
277 sorted.push_back({cast<IntegerAttr>(attr).getInt(), mem});
278 }
279
280 llvm::sort(sorted, [](auto a, auto b) { return a.first < b.first; });
281}
282
283bool SigExtractOp::canRewire(const DestructurableMemorySlot &slot,
284 SmallPtrSetImpl<Attribute> &usedIndices,
285 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
286 const DataLayout &dataLayout) {
287 if (slot.ptr != getInput())
288 return false;
289 APInt idx;
290 Type type = getLLHDElementType(getResult().getType());
291 if (!type.isSignlessInteger())
292 return false;
293 if (!matchPattern(getLowBit(), m_ConstantInt(&idx)))
294 return false;
295
296 // We rewire by pushing subslot values down to users, so we only support
297 // ProbeOp, DriveOp, and AssignOp users as these can deal with the naked
298 // (non-ref) types produced by SROA.
299 for (Operation *user : getResult().getUsers()) {
300 if (!isa<ProbeOp, DriveLikeOp>(user))
301 return false;
302 }
303
304 SmallVector<std::pair<unsigned, Type>> elements;
305 getSortedPtrs(slot.subelementTypes, elements);
306
307 int64_t index = idx.getZExtValue();
308 int64_t width = getLLHDTypeWidth(type);
309 int64_t coveredBits = 0;
310 for (auto [start, type] : elements) {
311 int64_t subslotWidth = type.getIntOrFloatBitWidth();
312 int64_t overlap =
313 intervalOverlap(index, index + width, start, start + subslotWidth);
314 if (overlap == 0)
315 continue;
316 usedIndices.insert(IntegerAttr::get(IndexType::get(getContext()), start));
317 coveredBits += overlap;
318 }
319
320 if (coveredBits != width)
321 return false;
322
323 mustBeSafelyUsed.emplace_back<MemorySlot>({getResult(), type});
324 return true;
325}
326
327DeletionKind SigExtractOp::rewire(const DestructurableMemorySlot &slot,
328 DenseMap<Attribute, MemorySlot> &subslots,
329 OpBuilder &builder,
330 const DataLayout &dataLayout) {
331 APInt idx;
332 [[maybe_unused]] bool result = matchPattern(getLowBit(), m_ConstantInt(&idx));
333 assert(result);
334 int64_t width = getLLHDTypeWidth(getResult().getType());
335 int64_t idxVal = idx.getZExtValue();
336
337 SmallVector<std::pair<unsigned, Value>> elements;
338 getSortedPtrs(subslots, elements);
339
340 for (Operation *user : llvm::make_early_inc_range(getResult().getUsers())) {
341 builder.setInsertionPoint(user);
342 // Decompose a ProbeOp into a concatenation of ProbeOps, one per subslot.
343 if (auto probeOp = dyn_cast<ProbeOp>(user)) {
344 SmallVector<Value> values;
345 for (auto [start, value] : elements) {
346 int64_t subslotWidth = cast<RefType>(value.getType())
347 .getNestedType()
348 .getIntOrFloatBitWidth();
349 int64_t overlap = intervalOverlap(idxVal, idxVal + width, start,
350 start + subslotWidth);
351 if (overlap == 0)
352 continue;
353 values.push_back(ProbeOp::create(builder, probeOp.getLoc(), value));
354 }
355 std::reverse(values.begin(), values.end());
356 Value value = comb::ConcatOp::create(builder, probeOp.getLoc(), values);
357 probeOp.replaceAllUsesWith(value);
358 probeOp.erase();
359 continue;
360 }
361
362 // Decompose a DriveOp or AssignOp into one op per subslot.
363 auto driveLike = cast<DriveLikeOp>(user);
364 Value driven = driveLike.getValue();
365 Value time = driveLike.getTime();
366 Value enable = driveLike.getEnable();
367 for (auto [start, sig] : elements) {
368 int64_t subslotWidth =
369 cast<RefType>(sig.getType()).getNestedType().getIntOrFloatBitWidth();
370 int64_t overlap =
371 intervalOverlap(idxVal, idxVal + width, start, start + subslotWidth);
372 if (overlap == 0)
373 continue;
374 Value val = comb::ExtractOp::create(builder, user->getLoc(), driven,
375 start - idxVal, subslotWidth);
376 if (time)
377 DriveOp::create(builder, user->getLoc(), sig, val, time, enable);
378 else
379 AssignOp::create(builder, user->getLoc(), sig, val, enable);
380 }
381 user->erase();
382 }
383
384 return DeletionKind::Delete;
385}
386
387LogicalResult SigExtractOp::ensureOnlySafeAccesses(
388 const MemorySlot &slot, SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
389 const DataLayout &dataLayout) {
390 return success();
391}
392
393//===----------------------------------------------------------------------===//
394// SigArraySliceOp
395//===----------------------------------------------------------------------===//
396
397OpFoldResult llhd::SigArraySliceOp::fold(FoldAdaptor adaptor) {
398 auto lowIndex = dyn_cast_or_null<IntegerAttr>(adaptor.getLowIndex());
399 if (!lowIndex)
400 return {};
401
402 // llhd.sig.array_slice(input, 0) with inputWidth == resultWidth => input
403 if (getResultWidth() == getInputWidth() && lowIndex.getValue().isZero())
404 return getInput();
405
406 return {};
407}
408
409template <class Op>
410static LogicalResult canonicalizeSigPtrArraySliceOp(Op op,
411 PatternRewriter &rewriter) {
412 IntegerAttr indexAttr;
413 if (!matchPattern(op.getLowIndex(), m_Constant(&indexAttr)))
414 return failure();
415
416 // llhd.sig.array_slice(llhd.sig.array_slice(target, a), b)
417 // => llhd.sig.array_slice(target, a+b)
418 IntegerAttr a;
419 if (matchPattern(op.getInput(),
420 m_Op<Op>(matchers::m_Any(), m_Constant(&a)))) {
421 auto sliceOp = op.getInput().template getDefiningOp<Op>();
422 rewriter.modifyOpInPlace(op, [&]() {
423 op.getInputMutable().assign(sliceOp.getInput());
424 Value newIndex = hw::ConstantOp::create(
425 rewriter, op->getLoc(), a.getValue() + indexAttr.getValue());
426 op.getLowIndexMutable().assign(newIndex);
427 });
428
429 return success();
430 }
431
432 return failure();
433}
434
435LogicalResult llhd::SigArraySliceOp::canonicalize(llhd::SigArraySliceOp op,
436 PatternRewriter &rewriter) {
437 return canonicalizeSigPtrArraySliceOp(op, rewriter);
438}
439
440//===----------------------------------------------------------------------===//
441// SigArrayGetOp
442//===----------------------------------------------------------------------===//
443
444bool SigArrayGetOp::canRewire(const DestructurableMemorySlot &slot,
445 SmallPtrSetImpl<Attribute> &usedIndices,
446 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
447 const DataLayout &dataLayout) {
448 if (slot.ptr != getInput())
449 return false;
450 APInt idx;
451 if (!matchPattern(getIndex(), m_ConstantInt(&idx)))
452 return false;
453 auto index =
454 IntegerAttr::get(IndexType::get(getContext()), idx.getZExtValue());
455 if (!slot.subelementTypes.contains(index))
456 return false;
457 usedIndices.insert(index);
458 mustBeSafelyUsed.emplace_back<MemorySlot>(
459 {getResult(), cast<RefType>(getResult().getType()).getNestedType()});
460 return true;
461}
462
463DeletionKind SigArrayGetOp::rewire(const DestructurableMemorySlot &slot,
464 DenseMap<Attribute, MemorySlot> &subslots,
465 OpBuilder &builder,
466 const DataLayout &dataLayout) {
467 APInt idx;
468 bool result = matchPattern(getIndex(), m_ConstantInt(&idx));
469 (void)result;
470 assert(result);
471 auto index =
472 IntegerAttr::get(IndexType::get(getContext()), idx.getZExtValue());
473 auto it = subslots.find(index);
474 assert(it != subslots.end());
475 replaceAllUsesWith(it->getSecond().ptr);
476 return DeletionKind::Delete;
477}
478
479LogicalResult SigArrayGetOp::ensureOnlySafeAccesses(
480 const MemorySlot &slot, SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
481 const DataLayout &dataLayout) {
482 return success();
483}
484
485//===----------------------------------------------------------------------===//
486// SigStructExtractOp
487//===----------------------------------------------------------------------===//
488
489LogicalResult llhd::SigStructExtractOp::inferReturnTypes(
490 MLIRContext *context, std::optional<Location> loc, ValueRange operands,
491 DictionaryAttr attrs, mlir::PropertyRef properties,
492 mlir::RegionRange regions, SmallVectorImpl<Type> &results) {
493 typename SigStructExtractOp::Adaptor adaptor(operands, attrs, properties,
494 regions);
495 auto nestedType = cast<RefType>(adaptor.getInput().getType()).getNestedType();
496 Type fieldType;
497
498 // Support both StructType and UnionType
499 if (auto structType = hw::type_dyn_cast<hw::StructType>(nestedType)) {
500 fieldType = structType.getFieldType(adaptor.getField());
501 } else if (auto unionType = dyn_cast<hw::UnionType>(nestedType)) {
502 fieldType = unionType.getFieldType(adaptor.getField());
503 } else {
504 context->getDiagEngine().emit(loc.value_or(UnknownLoc()),
505 DiagnosticSeverity::Error)
506 << "expected struct or union type";
507 return failure();
508 }
509
510 if (!fieldType) {
511 context->getDiagEngine().emit(loc.value_or(UnknownLoc()),
512 DiagnosticSeverity::Error)
513 << "invalid field name specified";
514 return failure();
515 }
516 results.push_back(RefType::get(fieldType));
517 return success();
518}
519
520bool SigStructExtractOp::canRewire(
521 const DestructurableMemorySlot &slot,
522 SmallPtrSetImpl<Attribute> &usedIndices,
523 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
524 const DataLayout &dataLayout) {
525 if (slot.ptr != getInput())
526 return false;
527
528 auto nestedType = cast<RefType>(getInput().getType()).getNestedType();
529 std::optional<uint32_t> index;
530
531 // Support both StructType and UnionType
532 if (auto structType = hw::type_dyn_cast<hw::StructType>(nestedType))
533 index = structType.getFieldIndex(getFieldAttr());
534 else if (auto unionType = dyn_cast<hw::UnionType>(nestedType))
535 index = unionType.getFieldIndex(getFieldAttr());
536 else
537 return false;
538
539 if (!index)
540 return false;
541 auto indexAttr = IntegerAttr::get(IndexType::get(getContext()), *index);
542 if (!slot.subelementTypes.contains(indexAttr))
543 return false;
544 usedIndices.insert(indexAttr);
545 mustBeSafelyUsed.emplace_back<MemorySlot>(
546 {getResult(), cast<RefType>(getResult().getType()).getNestedType()});
547 return true;
548}
549
550DeletionKind
551SigStructExtractOp::rewire(const DestructurableMemorySlot &slot,
552 DenseMap<Attribute, MemorySlot> &subslots,
553 OpBuilder &builder, const DataLayout &dataLayout) {
554 auto nestedType = cast<RefType>(getInput().getType()).getNestedType();
555 std::optional<unsigned> index;
556 if (auto structTy = hw::type_dyn_cast<hw::StructType>(nestedType))
557 index = structTy.getFieldIndex(getFieldAttr());
558 else if (auto unionTy = dyn_cast<hw::UnionType>(nestedType))
559 index = unionTy.getFieldIndex(getFieldAttr());
560 assert(index.has_value());
561 auto indexAttr = IntegerAttr::get(IndexType::get(getContext()), *index);
562 auto it = subslots.find(indexAttr);
563 assert(it != subslots.end());
564 replaceAllUsesWith(it->getSecond().ptr);
565 return DeletionKind::Delete;
566}
567
568LogicalResult SigStructExtractOp::ensureOnlySafeAccesses(
569 const MemorySlot &slot, SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
570 const DataLayout &dataLayout) {
571 return success();
572}
573
574//===----------------------------------------------------------------------===//
575// ProbeOp
576//===----------------------------------------------------------------------===//
577
578bool ProbeOp::canRewire(const DestructurableMemorySlot &slot,
579 SmallPtrSetImpl<Attribute> &usedIndices,
580 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
581 const DataLayout &dataLayout) {
582 for (auto [key, _] : slot.subelementTypes)
583 usedIndices.insert(key);
584
585 return isa<hw::StructType, hw::ArrayType, IntegerType>(slot.valueType);
586}
587
588DeletionKind ProbeOp::rewire(const DestructurableMemorySlot &slot,
589 DenseMap<Attribute, MemorySlot> &subslots,
590 OpBuilder &builder, const DataLayout &dataLayout) {
591 SmallVector<std::pair<unsigned, Value>> elements;
592 SmallVector<Value> probed;
593 getSortedPtrs(subslots, elements);
594 for (auto [_, val] : elements)
595 probed.push_back(ProbeOp::create(builder, getLoc(), val));
596
597 Value repl =
598 TypeSwitch<Type, Value>(getType())
599 .Case<hw::StructType>([&](auto ty) {
600 return hw::StructCreateOp::create(builder, getLoc(), getType(),
601 probed);
602 })
603 .Case<hw::ArrayType>([&](auto ty) {
604 std::reverse(probed.begin(), probed.end());
605 return hw::ArrayCreateOp::create(builder, getLoc(), probed);
606 })
607 .Case<IntegerType>([&](auto ty) {
608 std::reverse(probed.begin(), probed.end());
609 return comb::ConcatOp::create(builder, getLoc(), probed);
610 });
611
612 replaceAllUsesWith(repl);
613 return DeletionKind::Delete;
614}
615
616LogicalResult
617ProbeOp::ensureOnlySafeAccesses(const MemorySlot &slot,
618 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
619 const DataLayout &dataLayout) {
620 return success();
621}
622
623void ProbeOp::getEffects(
624 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
625 &effects) {
626 if (mayHaveSSADominance(*getOperation()->getParentRegion()))
627 effects.emplace_back(MemoryEffects::Read::get(), &getSignalMutable());
628}
629
630//===----------------------------------------------------------------------===//
631// ProbeOp
632//===----------------------------------------------------------------------===//
633
634LogicalResult ProbeNetOp::ensureOnlySafeAccesses(
635 const MemorySlot &slot, SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
636 const DataLayout &dataLayout) {
637 return success();
638}
639
640void ProbeNetOp::getEffects(
641 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
642 &effects) {
643 if (mayHaveSSADominance(*getOperation()->getParentRegion()))
644 effects.emplace_back(MemoryEffects::Read::get(), &getSignalMutable());
645}
646
647//===----------------------------------------------------------------------===//
648// DriveOp
649//===----------------------------------------------------------------------===//
650
651LogicalResult llhd::DriveOp::fold(FoldAdaptor adaptor,
652 SmallVectorImpl<OpFoldResult> &result) {
653 if (!getEnable())
654 return failure();
655
656 if (matchPattern(getEnable(), m_One())) {
657 getEnableMutable().clear();
658 return success();
659 }
660
661 return failure();
662}
663
664LogicalResult llhd::DriveOp::canonicalize(llhd::DriveOp op,
665 PatternRewriter &rewriter) {
666 if (!op.getEnable())
667 return failure();
668
669 if (matchPattern(op.getEnable(), m_Zero())) {
670 rewriter.eraseOp(op);
671 return success();
672 }
673
674 return failure();
675}
676
677bool DriveOp::canRewire(const DestructurableMemorySlot &slot,
678 SmallPtrSetImpl<Attribute> &usedIndices,
679 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
680 const DataLayout &dataLayout) {
681 for (auto [key, _] : slot.subelementTypes)
682 usedIndices.insert(key);
683
684 return isa<hw::StructType, hw::ArrayType, IntegerType>(slot.valueType);
685}
686
687DeletionKind DriveOp::rewire(const DestructurableMemorySlot &slot,
688 DenseMap<Attribute, MemorySlot> &subslots,
689 OpBuilder &builder, const DataLayout &dataLayout) {
690 SmallVector<std::pair<unsigned, Value>> driven;
691 getSortedPtrs(subslots, driven);
692
693 for (auto [idx, sig] : driven) {
694 Type nestedType = cast<RefType>(sig.getType()).getNestedType();
695 DriveOp::create(
696 builder, getLoc(), sig,
697 getValueAtIndex(builder, getLoc(), getValue(), idx, nestedType),
698 getTime(), getEnable());
699 }
700
701 return DeletionKind::Delete;
702}
703
704LogicalResult
705DriveOp::ensureOnlySafeAccesses(const MemorySlot &slot,
706 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
707 const DataLayout &dataLayout) {
708 return success();
709}
710
711//===----------------------------------------------------------------------===//
712// AssignOp
713//===----------------------------------------------------------------------===//
714
715LogicalResult llhd::AssignOp::fold(FoldAdaptor adaptor,
716 SmallVectorImpl<OpFoldResult> &result) {
717 if (!getEnable())
718 return failure();
719
720 if (matchPattern(getEnable(), m_One())) {
721 getEnableMutable().clear();
722 return success();
723 }
724
725 return failure();
726}
727
728LogicalResult llhd::AssignOp::canonicalize(llhd::AssignOp op,
729 PatternRewriter &rewriter) {
730 if (!op.getEnable())
731 return failure();
732
733 if (matchPattern(op.getEnable(), m_Zero())) {
734 rewriter.eraseOp(op);
735 return success();
736 }
737
738 return failure();
739}
740
741bool AssignOp::canRewire(const DestructurableMemorySlot &slot,
742 SmallPtrSetImpl<Attribute> &usedIndices,
743 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
744 const DataLayout &dataLayout) {
745 for (auto [key, _] : slot.subelementTypes)
746 usedIndices.insert(key);
747
748 return isa<hw::StructType, hw::ArrayType, IntegerType>(slot.valueType);
749}
750
751DeletionKind AssignOp::rewire(const DestructurableMemorySlot &slot,
752 DenseMap<Attribute, MemorySlot> &subslots,
753 OpBuilder &builder,
754 const DataLayout &dataLayout) {
755 SmallVector<std::pair<unsigned, Value>> assigned;
756 getSortedPtrs(subslots, assigned);
757
758 for (auto [idx, sig] : assigned) {
759 Type nestedType = cast<RefType>(sig.getType()).getNestedType();
760 AssignOp::create(
761 builder, getLoc(), sig,
762 getValueAtIndex(builder, getLoc(), getValue(), idx, nestedType),
763 getEnable());
764 }
765
766 return DeletionKind::Delete;
767}
768
769LogicalResult
770AssignOp::ensureOnlySafeAccesses(const MemorySlot &slot,
771 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
772 const DataLayout &dataLayout) {
773 return success();
774}
775
776//===----------------------------------------------------------------------===//
777// AssignNetOp
778//===----------------------------------------------------------------------===//
779
780LogicalResult llhd::AssignNetOp::fold(FoldAdaptor adaptor,
781 SmallVectorImpl<OpFoldResult> &result) {
782 if (!getEnable())
783 return failure();
784
785 if (matchPattern(getEnable(), m_One())) {
786 getEnableMutable().clear();
787 return success();
788 }
789
790 return failure();
791}
792
793LogicalResult llhd::AssignNetOp::canonicalize(llhd::AssignNetOp op,
794 PatternRewriter &rewriter) {
795 if (!op.getEnable())
796 return failure();
797
798 if (matchPattern(op.getEnable(), m_Zero())) {
799 rewriter.eraseOp(op);
800 return success();
801 }
802
803 return failure();
804}
805
806bool AssignNetOp::canRewire(const DestructurableMemorySlot &slot,
807 SmallPtrSetImpl<Attribute> &usedIndices,
808 SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
809 const DataLayout &dataLayout) {
810 for (auto [key, _] : slot.subelementTypes)
811 usedIndices.insert(key);
812
813 return isa<hw::StructType, hw::ArrayType, IntegerType>(slot.valueType);
814}
815
816DeletionKind AssignNetOp::rewire(const DestructurableMemorySlot &slot,
817 DenseMap<Attribute, MemorySlot> &subslots,
818 OpBuilder &builder,
819 const DataLayout &dataLayout) {
820 SmallVector<std::pair<unsigned, Value>> assigned;
821 getSortedPtrs(subslots, assigned);
822
823 for (auto [idx, sig] : assigned) {
824 Type nestedType = cast<NetType>(sig.getType()).getNestedType();
825 AssignNetOp::create(
826 builder, getLoc(), sig,
827 getValueAtIndex(builder, getLoc(), getValue(), idx, nestedType),
828 getEnable());
829 }
830
831 return DeletionKind::Delete;
832}
833
834LogicalResult AssignNetOp::ensureOnlySafeAccesses(
835 const MemorySlot &slot, SmallVectorImpl<MemorySlot> &mustBeSafelyUsed,
836 const DataLayout &dataLayout) {
837 return success();
838}
839
840//===----------------------------------------------------------------------===//
841// ProcessOp
842//===----------------------------------------------------------------------===//
843
844LogicalResult ProcessOp::canonicalize(ProcessOp op, PatternRewriter &rewriter) {
845 if (!op.getBody().hasOneBlock())
846 return failure();
847
848 auto &block = op.getBody().front();
849 auto haltOp = dyn_cast<HaltOp>(block.getTerminator());
850 if (!haltOp)
851 return failure();
852
853 if (op.getNumResults() == 0 && block.getOperations().size() == 1) {
854 rewriter.eraseOp(op);
855 return success();
856 }
857
858 // Only constants and halt terminator are expected in a single block.
859 if (!llvm::all_of(block.without_terminator(), [](auto &bodyOp) {
860 return bodyOp.template hasTrait<OpTrait::ConstantLike>();
861 }))
862 return failure();
863
864 auto yieldOperands = haltOp.getYieldOperands();
867 llvm::BitVector operandsToErase(yieldOperands.size());
868
869 for (auto [operandNo, operand] : llvm::enumerate(yieldOperands)) {
870 auto *defOp = operand.getDefiningOp();
871 if (defOp && defOp->hasTrait<OpTrait::ConstantLike>()) {
872 // If the constant is available outside the process, use it directly;
873 // otherwise move it outside.
874 if (!defOp->getParentRegion()->isProperAncestor(&op.getBody())) {
875 defOp->moveBefore(op);
876 }
877 rewriter.replaceAllUsesWith(op.getResult(operandNo), operand);
878 operandsToErase.set(operandNo);
879 continue;
880 }
881
882 // Identify duplicate operands to merge and compute updated result
883 // positions for the process operation.
884 if (!uniqueOperands.contains(operand)) {
885 const auto newPos = uniqueOperands.size();
886 uniqueOperands.insert(std::make_pair(operand, newPos));
887 origToNewPos.insert(std::make_pair(operandNo, newPos));
888 } else {
889 auto firstOccurrencePos = uniqueOperands.lookup(operand);
890 origToNewPos.insert(std::make_pair(operandNo, firstOccurrencePos));
891 operandsToErase.set(operandNo);
892 }
893 }
894
895 const auto countOperandsToErase = operandsToErase.count();
896 if (countOperandsToErase == 0)
897 return failure();
898
899 // Remove the process operation if all its results have been replaced with
900 // constants.
901 if (countOperandsToErase == op.getNumResults()) {
902 rewriter.eraseOp(op);
903 return success();
904 }
905
906 rewriter.modifyOpInPlace(haltOp,
907 [&] { haltOp->eraseOperands(operandsToErase); });
908
909 SmallVector<Type> resultTypes = llvm::to_vector(haltOp->getOperandTypes());
910 auto newProcessOp = ProcessOp::create(rewriter, op.getLoc(), resultTypes,
911 op->getOperands(), op->getAttrs());
912 newProcessOp.getBody().takeBody(op.getBody());
913
914 // Update old results with new values, accounting for pruned halt operands.
915 for (auto oldResult : op.getResults()) {
916 auto newResultPos = origToNewPos.find(oldResult.getResultNumber());
917 if (newResultPos == origToNewPos.end())
918 continue;
919 auto newResult = newProcessOp.getResult(newResultPos->getSecond());
920 rewriter.replaceAllUsesWith(oldResult, newResult);
921 }
922
923 rewriter.eraseOp(op);
924 return success();
925}
926
927//===----------------------------------------------------------------------===//
928// CombinationalOp
929//===----------------------------------------------------------------------===//
930
931LogicalResult CombinationalOp::canonicalize(CombinationalOp op,
932 PatternRewriter &rewriter) {
933 // Inline the combinational region if it consists of a single block and
934 // contains no side-effecting operations.
935 if (op.getBody().hasOneBlock() && isMemoryEffectFree(op)) {
936 auto &block = op.getBody().front();
937 auto *terminator = block.getTerminator();
938 rewriter.inlineBlockBefore(&block, op, ValueRange{});
939 rewriter.replaceOp(op, terminator->getOperands());
940 rewriter.eraseOp(terminator);
941 return success();
942 }
943 return failure();
944}
945
946//===----------------------------------------------------------------------===//
947// WaitOp
948//===----------------------------------------------------------------------===//
949
950static LogicalResult verifyYieldResults(Operation *op,
951 ValueRange yieldOperands) {
952 // Determine the result values of the parent.
953 auto *parentOp = op->getParentOp();
954 SmallVector<Type> resultTypes;
955 TypeSwitch<Operation *>(parentOp)
956 .Case<ProcessOp, CombinationalOp>([&](auto op) {
957 resultTypes.append(op.getResultTypes().begin(),
958 op.getResultTypes().end());
959 })
960 .Case<FinalOp>([](auto) {})
961 .Case<GlobalSignalOp>(
962 [&](auto op) { resultTypes.push_back(op.getType()); });
963
964 // Check that the number of yield operands matches the process.
965 if (yieldOperands.size() != resultTypes.size())
966 return op->emitOpError()
967 << "has " << yieldOperands.size()
968 << " yield operands, but enclosing '" << parentOp->getName()
969 << "' returns " << resultTypes.size();
970
971 // Check that the types match.
972 for (unsigned i = 0; i < yieldOperands.size(); ++i)
973 if (yieldOperands[i].getType() != resultTypes[i])
974 return op->emitError()
975 << "type of yield operand " << i << " ("
976 << yieldOperands[i].getType() << ") does not match enclosing '"
977 << parentOp->getName() << "' result type (" << resultTypes[i]
978 << ")";
979
980 return success();
981}
982
983LogicalResult WaitOp::verify() {
984 return verifyYieldResults(*this, getYieldOperands());
985}
986
987//===----------------------------------------------------------------------===//
988// HaltOp
989//===----------------------------------------------------------------------===//
990
991LogicalResult HaltOp::verify() {
992 return verifyYieldResults(*this, getYieldOperands());
993}
994
995//===----------------------------------------------------------------------===//
996// YieldOp
997//===----------------------------------------------------------------------===//
998
999LogicalResult YieldOp::verify() {
1000 return verifyYieldResults(*this, getYieldOperands());
1001}
1002
1003namespace {
1004struct IntegerTypeInterface
1005 : public DestructurableTypeInterface::ExternalModel<IntegerTypeInterface,
1006 IntegerType> {
1007 std::optional<DenseMap<Attribute, Type>>
1008 getSubelementIndexMap(Type type) const {
1009 // We always return the empty map, indicating that IntegerType is not
1010 // destructurable.
1011 //
1012 // It is not always profitable to SROA an integer, so an extra cost model
1013 // is used by SignalOp::getDestructurableSlots() to determine the best
1014 // slot configuration for a given integer SignalOp.
1015 //
1016 // SROA demands that any destructured type must implement
1017 // DestructurableTypeInterface so we do nothing here.
1018 return {};
1019 }
1020
1021 Type getTypeAtIndex(Type type, Attribute index) const {
1022 // As above, we never expect this to be called.
1023 llvm_unreachable("Not implemented");
1024 }
1025};
1026} // namespace
1027
1028void llhd::registerDestructableIntegerExternalModel(DialectRegistry &registry) {
1029 registry.addExtension(+[](MLIRContext *ctx, BuiltinDialect *dialect) {
1030 IntegerType::attachInterface<IntegerTypeInterface>(*ctx);
1031 // SROA on the IntegerTypeInterface can cause comb::ExtractOps to be
1032 // created.
1033 ctx->loadDialect<comb::CombDialect>();
1034 });
1035}
1036
1037//===----------------------------------------------------------------------===//
1038// GlobalSignalOp
1039//===----------------------------------------------------------------------===//
1040
1041LogicalResult GlobalSignalOp::verifyRegions() {
1042 if (auto *block = getInitBlock()) {
1043 auto &terminator = block->back();
1044 if (!isa<YieldOp>(terminator))
1045 return emitOpError() << "must have a 'llhd.yield' terminator";
1046 }
1047 return success();
1048}
1049
1050Block *GlobalSignalOp::getInitBlock() {
1051 if (getInitRegion().empty())
1052 return nullptr;
1053 return &getInitRegion().front();
1054}
1055
1056//===----------------------------------------------------------------------===//
1057// GetGlobalSignalOp
1058//===----------------------------------------------------------------------===//
1059
1060LogicalResult
1061GetGlobalSignalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
1062 // Resolve the target symbol.
1063 auto *symbol =
1064 symbolTable.lookupNearestSymbolFrom(*this, getGlobalNameAttr());
1065 if (!symbol)
1066 return emitOpError() << "references unknown symbol " << getGlobalNameAttr();
1067
1068 // Check that the symbol is a global signal.
1069 auto signal = dyn_cast<GlobalSignalOp>(symbol);
1070 if (!signal)
1071 return emitOpError() << "must reference a 'llhd.global_signal', but "
1072 << getGlobalNameAttr() << " is a '"
1073 << symbol->getName() << "'";
1074
1075 // Check that the types match.
1076 auto expType = signal.getType();
1077 auto actType = getType().getNestedType();
1078 if (expType != actType)
1079 return emitOpError() << "returns a " << actType << " reference, but "
1080 << getGlobalNameAttr() << " is of type " << expType;
1081
1082 return success();
1083}
1084
1085//===----------------------------------------------------------------------===//
1086// CoroutineOp
1087//===----------------------------------------------------------------------===//
1088
1089ParseResult CoroutineOp::parse(OpAsmParser &parser, OperationState &result) {
1090 auto buildFuncType =
1091 [](Builder &builder, ArrayRef<Type> argTypes, ArrayRef<Type> results,
1092 function_interface_impl::VariadicFlag,
1093 std::string &) { return builder.getFunctionType(argTypes, results); };
1094
1095 return function_interface_impl::parseFunctionOp(
1096 parser, result, /*allowVariadic=*/false,
1097 getFunctionTypeAttrName(result.name), buildFuncType,
1098 getArgAttrsAttrName(result.name), getResAttrsAttrName(result.name));
1099}
1100
1101void CoroutineOp::print(OpAsmPrinter &p) {
1102 function_interface_impl::printFunctionOp(
1103 p, *this, /*isVariadic=*/false, getFunctionTypeAttrName(),
1104 getArgAttrsAttrName(), getResAttrsAttrName());
1105}
1106
1107//===----------------------------------------------------------------------===//
1108// CallCoroutineOp
1109//===----------------------------------------------------------------------===//
1110
1111LogicalResult
1112CallCoroutineOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
1113 auto calleeName = getCalleeAttr();
1114 auto coroutine =
1115 symbolTable.lookupNearestSymbolFrom<CoroutineOp>(*this, calleeName);
1116 if (!coroutine)
1117 return emitOpError() << "'" << calleeName.getValue()
1118 << "' does not reference a valid 'llhd.coroutine'";
1119
1120 auto type = coroutine.getFunctionType();
1121 if (type.getNumInputs() != getNumOperands())
1122 return emitOpError() << "has " << getNumOperands()
1123 << " operands, but callee expects "
1124 << type.getNumInputs();
1125
1126 for (unsigned i = 0, e = type.getNumInputs(); i != e; ++i)
1127 if (getOperand(i).getType() != type.getInput(i))
1128 return emitOpError() << "operand " << i << " type mismatch: expected "
1129 << type.getInput(i) << ", got "
1130 << getOperand(i).getType();
1131
1132 if (type.getNumResults() != getNumResults())
1133 return emitOpError() << "has " << getNumResults()
1134 << " results, but callee returns "
1135 << type.getNumResults();
1136
1137 for (unsigned i = 0, e = type.getNumResults(); i != e; ++i)
1138 if (getResult(i).getType() != type.getResult(i))
1139 return emitOpError() << "result " << i << " type mismatch: expected "
1140 << type.getResult(i) << ", got "
1141 << getResult(i).getType();
1142
1143 return success();
1144}
1145
1146//===----------------------------------------------------------------------===//
1147// Auto-Generated Implementations
1148//===----------------------------------------------------------------------===//
1149
1150#define GET_OP_CLASSES
1151#include "circt/Dialect/LLHD/LLHD.cpp.inc"
1152#include "circt/Dialect/LLHD/LLHDEnums.cpp.inc"
1153#include "circt/Dialect/LLHD/LLHDInterfaces.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:263
static LogicalResult verifyYieldResults(Operation *op, ValueRange yieldOperands)
Definition LLHDOps.cpp:950
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:259
static LogicalResult canonicalizeSigPtrArraySliceOp(Op op, PatternRewriter &rewriter)
Definition LLHDOps.cpp:410
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:241
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:193
false return failure()
Parse an optional keyword or string and set instance into 'result'.
return success()
NamedAttrList attrs
static InstancePath empty
create(low_bit, result_type, input=None)
Definition comb.py:187
create(elements, Type result_type=None)
Definition hw.py:486
create(array_value, idx)
Definition hw.py:453
create(data_type, value)
Definition hw.py:433
create(elements, Type result_type=None)
Definition hw.py:547
create(struct_value, str field_name)
Definition hw.py:571
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
BaseTy type_dyn_cast(Type type)
Definition HWTypes.h:234
unsigned getLLHDTypeWidth(Type type)
Type getLLHDElementType(Type type)
void registerDestructableIntegerExternalModel(mlir::DialectRegistry &registry)
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
Definition LLVM.h:193