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