20#include "cosim.grpc.pb.h"
23#include <grpcpp/channel.h>
24#include <grpcpp/client_context.h>
25#include <grpcpp/create_channel.h>
26#include <grpcpp/security/credentials.h>
38using grpc::ClientContext;
39using grpc::ClientReader;
40using grpc::ClientReaderWriter;
41using grpc::ClientWriter;
46 throw std::runtime_error(msg +
". Code " + to_string(s.error_code()) +
47 ": " + s.error_message() +
" (" +
48 s.error_details() +
")");
55 std::unique_ptr<ChannelServer::Stub>
stub;
59 esi::cosim::ChannelDesc &desc);
67std::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);
124 auto channel = grpc::CreateChannel(hostname +
":" + std::to_string(port),
125 grpc::InsecureChannelCredentials());
136class CosimSysInfo :
public SysInfo {
138 CosimSysInfo(ChannelServer::Stub *rpcClient) : rpcClient(rpcClient) {}
140 uint32_t getEsiVersion()
const override {
141 ::esi::cosim::Manifest response = getManifest();
142 return response.esi_version();
145 std::vector<uint8_t> getCompressedManifest()
const override {
146 ::esi::cosim::Manifest response = getManifest();
147 std::string compressedManifestStr = response.compressed_manifest();
148 return std::vector<uint8_t>(compressedManifestStr.begin(),
149 compressedManifestStr.end());
153 ::esi::cosim::Manifest getManifest()
const {
154 ::esi::cosim::Manifest response;
158 ClientContext context;
160 Status s = rpcClient->GetManifest(&context, arg, &response);
162 std::this_thread::sleep_for(std::chrono::milliseconds(10));
163 }
while (response.esi_version() < 0);
167 esi::cosim::ChannelServer::Stub *rpcClient;
175 WriteCosimChannelPort(ChannelServer::Stub *rpcClient,
const ChannelDesc &desc,
176 const Type *type, std::string name)
178 ~WriteCosimChannelPort() =
default;
180 void connectImpl(std::optional<unsigned> bufferSize)
override {
181 if (desc.type() != getType()->getID())
182 throw std::runtime_error(
"Channel '" + name +
183 "' has wrong type. Expected " +
184 getType()->getID() +
", got " + desc.type());
185 if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_SERVER)
186 throw std::runtime_error(
"Channel '" + name +
187 "' is not a to server channel");
188 assert(desc.name() == name);
193 ClientContext context;
194 AddressedMessage msg;
195 msg.set_channel_name(name);
196 msg.mutable_message()->set_data(
data.getBytes(),
data.getSize());
197 VoidMessage response;
198 grpc::Status sendStatus = rpcClient->SendToServer(&context, msg, &response);
199 if (!sendStatus.ok())
200 throw std::runtime_error(
"Failed to write to channel '" + name +
201 "': " + std::to_string(sendStatus.error_code()) +
202 " " + sendStatus.error_message() +
203 ". Details: " + sendStatus.error_details());
212 ChannelServer::Stub *rpcClient;
223class ReadCosimChannelPort
225 public grpc::ClientReadReactor<esi::cosim::Message> {
227 ReadCosimChannelPort(ChannelServer::Stub *rpcClient,
const ChannelDesc &desc,
228 const Type *type, std::string name)
231 virtual ~ReadCosimChannelPort() { disconnect(); }
233 void connectImpl(std::optional<unsigned> bufferSize)
override {
235 if (desc.type() != getType()->getID())
236 throw std::runtime_error(
"Channel '" + name +
237 "' has wrong type. Expected " +
238 getType()->getID() +
", got " + desc.type());
239 if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_CLIENT)
240 throw std::runtime_error(
"Channel '" + name +
241 "' is not a to client channel");
242 assert(desc.name() == name);
245 context = std::make_unique<ClientContext>();
246 rpcClient->async()->ConnectToClientChannel(context.get(), &desc,
this);
248 StartRead(&incomingMessage);
253 void OnReadDone(
bool ok)
override {
259 const std::string &messageString = incomingMessage.data();
260 MessageData data(
reinterpret_cast<const uint8_t *
>(messageString.data()),
261 messageString.size());
262 while (!callback(data))
265 std::this_thread::sleep_for(std::chrono::milliseconds(10));
268 StartRead(&incomingMessage);
272 void disconnect()
override {
275 context->TryCancel();
281 ChannelServer::Stub *rpcClient;
287 std::unique_ptr<ClientContext> context;
289 esi::cosim::Message incomingMessage;
296 std::map<std::string, ChannelPort &> channelResults;
301 return channelResults;
302 const std::map<std::string, std::string> &channelAssignments = f->second;
305 for (
auto [name, dir, type] : bundleType->
getChannels()) {
306 auto f = channelAssignments.find(name);
307 if (f == channelAssignments.end())
308 throw std::runtime_error(
"Could not find channel assignment for '" +
309 idPath.
toStr() +
"." + name +
"'");
310 std::string channelName = f->second;
316 throw std::runtime_error(
"Could not find channel '" + channelName +
317 "' in cosimulation");
321 port =
new WriteCosimChannelPort(
rpcClient->
stub.get(), chDesc, type,
324 port =
new ReadCosimChannelPort(
rpcClient->
stub.get(), chDesc, type,
328 channelResults.emplace(name, *port);
330 return channelResults;
338 ClientContext context;
340 ListOfChannels response;
341 Status s = stub->ListChannels(&context, arg, &response);
343 for (
const auto &channel : response.channels())
344 if (channel.name() == channelName) {
352class CosimMMIO :
public MMIO {
357 ChannelDesc cmdArg, cmdResp;
358 if (!rpcClient->
getChannelDesc(
"__cosim_mmio_read_write.arg", cmdArg) ||
359 !rpcClient->
getChannelDesc(
"__cosim_mmio_read_write.result", cmdResp))
360 throw std::runtime_error(
"Could not find MMIO channels");
365 {{
"write", new BitsType(
"i1", 1)},
366 {
"offset", new UIntType(
"ui32", 32)},
367 {
"data", new BitsType(
"i64", 64)}}));
370 cmdArgPort = std::make_unique<WriteCosimChannelPort>(
371 rpcClient->
stub.get(), cmdArg, cmdType,
"__cosim_mmio_read_write.arg");
372 cmdRespPort = std::make_unique<ReadCosimChannelPort>(
373 rpcClient->
stub.get(), cmdResp, i64Type,
374 "__cosim_mmio_read_write.result");
389 uint64_t read(uint32_t addr)
const override {
390 MMIOCmd cmd{.offset = addr, .write =
false};
392 std::future<MessageData> result = cmdMMIO->call(arg);
394 return *result.get().as<uint64_t>();
397 void write(uint32_t addr, uint64_t data)
override {
398 MMIOCmd cmd{.data =
data, .offset =
addr, .write =
true};
400 std::future<MessageData> result = cmdMMIO->call(arg);
406 if (
auto t =
ctxt.getType(type->
getID())) {
410 ctxt.registerType(type);
413 std::unique_ptr<WriteCosimChannelPort> cmdArgPort;
414 std::unique_ptr<ReadCosimChannelPort> cmdRespPort;
415 std::unique_ptr<FuncService::Function> cmdMMIO;
419struct HostMemReadReq {
425struct HostMemReadResp {
430struct HostMemWriteReq {
436using HostMemWriteResp = uint8_t;
439class CosimHostMem :
public HostMem {
443 : acc(acc),
ctxt(
ctxt), rpcClient(rpcClient) {}
445 void start()
override {
453 ChannelDesc readArg, readResp;
454 if (!rpcClient->
getChannelDesc(
"__cosim_hostmem_read_req.data", readArg) ||
455 !rpcClient->
getChannelDesc(
"__cosim_hostmem_read_resp.data", readResp))
456 throw std::runtime_error(
"Could not find HostMem read channels");
460 {{
"tag", new UIntType(
"ui8", 8)},
461 {
"data", new BitsType(
"i64", 64)}}));
464 {{
"address", new UIntType(
"ui64", 64)},
465 {
"length", new UIntType(
"ui32", 32)},
466 {
"tag", new UIntType(
"ui8", 8)}}));
470 readRespPort = std::make_unique<WriteCosimChannelPort>(
471 rpcClient->
stub.get(), readResp, readRespType,
472 "__cosim_hostmem_read_resp.data");
473 readReqPort = std::make_unique<ReadCosimChannelPort>(
474 rpcClient->
stub.get(), readArg, readReqType,
475 "__cosim_hostmem_read_req.data");
476 readReqPort->connect(
477 [
this](
const MessageData &req) {
return serviceRead(req); });
480 ChannelDesc writeArg, writeResp;
481 if (!rpcClient->
getChannelDesc(
"__cosim_hostmem_write.arg", writeArg) ||
482 !rpcClient->
getChannelDesc(
"__cosim_hostmem_write.result", writeResp))
483 throw std::runtime_error(
"Could not find HostMem write channels");
486 getType(ctxt,
new UIntType(writeResp.type(), 8));
489 {{
"address", new UIntType(
"ui64", 64)},
490 {
"tag", new UIntType(
"ui8", 8)},
491 {
"data", new BitsType(
"i64", 64)}}));
494 writeRespPort = std::make_unique<WriteCosimChannelPort>(
495 rpcClient->
stub.get(), writeResp, writeRespType,
496 "__cosim_hostmem_write.result");
497 writeReqPort = std::make_unique<ReadCosimChannelPort>(
498 rpcClient->
stub.get(), writeArg, writeReqType,
499 "__cosim_hostmem_write.arg");
501 *writeRespPort, *writeReqPort));
502 write->connect([
this](
const MessageData &req) {
return serviceWrite(req); },
509 const HostMemReadReq *req = reqBytes.
as<HostMemReadReq>();
510 acc.getLogger().debug(
511 [&](std::string &subsystem, std::string &msg,
512 std::unique_ptr<std::map<std::string, std::any>> &details) {
513 subsystem =
"HostMem";
514 msg =
"Read request: addr=0x" +
toHex(req->address) +
515 " len=" + std::to_string(req->length) +
516 " tag=" + std::to_string(req->tag);
519 uint64_t *dataPtr =
reinterpret_cast<uint64_t *
>(req->address);
520 for (uint32_t i = 0, e = (req->length + 7) / 8; i < e; ++i) {
521 HostMemReadResp resp{.data = dataPtr[i], .tag = req->tag};
522 acc.getLogger().debug(
523 [&](std::string &subsystem, std::string &msg,
524 std::unique_ptr<std::map<std::string, std::any>> &details) {
525 subsystem =
"HostMem";
526 msg =
"Read result: data=0x" +
toHex(resp.data) +
527 " tag=" + std::to_string(resp.tag);
537 const HostMemWriteReq *req = reqBytes.
as<HostMemWriteReq>();
538 acc.getLogger().debug(
539 [&](std::string &subsystem, std::string &msg,
540 std::unique_ptr<std::map<std::string, std::any>> &details) {
541 subsystem =
"HostMem";
542 msg =
"Write request: addr=0x" +
toHex(req->address) +
" data=0x" +
543 toHex(req->data) +
" tag=" + std::to_string(req->tag);
545 uint64_t *dataPtr =
reinterpret_cast<uint64_t *
>(req->address);
546 *dataPtr = req->data;
547 HostMemWriteResp resp = req->tag;
551 struct CosimHostMemRegion :
public HostMemRegion {
552 CosimHostMemRegion(std::size_t size) {
556 virtual ~CosimHostMemRegion() { free(ptr); }
557 virtual void *getPtr()
const override {
return ptr; }
558 virtual std::size_t getSize()
const override {
return size; }
565 virtual std::unique_ptr<HostMemRegion>
567 return std::unique_ptr<HostMemRegion>(
new CosimHostMemRegion(size));
569 virtual bool mapMemory(
void *ptr, std::size_t size,
573 virtual void unmapMemory(
void *ptr)
const override {}
577 if (
auto t =
ctxt.getType(type->
getID())) {
581 ctxt.registerType(type);
587 std::unique_ptr<WriteCosimChannelPort> readRespPort;
588 std::unique_ptr<ReadCosimChannelPort> readReqPort;
589 std::unique_ptr<CallService::Callback> read;
590 std::unique_ptr<WriteCosimChannelPort> writeRespPort;
591 std::unique_ptr<ReadCosimChannelPort> writeReqPort;
592 std::unique_ptr<CallService::Callback> write;
603 if (prefix.size() > 0)
607 for (
auto client : clients) {
608 AppIDPath fullClientPath = prefix + client.relPath;
609 std::map<std::string, std::string> channelAssignments;
610 for (
auto assignment : client.channelAssignments)
611 if (assignment.second.type ==
"cosim")
612 channelAssignments[assignment.first] = std::any_cast<std::string>(
613 assignment.second.implOptions.at(
"name"));
621 }
else if (svcType ==
typeid(
SysInfo)) {
626 return new MMIOSysInfo(getService<services::MMIO>());
628 }
else if (svcType ==
typeid(
CustomService) && implName ==
"cosim") {
#define REGISTER_ACCELERATOR(Name, TAccelerator)
assert(baseType &&"element must be base type")
static void checkStatus(Status s, const std::string &msg)
Abstract class representing a connection to an accelerator.
virtual void disconnect()
Disconnect from the accelerator cleanly.
Context & getCtxt() const
std::map< std::string, services::Service * > ServiceTable
Context & ctxt
ESI accelerator context.
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.
const T * as() const
Cast to a type.
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
Structs are an ordered collection of fields, each with a name and a type.
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)
static std::unique_ptr< AcceleratorConnection > connect(Context &, std::string connectionString)
Parse the connection std::string and instantiate the accelerator.
virtual Service * createService(Service::Type service, AppIDPath path, std::string implName, const ServiceImplDetails &details, const HWClientDetails &clients) override
Called by getServiceImpl exclusively.
ManifestMethod manifestMethod
CosimAccelerator(Context &, std::string hostname, uint16_t port)
Construct and connect to a cosim server.
std::set< std::unique_ptr< ChannelPort > > channels
virtual std::map< std::string, ChannelPort & > requestChannelsFor(AppIDPath, const BundleType *, const ServiceTable &) override
Request the host side channel ports for a particular instance (identified by the AppID path).
StubContainer * rpcClient
static Callback * get(AcceleratorConnection &acc, AppID id, WriteChannelPort &result, ReadChannelPort &arg)
A service for which there are no standard services registered.
static Function * get(AppID id, WriteChannelPort &arg, ReadChannelPort &result)
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.
std::map< std::string, std::any > ServiceImplDetails
std::string toHex(uint32_t val)
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.