45 name(std::move(name)) {}
46 ~WriteCosimChannelPort() =
default;
49 if (desc.dir != RpcClient::ChannelDirection::ToServer)
50 throw std::runtime_error(
"Channel '" + name +
51 "' is not a to server channel");
57 conn.getLogger().trace(
59 &data](std::string &subsystem, std::string &msg,
60 std::unique_ptr<std::map<std::string, std::any>> &details) {
61 subsystem =
"cosim_write";
62 msg =
"Writing message to channel '" + name +
"'";
63 details = std::make_unique<std::map<std::string, std::any>>();
64 (*details)[
"channel"] = name;
65 (*details)[
"data_size"] =
data.getSize();
66 (*details)[
"message_data"] =
data.toHex();
69 client.writeToServer(name, data);
96 name(std::move(name)) {}
98 ~ReadCosimChannelPort() =
default;
101 if (desc.dir != RpcClient::ChannelDirection::ToClient)
102 throw std::runtime_error(
"Channel '" + name +
103 "' is not a to client channel");
107 client.connectClientReceiver(name, [
this](
const MessageData &data) {
109 conn.getLogger().trace(
111 std::string &subsystem, std::string &msg,
112 std::unique_ptr<std::map<std::string, std::any>> &details) {
113 subsystem =
"cosim_read";
114 msg =
"Received message from channel '" + name +
"'";
115 details = std::make_unique<std::map<std::string, std::any>>();
116 (*details)[
"channel"] = name;
117 (*details)[
"data_size"] =
data.getSize();
118 (*details)[
"message_data"] =
data.toHex();
125 conn.getLogger().trace(
127 std::string &subsystem, std::string &msg,
128 std::unique_ptr<std::map<std::string, std::any>> &details) {
129 subsystem =
"cosim_read";
130 msg =
"Message from channel '" + name +
"' consumed";
139 conn.getLogger().debug(
"cosim_read",
"Disconnecting channel " + name);
141 connection->disconnect();
152 std::unique_ptr<RpcClient::ReadChannelConnection> connection;
165std::unique_ptr<AcceleratorConnection>
168 std::string host =
"localhost";
171 if ((colon = connectionString.find(
':')) != std::string::npos) {
172 portStr = connectionString.substr(colon + 1);
173 host = connectionString.substr(0, colon);
174 }
else if (connectionString.ends_with(
"cosim.cfg")) {
175 std::ifstream cfg(connectionString);
176 std::string line, key, value;
178 while (getline(cfg, line))
179 if ((colon = line.find(
":")) != std::string::npos) {
180 key = line.substr(0, colon);
181 value = line.substr(colon + 1);
184 else if (key ==
"host")
188 if (portStr.size() == 0)
189 throw std::runtime_error(
"port line not found in file");
190 }
else if (connectionString ==
"env") {
191 char *hostEnv = getenv(
"ESI_COSIM_HOST");
196 char *portEnv = getenv(
"ESI_COSIM_PORT");
200 throw std::runtime_error(
"ESI_COSIM_PORT environment variable not set");
202 throw std::runtime_error(
"Invalid connection std::string '" +
203 connectionString +
"'");
205 uint16_t port = stoul(portStr);
206 auto conn = make_unique<CosimAccelerator>(
ctxt, host, port);
222 rpcClient = std::make_unique<RpcClient>(hostname, port);
230class CosimSysInfo :
public SysInfo {
240 :
SysInfo(conn), rpcClient(rpcClient) {
244 if (!rpcClient->
getChannelDesc(
"__cosim_cycle_count.arg", argDesc) ||
245 !rpcClient->
getChannelDesc(
"__cosim_cycle_count.result", resultDesc))
253 {{
"cycle", i64Type}, {
"freq", i64Type}}));
255 reqPort = std::make_unique<WriteCosimChannelPort>(
256 conn, *rpcClient, argDesc, i1Type,
"__cosim_cycle_count.arg");
257 respPort = std::make_unique<ReadCosimChannelPort>(
258 conn, *rpcClient, resultDesc, resultType,
"__cosim_cycle_count.result");
261 {{
"arg", BundleType::Direction::To, i1Type},
262 {
"result", BundleType::Direction::From, resultType}});
264 bundleType, *reqPort, *respPort));
268 uint32_t getEsiVersion()
const override {
return rpcClient->
getEsiVersion(); }
269 std::optional<uint64_t> getCycleCount()
const override {
272 return getCycleInfo().cycle;
274 std::optional<uint64_t> getCoreClockFrequency()
const override {
277 return getCycleInfo().freq;
280 std::vector<uint8_t> getCompressedManifest()
const override {
295 std::unique_ptr<WriteCosimChannelPort> reqPort;
296 std::unique_ptr<ReadCosimChannelPort> respPort;
297 std::unique_ptr<FuncService::Function> func;
299 CycleInfo getCycleInfo()
const {
301 std::future<MessageData> result = func->call(arg);
304 return *respMsg.
as<CycleInfo>();
310class CosimMMIO :
public MMIO {
314 :
MMIO(conn, idPath, clients) {
318 if (!rpcClient->
getChannelDesc(
"__cosim_mmio_read_write.arg", cmdArg) ||
319 !rpcClient->
getChannelDesc(
"__cosim_mmio_read_write.result", cmdResp))
320 throw std::runtime_error(
"Could not find MMIO channels");
324 ctxt,
new StructType(cmdArg.
type, {{
"write", new BitsType(
"i1", 1)},
325 {
"offset", new UIntType(
"ui32", 32)},
326 {
"data", new BitsType(
"i64", 64)}}));
329 cmdArgPort = std::make_unique<WriteCosimChannelPort>(
330 conn, *rpcClient, cmdArg, cmdType,
"__cosim_mmio_read_write.arg");
331 cmdRespPort = std::make_unique<ReadCosimChannelPort>(
332 conn, *rpcClient, cmdResp, i64Type,
"__cosim_mmio_read_write.result");
334 "cosimMMIO", {{
"arg", BundleType::Direction::To, cmdType},
335 {
"result", BundleType::Direction::From, i64Type}});
337 *cmdArgPort, *cmdRespPort));
350 uint64_t read(uint32_t addr)
const override {
351 MMIOCmd cmd{.data = 0, .offset =
addr, .write =
false};
353 std::future<MessageData> result = cmdMMIO->call(arg);
355 uint64_t ret = *result.get().as<uint64_t>();
357 [addr, ret](std::string &subsystem, std::string &msg,
358 std::unique_ptr<std::map<std::string, std::any>> &details) {
359 subsystem =
"cosim_mmio";
360 msg =
"MMIO[0x" +
toHex(addr) +
"] = 0x" +
toHex(ret);
365 void write(uint32_t addr, uint64_t data)
override {
368 data](std::string &subsystem, std::string &msg,
369 std::unique_ptr<std::map<std::string, std::any>> &details) {
370 subsystem =
"cosim_mmio";
371 msg =
"MMIO[0x" +
toHex(addr) +
"] <- 0x" +
toHex(data);
373 MMIOCmd cmd{.data =
data, .offset =
addr, .write =
true};
375 std::future<MessageData> result = cmdMMIO->call(arg);
388 std::unique_ptr<WriteCosimChannelPort> cmdArgPort;
389 std::unique_ptr<ReadCosimChannelPort> cmdRespPort;
390 std::unique_ptr<FuncService::Function> cmdMMIO;
394struct HostMemReadReq {
400struct HostMemReadResp {
405struct HostMemWriteReq {
412using HostMemWriteResp = uint8_t;
415class CosimHostMem :
public HostMem {
418 :
HostMem(acc), acc(acc), ctxt(ctxt), rpcClient(rpcClient) {}
420 void start()
override {
432 if (!rpcClient->
getChannelDesc(
"__cosim_hostmem_read_req.data", readArg) ||
433 !rpcClient->
getChannelDesc(
"__cosim_hostmem_read_resp.data", readResp))
434 throw std::runtime_error(
"Could not find HostMem read channels");
438 {{
"tag", new UIntType(
"ui8", 8)},
439 {
"data", new BitsType(
"i64", 64)}}));
442 {{
"address", new UIntType(
"ui64", 64)},
443 {
"length", new UIntType(
"ui32", 32)},
444 {
"tag", new UIntType(
"ui8", 8)}}));
448 readRespPort = std::make_unique<WriteCosimChannelPort>(
449 conn, *rpcClient, readResp, readRespType,
450 "__cosim_hostmem_read_resp.data");
451 readReqPort = std::make_unique<ReadCosimChannelPort>(
452 conn, *rpcClient, readArg, readReqType,
453 "__cosim_hostmem_read_req.data");
454 readReqPort->connect(
455 [
this](
const MessageData &req) {
return serviceRead(req); });
459 if (!rpcClient->
getChannelDesc(
"__cosim_hostmem_write.arg", writeArg) ||
460 !rpcClient->
getChannelDesc(
"__cosim_hostmem_write.result", writeResp))
461 throw std::runtime_error(
"Could not find HostMem write channels");
467 {{
"address", new UIntType(
"ui64", 64)},
468 {
"tag", new UIntType(
"ui8", 8)},
469 {
"data", new BitsType(
"i64", 64)}}));
472 writeRespPort = std::make_unique<WriteCosimChannelPort>(
473 conn, *rpcClient, writeResp, writeRespType,
474 "__cosim_hostmem_write.result");
475 writeReqPort = std::make_unique<ReadCosimChannelPort>(
476 conn, *rpcClient, writeArg, writeReqType,
"__cosim_hostmem_write.arg");
482 bundleType, *writeRespPort,
484 write->connect([
this](
const MessageData &req) {
return serviceWrite(req); },
491 const HostMemReadReq *req = reqBytes.
as<HostMemReadReq>();
492 acc.getLogger().trace(
493 [&](std::string &subsystem, std::string &msg,
494 std::unique_ptr<std::map<std::string, std::any>> &details) {
495 subsystem =
"hostmem";
496 msg =
"Read request: addr=0x" +
toHex(req->address) +
497 " len=" + std::to_string(req->length) +
498 " tag=" + std::to_string(req->tag);
501 uint64_t *dataPtr =
reinterpret_cast<uint64_t *
>(req->address);
502 for (uint32_t i = 0, e = (req->length + 7) / 8; i < e; ++i) {
503 HostMemReadResp resp{.data = dataPtr[i], .tag = req->tag};
504 acc.getLogger().trace(
505 [&](std::string &subsystem, std::string &msg,
506 std::unique_ptr<std::map<std::string, std::any>> &details) {
507 subsystem =
"HostMem";
508 msg =
"Read result: data=0x" +
toHex(resp.data) +
509 " tag=" + std::to_string(resp.tag);
519 const HostMemWriteReq *req = reqBytes.
as<HostMemWriteReq>();
520 acc.getLogger().trace(
521 [&](std::string &subsystem, std::string &msg,
522 std::unique_ptr<std::map<std::string, std::any>> &details) {
523 subsystem =
"hostmem";
524 msg =
"Write request: addr=0x" +
toHex(req->address) +
" data=0x" +
526 " valid_bytes=" + std::to_string(req->valid_bytes) +
527 " tag=" + std::to_string(req->tag);
529 uint8_t *dataPtr =
reinterpret_cast<uint8_t *
>(req->address);
530 for (uint8_t i = 0; i < req->valid_bytes; ++i)
531 dataPtr[i] = (req->data >> (i * 8)) & 0xFF;
532 HostMemWriteResp resp = req->tag;
536 struct CosimHostMemRegion :
public HostMemRegion {
537 CosimHostMemRegion(std::size_t size) {
539 memset(ptr, 0xFF, size);
542 virtual ~CosimHostMemRegion() { free(ptr); }
543 virtual void *getPtr()
const override {
return ptr; }
544 virtual std::size_t getSize()
const override {
return size; }
551 virtual std::unique_ptr<HostMemRegion>
553 auto ret = std::unique_ptr<HostMemRegion>(
new CosimHostMemRegion(size));
554 acc.getLogger().debug(
555 [&](std::string &subsystem, std::string &msg,
556 std::unique_ptr<std::map<std::string, std::any>> &details) {
557 subsystem =
"HostMem";
558 msg =
"Allocated host memory region at 0x" +
toHex(ret->getPtr()) +
559 " of size " + std::to_string(size);
563 virtual bool mapMemory(
void *ptr, std::size_t size,
567 virtual void unmapMemory(
void *ptr)
const override {}
581 std::unique_ptr<WriteCosimChannelPort> readRespPort;
582 std::unique_ptr<ReadCosimChannelPort> readReqPort;
583 std::unique_ptr<CallService::Callback> read;
584 std::unique_ptr<WriteCosimChannelPort> writeRespPort;
585 std::unique_ptr<ReadCosimChannelPort> writeReqPort;
586 std::unique_ptr<CallService::Callback> write;
599 if (prefix.size() > 0)
602 for (
auto client : clients) {
603 AppIDPath fullClientPath = prefix + client.relPath;
604 std::map<std::string, std::string> channelAssignments;
605 for (
auto assignment : client.channelAssignments)
606 if (assignment.second.type ==
"cosim")
607 channelAssignments[assignment.first] = std::any_cast<std::string>(
608 assignment.second.implOptions.at(
"name"));
614 const std::string &channelName,
616 const Type *type)
override;
620 std::map<AppIDPath, std::map<std::string, std::string>>
625std::unique_ptr<ChannelPort>
632 throw std::runtime_error(
"Could not find port for '" + idPath.
toStr() +
633 "." + channelName +
"'");
634 const std::map<std::string, std::string> &channelAssignments = f->second;
635 auto cosimChannelNameIter = channelAssignments.find(channelName);
636 if (cosimChannelNameIter == channelAssignments.end())
637 throw std::runtime_error(
"Could not find channel '" + idPath.
toStr() +
"." +
638 channelName +
"' in cosimulation");
643 if (!
conn.
rpcClient->getChannelDesc(cosimChannelNameIter->second, chDesc))
644 throw std::runtime_error(
"Could not find channel '" + idPath.
toStr() +
"." +
645 channelName +
"' in cosimulation");
647 std::unique_ptr<ChannelPort> port;
648 std::string fullChannelName = idPath.
toStr() +
"." + channelName;
650 port = std::make_unique<WriteCosimChannelPort>(
654 type, fullChannelName);
663 std::unique_ptr<Engine> engine =
nullptr;
664 if (engineTypeName ==
"cosim")
665 engine = std::make_unique<CosimEngine>(*
this, idPath, details, clients);
679 }
else if (svcType ==
typeid(
SysInfo)) {
682 return new CosimSysInfo(*
this,
rpcClient.get());
684 return new MMIOSysInfo(getService<services::MMIO>());
#define REGISTER_ACCELERATOR(Name, TAccelerator)
Abstract class representing a connection to an accelerator.
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...
virtual void disconnect()
Disconnect from the accelerator cleanly.
Logger & getLogger() const
std::string toStr() const
Bits are just an array of bits.
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.
virtual void connectImpl(const ConnectOptions &options)
Called by all connect methods to let backends initiate the underlying connections.
AcceleratorConnections, Accelerators, and Manifests must all share a context.
std::optional< const Type * > getType(Type::ID id) const
Resolve a type id to the type.
void registerType(Type *type)
Register a type with the context. Takes ownership of the pointer type.
Engines implement the actual channel communication between the host and the accelerator.
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.
std::function< bool(MessageData)> callback
Backends call this callback when new data is available.
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.
virtual bool tryWriteImpl(const MessageData &data)=0
Implementation for tryWrite(). Subclasses must implement this.
virtual void writeImpl(const MessageData &)=0
Implementation for write(). Subclasses must implement this.
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.
std::unique_ptr< RpcClient > rpcClient
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
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.
A gRPC client for communicating with the cosimulation server.
std::vector< uint8_t > getCompressedManifest() const
Get the compressed manifest from the server.
uint32_t getEsiVersion() const
Get the ESI version from the manifest.
bool getChannelDesc(const std::string &channelName, ChannelDesc &desc) const
Get the channel description for a channel name.
static Callback * get(AcceleratorConnection &acc, AppID id, const 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
Description of a channel from the server.
Options for allocating host memory.