mirror of https://github.com/llvm/circt.git
[ESI] Bidirectional channel requests and Cosim support (#3705)
Allow users to request bidirectional channels (InOut). Decomposes into one `to_server` and one `to_client` request. Cosim will detect that and wire them up to the same endpoint.
This commit is contained in:
parent
a6275fe60a
commit
dbbc205849
|
@ -8,8 +8,8 @@ class LoopbackTester(esi_cosim.CosimBase):
|
|||
"""Provides methods to test the loopback simulations."""
|
||||
|
||||
def test_two_chan_loopback(self, num_msgs):
|
||||
to_hw = self.openEP(1001, sendType=self.schema.I1, recvType=self.schema.I32)
|
||||
from_hw = self.openEP(1002,
|
||||
to_hw = self.openEP(1002, sendType=self.schema.I1, recvType=self.schema.I32)
|
||||
from_hw = self.openEP(1001,
|
||||
sendType=self.schema.I32,
|
||||
recvType=self.schema.I1)
|
||||
for _ in range(num_msgs):
|
||||
|
|
|
@ -54,6 +54,17 @@ def ToClientOp : ESI_Op<"service.to_client",
|
|||
}];
|
||||
}
|
||||
|
||||
def ServiceDeclInOutOp : ESI_Op<"service.inout",
|
||||
[HasParent<"::circt::esi::ServiceDeclOp">]> {
|
||||
let summary = "An ESI service port which has both directions";
|
||||
|
||||
let arguments = (ins SymbolNameAttr:$inner_sym,
|
||||
TypeAttr:$inType, TypeAttr:$outType);
|
||||
let assemblyFormat = [{
|
||||
$inner_sym attr-dict `:` qualified($inType) `->` qualified($outType)
|
||||
}];
|
||||
}
|
||||
|
||||
def ServiceInstanceOp : ESI_Op<"service.instance"> {
|
||||
let summary = "Instantiate a server module";
|
||||
let description = [{
|
||||
|
@ -134,3 +145,18 @@ def RequestToClientConnectionOp : ESI_Op<"service.req.to_client", [
|
|||
attr-dict `:` qualified(type($receiving))
|
||||
}];
|
||||
}
|
||||
|
||||
def RequestInOutChannelOp : ESI_Op<"service.req.inout", [
|
||||
DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
|
||||
let summary = "Request a bidirectional channel";
|
||||
|
||||
let arguments = (ins HWInnerRefAttr:$servicePort,
|
||||
ChannelType:$sending,
|
||||
StrArrayAttr:$clientNamePath);
|
||||
let results = (outs ChannelType:$receiving);
|
||||
|
||||
let assemblyFormat = [{
|
||||
$sending `->` $servicePort `(` $clientNamePath `)` attr-dict `:`
|
||||
qualified(type($sending)) `->` qualified(type($receiving))
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -20,8 +20,8 @@ class LoopbackTester(esi_cosim.CosimBase):
|
|||
ep.close().wait()
|
||||
|
||||
def test_two_chan_loopback(self, num_msgs):
|
||||
to_hw = self.openEP(11, sendType=self.schema.I1, recvType=self.schema.I8)
|
||||
from_hw = self.openEP(12, sendType=self.schema.I8, recvType=self.schema.I1)
|
||||
to_hw = self.openEP(12, sendType=self.schema.I1, recvType=self.schema.I8)
|
||||
from_hw = self.openEP(11, sendType=self.schema.I8, recvType=self.schema.I1)
|
||||
for _ in range(num_msgs):
|
||||
data = random.randint(0, 2**8 - 1)
|
||||
print(f"Sending {data}")
|
||||
|
|
|
@ -242,7 +242,6 @@ static OpType getServicePortDecl(Operation *op,
|
|||
StringAttr modName = servicePort.getModule();
|
||||
ServiceDeclOp serviceDeclOp = topSyms.lookup<ServiceDeclOp>(modName);
|
||||
if (!serviceDeclOp) {
|
||||
op->emitOpError("Cannot find module ") << modName;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -250,7 +249,6 @@ static OpType getServicePortDecl(Operation *op,
|
|||
for (auto portDecl : serviceDeclOp.getOps<OpType>())
|
||||
if (portDecl.inner_symAttr() == innerSym)
|
||||
return portDecl;
|
||||
op->emitOpError("Cannot find port named ") << innerSym;
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -259,17 +257,34 @@ static OpType getServicePortDecl(Operation *op,
|
|||
template <class PortTypeOp, class OpType>
|
||||
static LogicalResult
|
||||
reqPortMatches(OpType op, SymbolTableCollection &symbolTable, Type t) {
|
||||
auto portDecl =
|
||||
getServicePortDecl<PortTypeOp>(op, symbolTable, op.servicePort());
|
||||
if (!portDecl)
|
||||
return failure();
|
||||
hw::InnerRefAttr port = op.servicePort();
|
||||
auto portDecl = getServicePortDecl<PortTypeOp>(op, symbolTable, port);
|
||||
auto inoutPortDecl =
|
||||
getServicePortDecl<ServiceDeclInOutOp>(op, symbolTable, port);
|
||||
if (!portDecl && !inoutPortDecl)
|
||||
return op.emitOpError("Could not find service port declaration ")
|
||||
<< port.getModuleRef() << "::" << port.getName().getValue();
|
||||
|
||||
auto *ctxt = op.getContext();
|
||||
if (portDecl.type() != t &&
|
||||
portDecl.type() != ChannelType::get(ctxt, AnyType::get(ctxt)))
|
||||
return op.emitOpError("Request type does not match port type ")
|
||||
<< portDecl.type();
|
||||
auto anyChannelType = ChannelType::get(ctxt, AnyType::get(ctxt));
|
||||
if (portDecl) {
|
||||
if (portDecl.type() != t && portDecl.type() != anyChannelType)
|
||||
return op.emitOpError("Request type does not match port type ")
|
||||
<< portDecl.type();
|
||||
return success();
|
||||
}
|
||||
|
||||
assert(inoutPortDecl);
|
||||
if (isa<ToClientOp>(op)) {
|
||||
if (inoutPortDecl.inType() != t && inoutPortDecl.inType() != anyChannelType)
|
||||
return op.emitOpError("Request type does not match port type ")
|
||||
<< inoutPortDecl.inType();
|
||||
} else if (isa<ToServerOp>(op)) {
|
||||
if (inoutPortDecl.outType() != t &&
|
||||
inoutPortDecl.outType() != anyChannelType)
|
||||
return op.emitOpError("Request type does not match port type ")
|
||||
<< inoutPortDecl.outType();
|
||||
}
|
||||
return success();
|
||||
}
|
||||
|
||||
|
@ -283,5 +298,30 @@ LogicalResult RequestToServerConnectionOp::verifySymbolUses(
|
|||
return reqPortMatches<ToServerOp>(*this, symbolTable, sending().getType());
|
||||
}
|
||||
|
||||
LogicalResult
|
||||
RequestInOutChannelOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
|
||||
auto portDecl = getServicePortDecl<ServiceDeclInOutOp>(
|
||||
this->getOperation(), symbolTable, servicePort());
|
||||
if (!portDecl)
|
||||
return emitOpError("Could not find inout service port declaration.");
|
||||
|
||||
auto *ctxt = getContext();
|
||||
auto anyChannelType = ChannelType::get(ctxt, AnyType::get(ctxt));
|
||||
|
||||
// Check the input port type.
|
||||
if (portDecl.inType() != sending().getType() &&
|
||||
portDecl.inType() != anyChannelType)
|
||||
return emitOpError("Request to_server type does not match port type ")
|
||||
<< portDecl.inType();
|
||||
|
||||
// Check the output port type.
|
||||
if (portDecl.outType() != receiving().getType() &&
|
||||
portDecl.outType() != anyChannelType)
|
||||
return emitOpError("Request to_client type does not match port type ")
|
||||
<< portDecl.outType();
|
||||
|
||||
return success();
|
||||
}
|
||||
|
||||
#define GET_OP_CLASSES
|
||||
#include "circt/Dialect/ESI/ESI.cpp.inc"
|
||||
|
|
|
@ -70,35 +70,87 @@ static LogicalResult instantiateCosimEndpointOps(ServiceImplementReqOp req) {
|
|||
return StringAttr::get(ctxt, os.str());
|
||||
};
|
||||
|
||||
// Create Cosim endpoints for the incoming data requests.
|
||||
unsigned clientReqIdx = 0;
|
||||
for (auto toClientReq :
|
||||
llvm::make_early_inc_range(req.getOps<RequestToClientConnectionOp>())) {
|
||||
auto cosimIn = b.create<NullSourceOp>(
|
||||
toClientReq.getLoc(), ChannelType::get(ctxt, b.getI1Type()));
|
||||
auto cosim = b.create<CosimEndpointOp>(toClientReq.getLoc(),
|
||||
toClientReq.receiving().getType(),
|
||||
clk, rst, cosimIn, ++epIdCtr);
|
||||
cosim->setAttr("name", toStringAttr(toClientReq.clientNamePath()));
|
||||
req.getResult(clientReqIdx).replaceAllUsesWith(cosim.recv());
|
||||
toClientReq.erase();
|
||||
++clientReqIdx;
|
||||
}
|
||||
|
||||
// Since outgoing data gets passed in through block args, we need to translate
|
||||
// internal Values (with the block) to the external Values driving them.
|
||||
BlockAndValueMapping argMap;
|
||||
for (unsigned i = 0, e = portReqs->getArguments().size(); i < e; ++i)
|
||||
argMap.map(portReqs->getArgument(i), req->getOperand(i + 2));
|
||||
|
||||
// Create output Cosim endpoints.
|
||||
// Build a mapping of client names to request.
|
||||
DenseMap<ArrayAttr, SmallVector<Operation *, 0>> clientNameToOps;
|
||||
for (auto &op : req.getOps())
|
||||
if (auto req = dyn_cast<RequestToClientConnectionOp>(op))
|
||||
clientNameToOps[req.clientNamePathAttr()].push_back(&op);
|
||||
else if (auto req = dyn_cast<RequestToServerConnectionOp>(op))
|
||||
clientNameToOps[req.clientNamePathAttr()].push_back(&op);
|
||||
|
||||
// For any client names with two requests of opposite directions, mark a
|
||||
// paired.
|
||||
DenseMap<RequestToServerConnectionOp, RequestToClientConnectionOp> pairs;
|
||||
for (auto opsForClientName : clientNameToOps) {
|
||||
const SmallVector<Operation *, 0> &ops = opsForClientName.second;
|
||||
if (ops.size() != 2)
|
||||
continue;
|
||||
|
||||
// Only proceed if a to_client and to_server are found.
|
||||
RequestToClientConnectionOp to_client;
|
||||
RequestToServerConnectionOp to_server;
|
||||
if ((to_client = dyn_cast<RequestToClientConnectionOp>(ops[0]))) {
|
||||
if (!(to_server = dyn_cast<RequestToServerConnectionOp>(ops[1])))
|
||||
continue;
|
||||
} else if ((to_server = dyn_cast<RequestToServerConnectionOp>(ops[0]))) {
|
||||
if (!(to_client = dyn_cast<RequestToClientConnectionOp>(ops[1])))
|
||||
continue;
|
||||
}
|
||||
assert(to_client);
|
||||
assert(to_server);
|
||||
pairs[to_server] = to_client;
|
||||
}
|
||||
|
||||
// Create output Cosim endpoints. If the request is one half of a pair, store
|
||||
// it.
|
||||
DenseMap<RequestToClientConnectionOp, CosimEndpointOp> pairedCosimEndpoints;
|
||||
for (auto toServerReq :
|
||||
llvm::make_early_inc_range(req.getOps<RequestToServerConnectionOp>())) {
|
||||
|
||||
Type resType;
|
||||
auto foundClient = pairs.find(toServerReq);
|
||||
if (foundClient != pairs.end())
|
||||
resType = foundClient->second.receiving().getType();
|
||||
else
|
||||
resType = ChannelType::get(ctxt, b.getI1Type());
|
||||
|
||||
auto cosim = b.create<CosimEndpointOp>(
|
||||
toServerReq.getLoc(), ChannelType::get(ctxt, b.getI1Type()), clk, rst,
|
||||
toServerReq.getLoc(), resType, clk, rst,
|
||||
argMap.lookup(toServerReq.sending()), ++epIdCtr);
|
||||
cosim->setAttr("name", toStringAttr(toServerReq.clientNamePath()));
|
||||
toServerReq.erase();
|
||||
|
||||
if (foundClient != pairs.end())
|
||||
pairedCosimEndpoints[foundClient->second] = cosim;
|
||||
}
|
||||
|
||||
// Create Cosim endpoints for the incoming data requests.
|
||||
unsigned clientReqIdx = 0;
|
||||
for (auto toClientReq :
|
||||
llvm::make_early_inc_range(req.getOps<RequestToClientConnectionOp>())) {
|
||||
CosimEndpointOp cosim;
|
||||
|
||||
// If this is a paired request, use the paired endpoint.
|
||||
auto epFound = pairedCosimEndpoints.find(toClientReq);
|
||||
if (epFound != pairedCosimEndpoints.end()) {
|
||||
cosim = epFound->second;
|
||||
} else {
|
||||
auto cosimIn = b.create<NullSourceOp>(
|
||||
toClientReq.getLoc(), ChannelType::get(ctxt, b.getI1Type()));
|
||||
cosim = b.create<CosimEndpointOp>(toClientReq.getLoc(),
|
||||
toClientReq.receiving().getType(), clk,
|
||||
rst, cosimIn, ++epIdCtr);
|
||||
cosim->setAttr("name", toStringAttr(toClientReq.clientNamePath()));
|
||||
}
|
||||
req.getResult(clientReqIdx).replaceAllUsesWith(cosim.recv());
|
||||
toClientReq.erase();
|
||||
++clientReqIdx;
|
||||
}
|
||||
|
||||
// Erase the generation request.
|
||||
|
@ -202,6 +254,19 @@ LogicalResult ESIConnectServicesPass::process(hw::HWMutableModuleLike mod) {
|
|||
for (auto instOp : modBlock.getOps<ServiceInstanceOp>())
|
||||
localImplReqs[instOp.service_symbolAttr()] = new Block();
|
||||
|
||||
// Decompose the 'inout' requests int to 'in' and 'out' requests.
|
||||
mod.walk([&](RequestInOutChannelOp reqInOut) {
|
||||
ImplicitLocOpBuilder b(reqInOut.getLoc(), reqInOut);
|
||||
b.create<RequestToServerConnectionOp>(reqInOut.servicePortAttr(),
|
||||
reqInOut.sending(),
|
||||
reqInOut.clientNamePathAttr());
|
||||
auto toClientReq = b.create<RequestToClientConnectionOp>(
|
||||
reqInOut.receiving().getType(), reqInOut.servicePortAttr(),
|
||||
reqInOut.clientNamePathAttr());
|
||||
reqInOut.receiving().replaceAllUsesWith(toClientReq.receiving());
|
||||
reqInOut.erase();
|
||||
});
|
||||
|
||||
// Find all of the "local" requests.
|
||||
mod.walk([&](Operation *op) {
|
||||
if (auto req = dyn_cast<RequestToClientConnectionOp>(op)) {
|
||||
|
|
|
@ -63,7 +63,7 @@ esi.service.decl @HostComms {
|
|||
}
|
||||
|
||||
hw.module @Loopback (%clk: i1) -> () {
|
||||
// expected-error @+1 {{'esi.service.req.to_client' op Cannot find port named "Recv"}}
|
||||
// expected-error @+1 {{'esi.service.req.to_client' op Could not find service port declaration @HostComms::Recv}}
|
||||
%dataIn = esi.service.req.to_client <@HostComms::@Recv> (["loopback_tohw"]) : !esi.channel<i32>
|
||||
}
|
||||
// -----
|
||||
|
@ -79,8 +79,30 @@ hw.module @Loopback (%clk: i1) -> () {
|
|||
|
||||
// -----
|
||||
|
||||
esi.service.decl @HostComms {
|
||||
esi.service.inout @ReqResp : !esi.channel<i8> -> !esi.channel<i16>
|
||||
}
|
||||
|
||||
hw.module @Loopback (%clk: i1, %s: !esi.channel<i16>) -> () {
|
||||
// expected-error @+1 {{'esi.service.req.inout' op Request to_server type does not match port type '!esi.channel<i8>'}}
|
||||
%dataIn = esi.service.req.inout %s -> <@HostComms::@ReqResp> (["loopback_tohw"]) : !esi.channel<i16> -> !esi.channel<i16>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
esi.service.decl @HostComms {
|
||||
esi.service.inout @ReqResp : !esi.channel<i8> -> !esi.channel<i16>
|
||||
}
|
||||
|
||||
hw.module @Loopback (%clk: i1, %s: !esi.channel<i8>) -> () {
|
||||
// expected-error @+1 {{'esi.service.req.inout' op Request to_client type does not match port type '!esi.channel<i16>'}}
|
||||
%dataIn = esi.service.req.inout %s -> <@HostComms::@ReqResp> (["loopback_tohw"]) : !esi.channel<i8> -> !esi.channel<i8>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
hw.module @Loopback (%clk: i1) -> () {
|
||||
// expected-error @+1 {{Cannot find module "HostComms"}}
|
||||
// expected-error @+1 {{'esi.service.req.to_client' op Could not find service port declaration @HostComms::Recv}}
|
||||
%dataIn = esi.service.req.to_client <@HostComms::@Recv> (["loopback_tohw"]) : !esi.channel<i32>
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
esi.service.decl @HostComms {
|
||||
esi.service.to_server @Send : !esi.channel<!esi.any>
|
||||
esi.service.to_client @Recv : !esi.channel<i8>
|
||||
esi.service.inout @ReqResp : !esi.channel<i8> -> !esi.channel<i16>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: hw.module @Top(%clk: i1, %rst: i1) {
|
||||
|
@ -14,9 +15,9 @@ esi.service.decl @HostComms {
|
|||
// CHECK: hw.instance "m1" @Loopback(clk: %clk: i1) -> ()
|
||||
|
||||
// CONN-LABEL: hw.module @Top(%clk: i1, %rst: i1) {
|
||||
// CONN: [[R2:%.+]] = esi.cosim %clk, %rst, %m1.loopback_fromhw, 21 {name = "m1.loopback_fromhw"} : !esi.channel<i8> -> !esi.channel<i1>
|
||||
// CONN: [[R0:%.+]] = esi.null : !esi.channel<i1>
|
||||
// CONN: [[R1:%.+]] = esi.cosim %clk, %rst, [[R0]], 21 {name = "m1.loopback_tohw"} : !esi.channel<i1> -> !esi.channel<i8>
|
||||
// CONN: [[R2:%.+]] = esi.cosim %clk, %rst, %m1.loopback_fromhw, 22 {name = "m1.loopback_fromhw"} : !esi.channel<i8> -> !esi.channel<i1>
|
||||
// CONN: [[R1:%.+]] = esi.cosim %clk, %rst, [[R0]], 22 {name = "m1.loopback_tohw"} : !esi.channel<i1> -> !esi.channel<i8>
|
||||
// CONN: %m1.loopback_fromhw = hw.instance "m1" @Loopback(clk: %clk: i1, loopback_tohw: [[R1]]: !esi.channel<i8>) -> (loopback_fromhw: !esi.channel<i8>)
|
||||
hw.module @Top (%clk: i1, %rst: i1) {
|
||||
esi.service.instance @HostComms impl as "cosim" opts {EpID_start = 20} (%clk, %rst) : (i1, i1) -> ()
|
||||
|
@ -108,3 +109,41 @@ msft.module @MsLoopback {} (%clk: i1) -> () {
|
|||
esi.service.req.to_server %dataIn -> <@HostComms::@Send> (["loopback_fromhw"]) : !esi.channel<i8>
|
||||
msft.output
|
||||
}
|
||||
|
||||
|
||||
|
||||
// CONN-LABEL: msft.module @InOutTop {} (%clk: i1) -> (chksum: i8) {
|
||||
// CONN: %0:2 = esi.service.impl_req @HostComms impl as "topComms"(%clk, %m1.loopback_inout) : (i1, !esi.channel<i8>) -> (i8, !esi.channel<i16>) {
|
||||
// CONN: ^bb0(%arg0: !esi.channel<i8>):
|
||||
// CONN: %1 = esi.service.req.to_client <@HostComms::@ReqResp>(["m1", "loopback_inout"]) : !esi.channel<i16>
|
||||
// CONN: esi.service.req.to_server %arg0 -> <@HostComms::@ReqResp>(["m1", "loopback_inout"]) : !esi.channel<i8>
|
||||
// CONN: }
|
||||
// CONN: %m1.loopback_inout = msft.instance @m1 @InOutLoopback(%clk, %0#1) : (i1, !esi.channel<i16>) -> !esi.channel<i8>
|
||||
// CONN: msft.output %0#0 : i8
|
||||
msft.module @InOutTop {} (%clk: i1) -> (chksum: i8) {
|
||||
%c = esi.service.instance @HostComms impl as "topComms" (%clk) : (i1) -> (i8)
|
||||
msft.instance @m1 @InOutLoopback (%clk) : (i1) -> ()
|
||||
msft.output %c : i8
|
||||
}
|
||||
|
||||
// CONN-LABEL: msft.module @InOutLoopback {} (%clk: i1, %loopback_inout: !esi.channel<i16>) -> (loopback_inout: !esi.channel<i8>) {
|
||||
// CONN: %rawOutput, %valid = esi.unwrap.vr %loopback_inout, %ready : i16
|
||||
// CONN: %0 = comb.extract %rawOutput from 0 : (i16) -> i8
|
||||
// CONN: %chanOutput, %ready = esi.wrap.vr %0, %valid : i8
|
||||
// CONN: msft.output %chanOutput : !esi.channel<i8>
|
||||
msft.module @InOutLoopback {} (%clk: i1) -> () {
|
||||
%dataIn = esi.service.req.inout %dataTrunc -> <@HostComms::@ReqResp> (["loopback_inout"]) : !esi.channel<i8> -> !esi.channel<i16>
|
||||
%unwrap, %valid = esi.unwrap.vr %dataIn, %rdy: i16
|
||||
%trunc = comb.extract %unwrap from 0 : (i16) -> (i8)
|
||||
%dataTrunc, %rdy = esi.wrap.vr %trunc, %valid : i8
|
||||
msft.output
|
||||
}
|
||||
|
||||
// CONN-LABEL: msft.module @LoopbackCosimTop {} (%clk: i1, %rst: i1) {
|
||||
// CONN: [[R1:%.+]] = esi.cosim %clk, %rst, %m1.loopback_inout, 21 {name = "m1.loopback_inout"} : !esi.channel<i8> -> !esi.channel<i16>
|
||||
// CONN: %m1.loopback_inout = msft.instance @m1 @InOutLoopback(%clk, %0) : (i1, !esi.channel<i16>) -> !esi.channel<i8>
|
||||
msft.module @LoopbackCosimTop {} (%clk: i1, %rst: i1) {
|
||||
esi.service.instance @HostComms impl as "cosim" opts {EpID_start = 20} (%clk, %rst) : (i1, i1) -> ()
|
||||
msft.instance @m1 @InOutLoopback(%clk) : (i1) -> ()
|
||||
msft.output
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue