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