21#include "cosim.grpc.pb.h"
24#include <grpcpp/channel.h>
25#include <grpcpp/client_context.h>
26#include <grpcpp/create_channel.h>
27#include <grpcpp/security/credentials.h>
39using grpc::ClientContext;
40using grpc::ClientReader;
41using grpc::ClientReaderWriter;
42using grpc::ClientWriter;
47 throw std::runtime_error(msg +
". Code " + to_string(s.error_code()) +
48 ": " + s.error_message() +
" (" +
49 s.error_details() +
")");
56 std::unique_ptr<ChannelServer::Stub>
stub;
60 esi::cosim::ChannelDesc &desc);
68std::unique_ptr<AcceleratorConnection>
71 std::string host =
"localhost";
74 if ((colon = connectionString.find(
':')) != std::string::npos) {
75 portStr = connectionString.substr(colon + 1);
76 host = connectionString.substr(0, colon);
77 }
else if (connectionString.ends_with(
"cosim.cfg")) {
78 std::ifstream cfg(connectionString);
79 std::string line, key, value;
81 while (getline(cfg, line))
82 if ((colon = line.find(
":")) != std::string::npos) {
83 key = line.substr(0, colon);
84 value = line.substr(colon + 1);
87 else if (key ==
"host")
91 if (portStr.size() == 0)
92 throw std::runtime_error(
"port line not found in file");
93 }
else if (connectionString ==
"env") {
94 char *hostEnv = getenv(
"ESI_COSIM_HOST");
99 char *portEnv = getenv(
"ESI_COSIM_PORT");
103 throw std::runtime_error(
"ESI_COSIM_PORT environment variable not set");
105 throw std::runtime_error(
"Invalid connection std::string '" +
106 connectionString +
"'");
108 uint16_t port = stoul(portStr);
109 auto conn = make_unique<CosimAccelerator>(
ctxt, host, port);
125 auto channel = grpc::CreateChannel(hostname +
":" + std::to_string(port),
126 grpc::InsecureChannelCredentials());
137class CosimSysInfo :
public SysInfo {
140 :
SysInfo(conn), rpcClient(rpcClient) {}
142 uint32_t getEsiVersion()
const override {
143 ::esi::cosim::Manifest response = getManifest();
144 return response.esi_version();
147 std::vector<uint8_t> getCompressedManifest()
const override {
148 ::esi::cosim::Manifest response = getManifest();
149 std::string compressedManifestStr = response.compressed_manifest();
150 return std::vector<uint8_t>(compressedManifestStr.begin(),
151 compressedManifestStr.end());
155 ::esi::cosim::Manifest getManifest()
const {
156 ::esi::cosim::Manifest response;
160 ClientContext context;
162 Status s = rpcClient->GetManifest(&context, arg, &response);
164 std::this_thread::sleep_for(std::chrono::milliseconds(10));
165 }
while (response.esi_version() < 0);
169 esi::cosim::ChannelServer::Stub *rpcClient;
178 ChannelServer::Stub *rpcClient,
const ChannelDesc &desc,
179 const Type *type, std::string name)
182 ~WriteCosimChannelPort() =
default;
184 void connectImpl(std::optional<unsigned> bufferSize)
override {
185 if (desc.type() != getType()->getID())
186 throw std::runtime_error(
"Channel '" + name +
187 "' has wrong type. Expected " +
188 getType()->getID() +
", got " + desc.type());
189 if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_SERVER)
190 throw std::runtime_error(
"Channel '" + name +
191 "' is not a to server channel");
192 assert(desc.name() == name);
198 conn.getLogger().trace(
200 &data](std::string &subsystem, std::string &msg,
201 std::unique_ptr<std::map<std::string, std::any>> &details) {
202 subsystem =
"cosim_write";
203 msg =
"Writing message to channel '" + name +
"'";
204 details = std::make_unique<std::map<std::string, std::any>>();
205 (*details)[
"channel"] = name;
206 (*details)[
"data_size"] =
data.getSize();
207 (*details)[
"message_data"] =
data.toHex();
210 ClientContext context;
211 AddressedMessage msg;
212 msg.set_channel_name(name);
213 msg.mutable_message()->set_data(
data.getBytes(),
data.getSize());
214 VoidMessage response;
215 grpc::Status sendStatus = rpcClient->SendToServer(&context, msg, &response);
216 if (!sendStatus.ok())
217 throw std::runtime_error(
"Failed to write to channel '" + name +
218 "': " + std::to_string(sendStatus.error_code()) +
219 " " + sendStatus.error_message() +
220 ". Details: " + sendStatus.error_details());
230 ChannelServer::Stub *rpcClient;
241class ReadCosimChannelPort
243 public grpc::ClientReadReactor<esi::cosim::Message> {
246 ChannelServer::Stub *rpcClient,
const ChannelDesc &desc,
247 const Type *type, std::string name)
249 name(name), context(nullptr) {}
250 virtual ~ReadCosimChannelPort() { disconnect(); }
252 void connectImpl(std::optional<unsigned> bufferSize)
override {
254 if (desc.type() != getType()->getID())
255 throw std::runtime_error(
"Channel '" + name +
256 "' has wrong type. Expected " +
257 getType()->getID() +
", got " + desc.type());
258 if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_CLIENT)
259 throw std::runtime_error(
"Channel '" + name +
260 "' is not a to client channel");
261 assert(desc.name() == name);
266 context =
new ClientContext();
267 rpcClient->async()->ConnectToClientChannel(context, &desc,
this);
269 StartRead(&incomingMessage);
274 void OnReadDone(
bool ok)
override {
280 const std::string &messageString = incomingMessage.data();
281 MessageData data(
reinterpret_cast<const uint8_t *
>(messageString.data()),
282 messageString.size());
285 conn.getLogger().trace(
287 &data](std::string &subsystem, std::string &msg,
288 std::unique_ptr<std::map<std::string, std::any>> &details) {
289 subsystem =
"cosim_read";
290 msg =
"Received message from channel '" + name +
"'";
291 details = std::make_unique<std::map<std::string, std::any>>();
292 (*details)[
"channel"] = name;
293 (*details)[
"data_size"] =
data.getSize();
294 (*details)[
"message_data"] =
data.toHex();
297 while (!callback(data))
300 std::this_thread::sleep_for(std::chrono::milliseconds(10));
303 conn.getLogger().trace(
304 [
this](std::string &subsystem, std::string &msg,
305 std::unique_ptr<std::map<std::string, std::any>> &details) {
306 subsystem =
"cosim_read";
307 msg =
"Message from channel '" + name +
"' consumed";
311 StartRead(&incomingMessage);
315 void disconnect()
override {
316 Logger &logger = conn.getLogger();
317 logger.
debug(
"cosim_read",
"Disconnecting channel " + name);
320 context->TryCancel();
328 ChannelServer::Stub *rpcClient;
334 ClientContext *context;
336 esi::cosim::Message incomingMessage;
346 ClientContext context;
348 ListOfChannels response;
349 Status s = stub->ListChannels(&context, arg, &response);
351 for (
const auto &channel : response.channels())
352 if (channel.name() == channelName) {
360class CosimMMIO :
public MMIO {
364 :
MMIO(conn, clients) {
367 ChannelDesc cmdArg, cmdResp;
368 if (!rpcClient->
getChannelDesc(
"__cosim_mmio_read_write.arg", cmdArg) ||
369 !rpcClient->
getChannelDesc(
"__cosim_mmio_read_write.result", cmdResp))
370 throw std::runtime_error(
"Could not find MMIO channels");
375 {{
"write", new BitsType(
"i1", 1)},
376 {
"offset", new UIntType(
"ui32", 32)},
377 {
"data", new BitsType(
"i64", 64)}}));
380 cmdArgPort = std::make_unique<WriteCosimChannelPort>(
381 conn, rpcClient->
stub.get(), cmdArg, cmdType,
382 "__cosim_mmio_read_write.arg");
383 cmdRespPort = std::make_unique<ReadCosimChannelPort>(
384 conn, rpcClient->
stub.get(), cmdResp, i64Type,
385 "__cosim_mmio_read_write.result");
387 "cosimMMIO", {{
"arg", BundleType::Direction::To, cmdType},
388 {
"result", BundleType::Direction::From, i64Type}});
390 *cmdArgPort, *cmdRespPort));
403 uint64_t read(uint32_t addr)
const override {
404 MMIOCmd cmd{.offset =
addr, .write =
false};
406 std::future<MessageData> result = cmdMMIO->call(arg);
408 uint64_t ret = *result.get().as<uint64_t>();
410 [addr, ret](std::string &subsystem, std::string &msg,
411 std::unique_ptr<std::map<std::string, std::any>> &details) {
412 subsystem =
"cosim_mmio";
413 msg =
"MMIO[0x" +
toHex(addr) +
"] = 0x" +
toHex(ret);
418 void write(uint32_t addr, uint64_t data)
override {
421 data](std::string &subsystem, std::string &msg,
422 std::unique_ptr<std::map<std::string, std::any>> &details) {
423 subsystem =
"cosim_mmio";
424 msg =
"MMIO[0x" +
toHex(addr) +
"] <- 0x" +
toHex(data);
426 MMIOCmd cmd{.data =
data, .offset =
addr, .write =
true};
428 std::future<MessageData> result = cmdMMIO->call(arg);
434 if (
auto t =
ctxt.getType(type->
getID())) {
438 ctxt.registerType(type);
441 std::unique_ptr<WriteCosimChannelPort> cmdArgPort;
442 std::unique_ptr<ReadCosimChannelPort> cmdRespPort;
443 std::unique_ptr<FuncService::Function> cmdMMIO;
447struct HostMemReadReq {
453struct HostMemReadResp {
458struct HostMemWriteReq {
465using HostMemWriteResp = uint8_t;
468class CosimHostMem :
public HostMem {
474 void start()
override {
485 ChannelDesc readArg, readResp;
486 if (!rpcClient->
getChannelDesc(
"__cosim_hostmem_read_req.data", readArg) ||
487 !rpcClient->
getChannelDesc(
"__cosim_hostmem_read_resp.data", readResp))
488 throw std::runtime_error(
"Could not find HostMem read channels");
492 {{
"tag", new UIntType(
"ui8", 8)},
493 {
"data", new BitsType(
"i64", 64)}}));
496 {{
"address", new UIntType(
"ui64", 64)},
497 {
"length", new UIntType(
"ui32", 32)},
498 {
"tag", new UIntType(
"ui8", 8)}}));
502 readRespPort = std::make_unique<WriteCosimChannelPort>(
503 conn, rpcClient->
stub.get(), readResp, readRespType,
504 "__cosim_hostmem_read_resp.data");
505 readReqPort = std::make_unique<ReadCosimChannelPort>(
506 conn, rpcClient->
stub.get(), readArg, readReqType,
507 "__cosim_hostmem_read_req.data");
508 readReqPort->connect(
509 [
this](
const MessageData &req) {
return serviceRead(req); });
512 ChannelDesc writeArg, writeResp;
513 if (!rpcClient->
getChannelDesc(
"__cosim_hostmem_write.arg", writeArg) ||
514 !rpcClient->
getChannelDesc(
"__cosim_hostmem_write.result", writeResp))
515 throw std::runtime_error(
"Could not find HostMem write channels");
518 getType(ctxt,
new UIntType(writeResp.type(), 8));
521 {{
"address", new UIntType(
"ui64", 64)},
522 {
"tag", new UIntType(
"ui8", 8)},
523 {
"data", new BitsType(
"i64", 64)}}));
526 writeRespPort = std::make_unique<WriteCosimChannelPort>(
527 conn, rpcClient->
stub.get(), writeResp, writeRespType,
528 "__cosim_hostmem_write.result");
529 writeReqPort = std::make_unique<ReadCosimChannelPort>(
530 conn, rpcClient->
stub.get(), writeArg, writeReqType,
531 "__cosim_hostmem_write.arg");
537 bundleType, *writeRespPort,
539 write->connect([
this](
const MessageData &req) {
return serviceWrite(req); },
546 const HostMemReadReq *req = reqBytes.
as<HostMemReadReq>();
547 acc.getLogger().trace(
548 [&](std::string &subsystem, std::string &msg,
549 std::unique_ptr<std::map<std::string, std::any>> &details) {
550 subsystem =
"hostmem";
551 msg =
"Read request: addr=0x" +
toHex(req->address) +
552 " len=" + std::to_string(req->length) +
553 " tag=" + std::to_string(req->tag);
556 uint64_t *dataPtr =
reinterpret_cast<uint64_t *
>(req->address);
557 for (uint32_t i = 0, e = (req->length + 7) / 8; i < e; ++i) {
558 HostMemReadResp resp{.data = dataPtr[i], .tag = req->tag};
559 acc.getLogger().trace(
560 [&](std::string &subsystem, std::string &msg,
561 std::unique_ptr<std::map<std::string, std::any>> &details) {
562 subsystem =
"HostMem";
563 msg =
"Read result: data=0x" +
toHex(resp.data) +
564 " tag=" + std::to_string(resp.tag);
574 const HostMemWriteReq *req = reqBytes.
as<HostMemWriteReq>();
575 acc.getLogger().trace(
576 [&](std::string &subsystem, std::string &msg,
577 std::unique_ptr<std::map<std::string, std::any>> &details) {
578 subsystem =
"hostmem";
579 msg =
"Write request: addr=0x" +
toHex(req->address) +
" data=0x" +
581 " valid_bytes=" + std::to_string(req->valid_bytes) +
582 " tag=" + std::to_string(req->tag);
584 uint8_t *dataPtr =
reinterpret_cast<uint8_t *
>(req->address);
585 for (uint8_t i = 0; i < req->valid_bytes; ++i)
586 dataPtr[i] = (req->data >> (i * 8)) & 0xFF;
587 HostMemWriteResp resp = req->tag;
591 struct CosimHostMemRegion :
public HostMemRegion {
592 CosimHostMemRegion(std::size_t size) {
594 memset(ptr, 0xFF, size);
597 virtual ~CosimHostMemRegion() { free(ptr); }
598 virtual void *getPtr()
const override {
return ptr; }
599 virtual std::size_t getSize()
const override {
return size; }
606 virtual std::unique_ptr<HostMemRegion>
608 auto ret = std::unique_ptr<HostMemRegion>(
new CosimHostMemRegion(size));
609 acc.getLogger().debug(
610 [&](std::string &subsystem, std::string &msg,
611 std::unique_ptr<std::map<std::string, std::any>> &details) {
612 subsystem =
"HostMem";
613 msg =
"Allocated host memory region at 0x" +
toHex(ret->getPtr()) +
614 " of size " + std::to_string(size);
618 virtual bool mapMemory(
void *ptr, std::size_t size,
622 virtual void unmapMemory(
void *ptr)
const override {}
626 if (
auto t =
ctxt.getType(type->
getID())) {
630 ctxt.registerType(type);
636 std::unique_ptr<WriteCosimChannelPort> readRespPort;
637 std::unique_ptr<ReadCosimChannelPort> readReqPort;
638 std::unique_ptr<CallService::Callback> read;
639 std::unique_ptr<WriteCosimChannelPort> writeRespPort;
640 std::unique_ptr<ReadCosimChannelPort> writeReqPort;
641 std::unique_ptr<CallService::Callback> write;
654 if (prefix.size() > 0)
657 for (
auto client : clients) {
658 AppIDPath fullClientPath = prefix + client.relPath;
659 std::map<std::string, std::string> channelAssignments;
660 for (
auto assignment : client.channelAssignments)
661 if (assignment.second.type ==
"cosim")
662 channelAssignments[assignment.first] = std::any_cast<std::string>(
663 assignment.second.implOptions.at(
"name"));
669 const std::string &channelName,
671 const Type *type)
override;
675 std::map<AppIDPath, std::map<std::string, std::string>>
680std::unique_ptr<ChannelPort>
687 throw std::runtime_error(
"Could not find port for '" + idPath.
toStr() +
688 "." + channelName +
"'");
689 const std::map<std::string, std::string> &channelAssignments = f->second;
690 auto cosimChannelNameIter = channelAssignments.find(channelName);
691 if (cosimChannelNameIter == channelAssignments.end())
692 throw std::runtime_error(
"Could not find channel '" + idPath.
toStr() +
"." +
693 channelName +
"' in cosimulation");
699 throw std::runtime_error(
"Could not find channel '" + idPath.
toStr() +
"." +
700 channelName +
"' in cosimulation");
702 std::unique_ptr<ChannelPort> port;
703 std::string fullChannelName = idPath.
toStr() +
"." + channelName;
705 port = std::make_unique<WriteCosimChannelPort>(
708 port = std::make_unique<ReadCosimChannelPort>(
718 std::unique_ptr<Engine> engine =
nullptr;
719 if (engineTypeName ==
"cosim")
720 engine = std::make_unique<CosimEngine>(*
this, idPath, details, clients);
734 }
else if (svcType ==
typeid(
SysInfo)) {
739 return new MMIOSysInfo(getService<services::MMIO>());
#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
Context & ctxt
ESI accelerator context.
void registerEngine(AppIDPath idPath, std::unique_ptr< Engine > engine, const HWClientDetails &clients)
If createEngine is overridden, this method should be called to register the engine and all of the cha...
Logger & getLogger() 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.
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Engines implement the actual channel communication between the host and the accelerator.
void debug(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report a debug message.
void trace(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Log a trace message.
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.
void createEngine(const std::string &engineTypeName, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients) override
Create a new engine for channel communication with the accelerator.
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
StubContainer * rpcClient
Implement the magic cosim channel communication.
CosimEngine(CosimAccelerator &conn, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
std::map< AppIDPath, std::map< std::string, std::string > > clientChannelAssignments
std::unique_ptr< ChannelPort > createPort(AppIDPath idPath, const std::string &channelName, BundleType::Direction dir, const Type *type) override
Each engine needs to know how to create a ports.
static Callback * get(AcceleratorConnection &acc, AppID id, BundleType *type, WriteChannelPort &result, ReadChannelPort &arg)
static Function * get(AppID id, BundleType *type, 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::unique_ptr< Engine > createEngine(AcceleratorConnection &conn, const std::string &dmaEngineName, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Create an engine by name.
std::map< std::string, std::any > ServiceImplDetails
std::string toHex(void *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.