CIRCT 22.0.0git
Loading...
Searching...
No Matches
ESIServices.cpp
Go to the documentation of this file.
1//===- ESIServices.cpp - Code related to ESI services ---------------------===//
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
21
22#include "mlir/IR/BuiltinTypes.h"
23#include "mlir/IR/ImplicitLocOpBuilder.h"
24#include "mlir/IR/SymbolTable.h"
25
26#include <memory>
27#include <utility>
28
29namespace circt {
30namespace esi {
31#define GEN_PASS_DEF_ESICONNECTSERVICES
32#include "circt/Dialect/ESI/ESIPasses.h.inc"
33} // namespace esi
34} // namespace circt
35
36using namespace circt;
37using namespace circt::esi;
38
39//===----------------------------------------------------------------------===//
40// C++ service generators.
41//===----------------------------------------------------------------------===//
42
43/// The generator for the "cosim" impl_type.
44static LogicalResult
45instantiateCosimEndpointOps(ServiceImplementReqOp implReq,
46 ServiceDeclOpInterface,
47 ServiceImplRecordOp implRecord) {
48 constexpr StringLiteral cosimCycleCountName = "Cosim_CycleCount";
49
50 auto *ctxt = implReq.getContext();
51 OpBuilder b(implReq);
52 Value clk = implReq.getOperand(0);
53 Value rst = implReq.getOperand(1);
54 Location reqLoc = implReq.getLoc();
55
56 if (implReq.getImplOpts()) {
57 auto opts = implReq.getImplOpts()->getValue();
58 for (auto nameAttr : opts) {
59 return implReq.emitOpError("did not recognize option name ")
60 << nameAttr.getName();
61 }
62 }
63
64 Block &connImplBlock = implRecord.getReqDetails().front();
65 implRecord.setIsEngine(true);
66 OpBuilder implRecords = OpBuilder::atBlockEnd(&connImplBlock);
67
68 // Assemble the name to use for an endpoint.
69 auto toStringAttr = [&](ArrayAttr strArr, StringAttr channelName) {
70 std::string buff;
71 llvm::raw_string_ostream os(buff);
72 llvm::interleave(
73 strArr.getAsRange<AppIDAttr>(), os,
74 [&](AppIDAttr appid) {
75 os << appid.getName().getValue();
76 if (appid.getIndex())
77 os << "[" << appid.getIndex() << "]";
78 },
79 ".");
80 os << "." << channelName.getValue();
81 return StringAttr::get(ctxt, os.str());
82 };
83
84 auto getAssignment = [&](StringAttr name, StringAttr channelName) {
85 DictionaryAttr assignment = b.getDictionaryAttr({
86 b.getNamedAttr("type", b.getStringAttr("cosim")),
87 b.getNamedAttr("name", channelName),
88 });
89 return b.getNamedAttr(name, assignment);
90 };
91
92 llvm::DenseMap<ServiceImplementConnReqOp, unsigned> toClientResultNum;
93 for (auto req : implReq.getOps<ServiceImplementConnReqOp>())
94 toClientResultNum[req] = toClientResultNum.size();
95
96 // Iterate through the requests, building a cosim endpoint for each channel in
97 // the bundle.
98 // TODO: The cosim op should probably be able to take a bundle type and get
99 // lowered to the SV primitive later on. The SV primitive will also need some
100 // work to suit this new world order, so let's put this off.
101 for (auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
102 Location loc = req->getLoc();
103 ChannelBundleType bundleType = req.getToClient().getType();
104 SmallVector<NamedAttribute, 8> channelAssignments;
105
106 SmallVector<Value, 8> toServerValues;
107 for (BundledChannel ch : bundleType.getChannels()) {
108 if (ch.direction == ChannelDirection::to) {
109 ChannelType fromHostType = ch.type;
110 if (fromHostType.getSignaling() == ChannelSignaling::FIFO)
111 fromHostType = b.getType<ChannelType>(fromHostType.getInner(),
112 ChannelSignaling::ValidReady,
113 fromHostType.getDataDelay());
114 auto cosim = CosimFromHostEndpointOp::create(
115 b, loc, fromHostType, clk, rst,
116 toStringAttr(req.getRelativeAppIDPathAttr(), ch.name));
117 mlir::TypedValue<ChannelType> fromHost = cosim.getFromHost();
118 if (fromHostType.getSignaling() == ChannelSignaling::FIFO)
119 fromHost = ChannelBufferOp::create(
120 b, loc, ch.type, clk, rst, fromHost,
121 /*stages=*/b.getIntegerAttr(b.getI64Type(), 1),
122 /*name=*/StringAttr())
123 .getOutput();
124 toServerValues.push_back(fromHost);
125 channelAssignments.push_back(getAssignment(ch.name, cosim.getIdAttr()));
126 }
127 }
128
129 auto pack =
130 PackBundleOp::create(b, implReq.getLoc(), bundleType, toServerValues);
131 implReq.getResult(toClientResultNum[req])
132 .replaceAllUsesWith(pack.getBundle());
133
134 size_t chanIdx = 0;
135 for (BundledChannel ch : bundleType.getChannels()) {
136 if (ch.direction == ChannelDirection::from) {
137 Value fromChannel = pack.getFromChannels()[chanIdx++];
138 auto chType = cast<ChannelType>(fromChannel.getType());
139 if (chType.getSignaling() == ChannelSignaling::FIFO) {
140 auto cosimType = b.getType<ChannelType>(chType.getInner(),
141 ChannelSignaling::ValidReady,
142 chType.getDataDelay());
143 fromChannel = ChannelBufferOp::create(
144 b, loc, cosimType, clk, rst, fromChannel,
145 /*stages=*/b.getIntegerAttr(b.getI64Type(), 1),
146 /*name=*/StringAttr())
147 .getOutput();
148 }
149 auto cosim = CosimToHostEndpointOp::create(
150 b, loc, clk, rst, fromChannel,
151 toStringAttr(req.getRelativeAppIDPathAttr(), ch.name));
152 channelAssignments.push_back(getAssignment(ch.name, cosim.getIdAttr()));
153 }
154 }
155
156 ServiceImplClientRecordOp::create(
157 implRecords, req.getLoc(), req.getRelativeAppIDPathAttr(),
158 req.getServicePortAttr(), TypeAttr::get(bundleType),
159 b.getDictionaryAttr(channelAssignments), DictionaryAttr());
160 }
161
162 // Create and instantiate the cycle counter module for cosim.
163 // Declare external Cosim_CycleCount module.
164 Attribute cycleCountParams[] = {
165 hw::ParamDeclAttr::get("CORE_CLOCK_FREQUENCY_HZ", b.getI64Type())};
166 hw::PortInfo cycleCountPorts[] = {
167 {{b.getStringAttr("clk"), seq::ClockType::get(ctxt),
168 hw::ModulePort::Direction::Input},
169 0},
170 {{b.getStringAttr("rst"), b.getI1Type(),
171 hw::ModulePort::Direction::Input},
172 1},
173 };
174 auto parentModule = implReq->getParentOfType<mlir::ModuleOp>();
175 auto cosimCycleCountExternModule =
176 parentModule.lookupSymbol<hw::HWModuleExternOp>(cosimCycleCountName);
177 if (!cosimCycleCountExternModule) {
178 auto ip = b.saveInsertionPoint();
179 b.setInsertionPointToEnd(parentModule.getBody());
180 if (auto existingSym = parentModule.lookupSymbol(cosimCycleCountName)) {
181 return implReq
182 .emitOpError(
183 "symbol 'Cosim_CycleCount' already exists but is not a "
184 "hw.module.extern")
185 .attachNote(existingSym->getLoc())
186 << "existing symbol here";
187 }
188 cosimCycleCountExternModule = hw::HWModuleExternOp::create(
189 b, reqLoc, b.getStringAttr(cosimCycleCountName), cycleCountPorts,
190 cosimCycleCountName, ArrayAttr::get(ctxt, cycleCountParams));
191 b.restoreInsertionPoint(ip);
192 }
193
194 // Instantiate the external Cosim_CycleCount module.
195 uint64_t coreClockFreq = 0;
196 if (auto coreClockFreqAttr = dyn_cast_or_null<IntegerAttr>(
197 implReq->getAttr("esi.core_clock_frequency_hz")))
198 if (coreClockFreqAttr.getType().isUnsignedInteger(64))
199 coreClockFreq = coreClockFreqAttr.getUInt();
200 hw::InstanceOp::create(
201 b, reqLoc, cosimCycleCountExternModule, "__cycle_counter",
202 ArrayRef<Value>({clk, rst}),
203 b.getArrayAttr({hw::ParamDeclAttr::get(
204 "CORE_CLOCK_FREQUENCY_HZ", b.getI64IntegerAttr(coreClockFreq))}));
205
206 // Erase the generation request.
207 implReq.erase();
208 return success();
209}
210
211// Generator for "sv_mem" implementation type. Emits SV ops for an unpacked
212// array, hopefully inferred as a memory to the SV compiler.
213static LogicalResult
214instantiateSystemVerilogMemory(ServiceImplementReqOp implReq,
215 ServiceDeclOpInterface decl,
216 ServiceImplRecordOp) {
217 if (!decl)
218 return implReq.emitOpError(
219 "Must specify a service declaration to use 'sv_mem'.");
220
221 ImplicitLocOpBuilder b(implReq.getLoc(), implReq);
222 BackedgeBuilder bb(b, implReq.getLoc());
223
224 RandomAccessMemoryDeclOp ramDecl =
225 dyn_cast<RandomAccessMemoryDeclOp>(decl.getOperation());
226 if (!ramDecl)
227 return implReq.emitOpError(
228 "'sv_mem' implementation type can only be used to "
229 "implement RandomAccessMemory declarations");
230
231 if (implReq.getNumOperands() != 2)
232 return implReq.emitOpError("Implementation requires clk and rst operands");
233 auto clk = implReq.getOperand(0);
234 auto rst = implReq.getOperand(1);
235 auto write = b.getStringAttr("write");
236 auto read = b.getStringAttr("read");
237 auto none = hw::ConstantOp::create(
238 b, APInt(/*numBits*/ 0, /*val*/ 0, /*isSigned*/ false));
239 auto i1 = b.getI1Type();
240 auto c0 = hw::ConstantOp::create(b, i1, 0);
241
242 // List of reqs which have a result.
243 SmallVector<ServiceImplementConnReqOp, 8> toClientReqs(
244 llvm::make_filter_range(
245 implReq.getOps<ServiceImplementConnReqOp>(),
246 [](auto req) { return req.getToClient() != nullptr; }));
247
248 // Assemble a mapping of toClient results to actual consumers.
249 DenseMap<Value, Value> outputMap;
250 for (auto [bout, reqout] :
251 llvm::zip_longest(toClientReqs, implReq.getResults())) {
252 assert(bout.has_value());
253 assert(reqout.has_value());
254 Value toClient = bout->getToClient();
255 outputMap[toClient] = *reqout;
256 }
257
258 // Create the SV memory.
259 hw::UnpackedArrayType memType =
260 hw::UnpackedArrayType::get(ramDecl.getInnerType(), ramDecl.getDepth());
261 auto mem =
262 sv::RegOp::create(b, memType, implReq.getServiceSymbolAttr().getAttr())
263 .getResult();
264
265 // Do everything which doesn't actually write to the memory, store the signals
266 // needed for the actual memory writes for later.
267 SmallVector<std::tuple<Value, Value, Value>> writeGoAddressData;
268 for (auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
269 auto port = req.getServicePort().getName();
270 Value toClientResp;
271
272 if (port == write) {
273 // If this pair is doing a write...
274
275 // Construct the response channel.
276 auto doneValid = bb.get(i1);
277 auto ackChannel = WrapValidReadyOp::create(b, none, doneValid);
278
279 auto pack =
280 PackBundleOp::create(b, implReq.getLoc(), req.getToClient().getType(),
281 ackChannel.getChanOutput());
282 Value toServer =
283 pack.getFromChannels()[RandomAccessMemoryDeclOp::ReqDirChannelIdx];
284 toClientResp = pack.getBundle();
285
286 // Unwrap the write request and 'explode' the struct.
287 auto unwrap =
288 UnwrapValidReadyOp::create(b, toServer, ackChannel.getReady());
289
290 Value address = hw::StructExtractOp::create(b, unwrap.getRawOutput(),
291 b.getStringAttr("address"));
292 Value data = hw::StructExtractOp::create(b, unwrap.getRawOutput(),
293 b.getStringAttr("data"));
294
295 // Determine if the write should occur this cycle.
296 auto go = comb::AndOp::create(b, unwrap.getValid(), unwrap.getReady());
297 go->setAttr("sv.namehint", b.getStringAttr("write_go"));
298 // Register the 'go' signal and use it as the done message.
299 doneValid.setValue(
300 seq::CompRegOp::create(b, go, clk, rst, c0, "write_done"));
301 // Store the necessary data for the 'always' memory writing block.
302 writeGoAddressData.push_back(std::make_tuple(go, address, data));
303
304 } else if (port == read) {
305 // If it's a read...
306
307 // Construct the response channel.
308 auto dataValid = bb.get(i1);
309 auto data = bb.get(ramDecl.getInnerType());
310 auto dataChannel = WrapValidReadyOp::create(b, data, dataValid);
311
312 auto pack =
313 PackBundleOp::create(b, implReq.getLoc(), req.getToClient().getType(),
314 dataChannel.getChanOutput());
315 Value toServer =
316 pack.getFromChannels()[RandomAccessMemoryDeclOp::RespDirChannelIdx];
317 toClientResp = pack.getBundle();
318
319 // Unwrap the requested address and read from that memory location.
320 auto addressUnwrap =
321 UnwrapValidReadyOp::create(b, toServer, dataChannel.getReady());
322 Value unsignedAddress = addressUnwrap.getRawOutput();
323 Value signlessAddress = hw::BitcastOp::create(
324 b, b.getIntegerType(llvm::Log2_64_Ceil(ramDecl.getDepth())),
325 unsignedAddress);
326 Value memLoc = sv::ArrayIndexInOutOp::create(b, mem, signlessAddress);
327 auto readData = sv::ReadInOutOp::create(b, memLoc);
328
329 // Set the data on the response.
330 data.setValue(readData);
331 dataValid.setValue(addressUnwrap.getValid());
332 } else {
333 assert(false && "Port should be either 'read' or 'write'");
334 }
335
336 outputMap[req.getToClient()].replaceAllUsesWith(toClientResp);
337 }
338
339 // Now construct the memory writes.
340 auto hwClk = seq::FromClockOp::create(b, clk);
341 sv::AlwaysFFOp::create(
342 b, sv::EventControl::AtPosEdge, hwClk, sv::ResetType::SyncReset,
343 sv::EventControl::AtPosEdge, rst, [&] {
344 for (auto [go, address, data] : writeGoAddressData) {
345 Value a = address, d = data; // So the lambda can capture.
346 // If we're told to go, do the write.
347 sv::IfOp::create(b, go, [&] {
348 Value signlessAddress = hw::BitcastOp::create(
349 b, b.getIntegerType(llvm::Log2_64_Ceil(ramDecl.getDepth())), a);
350 Value memLoc =
351 sv::ArrayIndexInOutOp::create(b, mem, signlessAddress);
352 sv::PAssignOp::create(b, memLoc, d);
353 });
354 }
355 });
356
357 implReq.erase();
358 return success();
359}
360
361//===----------------------------------------------------------------------===//
362// Service generator dispatcher.
363//===----------------------------------------------------------------------===//
364
365LogicalResult
366ServiceGeneratorDispatcher::generate(ServiceImplementReqOp req,
367 ServiceDeclOpInterface decl) {
368 // Lookup based on 'impl_type' attribute and pass through the generate request
369 // if found.
370 auto genF = genLookupTable.find(req.getImplTypeAttr().getValue());
371 if (genF == genLookupTable.end()) {
372 if (failIfNotFound)
373 return req.emitOpError("Could not find service generator for attribute '")
374 << req.getImplTypeAttr() << "'";
375 return success();
376 }
377
378 // Since we always need a record of generation, create it here then pass it to
379 // the generator for possible modification.
380 OpBuilder b(req);
381 auto implRecord = ServiceImplRecordOp::create(
382 b, req.getLoc(), req.getAppID(), /*isEngine=*/false,
383 req.getServiceSymbolAttr(), req.getStdServiceAttr(),
384 req.getImplTypeAttr(), b.getDictionaryAttr({}));
385 implRecord.getReqDetails().emplaceBlock();
386
387 return genF->second(req, decl, implRecord);
388}
389
391 DenseMap<StringRef, ServiceGeneratorDispatcher::ServiceGeneratorFunc>{
394 false);
395
399
402 genLookupTable[implType] = std::move(gen);
403}
404
405//===----------------------------------------------------------------------===//
406// Wire up services pass.
407//===----------------------------------------------------------------------===//
408
409namespace {
410/// Find all the modules and use the partial order of the instantiation DAG
411/// to sort them. If we use this order when "bubbling" up operations, we
412/// guarantee one-pass completeness. As a side-effect, populate the module to
413/// instantiation sites mapping.
414///
415/// Assumption (unchecked): there is not a cycle in the instantiation graph.
416struct ModuleSorter {
417protected:
418 SymbolCache topLevelSyms;
419 DenseMap<Operation *, SmallVector<igraph::InstanceOpInterface, 1>>
420 moduleInstantiations;
421
422 void getAndSortModules(ModuleOp topMod,
423 SmallVectorImpl<hw::HWModuleLike> &mods);
424 void getAndSortModulesVisitor(hw::HWModuleLike mod,
425 SmallVectorImpl<hw::HWModuleLike> &mods,
426 DenseSet<Operation *> &modsSeen);
427};
428} // namespace
429
430void ModuleSorter::getAndSortModules(ModuleOp topMod,
431 SmallVectorImpl<hw::HWModuleLike> &mods) {
432 // Add here _before_ we go deeper to prevent infinite recursion.
433 DenseSet<Operation *> modsSeen;
434 mods.clear();
435 moduleInstantiations.clear();
436 topMod.walk([&](hw::HWModuleLike mod) {
437 getAndSortModulesVisitor(mod, mods, modsSeen);
438 });
439}
440
441// Run a post-order DFS.
442void ModuleSorter::getAndSortModulesVisitor(
443 hw::HWModuleLike mod, SmallVectorImpl<hw::HWModuleLike> &mods,
444 DenseSet<Operation *> &modsSeen) {
445 if (modsSeen.contains(mod))
446 return;
447 modsSeen.insert(mod);
448
449 mod.walk([&](igraph::InstanceOpInterface inst) {
450 auto targetNameAttrs = inst.getReferencedModuleNamesAttr();
451 for (auto targetNameAttr : targetNameAttrs) {
452 Operation *modOp =
453 topLevelSyms.getDefinition(cast<StringAttr>(targetNameAttr));
454 assert(modOp);
455 moduleInstantiations[modOp].push_back(inst);
456 if (auto modLike = dyn_cast<hw::HWModuleLike>(modOp))
457 getAndSortModulesVisitor(modLike, mods, modsSeen);
458 }
459 });
460
461 mods.push_back(mod);
462}
463namespace {
464/// Implements a pass to connect up ESI services clients to the nearest server
465/// instantiation. Wires up the ports and generates a generation request to
466/// call a user-specified generator.
467struct ESIConnectServicesPass
468 : public circt::esi::impl::ESIConnectServicesBase<ESIConnectServicesPass>,
469 ModuleSorter {
470
471 ESIConnectServicesPass(const ServiceGeneratorDispatcher &gen)
472 : genDispatcher(gen) {}
473 ESIConnectServicesPass()
474 : genDispatcher(ServiceGeneratorDispatcher::globalDispatcher()) {}
475
476 void runOnOperation() override;
477
478 /// Convert connection requests to service implement connection requests,
479 /// which have a relative appid path instead of just an appid. Leave being a
480 /// record for the manifest of the original request.
481 void convertReq(RequestConnectionOp);
482
483 /// "Bubble up" the specified requests to all of the instantiations of the
484 /// module specified. Create and connect up ports to tunnel the ESI channels
485 /// through.
486 LogicalResult surfaceReqs(hw::HWMutableModuleLike,
487 ArrayRef<ServiceImplementConnReqOp>);
488
489 /// For any service which is "local" (provides the requested service) in a
490 /// module, replace it with a ServiceImplementOp. Said op is to be replaced
491 /// with an instantiation by a generator.
492 LogicalResult replaceInst(ServiceInstanceOp,
493 ArrayRef<ServiceImplementConnReqOp> portReqs);
494
495 /// Figure out which requests are "local" vs need to be surfaced. Call
496 /// 'surfaceReqs' and/or 'replaceInst' as appropriate.
497 LogicalResult process(hw::HWModuleLike);
498
499 /// If the servicePort is referring to a std service, return the name of it.
500 StringAttr getStdService(FlatSymbolRefAttr serviceSym);
501
502private:
503 ServiceGeneratorDispatcher genDispatcher;
504};
505} // anonymous namespace
506
507void ESIConnectServicesPass::runOnOperation() {
508 ModuleOp outerMod = getOperation();
509 topLevelSyms.addDefinitions(outerMod);
510
511 outerMod.walk([&](RequestConnectionOp req) { convertReq(req); });
512
513 // Get a partially-ordered list of modules based on the instantiation DAG.
514 // It's _very_ important that we process modules before their instantiations
515 // so that the modules where they're instantiated correctly process the
516 // surfaced connections.
517 SmallVector<hw::HWModuleLike, 64> sortedMods;
518 getAndSortModules(outerMod, sortedMods);
519
520 // Process each module.
521 for (auto mod : sortedMods) {
522 hw::HWModuleLike mutableMod = dyn_cast<hw::HWModuleLike>(*mod);
523 if (mutableMod && failed(process(mutableMod))) {
524 signalPassFailure();
525 return;
526 }
527 }
528}
529
530// Get the std service name, if any.
531StringAttr ESIConnectServicesPass::getStdService(FlatSymbolRefAttr svcSym) {
532 if (!svcSym)
533 return {};
534 Operation *svcDecl = topLevelSyms.getDefinition(svcSym);
535 if (!isa<CustomServiceDeclOp>(svcDecl))
536 return svcDecl->getName().getIdentifier();
537 return {};
538}
539
540void ESIConnectServicesPass::convertReq(RequestConnectionOp req) {
541 OpBuilder b(req);
542 auto newReq = ServiceImplementConnReqOp::create(
543 b, req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(),
544 ArrayAttr::get(&getContext(), {req.getAppIDAttr()}));
545 newReq->setDialectAttrs(req->getDialectAttrs());
546 req.getToClient().replaceAllUsesWith(newReq.getToClient());
547
548 // Emit a record of the original request.
549 ServiceRequestRecordOp::create(
550 b, req.getLoc(), req.getAppID(), req.getServicePortAttr(),
551 getStdService(req.getServicePortAttr().getModuleRef()),
552 req.getToClient().getType());
553 req.erase();
554}
555
556LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) {
557 // If 'mod' doesn't have a body, assume it's an external module.
558 if (mod->getNumRegions() == 0 || mod->getRegion(0).empty())
559 return success();
560
561 Block &modBlock = mod->getRegion(0).front();
562
563 // The non-local reqs which need to be surfaced from this module.
564 SetVector<ServiceImplementConnReqOp> nonLocalReqs;
565 // Index the local services and create blocks in which to put the requests.
566 llvm::MapVector<SymbolRefAttr, llvm::SetVector<ServiceImplementConnReqOp>>
567 localImplReqs;
568 for (auto instOp : modBlock.getOps<ServiceInstanceOp>())
569 localImplReqs[instOp.getServiceSymbolAttr()] = {};
570 // AFTER we assemble the local services table (and it will not change the
571 // location of the values), get the pointer to the default service instance,
572 // if any.
573 llvm::SetVector<ServiceImplementConnReqOp> *anyServiceInst = nullptr;
574 if (auto *defaultService = localImplReqs.find(SymbolRefAttr());
575 defaultService != localImplReqs.end())
576 anyServiceInst = &defaultService->second;
577
578 auto sortConnReqs = [&]() {
579 // Sort the various requests by destination.
580 for (auto req : llvm::make_early_inc_range(
581 mod.getBodyBlock()->getOps<ServiceImplementConnReqOp>())) {
582 auto service = req.getServicePort().getModuleRef();
583 auto *reqListIter = localImplReqs.find(service);
584 if (reqListIter != localImplReqs.end())
585 reqListIter->second.insert(req);
586 else if (anyServiceInst)
587 anyServiceInst->insert(req);
588 else
589 nonLocalReqs.insert(req);
590 }
591 };
592 // Bootstrap the sorting.
593 sortConnReqs();
594
595 // Replace each service instance with a generation request. If a service
596 // generator is registered, generate the server.
597 for (auto instOp :
598 llvm::make_early_inc_range(modBlock.getOps<ServiceInstanceOp>())) {
599 auto portReqs = localImplReqs[instOp.getServiceSymbolAttr()];
600 if (failed(replaceInst(instOp, portReqs.getArrayRef())))
601 return failure();
602
603 // Find any new requests which were created by a generator.
604 for (RequestConnectionOp req : llvm::make_early_inc_range(
605 mod.getBodyBlock()->getOps<RequestConnectionOp>()))
606 convertReq(req);
607 sortConnReqs();
608 }
609
610 // Surface all of the requests which cannot be fulfilled locally.
611 if (nonLocalReqs.empty())
612 return success();
613
614 if (auto mutableMod = dyn_cast<hw::HWMutableModuleLike>(mod.getOperation()))
615 return surfaceReqs(mutableMod, nonLocalReqs.getArrayRef());
616 return mod.emitOpError(
617 "Cannot surface requests through module without mutable ports");
618}
619
620LogicalResult ESIConnectServicesPass::replaceInst(
621 ServiceInstanceOp instOp, ArrayRef<ServiceImplementConnReqOp> portReqs) {
622 auto declSym = instOp.getServiceSymbolAttr();
623 ServiceDeclOpInterface decl;
624 if (declSym) {
625 decl = dyn_cast_or_null<ServiceDeclOpInterface>(
626 topLevelSyms.getDefinition(declSym));
627 if (!decl)
628 return instOp.emitOpError("Could not find service declaration ")
629 << declSym;
630 }
631
632 // Compute the result types for the new op -- the instance op's output types
633 // + the to_client types.
634 SmallVector<Type, 8> resultTypes(instOp.getResultTypes().begin(),
635 instOp.getResultTypes().end());
636 for (auto req : portReqs)
637 resultTypes.push_back(req.getBundleType());
638
639 // Create the generation request op.
640 OpBuilder b(instOp);
641 auto implOp = ServiceImplementReqOp::create(
642 b, instOp.getLoc(), resultTypes, instOp.getAppIDAttr(),
643 instOp.getServiceSymbolAttr(), instOp.getImplTypeAttr(),
644 getStdService(declSym), instOp.getImplOptsAttr(), instOp.getOperands());
645 implOp->setDialectAttrs(instOp->getDialectAttrs());
646 Block &reqBlock = implOp.getPortReqs().emplaceBlock();
647
648 // Update the users.
649 for (auto [n, o] : llvm::zip(implOp.getResults(), instOp.getResults()))
650 o.replaceAllUsesWith(n);
651 unsigned instOpNumResults = instOp.getNumResults();
652 for (size_t idx = 0, e = portReqs.size(); idx < e; ++idx) {
653 ServiceImplementConnReqOp req = portReqs[idx];
654 req.getToClient().replaceAllUsesWith(
655 implOp.getResult(idx + instOpNumResults));
656 }
657
658 for (auto req : portReqs)
659 req->moveBefore(&reqBlock, reqBlock.end());
660
661 // Erase the instance first in case it consumes any channels or bundles. If it
662 // does, the service generator will fail to verify the IR as there will be
663 // multiple uses.
664 instOp.erase();
665
666 // Try to generate the service provider.
667 if (failed(genDispatcher.generate(implOp, decl)))
668 return implOp.emitOpError("failed to generate server");
669
670 return success();
671}
672
673LogicalResult
674ESIConnectServicesPass::surfaceReqs(hw::HWMutableModuleLike mod,
675 ArrayRef<ServiceImplementConnReqOp> reqs) {
676 auto *ctxt = mod.getContext();
677 Block *body = &mod->getRegion(0).front();
678
679 // Track initial operand/result counts and the new IO.
680 unsigned origNumInputs = mod.getNumInputPorts();
681 SmallVector<std::pair<unsigned, hw::PortInfo>> newInputs;
682
683 // Assemble a port name from an array.
684 auto getPortName = [&](ArrayAttr namePath) {
685 std::string portName;
686 llvm::raw_string_ostream nameOS(portName);
687 llvm::interleave(
688 namePath.getAsRange<AppIDAttr>(), nameOS,
689 [&](AppIDAttr appid) {
690 nameOS << appid.getName().getValue();
691 if (appid.getIndex())
692 nameOS << "_" << appid.getIndex();
693 },
694 ".");
695 return StringAttr::get(ctxt, nameOS.str());
696 };
697
698 for (auto req : reqs)
699 if (req->getParentWithTrait<OpTrait::IsIsolatedFromAbove>() != mod)
700 return req.emitOpError(
701 "Cannot surface requests through isolated from above ops");
702
703 // Insert new module input ESI ports.
704 for (auto req : reqs) {
705 newInputs.push_back(std::make_pair(
706 origNumInputs,
707 hw::PortInfo{{getPortName(req.getRelativeAppIDPathAttr()),
708 req.getBundleType(), hw::ModulePort::Direction::Input},
709 origNumInputs,
710 {},
711 req->getLoc()}));
712
713 // Replace uses with new block args which will correspond to said ports.
714 Value replValue = body->addArgument(req.getBundleType(), req->getLoc());
715 req.getToClient().replaceAllUsesWith(replValue);
716 }
717 mod.insertPorts(newInputs, {});
718
719 // Prepend a name to the instance tracking array.
720 auto prependNamePart = [&](ArrayAttr appIDPath, AppIDAttr appID) {
721 SmallVector<Attribute, 8> newAppIDPath;
722 newAppIDPath.push_back(appID);
723 newAppIDPath.append(appIDPath.begin(), appIDPath.end());
724 return ArrayAttr::get(appIDPath.getContext(), newAppIDPath);
725 };
726
727 // Update the module instantiations.
728 SmallVector<igraph::InstanceOpInterface, 1> newModuleInstantiations;
729 for (auto inst : moduleInstantiations[mod]) {
730 OpBuilder b(inst);
731
732 // Add new inputs for the new bundles being requested.
733 SmallVector<Value, 16> newOperands;
734 for (auto req : reqs) {
735 // If the instance has an AppID, prepend it.
736 ArrayAttr appIDPath = req.getRelativeAppIDPathAttr();
737 if (auto instAppID = dyn_cast_or_null<AppIDAttr>(
738 inst->getDiscardableAttr(AppIDAttr::AppIDAttrName)))
739 appIDPath = prependNamePart(appIDPath, instAppID);
740
741 // Clone the request.
742 auto clone = ServiceImplementConnReqOp::create(
743 b, req.getLoc(), req.getToClient().getType(),
744 req.getServicePortAttr(), appIDPath);
745 clone->setDialectAttrs(req->getDialectAttrs());
746 newOperands.push_back(clone.getToClient());
747 }
748 inst->insertOperands(inst->getNumOperands(), newOperands);
749 // Set the names, if we know how.
750 if (auto hwInst = dyn_cast<hw::InstanceOp>(*inst))
751 hwInst.setArgNamesAttr(b.getArrayAttr(mod.getInputNames()));
752 }
753
754 // Erase the original requests since they have been cloned into the proper
755 // destination modules.
756 for (auto req : reqs)
757 req.erase();
758 return success();
759}
760
761std::unique_ptr<OperationPass<ModuleOp>>
763 return std::make_unique<ESIConnectServicesPass>();
764}
assert(baseType &&"element must be base type")
static ServiceGeneratorDispatcher globalDispatcher(DenseMap< StringRef, ServiceGeneratorDispatcher::ServiceGeneratorFunc >{ {"cosim", instantiateCosimEndpointOps}, {"sv_mem", instantiateSystemVerilogMemory}}, false)
static LogicalResult instantiateCosimEndpointOps(ServiceImplementReqOp implReq, ServiceDeclOpInterface, ServiceImplRecordOp implRecord)
The generator for the "cosim" impl_type.
static LogicalResult instantiateSystemVerilogMemory(ServiceImplementReqOp implReq, ServiceDeclOpInterface decl, ServiceImplRecordOp)
static EvaluatorValuePtr unwrap(OMEvaluatorValue c)
Definition OM.cpp:111
static Block * getBodyBlock(FModuleLike mod)
Instantiate one of these and use it to build typed backedges.
Backedge get(mlir::Type resultType, mlir::LocationAttr optionalLoc={})
Create a typed backedge.
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
Definition SymCache.h:85
Class which "dispatches" a service implementation request to its specified generator.
Definition ESIServices.h:24
void registerGenerator(StringRef implType, ServiceGeneratorFunc gen)
Add a generator to this registry.
LogicalResult generate(ServiceImplementReqOp, ServiceDeclOpInterface)
Generate a service implementation if a generator exists in this registry.
static ServiceGeneratorDispatcher & globalDispatcher()
Get the global dispatcher.
DenseMap< StringRef, ServiceGeneratorFunc > genLookupTable
Definition ESIServices.h:52
std::function< LogicalResult(ServiceImplementReqOp, ServiceDeclOpInterface, ServiceImplRecordOp)> ServiceGeneratorFunc
Definition ESIServices.h:28
create(data_type, value)
Definition hw.py:441
create(data_type, value)
Definition hw.py:433
create(struct_value, str field_name)
Definition hw.py:556
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
Definition seq.py:157
create(value)
Definition sv.py:108
std::unique_ptr< OperationPass< ModuleOp > > createESIConnectServicesPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition esi.py:1
This holds the name, type, direction of a module's ports.