CIRCT 23.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),
167 hw::ModulePort::Direction::Input},
168 0},
169 {{b.getStringAttr("rst"), b.getI1Type(),
170 hw::ModulePort::Direction::Input},
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::RegOp::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 = WrapValidReadyOp::create(b, none, doneValid);
277
278 auto pack =
279 PackBundleOp::create(b, implReq.getLoc(), req.getToClient().getType(),
280 ackChannel.getChanOutput());
281 Value toServer =
282 pack.getFromChannels()[RandomAccessMemoryDeclOp::ReqDirChannelIdx];
283 toClientResp = pack.getBundle();
284
285 // Unwrap the write request and 'explode' the struct.
286 auto unwrap =
287 UnwrapValidReadyOp::create(b, toServer, ackChannel.getReady());
288
289 Value address = hw::StructExtractOp::create(b, unwrap.getRawOutput(),
290 b.getStringAttr("address"));
291 Value data = hw::StructExtractOp::create(b, unwrap.getRawOutput(),
292 b.getStringAttr("data"));
293
294 // Determine if the write should occur this cycle.
295 auto go = comb::AndOp::create(b, unwrap.getValid(), unwrap.getReady());
296 go->setAttr("sv.namehint", b.getStringAttr("write_go"));
297 // Register the 'go' signal and use it as the done message.
298 doneValid.setValue(
299 seq::CompRegOp::create(b, go, clk, rst, c0, "write_done"));
300 // Store the necessary data for the 'always' memory writing block.
301 writeGoAddressData.push_back(std::make_tuple(go, address, data));
302
303 } else if (port == read) {
304 // If it's a read...
305
306 // Construct the response channel.
307 auto dataValid = bb.get(i1);
308 auto data = bb.get(ramDecl.getInnerType());
309 auto dataChannel = WrapValidReadyOp::create(b, data, dataValid);
310
311 auto pack =
312 PackBundleOp::create(b, implReq.getLoc(), req.getToClient().getType(),
313 dataChannel.getChanOutput());
314 Value toServer =
315 pack.getFromChannels()[RandomAccessMemoryDeclOp::RespDirChannelIdx];
316 toClientResp = pack.getBundle();
317
318 // Unwrap the requested address and read from that memory location.
319 auto addressUnwrap =
320 UnwrapValidReadyOp::create(b, toServer, dataChannel.getReady());
321 Value unsignedAddress = addressUnwrap.getRawOutput();
322 Value signlessAddress = hw::BitcastOp::create(
323 b, b.getIntegerType(llvm::Log2_64_Ceil(ramDecl.getDepth())),
324 unsignedAddress);
325 Value memLoc = sv::ArrayIndexInOutOp::create(b, mem, signlessAddress);
326 auto readData = sv::ReadInOutOp::create(b, memLoc);
327
328 // Set the data on the response.
329 data.setValue(readData);
330 dataValid.setValue(addressUnwrap.getValid());
331 } else {
332 assert(false && "Port should be either 'read' or 'write'");
333 }
334
335 outputMap[req.getToClient()].replaceAllUsesWith(toClientResp);
336 }
337
338 // Now construct the memory writes.
339 auto hwClk = seq::FromClockOp::create(b, clk);
340 sv::AlwaysFFOp::create(
341 b, sv::EventControl::AtPosEdge, hwClk, sv::ResetType::SyncReset,
342 sv::EventControl::AtPosEdge, rst, [&] {
343 for (auto [go, address, data] : writeGoAddressData) {
344 Value a = address, d = data; // So the lambda can capture.
345 // If we're told to go, do the write.
346 sv::IfOp::create(b, go, [&] {
347 Value signlessAddress = hw::BitcastOp::create(
348 b, b.getIntegerType(llvm::Log2_64_Ceil(ramDecl.getDepth())), a);
349 Value memLoc =
350 sv::ArrayIndexInOutOp::create(b, mem, signlessAddress);
351 sv::PAssignOp::create(b, memLoc, d);
352 });
353 }
354 });
355
356 implReq.erase();
357 return success();
358}
359
360//===----------------------------------------------------------------------===//
361// Service generator dispatcher.
362//===----------------------------------------------------------------------===//
363
364LogicalResult
365ServiceGeneratorDispatcher::generate(ServiceImplementReqOp req,
366 ServiceDeclOpInterface decl) {
367 // Lookup based on 'impl_type' attribute and pass through the generate request
368 // if found.
369 auto genF = genLookupTable.find(req.getImplTypeAttr().getValue());
370 if (genF == genLookupTable.end()) {
371 if (failIfNotFound)
372 return req.emitOpError("Could not find service generator for attribute '")
373 << req.getImplTypeAttr() << "'";
374 return success();
375 }
376
377 // Since we always need a record of generation, create it here then pass it to
378 // the generator for possible modification.
379 OpBuilder b(req);
380 auto implRecord = ServiceImplRecordOp::create(
381 b, req.getLoc(), req.getAppID(), /*isEngine=*/UnitAttr(),
382 req.getServiceSymbolAttr(), req.getStdServiceAttr(),
383 req.getImplTypeAttr(), b.getDictionaryAttr({}));
384 implRecord.getReqDetails().emplaceBlock();
385
386 return genF->second(req, decl, implRecord);
387}
388
390 DenseMap<StringRef, ServiceGeneratorDispatcher::ServiceGeneratorFunc>{
393 false);
394
398
401 genLookupTable[implType] = std::move(gen);
402}
403
404//===----------------------------------------------------------------------===//
405// Wire up services pass.
406//===----------------------------------------------------------------------===//
407
408namespace {
409/// Find all the modules and use the partial order of the instantiation DAG
410/// to sort them. If we use this order when "bubbling" up operations, we
411/// guarantee one-pass completeness. As a side-effect, populate the module to
412/// instantiation sites mapping.
413///
414/// Assumption (unchecked): there is not a cycle in the instantiation graph.
415struct ModuleSorter {
416protected:
417 SymbolCache topLevelSyms;
418 DenseMap<Operation *, SmallVector<igraph::InstanceOpInterface, 1>>
419 moduleInstantiations;
420
421 void getAndSortModules(ModuleOp topMod,
422 SmallVectorImpl<hw::HWModuleLike> &mods);
423 void getAndSortModulesVisitor(hw::HWModuleLike mod,
424 SmallVectorImpl<hw::HWModuleLike> &mods,
425 DenseSet<Operation *> &modsSeen);
426};
427} // namespace
428
429void ModuleSorter::getAndSortModules(ModuleOp topMod,
430 SmallVectorImpl<hw::HWModuleLike> &mods) {
431 // Add here _before_ we go deeper to prevent infinite recursion.
432 DenseSet<Operation *> modsSeen;
433 mods.clear();
434 moduleInstantiations.clear();
435 topMod.walk([&](hw::HWModuleLike mod) {
436 getAndSortModulesVisitor(mod, mods, modsSeen);
437 });
438}
439
440// Run a post-order DFS.
441void ModuleSorter::getAndSortModulesVisitor(
442 hw::HWModuleLike mod, SmallVectorImpl<hw::HWModuleLike> &mods,
443 DenseSet<Operation *> &modsSeen) {
444 if (modsSeen.contains(mod))
445 return;
446 modsSeen.insert(mod);
447
448 mod.walk([&](igraph::InstanceOpInterface inst) {
449 auto targetNameAttrs = inst.getReferencedModuleNamesAttr();
450 for (auto targetNameAttr : targetNameAttrs) {
451 Operation *modOp =
452 topLevelSyms.getDefinition(cast<StringAttr>(targetNameAttr));
453 assert(modOp);
454 moduleInstantiations[modOp].push_back(inst);
455 if (auto modLike = dyn_cast<hw::HWModuleLike>(modOp))
456 getAndSortModulesVisitor(modLike, mods, modsSeen);
457 }
458 });
459
460 mods.push_back(mod);
461}
462namespace {
463/// Implements a pass to connect up ESI services clients to the nearest server
464/// instantiation. Wires up the ports and generates a generation request to
465/// call a user-specified generator.
466struct ESIConnectServicesPass
467 : public circt::esi::impl::ESIConnectServicesBase<ESIConnectServicesPass>,
468 ModuleSorter {
469
470 ESIConnectServicesPass(const ServiceGeneratorDispatcher &gen)
471 : genDispatcher(gen) {}
472 ESIConnectServicesPass()
473 : genDispatcher(ServiceGeneratorDispatcher::globalDispatcher()) {}
474
475 void runOnOperation() override;
476
477 /// Convert connection requests to service implement connection requests,
478 /// which have a relative appid path instead of just an appid. Leave being a
479 /// record for the manifest of the original request.
480 void convertReq(RequestConnectionOp);
481
482 /// "Bubble up" the specified requests to all of the instantiations of the
483 /// module specified. Create and connect up ports to tunnel the ESI channels
484 /// through.
485 LogicalResult surfaceReqs(hw::HWMutableModuleLike,
486 ArrayRef<ServiceImplementConnReqOp>);
487
488 /// For any service which is "local" (provides the requested service) in a
489 /// module, replace it with a ServiceImplementOp. Said op is to be replaced
490 /// with an instantiation by a generator.
491 LogicalResult replaceInst(ServiceInstanceOp,
492 ArrayRef<ServiceImplementConnReqOp> portReqs);
493
494 /// Figure out which requests are "local" vs need to be surfaced. Call
495 /// 'surfaceReqs' and/or 'replaceInst' as appropriate.
496 LogicalResult process(hw::HWModuleLike);
497
498 /// If the servicePort is referring to a std service, return the name of it.
499 StringAttr getStdService(FlatSymbolRefAttr serviceSym);
500
501private:
502 ServiceGeneratorDispatcher genDispatcher;
503};
504} // anonymous namespace
505
506void ESIConnectServicesPass::runOnOperation() {
507 ModuleOp outerMod = getOperation();
508 topLevelSyms.addDefinitions(outerMod);
509
510 outerMod.walk([&](RequestConnectionOp req) { convertReq(req); });
511
512 // Get a partially-ordered list of modules based on the instantiation DAG.
513 // It's _very_ important that we process modules before their instantiations
514 // so that the modules where they're instantiated correctly process the
515 // surfaced connections.
516 SmallVector<hw::HWModuleLike, 64> sortedMods;
517 getAndSortModules(outerMod, sortedMods);
518
519 // Process each module.
520 for (auto mod : sortedMods) {
521 hw::HWModuleLike mutableMod = dyn_cast<hw::HWModuleLike>(*mod);
522 if (mutableMod && failed(process(mutableMod))) {
523 signalPassFailure();
524 return;
525 }
526 }
527}
528
529// Get the std service name, if any.
530StringAttr ESIConnectServicesPass::getStdService(FlatSymbolRefAttr svcSym) {
531 if (!svcSym)
532 return {};
533 Operation *svcDecl = topLevelSyms.getDefinition(svcSym);
534 if (!isa<CustomServiceDeclOp>(svcDecl))
535 return svcDecl->getName().getIdentifier();
536 return {};
537}
538
539void ESIConnectServicesPass::convertReq(RequestConnectionOp req) {
540 OpBuilder b(req);
541 auto newReq = ServiceImplementConnReqOp::create(
542 b, req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(),
543 ArrayAttr::get(&getContext(), {req.getAppIDAttr()}));
544 newReq->setDialectAttrs(req->getDialectAttrs());
545 req.getToClient().replaceAllUsesWith(newReq.getToClient());
546
547 // Emit a record of the original request.
548 ServiceRequestRecordOp::create(
549 b, req.getLoc(), req.getAppID(), req.getServicePortAttr(),
550 getStdService(req.getServicePortAttr().getModuleRef()),
551 req.getToClient().getType());
552 req.erase();
553}
554
555LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) {
556 // If 'mod' doesn't have a body, assume it's an external module.
557 if (mod->getNumRegions() == 0 || mod->getRegion(0).empty())
558 return success();
559
560 Block &modBlock = mod->getRegion(0).front();
561
562 // The non-local reqs which need to be surfaced from this module.
563 SetVector<ServiceImplementConnReqOp> nonLocalReqs;
564 // Index the local services and create blocks in which to put the requests.
565 llvm::MapVector<SymbolRefAttr, llvm::SetVector<ServiceImplementConnReqOp>>
566 localImplReqs;
567 for (auto instOp : modBlock.getOps<ServiceInstanceOp>())
568 localImplReqs[instOp.getServiceSymbolAttr()] = {};
569 // AFTER we assemble the local services table (and it will not change the
570 // location of the values), get the pointer to the default service instance,
571 // if any.
572 llvm::SetVector<ServiceImplementConnReqOp> *anyServiceInst = nullptr;
573 if (auto *defaultService = localImplReqs.find(SymbolRefAttr());
574 defaultService != localImplReqs.end())
575 anyServiceInst = &defaultService->second;
576
577 auto sortConnReqs = [&]() {
578 // Sort the various requests by destination.
579 for (auto req : llvm::make_early_inc_range(
580 mod.getBodyBlock()->getOps<ServiceImplementConnReqOp>())) {
581 auto service = req.getServicePort().getModuleRef();
582 auto *reqListIter = localImplReqs.find(service);
583 if (reqListIter != localImplReqs.end())
584 reqListIter->second.insert(req);
585 else if (anyServiceInst)
586 anyServiceInst->insert(req);
587 else
588 nonLocalReqs.insert(req);
589 }
590 };
591 // Bootstrap the sorting.
592 sortConnReqs();
593
594 // Replace each service instance with a generation request. If a service
595 // generator is registered, generate the server.
596 for (auto instOp :
597 llvm::make_early_inc_range(modBlock.getOps<ServiceInstanceOp>())) {
598 auto portReqs = localImplReqs[instOp.getServiceSymbolAttr()];
599 if (failed(replaceInst(instOp, portReqs.getArrayRef())))
600 return failure();
601
602 // Find any new requests which were created by a generator.
603 for (RequestConnectionOp req : llvm::make_early_inc_range(
604 mod.getBodyBlock()->getOps<RequestConnectionOp>()))
605 convertReq(req);
606 sortConnReqs();
607 }
608
609 // Surface all of the requests which cannot be fulfilled locally.
610 if (nonLocalReqs.empty())
611 return success();
612
613 if (auto mutableMod = dyn_cast<hw::HWMutableModuleLike>(mod.getOperation()))
614 return surfaceReqs(mutableMod, nonLocalReqs.getArrayRef());
615 return mod.emitOpError(
616 "Cannot surface requests through module without mutable ports");
617}
618
619LogicalResult ESIConnectServicesPass::replaceInst(
620 ServiceInstanceOp instOp, ArrayRef<ServiceImplementConnReqOp> portReqs) {
621 auto declSym = instOp.getServiceSymbolAttr();
622 ServiceDeclOpInterface decl;
623 if (declSym) {
624 decl = dyn_cast_or_null<ServiceDeclOpInterface>(
625 topLevelSyms.getDefinition(declSym));
626 if (!decl)
627 return instOp.emitOpError("Could not find service declaration ")
628 << declSym;
629 }
630
631 // Compute the result types for the new op -- the instance op's output types
632 // + the to_client types.
633 SmallVector<Type, 8> resultTypes(instOp.getResultTypes().begin(),
634 instOp.getResultTypes().end());
635 for (auto req : portReqs)
636 resultTypes.push_back(req.getBundleType());
637
638 // Create the generation request op.
639 OpBuilder b(instOp);
640 auto implOp = ServiceImplementReqOp::create(
641 b, instOp.getLoc(), resultTypes, instOp.getAppIDAttr(),
642 instOp.getServiceSymbolAttr(), instOp.getImplTypeAttr(),
643 getStdService(declSym), instOp.getImplOptsAttr(), instOp.getOperands());
644 implOp->setDialectAttrs(instOp->getDialectAttrs());
645 Block &reqBlock = implOp.getPortReqs().emplaceBlock();
646
647 // Update the users.
648 for (auto [n, o] : llvm::zip(implOp.getResults(), instOp.getResults()))
649 o.replaceAllUsesWith(n);
650 unsigned instOpNumResults = instOp.getNumResults();
651 for (size_t idx = 0, e = portReqs.size(); idx < e; ++idx) {
652 ServiceImplementConnReqOp req = portReqs[idx];
653 req.getToClient().replaceAllUsesWith(
654 implOp.getResult(idx + instOpNumResults));
655 }
656
657 for (auto req : portReqs)
658 req->moveBefore(&reqBlock, reqBlock.end());
659
660 // Erase the instance first in case it consumes any channels or bundles. If it
661 // does, the service generator will fail to verify the IR as there will be
662 // multiple uses.
663 instOp.erase();
664
665 // Try to generate the service provider.
666 if (failed(genDispatcher.generate(implOp, decl)))
667 return implOp.emitOpError("failed to generate server");
668
669 return success();
670}
671
672LogicalResult
673ESIConnectServicesPass::surfaceReqs(hw::HWMutableModuleLike mod,
674 ArrayRef<ServiceImplementConnReqOp> reqs) {
675 auto *ctxt = mod.getContext();
676 Block *body = &mod->getRegion(0).front();
677
678 // Track initial operand/result counts and the new IO.
679 unsigned origNumInputs = mod.getNumInputPorts();
680 SmallVector<std::pair<unsigned, hw::PortInfo>> newInputs;
681
682 // Assemble a port name from an array.
683 auto getPortName = [&](ArrayAttr namePath) {
684 std::string portName;
685 llvm::raw_string_ostream nameOS(portName);
686 llvm::interleave(
687 namePath.getAsRange<AppIDAttr>(), nameOS,
688 [&](AppIDAttr appid) {
689 nameOS << appid.getName().getValue();
690 if (appid.getIndex())
691 nameOS << "_" << appid.getIndex();
692 },
693 ".");
694 return StringAttr::get(ctxt, nameOS.str());
695 };
696
697 for (auto req : reqs)
698 if (req->getParentWithTrait<OpTrait::IsIsolatedFromAbove>() != mod)
699 return req.emitOpError(
700 "Cannot surface requests through isolated from above ops");
701
702 // Insert new module input ESI ports.
703 for (auto req : reqs) {
704 newInputs.push_back(std::make_pair(
705 origNumInputs,
706 hw::PortInfo{{getPortName(req.getRelativeAppIDPathAttr()),
707 req.getBundleType(), hw::ModulePort::Direction::Input},
708 origNumInputs,
709 {},
710 req->getLoc()}));
711
712 // Replace uses with new block args which will correspond to said ports.
713 Value replValue = body->addArgument(req.getBundleType(), req->getLoc());
714 req.getToClient().replaceAllUsesWith(replValue);
715 }
716 mod.insertPorts(newInputs, {});
717
718 // Prepend a name to the instance tracking array.
719 auto prependNamePart = [&](ArrayAttr appIDPath, AppIDAttr appID) {
720 SmallVector<Attribute, 8> newAppIDPath;
721 newAppIDPath.push_back(appID);
722 newAppIDPath.append(appIDPath.begin(), appIDPath.end());
723 return ArrayAttr::get(appIDPath.getContext(), newAppIDPath);
724 };
725
726 // Update the module instantiations.
727 SmallVector<igraph::InstanceOpInterface, 1> newModuleInstantiations;
728 for (auto inst : moduleInstantiations[mod]) {
729 OpBuilder b(inst);
730
731 // Add new inputs for the new bundles being requested.
732 SmallVector<Value, 16> newOperands;
733 for (auto req : reqs) {
734 // If the instance has an AppID, prepend it.
735 ArrayAttr appIDPath = req.getRelativeAppIDPathAttr();
736 if (auto instAppID = dyn_cast_or_null<AppIDAttr>(
737 inst->getDiscardableAttr(AppIDAttr::AppIDAttrName)))
738 appIDPath = prependNamePart(appIDPath, instAppID);
739
740 // Clone the request.
741 auto clone = ServiceImplementConnReqOp::create(
742 b, req.getLoc(), req.getToClient().getType(),
743 req.getServicePortAttr(), appIDPath);
744 clone->setDialectAttrs(req->getDialectAttrs());
745 newOperands.push_back(clone.getToClient());
746 }
747 inst->insertOperands(inst->getNumOperands(), newOperands);
748 // Set the names, if we know how.
749 if (auto hwInst = dyn_cast<hw::InstanceOp>(*inst))
750 hwInst.setArgNamesAttr(b.getArrayAttr(mod.getInputNames()));
751 }
752
753 // Erase the original requests since they have been cloned into the proper
754 // destination modules.
755 for (auto req : reqs)
756 req.erase();
757 return success();
758}
759
760std::unique_ptr<OperationPass<ModuleOp>>
762 return std::make_unique<ESIConnectServicesPass>();
763}
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:568
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.