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