20 #include "cosim.grpc.pb.h"
22 #include <grpc/grpc.h>
23 #include <grpcpp/channel.h>
24 #include <grpcpp/client_context.h>
25 #include <grpcpp/create_channel.h>
26 #include <grpcpp/security/credentials.h>
38 using grpc::ClientContext;
39 using grpc::ClientReader;
40 using grpc::ClientReaderWriter;
41 using grpc::ClientWriter;
46 throw std::runtime_error(msg +
". Code " + to_string(s.error_code()) +
47 ": " + s.error_message() +
" (" +
48 s.error_details() +
")");
54 : stub(std::move(stub)) {}
55 std::unique_ptr<ChannelServer::Stub>
stub;
58 bool getChannelDesc(
const std::string &channelName,
59 esi::cosim::ChannelDesc &desc);
67 std::unique_ptr<AcceleratorConnection>
70 std::string host =
"localhost";
73 if ((colon = connectionString.find(
':')) != std::string::npos) {
74 portStr = connectionString.substr(colon + 1);
75 host = connectionString.substr(0, colon);
76 }
else if (connectionString.ends_with(
"cosim.cfg")) {
77 std::ifstream cfg(connectionString);
78 std::string line, key, value;
80 while (getline(cfg, line))
81 if ((colon = line.find(
":")) != std::string::npos) {
82 key = line.substr(0, colon);
83 value = line.substr(colon + 1);
86 else if (key ==
"host")
90 if (portStr.size() == 0)
91 throw std::runtime_error(
"port line not found in file");
92 }
else if (connectionString ==
"env") {
93 char *hostEnv = getenv(
"ESI_COSIM_HOST");
98 char *portEnv = getenv(
"ESI_COSIM_PORT");
102 throw std::runtime_error(
"ESI_COSIM_PORT environment variable not set");
104 throw std::runtime_error(
"Invalid connection std::string '" +
105 connectionString +
"'");
107 uint16_t port = stoul(portStr);
108 auto conn = make_unique<CosimAccelerator>(
ctxt, host, port);
112 char *manifestMethod = getenv(
"ESI_COSIM_MANIFEST_MMIO");
113 if (manifestMethod !=
nullptr)
114 conn->setManifestMethod(ManifestMethod::MMIO);
120 CosimAccelerator::CosimAccelerator(
Context &
ctxt, std::string hostname,
124 auto channel = grpc::CreateChannel(hostname +
":" + std::to_string(port),
125 grpc::InsecureChannelCredentials());
135 class CosimSysInfo :
public SysInfo {
137 CosimSysInfo(ChannelServer::Stub *rpcClient) : rpcClient(rpcClient) {}
139 uint32_t getEsiVersion()
const override {
140 ::esi::cosim::Manifest response = getManifest();
141 return response.esi_version();
144 std::vector<uint8_t> getCompressedManifest()
const override {
145 ::esi::cosim::Manifest response = getManifest();
146 std::string compressedManifestStr = response.compressed_manifest();
147 return std::vector<uint8_t>(compressedManifestStr.begin(),
148 compressedManifestStr.end());
152 ::esi::cosim::Manifest getManifest()
const {
153 ::esi::cosim::Manifest response;
157 ClientContext context;
159 Status s = rpcClient->GetManifest(&context, arg, &response);
161 std::this_thread::sleep_for(std::chrono::milliseconds(10));
162 }
while (response.esi_version() < 0);
166 esi::cosim::ChannelServer::Stub *rpcClient;
174 WriteCosimChannelPort(ChannelServer::Stub *rpcClient,
const ChannelDesc &desc,
175 const Type *type, std::string name)
177 ~WriteCosimChannelPort() =
default;
179 void connectImpl(std::optional<unsigned> bufferSize)
override {
180 if (desc.type() != getType()->getID())
181 throw std::runtime_error(
"Channel '" + name +
182 "' has wrong type. Expected " +
183 getType()->getID() +
", got " + desc.type());
184 if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_SERVER)
185 throw std::runtime_error(
"Channel '" + name +
186 "' is not a to server channel");
187 assert(desc.name() == name);
192 ClientContext context;
193 AddressedMessage msg;
194 msg.set_channel_name(name);
195 msg.mutable_message()->set_data(
data.getBytes(),
data.getSize());
196 VoidMessage response;
197 grpc::Status sendStatus = rpcClient->SendToServer(&context, msg, &response);
198 if (!sendStatus.ok())
199 throw std::runtime_error(
"Failed to write to channel '" + name +
200 "': " + std::to_string(sendStatus.error_code()) +
201 " " + sendStatus.error_message() +
202 ". Details: " + sendStatus.error_details());
206 ChannelServer::Stub *rpcClient;
217 class ReadCosimChannelPort
219 public grpc::ClientReadReactor<esi::cosim::Message> {
221 ReadCosimChannelPort(ChannelServer::Stub *rpcClient,
const ChannelDesc &desc,
222 const Type *type, std::string name)
225 virtual ~ReadCosimChannelPort() { disconnect(); }
227 void connectImpl(std::optional<unsigned> bufferSize)
override {
229 if (desc.type() != getType()->getID())
230 throw std::runtime_error(
"Channel '" + name +
231 "' has wrong type. Expected " +
232 getType()->getID() +
", got " + desc.type());
233 if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_CLIENT)
234 throw std::runtime_error(
"Channel '" + name +
235 "' is not a to server channel");
236 assert(desc.name() == name);
239 context = std::make_unique<ClientContext>();
240 rpcClient->async()->ConnectToClientChannel(context.get(), &desc,
this);
242 StartRead(&incomingMessage);
247 void OnReadDone(
bool ok)
override {
253 const std::string &messageString = incomingMessage.data();
254 MessageData data(
reinterpret_cast<const uint8_t *
>(messageString.data()),
255 messageString.size());
256 while (!callback(data))
259 std::this_thread::sleep_for(std::chrono::milliseconds(10));
262 StartRead(&incomingMessage);
266 void disconnect()
override {
269 context->TryCancel();
275 ChannelServer::Stub *rpcClient;
281 std::unique_ptr<ClientContext> context;
283 esi::cosim::Message incomingMessage;
288 std::map<std::string, ChannelPort &>
291 std::map<std::string, ChannelPort &> channelResults;
296 return channelResults;
297 const std::map<std::string, std::string> &channelAssignments = f->second;
300 for (
auto [name, dir, type] : bundleType->
getChannels()) {
301 auto f = channelAssignments.find(name);
302 if (f == channelAssignments.end())
303 throw std::runtime_error(
"Could not find channel assignment for '" +
304 idPath.
toStr() +
"." + name +
"'");
305 std::string channelName = f->second;
311 throw std::runtime_error(
"Could not find channel '" + channelName +
312 "' in cosimulation");
316 port =
new WriteCosimChannelPort(
rpcClient->
stub.get(), chDesc, type,
319 port =
new ReadCosimChannelPort(
rpcClient->
stub.get(), chDesc, type,
323 channelResults.emplace(name, *port);
325 return channelResults;
333 ClientContext context;
335 ListOfChannels response;
336 Status s = stub->ListChannels(&context, arg, &response);
338 for (
const auto &channel : response.channels())
339 if (channel.name() == channelName) {
347 class CosimMMIO :
public MMIO {
352 ChannelDesc readArg, readResp;
353 if (!rpcClient->
getChannelDesc(
"__cosim_mmio_read.arg", readArg) ||
355 throw std::runtime_error(
"Could not find MMIO channels");
361 readArgPort = std::make_unique<WriteCosimChannelPort>(
362 rpcClient->
stub.get(), readArg, i32Type,
"__cosim_mmio_read.arg");
363 readRespPort = std::make_unique<ReadCosimChannelPort>(
364 rpcClient->
stub.get(), readResp, i64Type,
"__cosim_mmio_read.result");
366 *readArgPort, *readRespPort));
371 uint64_t read(uint32_t addr)
const override {
373 std::future<MessageData> result = readMMIO->call(arg);
375 return *result.get().as<uint64_t>();
378 void write(uint32_t addr, uint64_t data)
override {
380 throw std::runtime_error(
"Cosim MMIO write not implemented");
385 if (
auto t =
ctxt.getType(type->
getID())) {
389 ctxt.registerType(type);
392 std::unique_ptr<WriteCosimChannelPort> readArgPort;
393 std::unique_ptr<ReadCosimChannelPort> readRespPort;
394 std::unique_ptr<FuncService::Function> readMMIO;
397 class CosimHostMem :
public HostMem {
401 struct CosimHostMemRegion :
public HostMemRegion {
402 CosimHostMemRegion(std::size_t size) {
406 virtual ~CosimHostMemRegion() { free(ptr); }
407 virtual void *getPtr()
const {
return ptr; }
408 virtual std::size_t getSize()
const {
return size; }
415 virtual std::unique_ptr<HostMemRegion> allocate(std::size_t size,
417 return std::unique_ptr<HostMemRegion>(
new CosimHostMemRegion(size));
419 virtual bool mapMemory(
void *ptr, std::size_t size,
423 virtual void unmapMemory(
void *ptr)
const {}
434 if (prefix.size() > 0)
437 if (implName ==
"cosim") {
439 for (
auto client : clients) {
440 AppIDPath fullClientPath = prefix + client.relPath;
441 std::map<std::string, std::string> channelAssignments;
442 for (
auto assignment : std::any_cast<std::map<std::string, std::any>>(
443 client.implOptions.at(
"channel_assignments")))
444 channelAssignments[assignment.first] =
445 std::any_cast<std::string>(assignment.second);
453 return new CosimHostMem();
454 }
else if (svcType ==
typeid(
SysInfo)) {
456 case ManifestMethod::Cosim:
458 case ManifestMethod::MMIO:
459 return new MMIOSysInfo(getService<services::MMIO>());
461 }
else if (svcType ==
typeid(
CustomService) && implName ==
"cosim") {
assert(baseType &&"element must be base type")
esi::backends::cosim::CosimAccelerator::StubContainer StubContainer
static void checkStatus(Status s, const std::string &msg)
REGISTER_ACCELERATOR("cosim", backends::cosim::CosimAccelerator)
Abstract class representing a connection to an accelerator.
Context & getCtxt() const
std::string toStr() const
static bool isWrite(BundleType::Direction bundleDir)
Compute the direction of a channel given the bundle direction and the bundle port's direction.
Bundles represent a collection of channels.
const ChannelVector & getChannels() const
Unidirectional channels are the basic communication primitive between the host and accelerator.
AcceleratorConnections, Accelerators, and Manifests must all share a context.
A logical chunk of data representing serialized data.
static MessageData from(T &t)
Cast from a type to its raw bytes.
A ChannelPort which reads data from the accelerator.
virtual void disconnect() override
Root class of the ESI type system.
A ChannelPort which sends data to the accelerator.
Connect to an ESI simulation.
std::map< AppIDPath, std::map< std::string, std::string > > clientChannelAssignments
void setManifestMethod(ManifestMethod method)
virtual std::map< std::string, ChannelPort & > requestChannelsFor(AppIDPath, const BundleType *) override
Request the host side channel ports for a particular instance (identified by the AppID path).
virtual Service * createService(Service::Type service, AppIDPath path, std::string implName, const ServiceImplDetails &details, const HWClientDetails &clients) override
Called by getServiceImpl exclusively.
ManifestMethod manifestMethod
std::set< std::unique_ptr< ChannelPort > > channels
StubContainer * rpcClient
A service for which there are no standard services registered.
Implement the SysInfo API for a standard MMIO protocol.
Parent class of all APIs modeled as 'services'.
const std::type_info & Type
Information about the Accelerator system.
def connect(destination, source)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
std::map< std::string, std::any > ServiceImplDetails
std::vector< HWClientDetail > HWClientDetails
Hack around C++ not having a way to forward declare a nested class.
std::unique_ptr< ChannelServer::Stub > stub
bool getChannelDesc(const std::string &channelName, esi::cosim::ChannelDesc &desc)
Get the type ID for a channel name.
StubContainer(std::unique_ptr< ChannelServer::Stub > stub)
Options for allocating host memory.