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