22 #include "mlir/IR/BuiltinTypes.h"
23 #include "mlir/IR/ImplicitLocOpBuilder.h"
30 #define GEN_PASS_DEF_ESICONNECTSERVICES
31 #include "circt/Dialect/ESI/ESIPasses.h.inc"
35 using namespace circt;
45 ServiceDeclOpInterface,
46 ServiceImplRecordOp implRecord) {
47 auto *
ctxt = implReq.getContext();
49 Value clk = implReq.getOperand(0);
50 Value rst = implReq.getOperand(1);
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();
60 Block &connImplBlock = implRecord.getReqDetails().front();
61 OpBuilder implRecords = OpBuilder::atBlockEnd(&connImplBlock);
64 auto toStringAttr = [&](ArrayAttr strArr, StringAttr channelName) {
66 llvm::raw_string_ostream os(buff);
68 strArr.getAsRange<AppIDAttr>(), os,
69 [&](AppIDAttr appid) {
70 os << appid.getName().getValue();
72 os <<
"[" << appid.getIndex() <<
"]";
75 os <<
"." << channelName.getValue();
79 auto getAssignment = [&](StringAttr name, StringAttr channelName) {
80 DictionaryAttr assignment = b.getDictionaryAttr({
81 b.getNamedAttr(
"type", b.getStringAttr(
"cosim")),
82 b.getNamedAttr(
"name", channelName),
84 return b.getNamedAttr(name, assignment);
87 llvm::DenseMap<ServiceImplementConnReqOp, unsigned> toClientResultNum;
88 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>())
89 toClientResultNum[req] = toClientResultNum.size();
96 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
97 Location loc = req->getLoc();
98 ChannelBundleType bundleType = req.getToClient().getType();
99 SmallVector<NamedAttribute, 8> channelAssignments;
101 SmallVector<Value, 8> toServerValues;
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()));
113 b.create<PackBundleOp>(implReq.getLoc(), bundleType, toServerValues);
114 implReq.getResult(toClientResultNum[req])
115 .replaceAllUsesWith(pack.getBundle());
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()));
127 implRecords.create<ServiceImplClientRecordOp>(
128 req.getLoc(), req.getRelativeAppIDPathAttr(), req.getServicePortAttr(),
129 TypeAttr::get(bundleType), b.getDictionaryAttr(channelAssignments),
142 ServiceDeclOpInterface decl,
143 ServiceImplRecordOp) {
145 return implReq.emitOpError(
146 "Must specify a service declaration to use 'sv_mem'.");
148 ImplicitLocOpBuilder b(implReq.getLoc(), implReq);
151 RandomAccessMemoryDeclOp ramDecl =
152 dyn_cast<RandomAccessMemoryDeclOp>(decl.getOperation());
154 return implReq.emitOpError(
155 "'sv_mem' implementation type can only be used to "
156 "implement RandomAccessMemory declarations");
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");
165 APInt( 0, 0,
false));
166 auto i1 = b.getI1Type();
170 SmallVector<ServiceImplementConnReqOp, 8> toClientReqs(
171 llvm::make_filter_range(
172 implReq.getOps<ServiceImplementConnReqOp>(),
173 [](
auto req) { return req.getToClient() != nullptr; }));
176 DenseMap<Value, Value> outputMap;
177 for (
auto [bout, reqout] :
178 llvm::zip_longest(toClientReqs, implReq.getResults())) {
180 assert(reqout.has_value());
181 Value toClient = bout->getToClient();
182 outputMap[toClient] = *reqout;
186 hw::UnpackedArrayType memType =
189 b.create<
sv::RegOp>(memType, implReq.getServiceSymbolAttr().getAttr())
194 SmallVector<std::tuple<Value, Value, Value>> writeGoAddressData;
195 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
196 auto port = req.getServicePort().getName();
203 auto doneValid = bb.
get(i1);
204 auto ackChannel = b.create<WrapValidReadyOp>(none, doneValid);
207 b.create<PackBundleOp>(implReq.getLoc(), req.getToClient().getType(),
208 ackChannel.getChanOutput());
210 pack.getFromChannels()[RandomAccessMemoryDeclOp::ReqDirChannelIdx];
211 toClientResp = pack.getBundle();
215 b.create<UnwrapValidReadyOp>(toServer, ackChannel.getReady());
218 b.getStringAttr(
"address"));
220 b.getStringAttr(
"data"));
224 go->setAttr(
"sv.namehint", b.getStringAttr(
"write_go"));
229 writeGoAddressData.push_back(std::make_tuple(go, address, data));
231 }
else if (port == read) {
235 auto dataValid = bb.
get(i1);
236 auto data = bb.
get(ramDecl.getInnerType());
237 auto dataChannel = b.create<WrapValidReadyOp>(data, dataValid);
240 b.create<PackBundleOp>(implReq.getLoc(), req.getToClient().getType(),
241 dataChannel.getChanOutput());
243 pack.getFromChannels()[RandomAccessMemoryDeclOp::RespDirChannelIdx];
244 toClientResp = pack.getBundle();
248 b.create<UnwrapValidReadyOp>(toServer, dataChannel.getReady());
250 b.create<sv::ArrayIndexInOutOp>(mem, addressUnwrap.getRawOutput());
254 data.setValue(readData);
255 dataValid.setValue(addressUnwrap.getValid());
257 assert(
false &&
"Port should be either 'read' or 'write'");
260 outputMap[req.getToClient()].replaceAllUsesWith(toClientResp);
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;
271 b.create<sv::IfOp>(go, [&] {
272 Value memLoc = b.create<sv::ArrayIndexInOutOp>(mem, a);
273 b.create<sv::PAssignOp>(memLoc, d);
288 ServiceDeclOpInterface decl) {
291 auto genF =
genLookupTable.find(req.getImplTypeAttr().getValue());
294 return req.emitOpError(
"Could not find service generator for attribute '")
295 << req.getImplTypeAttr() <<
"'";
302 auto implRecord = b.create<ServiceImplRecordOp>(
303 req.getLoc(), req.getAppID(), req.getServiceSymbolAttr(),
304 req.getStdServiceAttr(), req.getImplTypeAttr(), b.getDictionaryAttr({}));
305 implRecord.getReqDetails().emplaceBlock();
307 return genF->second(req, decl, implRecord);
311 DenseMap<StringRef, ServiceGeneratorDispatcher::ServiceGeneratorFunc>{
336 struct ModuleSorter {
339 DenseMap<Operation *, SmallVector<igraph::InstanceOpInterface, 1>>
340 moduleInstantiations;
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);
350 void ModuleSorter::getAndSortModules(ModuleOp topMod,
351 SmallVectorImpl<hw::HWModuleLike> &mods) {
353 DenseSet<Operation *> modsSeen;
355 moduleInstantiations.clear();
356 topMod.walk([&](hw::HWModuleLike mod) {
357 getAndSortModulesVisitor(mod, mods, modsSeen);
362 void ModuleSorter::getAndSortModulesVisitor(
363 hw::HWModuleLike mod, SmallVectorImpl<hw::HWModuleLike> &mods,
364 DenseSet<Operation *> &modsSeen) {
365 if (modsSeen.contains(mod))
367 modsSeen.insert(mod);
369 mod.walk([&](igraph::InstanceOpInterface inst) {
370 auto targetNameAttrs = inst.getReferencedModuleNamesAttr();
371 for (
auto targetNameAttr : targetNameAttrs) {
373 topLevelSyms.getDefinition(cast<StringAttr>(targetNameAttr));
375 moduleInstantiations[modOp].push_back(inst);
376 if (
auto modLike = dyn_cast<hw::HWModuleLike>(modOp))
377 getAndSortModulesVisitor(modLike, mods, modsSeen);
387 struct ESIConnectServicesPass
388 :
public circt::esi::impl::ESIConnectServicesBase<ESIConnectServicesPass>,
392 : genDispatcher(gen) {}
393 ESIConnectServicesPass()
396 void runOnOperation()
override;
401 void convertReq(RequestConnectionOp);
406 LogicalResult surfaceReqs(hw::HWMutableModuleLike,
407 ArrayRef<ServiceImplementConnReqOp>);
412 LogicalResult replaceInst(ServiceInstanceOp,
413 ArrayRef<ServiceImplementConnReqOp> portReqs);
417 LogicalResult process(hw::HWModuleLike);
420 StringAttr getStdService(FlatSymbolRefAttr serviceSym);
427 void ESIConnectServicesPass::runOnOperation() {
428 ModuleOp outerMod = getOperation();
429 topLevelSyms.addDefinitions(outerMod);
431 outerMod.walk([&](RequestConnectionOp req) { convertReq(req); });
437 SmallVector<hw::HWModuleLike, 64> sortedMods;
438 getAndSortModules(outerMod, sortedMods);
441 for (
auto mod : sortedMods) {
442 hw::HWModuleLike mutableMod = dyn_cast<hw::HWModuleLike>(*mod);
443 if (mutableMod && failed(process(mutableMod))) {
451 StringAttr ESIConnectServicesPass::getStdService(FlatSymbolRefAttr svcSym) {
454 Operation *svcDecl = topLevelSyms.getDefinition(svcSym);
455 if (!isa<CustomServiceDeclOp>(svcDecl))
456 return svcDecl->getName().getIdentifier();
460 void ESIConnectServicesPass::convertReq(RequestConnectionOp req) {
462 auto newReq = b.create<ServiceImplementConnReqOp>(
463 req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(),
465 newReq->setDialectAttrs(req->getDialectAttrs());
466 req.getToClient().replaceAllUsesWith(newReq.getToClient());
469 b.create<ServiceRequestRecordOp>(
470 req.getLoc(), req.getAppID(), req.getServicePortAttr(),
471 getStdService(req.getServicePortAttr().getModuleRef()),
472 req.getToClient().getType());
476 LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) {
478 if (mod->getNumRegions() == 0 || mod->getRegion(0).empty())
481 Block &modBlock = mod->getRegion(0).front();
484 SetVector<ServiceImplementConnReqOp> nonLocalReqs;
486 llvm::MapVector<SymbolRefAttr, llvm::SetVector<ServiceImplementConnReqOp>>
488 for (
auto instOp : modBlock.getOps<ServiceInstanceOp>())
489 localImplReqs[instOp.getServiceSymbolAttr()] = {};
493 llvm::SetVector<ServiceImplementConnReqOp> *anyServiceInst =
nullptr;
494 if (
auto defaultService = localImplReqs.find(SymbolRefAttr());
495 defaultService != localImplReqs.end())
496 anyServiceInst = &defaultService->second;
498 auto sortConnReqs = [&]() {
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);
509 nonLocalReqs.insert(req);
518 llvm::make_early_inc_range(modBlock.getOps<ServiceInstanceOp>())) {
519 auto portReqs = localImplReqs[instOp.getServiceSymbolAttr()];
520 if (failed(replaceInst(instOp, portReqs.getArrayRef())))
524 for (RequestConnectionOp req : llvm::make_early_inc_range(
525 mod.getBodyBlock()->getOps<RequestConnectionOp>()))
531 if (nonLocalReqs.empty())
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");
540 LogicalResult ESIConnectServicesPass::replaceInst(
541 ServiceInstanceOp instOp, ArrayRef<ServiceImplementConnReqOp> portReqs) {
542 auto declSym = instOp.getServiceSymbolAttr();
543 ServiceDeclOpInterface decl;
545 decl = dyn_cast_or_null<ServiceDeclOpInterface>(
546 topLevelSyms.getDefinition(declSym));
548 return instOp.emitOpError(
"Could not find service declaration ")
554 SmallVector<Type, 8> resultTypes(instOp.getResultTypes().begin(),
555 instOp.getResultTypes().end());
556 for (
auto req : portReqs)
557 resultTypes.push_back(req.getBundleType());
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();
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));
578 for (
auto req : portReqs)
579 req->moveBefore(&reqBlock, reqBlock.end());
587 if (failed(genDispatcher.generate(implOp, decl)))
588 return implOp.emitOpError(
"failed to generate server");
594 ESIConnectServicesPass::surfaceReqs(hw::HWMutableModuleLike mod,
595 ArrayRef<ServiceImplementConnReqOp> reqs) {
596 auto *
ctxt = mod.getContext();
597 Block *body = &mod->getRegion(0).front();
600 unsigned origNumInputs = mod.getNumInputPorts();
601 SmallVector<std::pair<unsigned, hw::PortInfo>> newInputs;
604 auto getPortName = [&](ArrayAttr namePath) {
605 std::string portName;
606 llvm::raw_string_ostream nameOS(portName);
608 namePath.getAsRange<AppIDAttr>(), nameOS,
609 [&](AppIDAttr appid) {
610 nameOS << appid.getName().getValue();
611 if (appid.getIndex())
612 nameOS <<
"_" << appid.getIndex();
618 for (
auto req : reqs)
619 if (req->getParentWithTrait<OpTrait::IsIsolatedFromAbove>() != mod)
620 return req.emitOpError(
621 "Cannot surface requests through isolated from above ops");
624 for (
auto req : reqs) {
625 newInputs.push_back(std::make_pair(
627 hw::PortInfo{{getPortName(req.getRelativeAppIDPathAttr()),
634 Value replValue = body->addArgument(req.getBundleType(), req->getLoc());
635 req.getToClient().replaceAllUsesWith(replValue);
637 mod.insertPorts(newInputs, {});
640 auto prependNamePart = [&](ArrayAttr appIDPath, AppIDAttr appID) {
641 SmallVector<Attribute, 8> newAppIDPath;
642 newAppIDPath.push_back(appID);
643 newAppIDPath.append(appIDPath.begin(), appIDPath.end());
648 SmallVector<igraph::InstanceOpInterface, 1> newModuleInstantiations;
649 for (
auto inst : moduleInstantiations[mod]) {
653 SmallVector<Value, 16> newOperands;
654 for (
auto req : reqs) {
656 ArrayAttr appIDPath = req.getRelativeAppIDPathAttr();
657 if (
auto instAppID = dyn_cast_or_null<AppIDAttr>(
658 inst->getDiscardableAttr(AppIDAttr::AppIDAttrName)))
659 appIDPath = prependNamePart(appIDPath, instAppID);
662 auto clone = b.create<ServiceImplementConnReqOp>(
663 req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(),
665 clone->setDialectAttrs(req->getDialectAttrs());
666 newOperands.push_back(clone.getToClient());
668 inst->insertOperands(inst->getNumOperands(), newOperands);
670 if (
auto hwInst = dyn_cast<hw::InstanceOp>(*inst))
671 hwInst.setArgNamesAttr(b.getArrayAttr(mod.getInputNames()));
676 for (
auto req : reqs)
681 std::unique_ptr<OperationPass<ModuleOp>>
683 return std::make_unique<ESIConnectServicesPass>();
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)
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...
Class which "dispatches" a service implementation request to its specified generator.
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
std::function< LogicalResult(ServiceImplementReqOp, ServiceDeclOpInterface, ServiceImplRecordOp)> ServiceGeneratorFunc
def create(data_type, value)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
std::unique_ptr< OperationPass< ModuleOp > > createESIConnectServicesPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.