CIRCT 23.0.0git
Loading...
Searching...
No Matches
ESILowerPorts.cpp
Go to the documentation of this file.
1//===- ESILowerPorts.cpp - Lower ESI ports pass ----------------*- 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#include "../PassDetails.h"
10
16#include "circt/Support/LLVM.h"
18
19#include "mlir/Transforms/DialectConversion.h"
20
21#include "llvm/Support/MathExtras.h"
22
23namespace circt {
24namespace esi {
25#define GEN_PASS_DEF_LOWERESIPORTS
26#include "circt/Dialect/ESI/ESIPasses.h.inc"
27} // namespace esi
28} // namespace circt
29
30using namespace circt;
31using namespace circt::esi;
32using namespace circt::esi::detail;
33using namespace circt::hw;
34
35// Returns either the string dialect attr stored in 'op' going by the name
36// 'attrName' or 'def' if the attribute doesn't exist in 'op'.
37inline static StringRef getStringAttributeOr(Operation *op, StringRef attrName,
38 StringRef def) {
39 auto attr = op->getAttrOfType<StringAttr>(attrName);
40 if (attr)
41 return attr.getValue();
42 return def;
43}
44
45namespace {
46
47/// Per-channel ESI signaling protocol policies (ValidReady, FIFO, ValidOnly).
48/// These are stateless policy types with only static methods; they are never
49/// instantiated. They are used as the policy type parameter of the
50/// channel-port lowering containers below, which share their logic between
51/// scalar channel ports and arrays of channel ports.
52///
53/// Each protocol has one or more control signals associated with the channel
54/// data to, at minimum, indicate the validity of the data. The protocol defines
55/// how these signals are named and whether there is a backpressure handshake
56/// signal.
57///
58/// Each policy provides:
59/// - getValiditySuffix(module): suffix for the forward validity signal.
60/// - hasBackpressure: compile-time flag, true iff the protocol has a
61/// backpressure handshake signal.
62/// - getBackpressureSuffix(module): suffix for the backpressure handshake
63/// signal (only defined when 'hasBackpressure' is true).
64/// - wrap(b, loc, chanTy, data, validity): recreate a channel of type 'chanTy'
65/// from its 'data' and 'validity' signals; returns {channel, backpressure}
66/// (the backpressure value is null if the protocol has no backpressure
67/// signal).
68/// - unwrap(b, loc, chan, backpressure): break 'chan' into its {data,
69/// validity} signals ('backpressure' is unused for protocols without a
70/// backpressure signal).
71///
72/// Note: if we add a credit control signaling protocol in the future, this will
73/// have to be re-thought.
74
75/// ValidReady: 'valid' validity, 'ready' backpressure handshake.
76struct ValidReadyProtocol {
77 static constexpr bool hasBackpressure = true;
78 static StringRef getValiditySuffix(Operation *module) {
79 return getStringAttributeOr(module, extModPortValidSuffix, "_valid");
80 }
81 static StringRef getBackpressureSuffix(Operation *module) {
82 return getStringAttributeOr(module, extModPortReadySuffix, "_ready");
83 }
84 static std::pair<Value, Value> wrap(OpBuilder &b, Location loc,
85 ChannelType chanTy, Value data,
86 Value validity) {
87 auto wrap = WrapValidReadyOp::create(b, loc, data, validity);
88 return {wrap.getChanOutput(), wrap.getReady()};
89 }
90 static std::pair<Value, Value> unwrap(OpBuilder &b, Location loc, Value chan,
91 Value backpressure) {
92 auto unwrap = UnwrapValidReadyOp::create(b, loc, chan, backpressure);
93 return {unwrap.getRawOutput(), unwrap.getValid()};
94 }
95};
96
97/// FIFO: 'empty' validity (empty == !valid), 'rden' (read-enable)
98/// "backpressure" handshake.
99struct FIFOProtocol {
100 static constexpr bool hasBackpressure = true;
101 static StringRef getValiditySuffix(Operation *module) {
102 return getStringAttributeOr(module, extModPortEmptySuffix, "_empty");
103 }
104 static StringRef getBackpressureSuffix(Operation *module) {
105 return getStringAttributeOr(module, extModPortRdenSuffix, "_rden");
106 }
107 static std::pair<Value, Value> wrap(OpBuilder &b, Location loc,
108 ChannelType chanTy, Value data,
109 Value validity) {
110 auto wrap = WrapFIFOOp::create(
111 b, loc, ArrayRef<Type>({chanTy, b.getI1Type()}), data, validity);
112 return {wrap.getChanOutput(), wrap.getRden()};
113 }
114 static std::pair<Value, Value> unwrap(OpBuilder &b, Location loc, Value chan,
115 Value backpressure) {
116 auto unwrap = UnwrapFIFOOp::create(b, loc, chan, backpressure);
117 return {unwrap.getData(), unwrap.getEmpty()};
118 }
119};
120
121/// ValidOnly: forward 'valid' validity, no backpressure handshake.
122struct ValidOnlyProtocol {
123 static constexpr bool hasBackpressure = false;
124 static StringRef getValiditySuffix(Operation *module) {
125 return getStringAttributeOr(module, extModPortValidSuffix, "_valid");
126 }
127 static std::pair<Value, Value> wrap(OpBuilder &b, Location loc,
128 ChannelType chanTy, Value data,
129 Value validity) {
130 auto wrap = WrapValidOnlyOp::create(b, loc, data, validity);
131 return {wrap.getChanOutput(), Value()};
132 }
133 static std::pair<Value, Value> unwrap(OpBuilder &b, Location loc, Value chan,
134 Value /*backpressure*/) {
135 auto unwrap = UnwrapValidOnlyOp::create(b, loc, chan);
136 return {unwrap.getRawOutput(), unwrap.getValid()};
137 }
138};
139
140/// Return true if 'type' contains an ESI channel nested inside an aggregate.
141/// Used to detect (and reject) ports which embed channels in a way this pass
142/// cannot lower (e.g. arrays of arrays of channels, or structs of channels).
143static bool containsChannel(Type type) {
144 if (auto arr = dyn_cast<hw::ArrayType>(type)) {
145 Type elem = arr.getElementType();
146 return isa<esi::ChannelType>(elem) || containsChannel(elem);
147 }
148 if (auto str = dyn_cast<hw::StructType>(type))
149 return llvm::any_of(str.getElements(), [](const auto &field) {
150 return isa<esi::ChannelType>(field.type) || containsChannel(field.type);
151 });
152 return false;
153}
154
155/// Lower a single ESI channel port into its constituent wire-level signals
156/// using the 'Protocol' signaling policy.
157template <typename Protocol>
158class ScalarChannelPort : public PortConversion {
159public:
161
162 void mapInputSignals(OpBuilder &b, Operation *inst, Value instValue,
163 SmallVectorImpl<Value> &newOperands,
164 ArrayRef<Backedge> newResults) override;
165 void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue,
166 SmallVectorImpl<Value> &newOperands,
167 ArrayRef<Backedge> newResults) override;
168
169private:
170 void buildInputSignals() override;
171 void buildOutputSignals() override;
172
173 // Port info for the lowered signals. 'backpressurePort' is only valid when
174 // 'Protocol::hasBackpressure' is true.
175 PortInfo dataPort, validityPort, backpressurePort;
176};
177
178/// Lower a port which is an array of ESI channels into arrays of the
179/// constituent wire-level signals (one array element per channel) using the
180/// 'Protocol' signaling policy.
181template <typename Protocol>
182class ArrayChannelPort : public PortConversion {
183public:
185
186 void mapInputSignals(OpBuilder &b, Operation *inst, Value instValue,
187 SmallVectorImpl<Value> &newOperands,
188 ArrayRef<Backedge> newResults) override;
189 void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue,
190 SmallVectorImpl<Value> &newOperands,
191 ArrayRef<Backedge> newResults) override;
192
193private:
194 void buildInputSignals() override;
195 void buildOutputSignals() override;
196
197 // Port info for the lowered signal arrays. 'backpressurePort' is only valid
198 // when 'Protocol::hasBackpressure' is true.
199 PortInfo dataPort, validityPort, backpressurePort;
200};
201
202// Emit an error about an unknown signaling standard on 'port'.
203static FailureOr<std::unique_ptr<PortConversion>>
204emitUnknownSignaling(Operation *op, hw::PortInfo port,
205 ChannelSignaling signaling) {
206 auto error =
207 op->emitOpError("encountered unknown signaling standard on port '")
208 << stringifyEnum(signaling) << "'";
209 error.attachNote(port.loc);
210 return error;
211}
212
213/// Instantiate the channel-port lowering container 'PortKind' (e.g.
214/// ScalarChannelPort or ArrayChannelPort) with the protocol policy selected by
215/// 'signaling'. Returns failure for an unknown signaling standard.
216template <template <typename> class PortKind>
217static FailureOr<std::unique_ptr<PortConversion>>
218buildChannelPort(PortConverterImpl &converter, hw::PortInfo port,
219 ChannelSignaling signaling) {
220 switch (signaling) {
221 case ChannelSignaling::ValidReady:
222 return {std::make_unique<PortKind<ValidReadyProtocol>>(converter, port)};
223 case ChannelSignaling::FIFO:
224 return {std::make_unique<PortKind<FIFOProtocol>>(converter, port)};
225 case ChannelSignaling::ValidOnly:
226 return {std::make_unique<PortKind<ValidOnlyProtocol>>(converter, port)};
227 }
228 return emitUnknownSignaling(converter.getModule(), port, signaling);
229}
230
231class ESIPortConversionBuilder : public PortConversionBuilder {
232public:
234 FailureOr<std::unique_ptr<PortConversion>> build(hw::PortInfo port) override {
235 return llvm::TypeSwitch<Type, FailureOr<std::unique_ptr<PortConversion>>>(
236 port.type)
237 .Case([&](esi::ChannelType chanTy)
238 -> FailureOr<std::unique_ptr<PortConversion>> {
239 return buildChannelPort<ScalarChannelPort>(converter, port,
240 chanTy.getSignaling());
241 })
242 .Case([&](hw::ArrayType arrTy)
243 -> FailureOr<std::unique_ptr<PortConversion>> {
244 auto chanTy = dyn_cast<esi::ChannelType>(arrTy.getElementType());
245 if (!chanTy) {
246 // Channels nested deeper than a top-level array of channels are
247 // not supported.
248 if (containsChannel(arrTy)) {
249 auto error = converter.getModule().emitOpError(
250 "cannot lower port containing channels nested inside an "
251 "aggregate other than a single array of channels");
252 error.attachNote(port.loc);
253 return error;
254 }
255 return PortConversionBuilder::build(port);
256 }
257 // The lowering decomposes the array element-by-element, so the size
258 // must be a known, non-zero constant. A parametric size makes
259 // `getNumElements()` return -1 and a zero size would assert in
260 // hw.array_create; emit a diagnostic rather than crash.
261 if (!isa<IntegerAttr>(arrTy.getSizeAttr()) ||
262 arrTy.getNumElements() == 0)
263 return converter.getModule()
264 .emitOpError("cannot lower array-of-channels port with a "
265 "non-constant or zero-length size")
266 .attachNote(port.loc);
267 return buildChannelPort<ArrayChannelPort>(converter, port,
268 chanTy.getSignaling());
269 })
270 .Default([&](auto) { return PortConversionBuilder::build(port); });
271 }
272};
273} // namespace
274
275/// Extract element 'idx' from the array-typed value 'array'.
276static Value getArrayElement(OpBuilder &b, Location loc, Value array,
277 size_t idx) {
278 auto arrTy = cast<hw::ArrayType>(array.getType());
279 IntegerType idxType = b.getIntegerType(
280 std::max(1u, llvm::Log2_64_Ceil(arrTy.getNumElements())));
281 Value idxVal = hw::ConstantOp::create(b, loc, idxType, idx);
282 return hw::ArrayGetOp::create(b, loc, array, idxVal);
283}
284
285/// Pack a list of element values (with 'elements[i]' destined for array index
286/// 'i') into an hw.array. hw.array_create takes its operands in reverse order
287/// (operand 0 becomes the highest index), so the list is reversed to keep
288/// 'elements[i]' at array index 'i'.
289static Value packArray(OpBuilder &b, Location loc, ArrayRef<Value> elements) {
290 SmallVector<Value> reversed(elements.rbegin(), elements.rend());
291 return hw::ArrayCreateOp::create(b, loc, reversed);
292}
293
294//===----------------------------------------------------------------------===//
295// ScalarChannelPort
296//===----------------------------------------------------------------------===//
297
298template <typename Protocol>
299void ScalarChannelPort<Protocol>::buildInputSignals() {
300 Operation *module = converter.getModule();
301 Type i1 = IntegerType::get(getContext(), 1, IntegerType::Signless);
302 auto chanTy = cast<ChannelType>(origPort.type);
303
304 StringRef inSuffix = getStringAttributeOr(module, extModPortInSuffix, "");
305 StringRef outSuffix = getStringAttributeOr(module, extModPortOutSuffix, "");
306
307 // The data and forward validity signals come into the module alongside the
308 // data.
309 Value data =
310 converter.createNewInput(origPort, inSuffix, chanTy.getInner(), dataPort);
311 Value validity = converter.createNewInput(
312 origPort, Protocol::getValiditySuffix(module) + inSuffix, i1,
313 validityPort);
314
315 Value backpressure;
316 if (body) {
317 ImplicitLocOpBuilder b(origPort.loc, body, body->begin());
318 // Recreate the original channel value from the lowered signals. (A later
319 // pass takes care of eliminating the ESI ops.)
320 auto [chan, bp] = Protocol::wrap(b, b.getLoc(), chanTy, data, validity);
321 backpressure = bp;
322 body->getArgument(origPort.argNum).replaceAllUsesWith(chan);
323 }
324
325 // The backpressure handshake signal (if any) leaves the module.
326 if constexpr (Protocol::hasBackpressure)
327 converter.createNewOutput(
328 origPort, Protocol::getBackpressureSuffix(module) + outSuffix, i1,
329 backpressure, backpressurePort);
330}
331
332template <typename Protocol>
333void ScalarChannelPort<Protocol>::mapInputSignals(
334 OpBuilder &b, Operation *inst, Value, SmallVectorImpl<Value> &newOperands,
335 ArrayRef<Backedge> newResults) {
336 Value backpressure;
337 if constexpr (Protocol::hasBackpressure)
338 backpressure = newResults[backpressurePort.argNum];
339 auto [data, validity] = Protocol::unwrap(
340 b, inst->getLoc(), inst->getOperand(origPort.argNum), backpressure);
341 newOperands[dataPort.argNum] = data;
342 newOperands[validityPort.argNum] = validity;
343}
344
345template <typename Protocol>
346void ScalarChannelPort<Protocol>::buildOutputSignals() {
347 Operation *module = converter.getModule();
348 Type i1 = IntegerType::get(getContext(), 1, IntegerType::Signless);
349 auto chanTy = cast<ChannelType>(origPort.type);
350
351 StringRef inSuffix = getStringAttributeOr(module, extModPortInSuffix, "");
352 StringRef outSuffix = getStringAttributeOr(module, extModPortOutSuffix, "");
353
354 // The backpressure handshake signal (if any) comes into the module.
355 Value backpressure;
356 if constexpr (Protocol::hasBackpressure)
357 backpressure = converter.createNewInput(
358 origPort, Protocol::getBackpressureSuffix(module) + inSuffix, i1,
359 backpressurePort);
360
361 Value data, validity;
362 if (body) {
363 auto *terminator = body->getTerminator();
364 ImplicitLocOpBuilder b(origPort.loc, terminator);
365 auto unwrapped = Protocol::unwrap(
366 b, b.getLoc(), terminator->getOperand(origPort.argNum), backpressure);
367 data = unwrapped.first;
368 validity = unwrapped.second;
369 }
370
371 // The data and forward validity signals leave the module.
372 converter.createNewOutput(origPort, outSuffix, chanTy.getInner(), data,
373 dataPort);
374 converter.createNewOutput(origPort,
375 Protocol::getValiditySuffix(module) + outSuffix, i1,
376 validity, validityPort);
377}
378
379template <typename Protocol>
380void ScalarChannelPort<Protocol>::mapOutputSignals(
381 OpBuilder &b, Operation *inst, Value, SmallVectorImpl<Value> &newOperands,
382 ArrayRef<Backedge> newResults) {
383 auto chanTy = cast<ChannelType>(origPort.type);
384 auto [chan, backpressure] =
385 Protocol::wrap(b, inst->getLoc(), chanTy, newResults[dataPort.argNum],
386 newResults[validityPort.argNum]);
387 inst->getResult(origPort.argNum).replaceAllUsesWith(chan);
388 if constexpr (Protocol::hasBackpressure)
389 newOperands[backpressurePort.argNum] = backpressure;
390}
391
392//===----------------------------------------------------------------------===//
393// ArrayChannelPort
394//===----------------------------------------------------------------------===//
395
396template <typename Protocol>
397void ArrayChannelPort<Protocol>::buildInputSignals() {
398 Operation *module = converter.getModule();
399 Type i1 = IntegerType::get(getContext(), 1, IntegerType::Signless);
400 auto arrTy = cast<hw::ArrayType>(origPort.type);
401 auto chanTy = cast<ChannelType>(arrTy.getElementType());
402 size_t numElems = arrTy.getNumElements();
403 auto dataArrTy = hw::ArrayType::get(chanTy.getInner(), numElems);
404 auto validityArrTy = hw::ArrayType::get(i1, numElems);
405
406 StringRef inSuffix = getStringAttributeOr(module, extModPortInSuffix, "");
407 StringRef outSuffix = getStringAttributeOr(module, extModPortOutSuffix, "");
408
409 // The data and forward validity signal arrays come into the module alongside
410 // the data.
411 Value dataArr =
412 converter.createNewInput(origPort, inSuffix, dataArrTy, dataPort);
413 Value validityArr = converter.createNewInput(
414 origPort, Protocol::getValiditySuffix(module) + inSuffix, validityArrTy,
415 validityPort);
416
417 Value backpressureArr;
418 if (body) {
419 ImplicitLocOpBuilder b(origPort.loc, body, body->begin());
420 // Recreate the original array of channels by wrapping each element's
421 // lowered signals back into a channel.
422 SmallVector<Value> chans, backpressures;
423 for (size_t i = 0; i < numElems; ++i) {
424 Value data = getArrayElement(b, b.getLoc(), dataArr, i);
425 Value validity = getArrayElement(b, b.getLoc(), validityArr, i);
426 auto [chan, bp] = Protocol::wrap(b, b.getLoc(), chanTy, data, validity);
427 chans.push_back(chan);
428 if constexpr (Protocol::hasBackpressure)
429 backpressures.push_back(bp);
430 }
431 body->getArgument(origPort.argNum)
432 .replaceAllUsesWith(packArray(b, b.getLoc(), chans));
433 if (!backpressures.empty())
434 backpressureArr = packArray(b, b.getLoc(), backpressures);
435 }
436
437 // The backpressure handshake signal array (if any) leaves the module.
438 if constexpr (Protocol::hasBackpressure)
439 converter.createNewOutput(
440 origPort, Protocol::getBackpressureSuffix(module) + outSuffix,
441 validityArrTy, backpressureArr, backpressurePort);
442}
443
444template <typename Protocol>
445void ArrayChannelPort<Protocol>::mapInputSignals(
446 OpBuilder &b, Operation *inst, Value, SmallVectorImpl<Value> &newOperands,
447 ArrayRef<Backedge> newResults) {
448 Location loc = inst->getLoc();
449 Value chanArr = inst->getOperand(origPort.argNum);
450 size_t numElems = cast<hw::ArrayType>(origPort.type).getNumElements();
451
452 Value backpressureArr;
453 if constexpr (Protocol::hasBackpressure)
454 backpressureArr = newResults[backpressurePort.argNum];
455
456 // Unwrap each channel element into its data and validity signals.
457 SmallVector<Value> datas, validities;
458 for (size_t i = 0; i < numElems; ++i) {
459 Value chan = getArrayElement(b, loc, chanArr, i);
460 Value backpressure;
461 if constexpr (Protocol::hasBackpressure)
462 backpressure = getArrayElement(b, loc, backpressureArr, i);
463 auto [data, validity] = Protocol::unwrap(b, loc, chan, backpressure);
464 datas.push_back(data);
465 validities.push_back(validity);
466 }
467 newOperands[dataPort.argNum] = packArray(b, loc, datas);
468 newOperands[validityPort.argNum] = packArray(b, loc, validities);
469}
470
471template <typename Protocol>
472void ArrayChannelPort<Protocol>::buildOutputSignals() {
473 Operation *module = converter.getModule();
474 Type i1 = IntegerType::get(getContext(), 1, IntegerType::Signless);
475 auto arrTy = cast<hw::ArrayType>(origPort.type);
476 auto chanTy = cast<ChannelType>(arrTy.getElementType());
477 size_t numElems = arrTy.getNumElements();
478 auto dataArrTy = hw::ArrayType::get(chanTy.getInner(), numElems);
479 auto validityArrTy = hw::ArrayType::get(i1, numElems);
480
481 StringRef inSuffix = getStringAttributeOr(module, extModPortInSuffix, "");
482 StringRef outSuffix = getStringAttributeOr(module, extModPortOutSuffix, "");
483
484 // The backpressure handshake signal array (if any) comes into the module.
485 Value backpressureArr;
486 if constexpr (Protocol::hasBackpressure)
487 backpressureArr = converter.createNewInput(
488 origPort, Protocol::getBackpressureSuffix(module) + inSuffix,
489 validityArrTy, backpressurePort);
490
491 Value dataArr, validityArr;
492 if (body) {
493 auto *terminator = body->getTerminator();
494 ImplicitLocOpBuilder b(origPort.loc, terminator);
495 Value chanArr = terminator->getOperand(origPort.argNum);
496 // Unwrap each channel element into its data and validity signals.
497 SmallVector<Value> datas, validities;
498 for (size_t i = 0; i < numElems; ++i) {
499 Value chan = getArrayElement(b, b.getLoc(), chanArr, i);
500 Value backpressure;
501 if constexpr (Protocol::hasBackpressure)
502 backpressure = getArrayElement(b, b.getLoc(), backpressureArr, i);
503 auto [data, validity] =
504 Protocol::unwrap(b, b.getLoc(), chan, backpressure);
505 datas.push_back(data);
506 validities.push_back(validity);
507 }
508 dataArr = packArray(b, b.getLoc(), datas);
509 validityArr = packArray(b, b.getLoc(), validities);
510 }
511
512 // The data and forward validity signal arrays leave the module.
513 converter.createNewOutput(origPort, outSuffix, dataArrTy, dataArr, dataPort);
514 converter.createNewOutput(origPort,
515 Protocol::getValiditySuffix(module) + outSuffix,
516 validityArrTy, validityArr, validityPort);
517}
518
519template <typename Protocol>
520void ArrayChannelPort<Protocol>::mapOutputSignals(
521 OpBuilder &b, Operation *inst, Value, SmallVectorImpl<Value> &newOperands,
522 ArrayRef<Backedge> newResults) {
523 Location loc = inst->getLoc();
524 auto arrTy = cast<hw::ArrayType>(origPort.type);
525 auto chanTy = cast<ChannelType>(arrTy.getElementType());
526 size_t numElems = arrTy.getNumElements();
527
528 Value dataArr = newResults[dataPort.argNum];
529 Value validityArr = newResults[validityPort.argNum];
530
531 // Wrap each element's data and validity signals back into a channel.
532 SmallVector<Value> chans, backpressures;
533 for (size_t i = 0; i < numElems; ++i) {
534 Value data = getArrayElement(b, loc, dataArr, i);
535 Value validity = getArrayElement(b, loc, validityArr, i);
536 auto [chan, bp] = Protocol::wrap(b, loc, chanTy, data, validity);
537 chans.push_back(chan);
538 if (bp)
539 backpressures.push_back(bp);
540 }
541 inst->getResult(origPort.argNum).replaceAllUsesWith(packArray(b, loc, chans));
542 if constexpr (Protocol::hasBackpressure)
543 newOperands[backpressurePort.argNum] = packArray(b, loc, backpressures);
544}
545
546namespace {
547/// Convert all the ESI ports on modules to some lower construct. SV
548/// interfaces for now on external modules, ready/valid to modules defined
549/// internally. In the future, it may be possible to select a different
550/// format.
551struct ESIPortsPass : public circt::esi::impl::LowerESIPortsBase<ESIPortsPass> {
552 void runOnOperation() override;
553
554private:
555 bool updateFunc(HWModuleExternOp mod);
556 void updateInstance(HWModuleExternOp mod, InstanceOp inst);
557 ESIHWBuilder *build;
558};
559} // anonymous namespace
560
561/// Iterate through the `hw.module[.extern]`s and lower their ports.
562void ESIPortsPass::runOnOperation() {
563 ModuleOp top = getOperation();
564 ESIHWBuilder b(top);
565 build = &b;
566
567 // Find all externmodules and try to modify them. Remember the modified
568 // ones.
569 DenseMap<SymbolRefAttr, HWModuleExternOp> externModsMutated;
570 for (auto mod : top.getOps<HWModuleExternOp>())
571 if (mod->hasAttrOfType<UnitAttr>(extModBundleSignalsAttrName) &&
572 updateFunc(mod))
573 externModsMutated[FlatSymbolRefAttr::get(mod)] = mod;
574
575 // Find all instances and update them.
576 top.walk([&externModsMutated, this](InstanceOp inst) {
577 auto mapIter = externModsMutated.find(inst.getModuleNameAttr());
578 if (mapIter != externModsMutated.end())
579 updateInstance(mapIter->second, inst);
580 });
581
582 // Find all modules and run port conversion on them.
583 circt::hw::InstanceGraph &instanceGraph =
584 getAnalysis<circt::hw::InstanceGraph>();
585
586 for (auto mod : top.getOps<HWMutableModuleLike>()) {
587 if (failed(
588 PortConverter<ESIPortConversionBuilder>(instanceGraph, mod).run()))
589 return signalPassFailure();
590 }
591
592 build = nullptr;
593}
594
595/// Convert all input and output ChannelTypes into SV Interfaces. For inputs,
596/// just switch the type to `ModportType`. For outputs, append a `ModportType`
597/// to the inputs and remove the output channel from the results. Returns true
598/// if 'mod' was updated. Delay updating the instances to amortize the IR walk
599/// over all the module updates.
600bool ESIPortsPass::updateFunc(HWModuleExternOp mod) {
601 auto *ctxt = &getContext();
602
603 bool updated = false;
604
605 SmallVector<Attribute> newArgNames, newResultNames;
606 SmallVector<Location> newArgLocs, newResultLocs;
607
608 // Reconstruct the list of operand types, changing the type whenever an ESI
609 // port is found.
610 SmallVector<Type, 16> newArgTypes;
611 size_t nextArgNo = 0;
612 for (auto argTy : mod.getInputTypes()) {
613 auto chanTy = dyn_cast<ChannelType>(argTy);
614 newArgNames.push_back(mod.getInputNameAttr(nextArgNo));
615 newArgLocs.push_back(mod.getInputLoc(nextArgNo));
616 nextArgNo++;
617
618 if (!chanTy) {
619 newArgTypes.push_back(argTy);
620 continue;
621 }
622
623 // When we find one, construct an interface, and add the 'source' modport
624 // to the type list.
625 auto iface = build->getOrConstructInterface(chanTy);
626 newArgTypes.push_back(iface.getModportType(ESIHWBuilder::sourceStr));
627 updated = true;
628 }
629
630 // Iterate through the results and append to one of the two below lists. The
631 // first for non-ESI-ports. The second, ports which have been re-located to
632 // an operand.
633 SmallVector<Type, 8> newResultTypes;
634 SmallVector<DictionaryAttr, 4> newResultAttrs;
635 for (size_t resNum = 0, numRes = mod.getNumOutputPorts(); resNum < numRes;
636 ++resNum) {
637 Type resTy = mod.getOutputTypes()[resNum];
638 auto chanTy = dyn_cast<ChannelType>(resTy);
639 auto resNameAttr = mod.getOutputNameAttr(resNum);
640 auto resLocAttr = mod.getOutputLoc(resNum);
641 if (!chanTy) {
642 newResultTypes.push_back(resTy);
643 newResultNames.push_back(resNameAttr);
644 newResultLocs.push_back(resLocAttr);
645 continue;
646 }
647
648 // When we find one, construct an interface, and add the 'sink' modport to
649 // the type list.
650 sv::InterfaceOp iface = build->getOrConstructInterface(chanTy);
651 sv::ModportType sinkPort = iface.getModportType(ESIHWBuilder::sinkStr);
652 newArgTypes.push_back(sinkPort);
653 newArgNames.push_back(resNameAttr);
654 newArgLocs.push_back(resLocAttr);
655 updated = true;
656 }
657
658 mod->removeAttr(extModBundleSignalsAttrName);
659 if (!updated)
660 return false;
661
662 // Set the new types.
663 auto newFuncType = FunctionType::get(ctxt, newArgTypes, newResultTypes);
664 auto newModType =
665 hw::detail::fnToMod(newFuncType, newArgNames, newResultNames);
666 mod.setHWModuleType(newModType);
667 mod.setInputLocs(newArgLocs);
668 mod.setOutputLocs(newResultLocs);
669 return true;
670}
671
672static StringRef getOperandName(Value operand) {
673 if (BlockArgument arg = dyn_cast<BlockArgument>(operand)) {
674 auto *op = arg.getParentBlock()->getParentOp();
675 if (HWModuleLike mod = dyn_cast_or_null<HWModuleLike>(op))
676 return mod.getInputName(arg.getArgNumber());
677 } else {
678 auto *srcOp = operand.getDefiningOp();
679 if (auto instOp = dyn_cast<InstanceOp>(srcOp))
680 return instOp.getInstanceName();
681
682 if (auto srcName = srcOp->getAttrOfType<StringAttr>("name"))
683 return srcName.getValue();
684 }
685 return "";
686}
687
688/// Create a reasonable name for a SV interface instance.
689static std::string &constructInstanceName(Value operand, sv::InterfaceOp iface,
690 std::string &name) {
691 llvm::raw_string_ostream s(name);
692 // Drop the "IValidReady_" part of the interface name.
693 s << llvm::toLower(iface.getSymName()[12]) << iface.getSymName().substr(13);
694
695 // Indicate to where the source is connected.
696 if (operand.hasOneUse()) {
697 Operation *dstOp = *operand.getUsers().begin();
698 if (auto instOp = dyn_cast<InstanceOp>(dstOp))
699 s << "To" << llvm::toUpper(instOp.getInstanceName()[0])
700 << instOp.getInstanceName().substr(1);
701 else if (auto dstName = dstOp->getAttrOfType<StringAttr>("name"))
702 s << "To" << dstName.getValue();
703 }
704
705 // Indicate to where the sink is connected.
706 StringRef operName = getOperandName(operand);
707 if (!operName.empty())
708 s << "From" << llvm::toUpper(operName[0]) << operName.substr(1);
709 return s.str();
710}
711
712/// Update an instance of an updated module by adding `esi.(un)wrap.iface`
713/// around the instance. Create a new instance at the end from the lists built
714/// up before.
715void ESIPortsPass::updateInstance(HWModuleExternOp mod, InstanceOp inst) {
716 using namespace circt::sv;
717 circt::ImplicitLocOpBuilder instBuilder(inst.getLoc(), inst);
718
719 // op counter for error reporting purposes.
720 size_t opNum = 0;
721 // List of new operands.
722 SmallVector<Value, 16> newOperands;
723
724 // Fill the new operand list with old plain operands and mutated ones.
725 std::string nameStringBuffer; // raw_string_ostream uses std::string.
726 for (auto op : inst.getOperands()) {
727 auto instChanTy = dyn_cast<ChannelType>(op.getType());
728 if (!instChanTy) {
729 newOperands.push_back(op);
730 ++opNum;
731 continue;
732 }
733
734 // Get the interface from the cache, and make sure it's the same one as
735 // being used in the module.
736 auto iface = build->getOrConstructInterface(instChanTy);
737 if (iface.getModportType(ESIHWBuilder::sourceStr) !=
738 mod.getInputTypes()[opNum]) {
739 inst.emitOpError("ESI ChannelType (operand #")
740 << opNum << ") doesn't match module!";
741 ++opNum;
742 newOperands.push_back(op);
743 continue;
744 }
745 ++opNum;
746
747 // Build a gasket by instantiating an interface, connecting one end to an
748 // `esi.unwrap.iface` and the other end to the instance.
749 auto ifaceInst =
750 InterfaceInstanceOp::create(instBuilder, iface.getInterfaceType());
751 nameStringBuffer.clear();
752 ifaceInst->setAttr(
753 "name",
754 StringAttr::get(mod.getContext(),
755 constructInstanceName(op, iface, nameStringBuffer)));
756 GetModportOp sinkModport =
757 GetModportOp::create(instBuilder, ifaceInst, ESIHWBuilder::sinkStr);
758 UnwrapSVInterfaceOp::create(instBuilder, op, sinkModport);
759 GetModportOp sourceModport =
760 GetModportOp::create(instBuilder, ifaceInst, ESIHWBuilder::sourceStr);
761 // Finally, add the correct modport to the list of operands.
762 newOperands.push_back(sourceModport);
763 }
764
765 // Go through the results and get both a list of the plain old values being
766 // produced and their types.
767 SmallVector<Value, 8> newResults;
768 SmallVector<Type, 8> newResultTypes;
769 for (size_t resNum = 0, numRes = inst.getNumResults(); resNum < numRes;
770 ++resNum) {
771 Value res = inst.getResult(resNum);
772 auto instChanTy = dyn_cast<ChannelType>(res.getType());
773 if (!instChanTy) {
774 newResults.push_back(res);
775 newResultTypes.push_back(res.getType());
776 continue;
777 }
778
779 // Get the interface from the cache, and make sure it's the same one as
780 // being used in the module.
781 auto iface = build->getOrConstructInterface(instChanTy);
782 if (iface.getModportType(ESIHWBuilder::sinkStr) !=
783 mod.getInputTypes()[opNum]) {
784 inst.emitOpError("ESI ChannelType (result #")
785 << resNum << ", operand #" << opNum << ") doesn't match module!";
786 ++opNum;
787 newResults.push_back(res);
788 newResultTypes.push_back(res.getType());
789 continue;
790 }
791 ++opNum;
792
793 // Build a gasket by instantiating an interface, connecting one end to an
794 // `esi.wrap.iface` and the other end to the instance. Append it to the
795 // operand list.
796 auto ifaceInst =
797 InterfaceInstanceOp::create(instBuilder, iface.getInterfaceType());
798 nameStringBuffer.clear();
799 ifaceInst->setAttr(
800 "name",
801 StringAttr::get(mod.getContext(),
802 constructInstanceName(res, iface, nameStringBuffer)));
803 GetModportOp sourceModport =
804 GetModportOp::create(instBuilder, ifaceInst, ESIHWBuilder::sourceStr);
805 auto newChannel =
806 WrapSVInterfaceOp::create(instBuilder, res.getType(), sourceModport);
807 // Connect all the old users of the output channel with the newly
808 // wrapped replacement channel.
809 res.replaceAllUsesWith(newChannel);
810 GetModportOp sinkModport =
811 GetModportOp::create(instBuilder, ifaceInst, ESIHWBuilder::sinkStr);
812 // And add the modport on the other side to the new operand list.
813 newOperands.push_back(sinkModport);
814 }
815
816 // Create the new instance!
817 auto newInst = hw::InstanceOp::create(
818 instBuilder, mod, inst.getInstanceNameAttr(), newOperands,
819 inst.getParameters(), inst.getInnerSymAttr());
820
821 // Go through the old list of non-ESI result values, and replace them with
822 // the new non-ESI results.
823 for (size_t resNum = 0, numRes = newResults.size(); resNum < numRes;
824 ++resNum) {
825 newResults[resNum].replaceAllUsesWith(newInst.getResult(resNum));
826 }
827 // Erase the old instance!
828 inst.erase();
829}
830
831std::unique_ptr<OperationPass<ModuleOp>>
833 return std::make_unique<ESIPortsPass>();
834}
return wrap(CMemoryType::get(unwrap(ctx), baseType, numElements))
static StringRef getOperandName(Value operand)
static Value packArray(OpBuilder &b, Location loc, ArrayRef< Value > elements)
Pack a list of element values (with 'elements[i]' destined for array index 'i') into an hw....
static Value getArrayElement(OpBuilder &b, Location loc, Value array, size_t idx)
Extract element 'idx' from the array-typed value 'array'.
static StringRef getStringAttributeOr(Operation *op, StringRef attrName, StringRef def)
static std::string & constructInstanceName(Value operand, sv::InterfaceOp iface, std::string &name)
Create a reasonable name for a SV interface instance.
static EvaluatorValuePtr unwrap(OMEvaluatorValue c)
Definition OM.cpp:111
Assist the lowering steps for conversions which need to create auxiliary IR.
Definition PassDetails.h:56
static constexpr char sinkStr[]
Definition PassDetails.h:78
static constexpr char sourceStr[]
Definition PassDetails.h:77
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
PortConversionBuilder(PortConverterImpl &converter)
virtual FailureOr< std::unique_ptr< PortConversion > > build(hw::PortInfo port)
Base class for the port conversion of a particular port.
virtual void buildInputSignals()=0
virtual void mapInputSignals(OpBuilder &b, Operation *inst, Value instValue, SmallVectorImpl< Value > &newOperands, ArrayRef< Backedge > newResults)=0
Update an instance port to the new port information.
PortConversion(PortConverterImpl &converter, hw::PortInfo origPort)
virtual void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue, SmallVectorImpl< Value > &newOperands, ArrayRef< Backedge > newResults)=0
virtual void buildOutputSignals()=0
void createNewOutput(hw::PortInfo origPort, const Twine &suffix, Type type, Value output, hw::PortInfo &newPort)
Same as above.
hw::HWMutableModuleLike getModule() const
Value createNewInput(hw::PortInfo origPort, const Twine &suffix, Type type, hw::PortInfo &newPort)
These two methods take care of allocating new ports in the correct place based on the position of 'or...
Channels are the basic communication primitives.
Definition Types.h:125
create(elements, Type result_type=None)
Definition hw.py:483
create(array_value, idx)
Definition hw.py:450
create(data_type, value)
Definition hw.py:433
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
constexpr StringRef extModPortValidSuffix
Suffix lowered valid ports with this suffix.
Definition ESIDialect.h:51
constexpr StringRef extModPortRdenSuffix
Suffix lowered read enable ports with this suffix.
Definition ESIDialect.h:57
constexpr StringRef extModPortReadySuffix
Suffix lowered ready ports with this suffix.
Definition ESIDialect.h:54
constexpr StringRef extModBundleSignalsAttrName
Name of dialect attribute which governs whether or not to bundle (i.e.
Definition ESIDialect.h:39
constexpr StringRef extModPortInSuffix
Suffix all lowered input ports with this suffix. Defaults to nothing.
Definition ESIDialect.h:46
std::unique_ptr< OperationPass< ModuleOp > > createESIPortLoweringPass()
constexpr StringRef extModPortOutSuffix
Suffix all lowered output ports with this suffix. Defaults to nothing.
Definition ESIDialect.h:48
constexpr StringRef extModPortEmptySuffix
Suffix lowered empty ports with this suffix.
Definition ESIDialect.h:60
ModuleType fnToMod(Operation *op, ArrayRef< Attribute > inputNames, ArrayRef< Attribute > outputNames)
Definition HWTypes.cpp:1052
void error(Twine message)
Definition LSPUtils.cpp:16
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition esi.py:1
int run(Type[Generator] generator=CppGenerator, List[str] cmdline_args=sys.argv)
mlir::Type type
Definition HWTypes.h:32
This holds the name, type, direction of a module's ports.