22#include "mlir/IR/BuiltinTypes.h"
23#include "mlir/IR/ImplicitLocOpBuilder.h"
24#include "mlir/IR/SymbolTable.h"
31#define GEN_PASS_DEF_ESICONNECTSERVICES
32#include "circt/Dialect/ESI/ESIPasses.h.inc"
46 ServiceDeclOpInterface,
47 ServiceImplRecordOp implRecord) {
48 constexpr StringLiteral cosimCycleCountName =
"Cosim_CycleCount";
50 auto *ctxt = implReq.getContext();
52 Value clk = implReq.getOperand(0);
53 Value rst = implReq.getOperand(1);
54 Location reqLoc = implReq.getLoc();
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();
64 Block &connImplBlock = implRecord.getReqDetails().front();
65 implRecord.setIsEngine(
true);
66 OpBuilder implRecords = OpBuilder::atBlockEnd(&connImplBlock);
69 auto toStringAttr = [&](ArrayAttr strArr, StringAttr channelName) {
71 llvm::raw_string_ostream os(buff);
73 strArr.getAsRange<AppIDAttr>(), os,
74 [&](AppIDAttr appid) {
75 os << appid.getName().getValue();
77 os <<
"[" << appid.getIndex() <<
"]";
80 os <<
"." << channelName.getValue();
81 return StringAttr::get(ctxt, os.str());
84 auto getAssignment = [&](StringAttr name, StringAttr channelName) {
85 DictionaryAttr assignment = b.getDictionaryAttr({
86 b.getNamedAttr(
"type", b.getStringAttr(
"cosim")),
87 b.getNamedAttr(
"name", channelName),
89 return b.getNamedAttr(name, assignment);
92 llvm::DenseMap<ServiceImplementConnReqOp, unsigned> toClientResultNum;
93 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>())
94 toClientResultNum[req] = toClientResultNum.size();
101 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
102 Location loc = req->getLoc();
103 ChannelBundleType bundleType = req.getToClient().getType();
104 SmallVector<NamedAttribute, 8> channelAssignments;
106 SmallVector<Value, 8> toServerValues;
108 if (ch.direction == ChannelDirection::to) {
109 ChannelType fromHostType = ch.type;
110 if (fromHostType.getSignaling() == ChannelSignaling::FIFO)
111 fromHostType = b.getType<ChannelType>(fromHostType.getInner(),
112 ChannelSignaling::ValidReady,
113 fromHostType.getDataDelay());
114 auto cosim = CosimFromHostEndpointOp::create(
115 b, loc, fromHostType, clk, rst,
116 toStringAttr(req.getRelativeAppIDPathAttr(), ch.name));
117 mlir::TypedValue<ChannelType> fromHost = cosim.getFromHost();
118 if (fromHostType.getSignaling() == ChannelSignaling::FIFO)
119 fromHost = ChannelBufferOp::create(
120 b, loc, ch.type, clk, rst, fromHost,
121 b.getIntegerAttr(b.getI64Type(), 1),
124 toServerValues.push_back(fromHost);
125 channelAssignments.push_back(getAssignment(ch.name, cosim.getIdAttr()));
130 PackBundleOp::create(b, implReq.getLoc(), bundleType, toServerValues);
131 implReq.getResult(toClientResultNum[req])
132 .replaceAllUsesWith(pack.getBundle());
136 if (ch.direction == ChannelDirection::from) {
137 Value fromChannel = pack.getFromChannels()[chanIdx++];
138 auto chType = cast<ChannelType>(fromChannel.getType());
139 if (chType.getSignaling() == ChannelSignaling::FIFO) {
140 auto cosimType = b.getType<ChannelType>(chType.getInner(),
141 ChannelSignaling::ValidReady,
142 chType.getDataDelay());
143 fromChannel = ChannelBufferOp::create(
144 b, loc, cosimType, clk, rst, fromChannel,
145 b.getIntegerAttr(b.getI64Type(), 1),
149 auto cosim = CosimToHostEndpointOp::create(
150 b, loc, clk, rst, fromChannel,
151 toStringAttr(req.getRelativeAppIDPathAttr(), ch.name));
152 channelAssignments.push_back(getAssignment(ch.name, cosim.getIdAttr()));
156 ServiceImplClientRecordOp::create(
157 implRecords, req.getLoc(), req.getRelativeAppIDPathAttr(),
158 req.getServicePortAttr(), TypeAttr::get(bundleType),
159 b.getDictionaryAttr(channelAssignments), DictionaryAttr());
164 Attribute cycleCountParams[] = {
165 hw::ParamDeclAttr::get(
"CORE_CLOCK_FREQUENCY_HZ", b.getI64Type())};
167 {{b.getStringAttr(
"clk"), seq::ClockType::get(ctxt),
168 hw::ModulePort::Direction::Input},
170 {{b.getStringAttr(
"rst"), b.getI1Type(),
171 hw::ModulePort::Direction::Input},
174 auto parentModule = implReq->getParentOfType<mlir::ModuleOp>();
175 auto cosimCycleCountExternModule =
177 if (!cosimCycleCountExternModule) {
178 auto ip = b.saveInsertionPoint();
179 b.setInsertionPointToEnd(parentModule.getBody());
180 if (
auto existingSym = parentModule.lookupSymbol(cosimCycleCountName)) {
183 "symbol 'Cosim_CycleCount' already exists but is not a "
185 .attachNote(existingSym->getLoc())
186 <<
"existing symbol here";
188 cosimCycleCountExternModule = hw::HWModuleExternOp::create(
189 b, reqLoc, b.getStringAttr(cosimCycleCountName), cycleCountPorts,
190 cosimCycleCountName, ArrayAttr::get(ctxt, cycleCountParams));
191 b.restoreInsertionPoint(ip);
195 uint64_t coreClockFreq = 0;
196 if (
auto coreClockFreqAttr = dyn_cast_or_null<IntegerAttr>(
197 implReq->getAttr(
"esi.core_clock_frequency_hz")))
198 if (coreClockFreqAttr.getType().isUnsignedInteger(64))
199 coreClockFreq = coreClockFreqAttr.getUInt();
200 hw::InstanceOp::create(
201 b, reqLoc, cosimCycleCountExternModule,
"__cycle_counter",
202 ArrayRef<Value>({clk, rst}),
203 b.getArrayAttr({hw::ParamDeclAttr::get(
204 "CORE_CLOCK_FREQUENCY_HZ", b.getI64IntegerAttr(coreClockFreq))}));
215 ServiceDeclOpInterface decl,
216 ServiceImplRecordOp) {
218 return implReq.emitOpError(
219 "Must specify a service declaration to use 'sv_mem'.");
221 ImplicitLocOpBuilder b(implReq.getLoc(), implReq);
224 RandomAccessMemoryDeclOp ramDecl =
225 dyn_cast<RandomAccessMemoryDeclOp>(decl.getOperation());
227 return implReq.emitOpError(
228 "'sv_mem' implementation type can only be used to "
229 "implement RandomAccessMemory declarations");
231 if (implReq.getNumOperands() != 2)
232 return implReq.emitOpError(
"Implementation requires clk and rst operands");
233 auto clk = implReq.getOperand(0);
234 auto rst = implReq.getOperand(1);
235 auto write = b.getStringAttr(
"write");
236 auto read = b.getStringAttr(
"read");
238 b, APInt( 0, 0,
false));
239 auto i1 = b.getI1Type();
243 SmallVector<ServiceImplementConnReqOp, 8> toClientReqs(
244 llvm::make_filter_range(
245 implReq.getOps<ServiceImplementConnReqOp>(),
246 [](
auto req) { return req.getToClient() != nullptr; }));
249 DenseMap<Value, Value> outputMap;
250 for (
auto [bout, reqout] :
251 llvm::zip_longest(toClientReqs, implReq.getResults())) {
253 assert(reqout.has_value());
254 Value toClient = bout->getToClient();
255 outputMap[toClient] = *reqout;
259 hw::UnpackedArrayType memType =
260 hw::UnpackedArrayType::get(ramDecl.getInnerType(), ramDecl.getDepth());
262 sv::RegOp::create(b, memType, implReq.getServiceSymbolAttr().getAttr())
267 SmallVector<std::tuple<Value, Value, Value>> writeGoAddressData;
268 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
269 auto port = req.getServicePort().getName();
276 auto doneValid = bb.
get(i1);
277 auto ackChannel = WrapValidReadyOp::create(b, none, doneValid);
280 PackBundleOp::create(b, implReq.getLoc(), req.getToClient().getType(),
281 ackChannel.getChanOutput());
283 pack.getFromChannels()[RandomAccessMemoryDeclOp::ReqDirChannelIdx];
284 toClientResp = pack.getBundle();
288 UnwrapValidReadyOp::create(b, toServer, ackChannel.getReady());
291 b.getStringAttr(
"address"));
293 b.getStringAttr(
"data"));
296 auto go = comb::AndOp::create(b,
unwrap.getValid(),
unwrap.getReady());
297 go->setAttr(
"sv.namehint", b.getStringAttr(
"write_go"));
302 writeGoAddressData.push_back(std::make_tuple(go, address, data));
304 }
else if (port == read) {
308 auto dataValid = bb.
get(i1);
309 auto data = bb.
get(ramDecl.getInnerType());
310 auto dataChannel = WrapValidReadyOp::create(b, data, dataValid);
313 PackBundleOp::create(b, implReq.getLoc(), req.getToClient().getType(),
314 dataChannel.getChanOutput());
316 pack.getFromChannels()[RandomAccessMemoryDeclOp::RespDirChannelIdx];
317 toClientResp = pack.getBundle();
321 UnwrapValidReadyOp::create(b, toServer, dataChannel.getReady());
322 Value unsignedAddress = addressUnwrap.getRawOutput();
324 b, b.getIntegerType(llvm::Log2_64_Ceil(ramDecl.getDepth())),
326 Value memLoc = sv::ArrayIndexInOutOp::create(b, mem, signlessAddress);
330 data.setValue(readData);
331 dataValid.setValue(addressUnwrap.getValid());
333 assert(
false &&
"Port should be either 'read' or 'write'");
336 outputMap[req.getToClient()].replaceAllUsesWith(toClientResp);
340 auto hwClk = seq::FromClockOp::create(b, clk);
341 sv::AlwaysFFOp::create(
342 b, sv::EventControl::AtPosEdge, hwClk, sv::ResetType::SyncReset,
343 sv::EventControl::AtPosEdge, rst, [&] {
344 for (
auto [go, address, data] : writeGoAddressData) {
345 Value a = address, d = data;
347 sv::IfOp::create(b, go, [&] {
349 b, b.getIntegerType(llvm::Log2_64_Ceil(ramDecl.getDepth())), a);
351 sv::ArrayIndexInOutOp::create(b, mem, signlessAddress);
352 sv::PAssignOp::create(b, memLoc, d);
367 ServiceDeclOpInterface decl) {
370 auto genF =
genLookupTable.find(req.getImplTypeAttr().getValue());
373 return req.emitOpError(
"Could not find service generator for attribute '")
374 << req.getImplTypeAttr() <<
"'";
381 auto implRecord = ServiceImplRecordOp::create(
382 b, req.getLoc(), req.getAppID(),
false,
383 req.getServiceSymbolAttr(), req.getStdServiceAttr(),
384 req.getImplTypeAttr(), b.getDictionaryAttr({}));
385 implRecord.getReqDetails().emplaceBlock();
387 return genF->second(req, decl, implRecord);
391 DenseMap<StringRef, ServiceGeneratorDispatcher::ServiceGeneratorFunc>{
397 return ::globalDispatcher;
419 DenseMap<Operation *, SmallVector<igraph::InstanceOpInterface, 1>>
420 moduleInstantiations;
422 void getAndSortModules(ModuleOp topMod,
423 SmallVectorImpl<hw::HWModuleLike> &mods);
424 void getAndSortModulesVisitor(hw::HWModuleLike mod,
425 SmallVectorImpl<hw::HWModuleLike> &mods,
426 DenseSet<Operation *> &modsSeen);
430void ModuleSorter::getAndSortModules(ModuleOp topMod,
431 SmallVectorImpl<hw::HWModuleLike> &mods) {
433 DenseSet<Operation *> modsSeen;
435 moduleInstantiations.clear();
436 topMod.walk([&](hw::HWModuleLike mod) {
437 getAndSortModulesVisitor(mod, mods, modsSeen);
442void ModuleSorter::getAndSortModulesVisitor(
443 hw::HWModuleLike mod, SmallVectorImpl<hw::HWModuleLike> &mods,
444 DenseSet<Operation *> &modsSeen) {
445 if (modsSeen.contains(mod))
447 modsSeen.insert(mod);
449 mod.walk([&](igraph::InstanceOpInterface inst) {
450 auto targetNameAttrs = inst.getReferencedModuleNamesAttr();
451 for (
auto targetNameAttr : targetNameAttrs) {
453 topLevelSyms.getDefinition(cast<StringAttr>(targetNameAttr));
455 moduleInstantiations[modOp].push_back(inst);
456 if (
auto modLike = dyn_cast<hw::HWModuleLike>(modOp))
457 getAndSortModulesVisitor(modLike, mods, modsSeen);
467struct ESIConnectServicesPass
468 :
public circt::esi::impl::ESIConnectServicesBase<ESIConnectServicesPass>,
472 : genDispatcher(gen) {}
473 ESIConnectServicesPass()
476 void runOnOperation()
override;
481 void convertReq(RequestConnectionOp);
486 LogicalResult surfaceReqs(hw::HWMutableModuleLike,
487 ArrayRef<ServiceImplementConnReqOp>);
492 LogicalResult replaceInst(ServiceInstanceOp,
493 ArrayRef<ServiceImplementConnReqOp> portReqs);
497 LogicalResult process(hw::HWModuleLike);
500 StringAttr getStdService(FlatSymbolRefAttr serviceSym);
507void ESIConnectServicesPass::runOnOperation() {
508 ModuleOp outerMod = getOperation();
509 topLevelSyms.addDefinitions(outerMod);
511 outerMod.walk([&](RequestConnectionOp req) { convertReq(req); });
517 SmallVector<hw::HWModuleLike, 64> sortedMods;
518 getAndSortModules(outerMod, sortedMods);
521 for (
auto mod : sortedMods) {
522 hw::HWModuleLike mutableMod = dyn_cast<hw::HWModuleLike>(*mod);
523 if (mutableMod && failed(process(mutableMod))) {
531StringAttr ESIConnectServicesPass::getStdService(FlatSymbolRefAttr svcSym) {
534 Operation *svcDecl = topLevelSyms.getDefinition(svcSym);
535 if (!isa<CustomServiceDeclOp>(svcDecl))
536 return svcDecl->getName().getIdentifier();
540void ESIConnectServicesPass::convertReq(RequestConnectionOp req) {
542 auto newReq = ServiceImplementConnReqOp::create(
543 b, req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(),
544 ArrayAttr::get(&getContext(), {req.getAppIDAttr()}));
545 newReq->setDialectAttrs(req->getDialectAttrs());
546 req.getToClient().replaceAllUsesWith(newReq.getToClient());
549 ServiceRequestRecordOp::create(
550 b, req.getLoc(), req.getAppID(), req.getServicePortAttr(),
551 getStdService(req.getServicePortAttr().getModuleRef()),
552 req.getToClient().getType());
556LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) {
558 if (mod->getNumRegions() == 0 || mod->getRegion(0).empty())
561 Block &modBlock = mod->getRegion(0).front();
564 SetVector<ServiceImplementConnReqOp> nonLocalReqs;
566 llvm::MapVector<SymbolRefAttr, llvm::SetVector<ServiceImplementConnReqOp>>
568 for (
auto instOp : modBlock.getOps<ServiceInstanceOp>())
569 localImplReqs[instOp.getServiceSymbolAttr()] = {};
573 llvm::SetVector<ServiceImplementConnReqOp> *anyServiceInst =
nullptr;
574 if (
auto *defaultService = localImplReqs.find(SymbolRefAttr());
575 defaultService != localImplReqs.end())
576 anyServiceInst = &defaultService->second;
578 auto sortConnReqs = [&]() {
580 for (
auto req :
llvm::make_early_inc_range(
581 mod.
getBodyBlock()->getOps<ServiceImplementConnReqOp>())) {
582 auto service = req.getServicePort().getModuleRef();
583 auto *reqListIter = localImplReqs.find(service);
584 if (reqListIter != localImplReqs.end())
585 reqListIter->second.insert(req);
586 else if (anyServiceInst)
587 anyServiceInst->insert(req);
589 nonLocalReqs.insert(req);
598 llvm::make_early_inc_range(modBlock.getOps<ServiceInstanceOp>())) {
599 auto portReqs = localImplReqs[instOp.getServiceSymbolAttr()];
600 if (failed(replaceInst(instOp, portReqs.getArrayRef())))
604 for (RequestConnectionOp req :
llvm::make_early_inc_range(
611 if (nonLocalReqs.empty())
614 if (
auto mutableMod = dyn_cast<hw::HWMutableModuleLike>(mod.getOperation()))
615 return surfaceReqs(mutableMod, nonLocalReqs.getArrayRef());
616 return mod.emitOpError(
617 "Cannot surface requests through module without mutable ports");
620LogicalResult ESIConnectServicesPass::replaceInst(
621 ServiceInstanceOp instOp, ArrayRef<ServiceImplementConnReqOp> portReqs) {
622 auto declSym = instOp.getServiceSymbolAttr();
623 ServiceDeclOpInterface decl;
625 decl = dyn_cast_or_null<ServiceDeclOpInterface>(
626 topLevelSyms.getDefinition(declSym));
628 return instOp.emitOpError(
"Could not find service declaration ")
634 SmallVector<Type, 8> resultTypes(instOp.getResultTypes().begin(),
635 instOp.getResultTypes().end());
636 for (
auto req : portReqs)
637 resultTypes.push_back(req.getBundleType());
641 auto implOp = ServiceImplementReqOp::create(
642 b, instOp.getLoc(), resultTypes, instOp.getAppIDAttr(),
643 instOp.getServiceSymbolAttr(), instOp.getImplTypeAttr(),
644 getStdService(declSym), instOp.getImplOptsAttr(), instOp.getOperands());
645 implOp->setDialectAttrs(instOp->getDialectAttrs());
646 Block &reqBlock = implOp.getPortReqs().emplaceBlock();
649 for (
auto [n, o] :
llvm::zip(implOp.getResults(), instOp.getResults()))
650 o.replaceAllUsesWith(n);
651 unsigned instOpNumResults = instOp.getNumResults();
652 for (
size_t idx = 0, e = portReqs.size(); idx < e; ++idx) {
653 ServiceImplementConnReqOp req = portReqs[idx];
654 req.getToClient().replaceAllUsesWith(
655 implOp.getResult(idx + instOpNumResults));
658 for (
auto req : portReqs)
659 req->moveBefore(&reqBlock, reqBlock.
end());
667 if (failed(genDispatcher.generate(implOp, decl)))
668 return implOp.emitOpError(
"failed to generate server");
674ESIConnectServicesPass::surfaceReqs(hw::HWMutableModuleLike mod,
675 ArrayRef<ServiceImplementConnReqOp> reqs) {
676 auto *ctxt = mod.getContext();
677 Block *body = &mod->getRegion(0).front();
680 unsigned origNumInputs = mod.getNumInputPorts();
681 SmallVector<std::pair<unsigned, hw::PortInfo>> newInputs;
684 auto getPortName = [&](ArrayAttr namePath) {
685 std::string portName;
686 llvm::raw_string_ostream nameOS(portName);
688 namePath.getAsRange<AppIDAttr>(), nameOS,
689 [&](AppIDAttr appid) {
690 nameOS << appid.getName().getValue();
691 if (appid.getIndex())
692 nameOS <<
"_" << appid.getIndex();
695 return StringAttr::get(ctxt, nameOS.str());
698 for (
auto req : reqs)
699 if (req->getParentWithTrait<
OpTrait::IsIsolatedFromAbove>() != mod)
700 return req.emitOpError(
701 "Cannot surface requests through isolated from above ops");
704 for (
auto req : reqs) {
705 newInputs.push_back(std::make_pair(
707 hw::PortInfo{{getPortName(req.getRelativeAppIDPathAttr()),
714 Value replValue = body->addArgument(req.getBundleType(), req->getLoc());
715 req.getToClient().replaceAllUsesWith(replValue);
717 mod.insertPorts(newInputs, {});
720 auto prependNamePart = [&](ArrayAttr appIDPath, AppIDAttr appID) {
721 SmallVector<Attribute, 8> newAppIDPath;
722 newAppIDPath.push_back(appID);
723 newAppIDPath.append(appIDPath.begin(), appIDPath.end());
724 return ArrayAttr::get(appIDPath.getContext(), newAppIDPath);
728 SmallVector<igraph::InstanceOpInterface, 1> newModuleInstantiations;
729 for (
auto inst : moduleInstantiations[mod]) {
733 SmallVector<Value, 16> newOperands;
734 for (
auto req : reqs) {
736 ArrayAttr appIDPath = req.getRelativeAppIDPathAttr();
737 if (
auto instAppID = dyn_cast_or_null<AppIDAttr>(
738 inst->getDiscardableAttr(AppIDAttr::AppIDAttrName)))
739 appIDPath = prependNamePart(appIDPath, instAppID);
742 auto clone = ServiceImplementConnReqOp::create(
743 b, req.getLoc(), req.getToClient().getType(),
744 req.getServicePortAttr(), appIDPath);
745 clone->setDialectAttrs(req->getDialectAttrs());
746 newOperands.push_back(clone.getToClient());
748 inst->insertOperands(inst->getNumOperands(), newOperands);
750 if (
auto hwInst = dyn_cast<hw::InstanceOp>(*inst))
751 hwInst.setArgNamesAttr(b.getArrayAttr(mod.getInputNames()));
756 for (
auto req : reqs)
761std::unique_ptr<OperationPass<ModuleOp>>
763 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)
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...
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
create(cls, result_type, reset=None, reset_value=None, name=None, sym_name=None, **kwargs)
std::unique_ptr< OperationPass< ModuleOp > > createESIConnectServicesPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
This holds the name, type, direction of a module's ports.