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