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;
40 ServiceDeclOpInterface decl) {
46 return req.emitOpError(
"Could not find service generator for attribute '")
47 << req.getImplTypeAttr() <<
"'";
54 auto implRecord = b.create<ServiceImplRecordOp>(
55 req.getLoc(), req.getAppID(), req.getServiceSymbolAttr(),
56 req.getStdServiceAttr(), req.getImplTypeAttr(), b.getDictionaryAttr({}));
57 implRecord.getReqDetails().emplaceBlock();
59 return genF->second(req, decl, implRecord);
65 ServiceDeclOpInterface,
66 ServiceImplRecordOp implRecord) {
67 auto *
ctxt = implReq.getContext();
69 Value clk = implReq.getOperand(0);
70 Value rst = implReq.getOperand(1);
72 if (implReq.getImplOpts()) {
73 auto opts = implReq.getImplOpts()->getValue();
74 for (
auto nameAttr : opts) {
75 return implReq.emitOpError(
"did not recognize option name ")
76 << nameAttr.getName();
80 Block &connImplBlock = implRecord.getReqDetails().front();
81 OpBuilder implRecords = OpBuilder::atBlockEnd(&connImplBlock);
84 auto toStringAttr = [&](ArrayAttr strArr, StringAttr channelName) {
86 llvm::raw_string_ostream os(buff);
88 strArr.getAsRange<AppIDAttr>(), os,
89 [&](AppIDAttr appid) {
90 os << appid.getName().getValue();
92 os <<
"[" << appid.getIndex() <<
"]";
95 os <<
"." << channelName.getValue();
99 llvm::DenseMap<ServiceImplementConnReqOp, unsigned> toClientResultNum;
100 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>())
101 toClientResultNum[req] = toClientResultNum.size();
108 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
109 Location loc = req->getLoc();
110 ChannelBundleType bundleType = req.getToClient().getType();
111 SmallVector<NamedAttribute, 8> channelAssignments;
113 SmallVector<Value, 8> toServerValues;
115 if (ch.direction == ChannelDirection::to) {
116 auto cosim = b.create<CosimFromHostEndpointOp>(
117 loc, ch.type, clk, rst,
118 toStringAttr(req.getRelativeAppIDPathAttr(), ch.name));
119 toServerValues.push_back(cosim.getFromHost());
120 channelAssignments.push_back(
121 b.getNamedAttr(ch.name, cosim.getIdAttr()));
126 b.create<PackBundleOp>(implReq.getLoc(), bundleType, toServerValues);
127 implReq.getResult(toClientResultNum[req])
128 .replaceAllUsesWith(pack.getBundle());
132 if (ch.direction == ChannelDirection::from) {
133 auto cosim = b.create<CosimToHostEndpointOp>(
134 loc, clk, rst, pack.getFromChannels()[chanIdx++],
135 toStringAttr(req.getRelativeAppIDPathAttr(), ch.name));
136 channelAssignments.push_back(
137 b.getNamedAttr(ch.name, cosim.getIdAttr()));
141 implRecords.create<ServiceImplClientRecordOp>(
142 req.getLoc(), req.getRelativeAppIDPathAttr(), req.getServicePortAttr(),
144 b.getDictionaryAttr(b.getNamedAttr(
145 "channel_assignments", b.getDictionaryAttr(channelAssignments))));
157 ServiceDeclOpInterface decl,
158 ServiceImplRecordOp) {
160 return implReq.emitOpError(
161 "Must specify a service declaration to use 'sv_mem'.");
163 ImplicitLocOpBuilder b(implReq.getLoc(), implReq);
166 RandomAccessMemoryDeclOp ramDecl =
167 dyn_cast<RandomAccessMemoryDeclOp>(decl.getOperation());
169 return implReq.emitOpError(
170 "'sv_mem' implementation type can only be used to "
171 "implement RandomAccessMemory declarations");
173 if (implReq.getNumOperands() != 2)
174 return implReq.emitOpError(
"Implementation requires clk and rst operands");
175 auto clk = implReq.getOperand(0);
176 auto rst = implReq.getOperand(1);
177 auto write = b.getStringAttr(
"write");
178 auto read = b.getStringAttr(
"read");
180 APInt( 0, 0,
false));
181 auto i1 = b.getI1Type();
185 SmallVector<ServiceImplementConnReqOp, 8> toClientReqs(
186 llvm::make_filter_range(
187 implReq.getOps<ServiceImplementConnReqOp>(),
188 [](
auto req) { return req.getToClient() != nullptr; }));
191 DenseMap<Value, Value> outputMap;
192 for (
auto [bout, reqout] :
193 llvm::zip_longest(toClientReqs, implReq.getResults())) {
195 assert(reqout.has_value());
196 Value toClient = bout->getToClient();
197 outputMap[toClient] = *reqout;
201 hw::UnpackedArrayType memType =
204 b.create<
sv::RegOp>(memType, implReq.getServiceSymbolAttr().getAttr())
209 SmallVector<std::tuple<Value, Value, Value>> writeGoAddressData;
210 for (
auto req : implReq.getOps<ServiceImplementConnReqOp>()) {
211 auto port = req.getServicePort().getName();
218 auto doneValid = bb.
get(i1);
219 auto ackChannel = b.create<WrapValidReadyOp>(none, doneValid);
222 b.create<PackBundleOp>(implReq.getLoc(), req.getToClient().getType(),
223 ackChannel.getChanOutput());
225 pack.getFromChannels()[RandomAccessMemoryDeclOp::ReqDirChannelIdx];
226 toClientResp = pack.getBundle();
230 b.create<UnwrapValidReadyOp>(toServer, ackChannel.getReady());
233 b.getStringAttr(
"address"));
235 b.getStringAttr(
"data"));
239 go->setAttr(
"sv.namehint", b.getStringAttr(
"write_go"));
244 writeGoAddressData.push_back(std::make_tuple(go, address, data));
246 }
else if (port == read) {
250 auto dataValid = bb.
get(i1);
251 auto data = bb.
get(ramDecl.getInnerType());
252 auto dataChannel = b.create<WrapValidReadyOp>(data, dataValid);
255 b.create<PackBundleOp>(implReq.getLoc(), req.getToClient().getType(),
256 dataChannel.getChanOutput());
258 pack.getFromChannels()[RandomAccessMemoryDeclOp::RespDirChannelIdx];
259 toClientResp = pack.getBundle();
263 b.create<UnwrapValidReadyOp>(toServer, dataChannel.getReady());
265 b.create<sv::ArrayIndexInOutOp>(mem, addressUnwrap.getRawOutput());
269 data.setValue(readData);
270 dataValid.setValue(addressUnwrap.getValid());
272 assert(
false &&
"Port should be either 'read' or 'write'");
275 outputMap[req.getToClient()].replaceAllUsesWith(toClientResp);
279 auto hwClk = b.create<seq::FromClockOp>(clk);
280 b.create<sv::AlwaysFFOp>(
281 sv::EventControl::AtPosEdge, hwClk, sv::ResetType::SyncReset,
282 sv::EventControl::AtPosEdge, rst, [&] {
283 for (
auto [go, address, data] : writeGoAddressData) {
284 Value a = address, d = data;
286 b.create<sv::IfOp>(go, [&] {
287 Value memLoc = b.create<sv::ArrayIndexInOutOp>(mem, a);
288 b.create<sv::PAssignOp>(memLoc, d);
298 DenseMap<StringRef, ServiceGeneratorDispatcher::ServiceGeneratorFunc>{
320 struct ESIConnectServicesPass
321 :
public circt::esi::impl::ESIConnectServicesBase<ESIConnectServicesPass>,
325 : genDispatcher(gen) {}
326 ESIConnectServicesPass()
329 void runOnOperation()
override;
334 void convertReq(RequestConnectionOp);
339 LogicalResult surfaceReqs(hw::HWMutableModuleLike,
340 ArrayRef<ServiceImplementConnReqOp>);
345 LogicalResult replaceInst(ServiceInstanceOp, Block *portReqs);
349 LogicalResult process(hw::HWModuleLike);
352 StringAttr getStdService(FlatSymbolRefAttr serviceSym);
359 void ESIConnectServicesPass::runOnOperation() {
360 ModuleOp outerMod = getOperation();
361 topLevelSyms.addDefinitions(outerMod);
363 outerMod.walk([&](RequestConnectionOp req) { convertReq(req); });
369 SmallVector<hw::HWModuleLike, 64> sortedMods;
370 getAndSortModules(outerMod, sortedMods);
373 for (
auto mod : sortedMods) {
374 hw::HWModuleLike mutableMod = dyn_cast<hw::HWModuleLike>(*mod);
375 if (mutableMod && failed(process(mutableMod))) {
383 StringAttr ESIConnectServicesPass::getStdService(FlatSymbolRefAttr svcSym) {
386 Operation *svcDecl = topLevelSyms.getDefinition(svcSym);
387 if (!isa<CustomServiceDeclOp>(svcDecl))
388 return svcDecl->getName().getIdentifier();
392 void ESIConnectServicesPass::convertReq(RequestConnectionOp req) {
394 auto newReq = b.create<ServiceImplementConnReqOp>(
395 req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(),
397 newReq->setDialectAttrs(req->getDialectAttrs());
398 req.getToClient().replaceAllUsesWith(newReq.getToClient());
401 b.create<ServiceRequestRecordOp>(
402 req.getLoc(), req.getAppID(), req.getServicePortAttr(),
403 getStdService(req.getServicePortAttr().getModuleRef()),
404 req.getToClient().getType());
408 LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) {
410 if (mod->getNumRegions() == 0 || mod->getRegion(0).empty())
413 Block &modBlock = mod->getRegion(0).front();
416 SmallVector<ServiceImplementConnReqOp, 4> nonLocalReqs;
418 DenseMap<SymbolRefAttr, Block *> localImplReqs;
419 Block *anyServiceInst =
nullptr;
420 for (
auto instOp : modBlock.getOps<ServiceInstanceOp>()) {
421 auto *b =
new Block();
422 localImplReqs[instOp.getServiceSymbolAttr()] = b;
423 if (!instOp.getServiceSymbolAttr())
428 mod.walk([&](ServiceImplementConnReqOp req) {
429 auto service = req.getServicePort().getModuleRef();
430 auto implOpF = localImplReqs.find(service);
431 if (implOpF != localImplReqs.end())
432 req->moveBefore(implOpF->second, implOpF->second->end());
433 else if (anyServiceInst)
434 req->moveBefore(anyServiceInst, anyServiceInst->end());
436 nonLocalReqs.push_back(req);
442 llvm::make_early_inc_range(modBlock.getOps<ServiceInstanceOp>())) {
443 Block *portReqs = localImplReqs[instOp.getServiceSymbolAttr()];
444 if (failed(replaceInst(instOp, portReqs)))
449 if (nonLocalReqs.empty())
452 if (
auto mutableMod = dyn_cast<hw::HWMutableModuleLike>(mod.getOperation()))
453 return surfaceReqs(mutableMod, nonLocalReqs);
454 return mod.emitOpError(
455 "Cannot surface requests through module without mutable ports");
458 LogicalResult ESIConnectServicesPass::replaceInst(ServiceInstanceOp instOp,
461 auto declSym = instOp.getServiceSymbolAttr();
462 ServiceDeclOpInterface decl;
464 decl = dyn_cast_or_null<ServiceDeclOpInterface>(
465 topLevelSyms.getDefinition(declSym));
467 return instOp.emitOpError(
"Could not find service declaration ")
473 SmallVector<Type, 8> resultTypes(instOp.getResultTypes().begin(),
474 instOp.getResultTypes().end());
475 for (
auto req : portReqs->getOps<ServiceImplementConnReqOp>())
476 resultTypes.push_back(req.getBundleType());
480 auto implOp = b.create<ServiceImplementReqOp>(
481 instOp.getLoc(), resultTypes, instOp.getAppIDAttr(),
482 instOp.getServiceSymbolAttr(), instOp.getImplTypeAttr(),
483 getStdService(declSym), instOp.getImplOptsAttr(), instOp.getOperands());
484 implOp->setDialectAttrs(instOp->getDialectAttrs());
485 implOp.getPortReqs().push_back(portReqs);
488 for (
auto [n, o] : llvm::zip(implOp.getResults(), instOp.getResults()))
489 o.replaceAllUsesWith(n);
490 unsigned instOpNumResults = instOp.getNumResults();
491 for (
auto [idx, req] :
492 llvm::enumerate(portReqs->getOps<ServiceImplementConnReqOp>())) {
493 req.getToClient().replaceAllUsesWith(
494 implOp.getResult(idx + instOpNumResults));
503 if (failed(genDispatcher.generate(implOp, decl)))
504 return implOp.emitOpError(
"failed to generate server");
510 ESIConnectServicesPass::surfaceReqs(hw::HWMutableModuleLike mod,
511 ArrayRef<ServiceImplementConnReqOp> reqs) {
512 auto *
ctxt = mod.getContext();
513 Block *body = &mod->getRegion(0).front();
516 unsigned origNumInputs = mod.getNumInputPorts();
517 SmallVector<std::pair<unsigned, hw::PortInfo>> newInputs;
520 auto getPortName = [&](ArrayAttr namePath) {
521 std::string portName;
522 llvm::raw_string_ostream nameOS(portName);
524 namePath.getAsRange<AppIDAttr>(), nameOS,
525 [&](AppIDAttr appid) {
526 nameOS << appid.getName().getValue();
527 if (appid.getIndex())
528 nameOS <<
"_" << appid.getIndex();
534 for (
auto req : reqs)
535 if (req->getParentWithTrait<OpTrait::IsIsolatedFromAbove>() != mod)
536 return req.emitOpError(
537 "Cannot surface requests through isolated from above ops");
540 for (
auto req : reqs) {
541 newInputs.push_back(std::make_pair(
543 hw::PortInfo{{getPortName(req.getRelativeAppIDPathAttr()),
550 Value replValue = body->addArgument(req.getBundleType(), req->getLoc());
551 req.getToClient().replaceAllUsesWith(replValue);
553 mod.insertPorts(newInputs, {});
556 auto prependNamePart = [&](ArrayAttr appIDPath, AppIDAttr appID) {
557 SmallVector<Attribute, 8> newAppIDPath;
558 newAppIDPath.push_back(appID);
559 newAppIDPath.append(appIDPath.begin(), appIDPath.end());
564 SmallVector<igraph::InstanceOpInterface, 1> newModuleInstantiations;
565 for (
auto inst : moduleInstantiations[mod]) {
569 SmallVector<Value, 16> newOperands;
570 for (
auto req : reqs) {
572 ArrayAttr appIDPath = req.getRelativeAppIDPathAttr();
573 if (
auto instAppID = dyn_cast_or_null<AppIDAttr>(
574 inst->getDiscardableAttr(AppIDAttr::AppIDAttrName)))
575 appIDPath = prependNamePart(appIDPath, instAppID);
578 auto clone = b.create<ServiceImplementConnReqOp>(
579 req.getLoc(), req.getToClient().getType(), req.getServicePortAttr(),
581 clone->setDialectAttrs(req->getDialectAttrs());
582 newOperands.push_back(clone.getToClient());
584 inst->insertOperands(inst->getNumOperands(), newOperands);
586 if (
auto hwInst = dyn_cast<hw::InstanceOp>(*inst))
587 hwInst.setArgNamesAttr(b.getArrayAttr(mod.getInputNames()));
592 for (
auto req : reqs)
597 std::unique_ptr<OperationPass<ModuleOp>>
599 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.
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.
A set of methods which are broadly useful in a number of dialects.