CIRCT 22.0.0git
Loading...
Searching...
No Matches
ESITypes.cpp
Go to the documentation of this file.
1//===- ESITypes.cpp - ESI types code defs -----------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Definitions for esi data types. Anything which doesn't have to be public
10// should go in here.
11//
12//===----------------------------------------------------------------------===//
13
17#include "mlir/IR/Attributes.h"
18#include "mlir/IR/DialectImplementation.h"
19#include "llvm/ADT/ArrayRef.h"
20#include "llvm/ADT/DenseMap.h"
21#include "llvm/ADT/TypeSwitch.h"
22
23using namespace circt;
24using namespace circt::esi;
25
26AnyType AnyType::get(MLIRContext *context) { return Base::get(context); }
27
28LogicalResult
29WindowFieldType::verify(llvm::function_ref<InFlightDiagnostic()> emitError,
30 StringAttr fieldName, uint64_t numItems,
31 uint64_t bulkCountWidth) {
32 if (numItems > 0 && bulkCountWidth > 0)
33 return emitError() << "cannot specify both numItems and countWidth for "
34 "field '"
35 << fieldName.getValue() << "'";
36 return success();
37}
38
39static unsigned getSignalingBitWidth(ChannelSignaling signaling) {
40 switch (signaling) {
41 case ChannelSignaling::ValidReady:
42 return 2;
43 case ChannelSignaling::FIFO:
44 return 2;
45 }
46 llvm_unreachable("Unhandled ChannelSignaling");
47}
48std::optional<int64_t> ChannelType::getBitWidth() const {
49 int64_t innerWidth = circt::hw::getBitWidth(getInner());
50 if (innerWidth < 0)
51 return std::nullopt;
52 return innerWidth + getSignalingBitWidth(getSignaling());
53}
54
55/// Get the list of users with snoops filtered out. Returns a filtered range
56/// which is lazily constructed.
57static auto getChannelConsumers(mlir::TypedValue<ChannelType> chan) {
58 return llvm::make_filter_range(chan.getUses(), [](auto &use) {
59 return !isa<SnoopValidReadyOp, SnoopTransactionOp>(use.getOwner());
60 });
61}
62SmallVector<std::reference_wrapper<OpOperand>, 4>
63ChannelType::getConsumers(mlir::TypedValue<ChannelType> chan) {
64 return SmallVector<std::reference_wrapper<OpOperand>, 4>(
66}
67bool ChannelType::hasOneConsumer(mlir::TypedValue<ChannelType> chan) {
68 auto consumers = getChannelConsumers(chan);
69 if (consumers.empty())
70 return false;
71 return ++consumers.begin() == consumers.end();
72}
73bool ChannelType::hasNoConsumers(mlir::TypedValue<ChannelType> chan) {
74 return getChannelConsumers(chan).empty();
75}
76OpOperand *ChannelType::getSingleConsumer(mlir::TypedValue<ChannelType> chan) {
77 auto consumers = getChannelConsumers(chan);
78 auto iter = consumers.begin();
79 if (iter == consumers.end())
80 return nullptr;
81 OpOperand *result = &*iter;
82 if (++iter != consumers.end())
83 return nullptr;
84 return result;
85}
86LogicalResult ChannelType::verifyChannel(mlir::TypedValue<ChannelType> chan) {
87 auto consumers = getChannelConsumers(chan);
88 if (consumers.empty() || ++consumers.begin() == consumers.end())
89 return success();
90 auto err = chan.getDefiningOp()->emitOpError(
91 "channels must have at most one consumer");
92 for (auto &consumer : consumers)
93 err.attachNote(consumer.getOwner()->getLoc()) << "channel used here";
94 return err;
95}
96
97std::optional<int64_t> WindowType::getBitWidth() const {
98 return hw::getBitWidth(getLoweredType());
99}
100
101LogicalResult
102WindowType::verify(llvm::function_ref<InFlightDiagnostic()> emitError,
103 StringAttr name, Type into,
104 ArrayRef<WindowFrameType> frames) {
105 auto structInto = hw::type_dyn_cast<hw::StructType>(into);
106 if (!structInto)
107 return emitError() << "only windows into structs are currently supported";
108
109 // Build a map from field name to type for quick lookup.
111 for (hw::StructType::FieldInfo field : structInto.getElements())
112 fieldTypes[field.name] = field.type;
113
114 // Track which fields have been consumed (for non-bulk-transfer uses).
115 DenseSet<StringAttr> consumedFields;
116 // Track which fields have been used as bulk transfer fields (header with
117 // countWidth). These fields can be reused in data frames with numItems.
118 DenseSet<StringAttr> bulkTransferFields;
119
120 for (auto frame : frames) {
121 bool encounteredArrayOrListWithNumItems = false;
122
123 for (WindowFieldType field : frame.getMembers()) {
124 auto fieldTypeIter = fieldTypes.find(field.getFieldName());
125 if (fieldTypeIter == fieldTypes.end())
126 return emitError() << "invalid field name: " << field.getFieldName();
127
128 Type fieldType = fieldTypeIter->getSecond();
129
130 // Check if this field was already consumed by a previous frame.
131 // Bulk transfer mode allows the same list field to appear in a header
132 // frame (with countWidth) and later in data frames (with numItems),
133 // but only if the field was first introduced as a bulk transfer field.
134 bool isBulkTransferHeader = field.getBulkCountWidth() > 0;
135 bool isBulkTransferData =
136 bulkTransferFields.contains(field.getFieldName());
137
138 if (consumedFields.contains(field.getFieldName())) {
139 // Field was already consumed in a non-bulk-transfer context.
140 // Cannot reuse it.
141 return emitError() << "field '" << field.getFieldName()
142 << "' already consumed by a previous frame";
143 }
144
145 // If we encounter an array or list field with numItems, no subsequent
146 // fields in this frame can be arrays or lists with numItems.
147 bool isArrayOrListWithNumItems =
148 hw::type_isa<hw::ArrayType, esi::ListType>(fieldType) &&
149 field.getNumItems() > 0;
150 if (isArrayOrListWithNumItems) {
151 if (encounteredArrayOrListWithNumItems)
152 return emitError()
153 << "cannot have two array or list fields with num items (in "
154 << field.getFieldName() << ")";
155 encounteredArrayOrListWithNumItems = true;
156 }
157
158 // If 'numItems' is specified, gotta run more checks.
159 uint64_t numItems = field.getNumItems();
160 if (numItems > 0) {
161 if (auto arrField = hw::type_dyn_cast<hw::ArrayType>(fieldType)) {
162 if (numItems > arrField.getNumElements())
163 return emitError()
164 << "num items is larger than array size in field "
165 << field.getFieldName();
166 } else if (!hw::type_isa<esi::ListType>(fieldType)) {
167 return emitError() << "specification of num items only allowed on "
168 "array or list fields (in "
169 << field.getFieldName() << ")";
170 }
171 }
172
173 // Bulk transfer mode validation: bulkCountWidth is only valid on list
174 // fields.
175 uint64_t bulkCountWidth = field.getBulkCountWidth();
176 if (bulkCountWidth > 0) {
177 if (!hw::type_isa<esi::ListType>(fieldType))
178 return emitError() << "bulk transfer (countWidth) only allowed on "
179 "list fields (in "
180 << field.getFieldName() << ")";
181 // Check that this field hasn't already been declared as a bulk
182 // transfer field (countWidth specified twice).
183 if (bulkTransferFields.contains(field.getFieldName()))
184 return emitError() << "field '" << field.getFieldName()
185 << "' already has countWidth specified";
186 // Mark this field as a bulk transfer field.
187 bulkTransferFields.insert(field.getFieldName());
188 }
189
190 // Disallow list fields from appearing in multiple data frames.
191 if (isBulkTransferData) {
192 // This is a data frame for a bulk transfer field. Mark it as consumed
193 // so it can't appear in another data frame.
194 consumedFields.insert(field.getFieldName());
195 } else if (!isBulkTransferHeader) {
196 // Mark this field as consumed for non-bulk-transfer uses.
197 consumedFields.insert(field.getFieldName());
198 }
199 }
200 }
201 return success();
202}
203
204Type WindowType::getLoweredType() const {
205 // Assemble a fast lookup of struct fields to types.
206 auto into = hw::type_cast<hw::StructType>(getInto());
208 for (hw::StructType::FieldInfo field : into.getElements())
209 intoFields[field.name] = field.type;
210
211 auto getInnerTypeOrSelf = [&](Type t) {
212 return TypeSwitch<Type, Type>(t)
213 .Case<hw::ArrayType>(
214 [](hw::ArrayType arr) { return arr.getElementType(); })
215 .Case<esi::ListType>(
216 [](esi::ListType list) { return list.getElementType(); })
217 .Default([&](Type t) { return t; });
218 };
219
220 // Helper to wrap a lowered type in a TypeAlias if the 'into' type is a
221 // TypeAlias.
222 auto wrapInTypeAliasIfNeeded = [&](Type loweredType) -> Type {
223 if (auto intoAlias = dyn_cast<hw::TypeAliasType>(getInto())) {
224 auto intoRef = intoAlias.getRef();
225 std::string aliasName = (Twine(intoRef.getLeafReference().getValue()) +
226 "_" + getName().getValue())
227 .str();
228 auto newRef = SymbolRefAttr::get(
229 intoRef.getRootReference(),
230 {FlatSymbolRefAttr::get(StringAttr::get(getContext(), aliasName))});
231 return hw::TypeAliasType::get(newRef, loweredType);
232 }
233 return loweredType;
234 };
235
236 // First pass: identify which list fields have bulk transfer headers.
237 // These fields should not have '_size' or 'last' in their data frames.
238 DenseSet<StringAttr> bulkTransferFields;
239 for (WindowFrameType frame : getFrames()) {
240 for (WindowFieldType field : frame.getMembers()) {
241 if (field.getBulkCountWidth() > 0)
242 bulkTransferFields.insert(field.getFieldName());
243 }
244 }
245
246 // Build the union, frame by frame
247 SmallVector<hw::UnionType::FieldInfo, 4> unionFields;
248 for (WindowFrameType frame : getFrames()) {
249
250 // ... field by field.
251 SmallVector<hw::StructType::FieldInfo, 4> fields;
252 SmallVector<hw::StructType::FieldInfo, 4> leftOverFields;
253 bool hasLeftOver = false;
254 StringAttr leftOverName;
255
256 for (WindowFieldType field : frame.getMembers()) {
257 auto fieldTypeIter = intoFields.find(field.getFieldName());
258 assert(fieldTypeIter != intoFields.end());
259 auto fieldType = fieldTypeIter->getSecond();
260
261 // Check for bulk transfer mode (countWidth specified on a list field).
262 uint64_t bulkCountWidth = field.getBulkCountWidth();
263 if (bulkCountWidth > 0) {
264 // Bulk transfer header: add a 'fieldname_count' field with the
265 // specified width. This count indicates how many items will be
266 // transmitted in subsequent data frames.
267 fields.push_back(
268 {StringAttr::get(getContext(),
269 Twine(field.getFieldName().getValue()) + "_count"),
270 IntegerType::get(getContext(), bulkCountWidth)});
271 // Don't add any data or 'last' fields for the header entry.
272 continue;
273 }
274
275 // Check if this field is part of a bulk transfer (has a header with
276 // countWidth elsewhere).
277 bool isBulkTransferData =
278 bulkTransferFields.contains(field.getFieldName());
279
280 // If the number of items isn't specified, just use the type.
281 if (field.getNumItems() == 0) {
282 // Directly use the type from the struct unless it's an array or list,
283 // in which case we want the inner type.
284 auto type = getInnerTypeOrSelf(fieldType);
285 fields.push_back({field.getFieldName(), type});
286 leftOverFields.push_back({field.getFieldName(), type});
287
288 if (hw::type_isa<esi::ListType>(fieldType) && !isBulkTransferData) {
289 // Lists need a 'last' signal to indicate the end of the list
290 // (only in streaming mode, not bulk transfer).
291 auto lastType = IntegerType::get(getContext(), 1);
292 auto lastField = StringAttr::get(getContext(), "last");
293 fields.push_back({lastField, lastType});
294 leftOverFields.push_back({lastField, lastType});
295 }
296 } else {
297 if (auto array =
298 hw::type_dyn_cast<hw::ArrayType>(fieldTypeIter->getSecond())) {
299 // The first union entry should be an array of length numItems.
300 fields.push_back(
301 {field.getFieldName(), hw::ArrayType::get(array.getElementType(),
302 field.getNumItems())});
303
304 // If the array size is not a multiple of numItems, we need another
305 // frame for the left overs.
306 size_t leftOver = array.getNumElements() % field.getNumItems();
307 if (leftOver) {
308 // The verifier checks that there is only one field per frame with
309 // numItems > 0.
310 assert(!hasLeftOver);
311 hasLeftOver = true;
312 leftOverFields.push_back(
313 {field.getFieldName(),
314 hw::ArrayType::get(array.getElementType(), leftOver)});
315
316 leftOverName = StringAttr::get(
317 getContext(), Twine(frame.getName().getValue(), "_leftOver"));
318 }
319 } else if (auto list = hw::type_cast<esi::ListType>(
320 fieldTypeIter->getSecond())) {
321 // Add array of length numItems.
322 fields.push_back(
323 {field.getFieldName(),
324 hw::ArrayType::get(list.getElementType(), field.getNumItems())});
325
326 if (!isBulkTransferData) {
327 // Streaming mode: add _size and last fields.
328 // _size is clog2(numItems) in width.
329 fields.push_back(
330 {StringAttr::get(
331 getContext(),
332 Twine(field.getFieldName().getValue(), "_size")),
333 IntegerType::get(getContext(),
334 llvm::Log2_64_Ceil(field.getNumItems()))});
335 // Lists need a 'last' signal to indicate the end of the list.
336 fields.push_back({StringAttr::get(getContext(), "last"),
337 IntegerType::get(getContext(), 1)});
338 }
339 } else {
340 llvm_unreachable("numItems specified on non-array/list field");
341 }
342 }
343 }
344
345 // Special case: if we have one data frame and it doesn't have a name, don't
346 // use a union.
347 if (getFrames().size() == 1 && frame.getName().getValue().empty() &&
348 !hasLeftOver) {
349 auto loweredStruct = hw::StructType::get(getContext(), fields);
350 return wrapInTypeAliasIfNeeded(loweredStruct);
351 }
352
353 if (!fields.empty())
354 unionFields.push_back(
355 {frame.getName(), hw::StructType::get(getContext(), fields), 0});
356
357 if (hasLeftOver)
358 unionFields.push_back(
359 {leftOverName, hw::StructType::get(getContext(), leftOverFields), 0});
360 }
361
362 auto unionType = hw::UnionType::get(getContext(), unionFields);
363 return wrapInTypeAliasIfNeeded(unionType);
364}
365
366namespace mlir {
367template <>
368struct FieldParser<::BundledChannel, ::BundledChannel> {
369 static FailureOr<::BundledChannel> parse(AsmParser &p) {
370 ChannelType type;
371 std::string name;
372 if (p.parseType(type))
373 return failure();
374 auto dir = FieldParser<::ChannelDirection>::parse(p);
375 if (failed(dir))
376 return failure();
377 if (p.parseKeywordOrString(&name))
378 return failure();
379 return BundledChannel{StringAttr::get(p.getContext(), name), *dir, type};
380 }
381};
382} // namespace mlir
383
384namespace llvm {
385inline ::llvm::raw_ostream &operator<<(::llvm::raw_ostream &p,
386 ::BundledChannel channel) {
387 p << channel.type << " " << channel.direction << " " << channel.name;
388 return p;
389}
390} // namespace llvm
391
392ChannelBundleType ChannelBundleType::getReversed() const {
393 SmallVector<BundledChannel, 4> reversed;
394 for (auto channel : getChannels())
395 reversed.push_back({channel.name, flip(channel.direction), channel.type});
396 return ChannelBundleType::get(getContext(), reversed, getResettable());
397}
398
399std::optional<int64_t> ChannelBundleType::getBitWidth() const {
400 int64_t totalWidth = 0;
401 for (auto channel : getChannels()) {
402 std::optional<int64_t> channelWidth = channel.type.getBitWidth();
403 if (!channelWidth)
404 return std::nullopt;
405 totalWidth += *channelWidth;
406 }
407 return totalWidth;
408}
409
410#define GET_TYPEDEF_CLASSES
411#include "circt/Dialect/ESI/ESITypes.cpp.inc"
412
413void ESIDialect::registerTypes() {
414 addTypes<
415#define GET_TYPEDEF_LIST
416#include "circt/Dialect/ESI/ESITypes.cpp.inc"
417 >();
418}
419
420mlir::Type circt::esi::innerType(mlir::Type type) {
421 circt::esi::ChannelType chan =
422 dyn_cast_or_null<circt::esi::ChannelType>(type);
423 if (chan) // Unwrap the channel if it's a channel.
424 type = chan.getInner();
425
426 return type;
427}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static unsigned getSignalingBitWidth(ChannelSignaling signaling)
Definition ESITypes.cpp:39
static auto getChannelConsumers(mlir::TypedValue< ChannelType > chan)
Get the list of users with snoops filtered out.
Definition ESITypes.cpp:57
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:216
Lists represent variable-length sequences of elements of a single type.
Definition Types.h:319
const Type * getElementType() const
Definition Types.h:324
mlir::Type innerType(mlir::Type type)
Definition ESITypes.cpp:420
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
ModulePort::Direction flip(ModulePort::Direction direction)
Flip a port direction.
Definition HWOps.cpp:36
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
Definition HWTypes.cpp:110
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
inline ::llvm::raw_ostream & operator<<(::llvm::raw_ostream &p, ::BundledChannel channel)
Definition ESITypes.cpp:385
ChannelDirection direction
Definition ESITypes.h:38
static FailureOr<::BundledChannel > parse(AsmParser &p)
Definition ESITypes.cpp:369