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 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 b.getIntegerAttr(b.getI64Type(), 1),
123 toServerValues.push_back(fromHost);
124 channelAssignments.push_back(getAssignment(ch.name, cosim.getIdAttr()));
129 PackBundleOp::create(b, implReq.getLoc(), bundleType, toServerValues);
130 implReq.getResult(toClientResultNum[req])
131 .replaceAllUsesWith(pack.getBundle());
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 b.getIntegerAttr(b.getI64Type(), 1),
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()));
155 ServiceImplClientRecordOp::create(
156 implRecords, req.getLoc(), req.getRelativeAppIDPathAttr(),
157 req.getServicePortAttr(), TypeAttr::get(bundleType),
158 b.getDictionaryAttr(channelAssignments), DictionaryAttr());
163 Attribute cycleCountParams[] = {
164 hw::ParamDeclAttr::get(
"CORE_CLOCK_FREQUENCY_HZ", b.getI64Type())};
166 {{b.getStringAttr(
"clk"), seq::ClockType::get(ctxt),
169 {{b.getStringAttr(
"rst"), b.getI1Type(),
173 auto parentModule = implReq->getParentOfType<mlir::ModuleOp>();
174 auto cosimCycleCountExternModule =
176 if (!cosimCycleCountExternModule) {
177 auto ip = b.saveInsertionPoint();
178 b.setInsertionPointToEnd(parentModule.getBody());
179 if (
auto *existingSym = parentModule.lookupSymbol(cosimCycleCountName)) {
182 "symbol 'Cosim_CycleCount' already exists but is not a "
184 .attachNote(existingSym->getLoc())
185 <<
"existing symbol here";
187 cosimCycleCountExternModule = hw::HWModuleExternOp::create(
188 b, reqLoc, b.getStringAttr(cosimCycleCountName), cycleCountPorts,
189 cosimCycleCountName, ArrayAttr::get(ctxt, cycleCountParams));
190 b.restoreInsertionPoint(ip);
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))}));
214 ServiceDeclOpInterface decl,
215 ServiceImplRecordOp) {
217 return implReq.emitOpError(
218 "Must specify a service declaration to use 'sv_mem'.");
220 ImplicitLocOpBuilder b(implReq.getLoc(), implReq);
223 RandomAccessMemoryDeclOp ramDecl =
224 dyn_cast<RandomAccessMemoryDeclOp>(decl.getOperation());
226 return implReq.emitOpError(
227 "'sv_mem' implementation type can only be used to "
228 "implement RandomAccessMemory declarations");
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");
237 b, APInt( 0, 0,
false));
238 auto i1 = b.getI1Type();
242 SmallVector<ServiceImplementConnReqOp, 8> toClientReqs(
243 llvm::make_filter_range(
244 implReq.getOps<ServiceImplementConnReqOp>(),
245 [](
auto req) { return req.getToClient() != nullptr; }));
248 DenseMap<Value, Value> outputMap;
249 for (
auto [bout, reqout] :
250 llvm::zip_longest(toClientReqs, implReq.getResults())) {
252 assert(reqout.has_value());
253 Value toClient = bout->getToClient();
254 outputMap[toClient] = *reqout;
258 hw::UnpackedArrayType memType =
259 hw::UnpackedArrayType::get(ramDecl.getInnerType(), ramDecl.getDepth());
266 SmallVector<std::tuple<Value, Value, Value>> writeGoAddressData;
267 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
268 auto port = req.getServicePort().getName();
275 auto doneValid = bb.
get(i1);
277 WrapValidReadyOp::create(b, none, doneValid, 0);
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());
311 WrapValidReadyOp::create(b, data, dataValid, 0);
314 PackBundleOp::create(b, implReq.getLoc(), req.getToClient().getType(),
315 dataChannel.getChanOutput());
317 pack.getFromChannels()[RandomAccessMemoryDeclOp::RespDirChannelIdx];
318 toClientResp = pack.getBundle();
322 UnwrapValidReadyOp::create(b, toServer, dataChannel.getReady());
323 Value unsignedAddress = addressUnwrap.getRawOutput();
325 b, b.getIntegerType(llvm::Log2_64_Ceil(ramDecl.getDepth())),
327 Value memLoc = sv::ArrayIndexInOutOp::create(b, mem, signlessAddress);
331 data.setValue(readData);
332 dataValid.setValue(addressUnwrap.getValid());
334 assert(
false &&
"Port should be either 'read' or 'write'");
337 outputMap[req.getToClient()].replaceAllUsesWith(toClientResp);
341 auto hwClk = seq::FromClockOp::create(b, clk);
342 sv::AlwaysFFOp::create(
343 b, sv::EventControl::AtPosEdge, hwClk, sv::ResetType::SyncReset,
344 sv::EventControl::AtPosEdge, rst, [&] {
345 for (
auto [go, address, data] : writeGoAddressData) {
346 Value a = address, d = data;
348 sv::IfOp::create(b, go, [&] {
350 b, b.getIntegerType(llvm::Log2_64_Ceil(ramDecl.getDepth())), a);
352 sv::ArrayIndexInOutOp::create(b, mem, signlessAddress);
353 sv::PAssignOp::create(b, memLoc, d);
368 ServiceDeclOpInterface decl) {
371 auto genF =
genLookupTable.find(req.getImplTypeAttr().getValue());
374 return req.emitOpError(
"Could not find service generator for attribute '")
375 << req.getImplTypeAttr() <<
"'";
382 auto implRecord = ServiceImplRecordOp::create(
383 b, req.getLoc(), req.getAppID(), UnitAttr(),
384 req.getServiceSymbolAttr(), req.getStdServiceAttr(),
385 req.getImplTypeAttr(), b.getDictionaryAttr({}));
386 implRecord.getReqDetails().emplaceBlock();
388 return genF->second(req, decl, implRecord);
392 DenseMap<StringRef, ServiceGeneratorDispatcher::ServiceGeneratorFunc>{
398 return ::globalDispatcher;
420 DenseMap<Operation *, SmallVector<igraph::InstanceOpInterface, 1>>
421 moduleInstantiations;
423 void getAndSortModules(ModuleOp topMod,
424 SmallVectorImpl<hw::HWModuleLike> &mods);
425 void getAndSortModulesVisitor(hw::HWModuleLike mod,
426 SmallVectorImpl<hw::HWModuleLike> &mods,
427 DenseSet<Operation *> &modsSeen);
431void ModuleSorter::getAndSortModules(ModuleOp topMod,
432 SmallVectorImpl<hw::HWModuleLike> &mods) {
434 DenseSet<Operation *> modsSeen;
436 moduleInstantiations.clear();
437 topMod.walk([&](hw::HWModuleLike mod) {
438 getAndSortModulesVisitor(mod, mods, modsSeen);
443void ModuleSorter::getAndSortModulesVisitor(
444 hw::HWModuleLike mod, SmallVectorImpl<hw::HWModuleLike> &mods,
445 DenseSet<Operation *> &modsSeen) {
446 if (modsSeen.contains(mod))
448 modsSeen.insert(mod);
450 mod.walk([&](igraph::InstanceOpInterface inst) {
451 auto targetNameAttrs = inst.getReferencedModuleNamesAttr();
452 for (
auto targetNameAttr : targetNameAttrs) {
454 topLevelSyms.getDefinition(cast<StringAttr>(targetNameAttr));
456 moduleInstantiations[modOp].push_back(inst);
457 if (
auto modLike = dyn_cast<hw::HWModuleLike>(modOp))
458 getAndSortModulesVisitor(modLike, mods, modsSeen);
468struct ESIConnectServicesPass
469 :
public circt::esi::impl::ESIConnectServicesBase<ESIConnectServicesPass>,
472 ESIConnectServicesPass(
const ServiceGeneratorDispatcher &gen)
473 : genDispatcher(gen) {}
474 ESIConnectServicesPass()
477 void runOnOperation()
override;
482 void convertReq(RequestConnectionOp);
487 LogicalResult surfaceReqs(hw::HWMutableModuleLike,
488 ArrayRef<ServiceImplementConnReqOp>);
493 LogicalResult replaceInst(ServiceInstanceOp,
494 ArrayRef<ServiceImplementConnReqOp> portReqs);
498 LogicalResult process(hw::HWModuleLike);
501 StringAttr getStdService(FlatSymbolRefAttr serviceSym);
504 ServiceGeneratorDispatcher genDispatcher;
508void ESIConnectServicesPass::runOnOperation() {
509 ModuleOp outerMod = getOperation();
510 topLevelSyms.addDefinitions(outerMod);
512 outerMod.walk([&](RequestConnectionOp req) { convertReq(req); });
518 SmallVector<hw::HWModuleLike, 64> sortedMods;
519 getAndSortModules(outerMod, sortedMods);
522 for (
auto mod : sortedMods) {
523 hw::HWModuleLike mutableMod = dyn_cast<hw::HWModuleLike>(*mod);
524 if (mutableMod && failed(process(mutableMod))) {
532StringAttr ESIConnectServicesPass::getStdService(FlatSymbolRefAttr svcSym) {
535 Operation *svcDecl = topLevelSyms.getDefinition(svcSym);
536 if (!isa<CustomServiceDeclOp>(svcDecl))
537 return svcDecl->getName().getIdentifier();
541void ESIConnectServicesPass::convertReq(RequestConnectionOp req) {
543 auto newReq = ServiceImplementConnReqOp::create(
544 b, req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(),
545 ArrayAttr::get(&getContext(), {req.getAppIDAttr()}),
546 req.getOptionsAttr());
547 newReq->setDialectAttrs(req->getDialectAttrs());
548 req.getToClient().replaceAllUsesWith(newReq.getToClient());
551 ServiceRequestRecordOp::create(
552 b, req.getLoc(), req.getAppID(), req.getServicePortAttr(),
553 getStdService(req.getServicePortAttr().getModuleRef()),
554 req.getToClient().getType());
558LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) {
560 if (mod->getNumRegions() == 0 || mod->getRegion(0).empty())
563 Block &modBlock = mod->getRegion(0).front();
566 SetVector<ServiceImplementConnReqOp> nonLocalReqs;
568 llvm::MapVector<SymbolRefAttr, llvm::SetVector<ServiceImplementConnReqOp>>
570 for (
auto instOp : modBlock.getOps<ServiceInstanceOp>())
571 localImplReqs[instOp.getServiceSymbolAttr()] = {};
575 llvm::SetVector<ServiceImplementConnReqOp> *anyServiceInst =
nullptr;
576 if (
auto *defaultService = localImplReqs.find(SymbolRefAttr());
577 defaultService != localImplReqs.end())
578 anyServiceInst = &defaultService->second;
580 auto sortConnReqs = [&]() {
582 for (
auto req : llvm::make_early_inc_range(
583 mod.getBodyBlock()->getOps<ServiceImplementConnReqOp>())) {
584 auto service = req.getServicePort().getModuleRef();
585 auto *reqListIter = localImplReqs.find(service);
586 if (reqListIter != localImplReqs.end())
587 reqListIter->second.insert(req);
588 else if (anyServiceInst)
589 anyServiceInst->insert(req);
591 nonLocalReqs.insert(req);
600 llvm::make_early_inc_range(modBlock.getOps<ServiceInstanceOp>())) {
601 auto portReqs = localImplReqs[instOp.getServiceSymbolAttr()];
602 if (failed(replaceInst(instOp, portReqs.getArrayRef())))
606 for (RequestConnectionOp req : llvm::make_early_inc_range(
607 mod.getBodyBlock()->getOps<RequestConnectionOp>()))
613 if (nonLocalReqs.empty())
616 if (
auto mutableMod = dyn_cast<hw::HWMutableModuleLike>(mod.getOperation()))
617 return surfaceReqs(mutableMod, nonLocalReqs.getArrayRef());
618 return mod.emitOpError(
619 "Cannot surface requests through module without mutable ports");
622LogicalResult ESIConnectServicesPass::replaceInst(
623 ServiceInstanceOp instOp, ArrayRef<ServiceImplementConnReqOp> portReqs) {
624 auto declSym = instOp.getServiceSymbolAttr();
625 ServiceDeclOpInterface decl;
627 decl = dyn_cast_or_null<ServiceDeclOpInterface>(
628 topLevelSyms.getDefinition(declSym));
630 return instOp.emitOpError(
"Could not find service declaration ")
636 SmallVector<Type, 8> resultTypes(instOp.getResultTypes().begin(),
637 instOp.getResultTypes().end());
638 for (
auto req : portReqs)
639 resultTypes.push_back(req.getBundleType());
643 auto implOp = ServiceImplementReqOp::create(
644 b, instOp.getLoc(), resultTypes, instOp.getAppIDAttr(),
645 instOp.getServiceSymbolAttr(), instOp.getImplTypeAttr(),
646 getStdService(declSym), instOp.getImplOptsAttr(), instOp.getOperands());
647 implOp->setDialectAttrs(instOp->getDialectAttrs());
648 Block &reqBlock = implOp.getPortReqs().emplaceBlock();
651 for (
auto [n, o] : llvm::zip(implOp.getResults(), instOp.getResults()))
652 o.replaceAllUsesWith(n);
653 unsigned instOpNumResults = instOp.getNumResults();
654 for (
size_t idx = 0, e = portReqs.size(); idx < e; ++idx) {
655 ServiceImplementConnReqOp req = portReqs[idx];
656 req.getToClient().replaceAllUsesWith(
657 implOp.getResult(idx + instOpNumResults));
660 for (
auto req : portReqs)
661 req->moveBefore(&reqBlock, reqBlock.end());
669 if (failed(genDispatcher.generate(implOp, decl)))
670 return implOp.emitOpError(
"failed to generate server");
676ESIConnectServicesPass::surfaceReqs(hw::HWMutableModuleLike mod,
677 ArrayRef<ServiceImplementConnReqOp> reqs) {
678 auto *ctxt = mod.getContext();
679 Block *body = &mod->getRegion(0).front();
682 unsigned origNumInputs = mod.getNumInputPorts();
683 SmallVector<std::pair<unsigned, hw::PortInfo>> newInputs;
686 auto getPortName = [&](ArrayAttr namePath) {
687 std::string portName;
688 llvm::raw_string_ostream nameOS(portName);
690 namePath.getAsRange<AppIDAttr>(), nameOS,
691 [&](AppIDAttr appid) {
692 nameOS << appid.getName().getValue();
693 if (appid.getIndex())
694 nameOS <<
"_" << appid.getIndex();
697 return StringAttr::get(ctxt, nameOS.str());
700 for (
auto req : reqs)
701 if (req->getParentWithTrait<OpTrait::IsIsolatedFromAbove>() != mod)
702 return req.emitOpError(
703 "Cannot surface requests through isolated from above ops");
706 for (
auto req : reqs) {
707 newInputs.push_back(std::make_pair(
709 hw::PortInfo{{getPortName(req.getRelativeAppIDPathAttr()),
716 Value replValue = body->addArgument(req.getBundleType(), req->getLoc());
717 req.getToClient().replaceAllUsesWith(replValue);
719 mod.insertPorts(newInputs, {});
722 auto prependNamePart = [&](ArrayAttr appIDPath, AppIDAttr appID) {
723 SmallVector<Attribute, 8> newAppIDPath;
724 newAppIDPath.push_back(appID);
725 newAppIDPath.append(appIDPath.begin(), appIDPath.end());
726 return ArrayAttr::get(appIDPath.getContext(), newAppIDPath);
730 SmallVector<igraph::InstanceOpInterface, 1> newModuleInstantiations;
731 for (
auto inst : moduleInstantiations[mod]) {
735 SmallVector<Value, 16> newOperands;
736 for (
auto req : reqs) {
738 ArrayAttr appIDPath = req.getRelativeAppIDPathAttr();
739 if (
auto instAppID = dyn_cast_or_null<AppIDAttr>(
740 inst->getDiscardableAttr(AppIDAttr::AppIDAttrName)))
741 appIDPath = prependNamePart(appIDPath, instAppID);
744 auto clone = ServiceImplementConnReqOp::create(
745 b, req.getLoc(), req.getToClient().getType(),
746 req.getServicePortAttr(), appIDPath,
747 req.getOptionsAttr());
748 clone->setDialectAttrs(req->getDialectAttrs());
749 newOperands.push_back(clone.getToClient());
751 inst->insertOperands(inst->getNumOperands(), newOperands);
753 if (
auto hwInst = dyn_cast<hw::InstanceOp>(*inst))
754 hwInst.setArgNamesAttr(b.getArrayAttr(mod.getInputNames()));
759 for (
auto req : reqs)
764std::unique_ptr<OperationPass<ModuleOp>>
766 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)
false return failure()
Parse an optional keyword or string and set instance into 'result'.
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.
ServiceGeneratorDispatcher(DenseMap< StringRef, ServiceGeneratorFunc > genLookupTable, bool failIfNotFound)
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)
create(data_type, name=None, sym_name=None)
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.