47 name(std::move(name)) {}
48 ~WriteCosimChannelPort() =
default;
51 if (desc.dir != RpcClient::ChannelDirection::ToServer)
52 throw std::runtime_error(
"Channel '" + name +
53 "' is not a to server channel");
59 for (
const auto &frame : frames) {
60 conn.getLogger().trace(
62 &data](std::string &subsystem, std::string &msg,
63 std::unique_ptr<std::map<std::string, std::any>> &details) {
64 subsystem =
"cosim_write";
65 msg =
"Writing message to channel '" + name +
"'";
66 details = std::make_unique<std::map<std::string, std::any>>();
67 (*details)[
"channel"] = name;
68 (*details)[
"data_size"] =
data.getSize();
69 (*details)[
"message_data"] =
data.toHex();
72 client.writeToServer(name, frame);
103 name(std::move(name)) {}
105 ~ReadCosimChannelPort() =
default;
108 if (desc.dir != RpcClient::ChannelDirection::ToClient)
109 throw std::runtime_error(
"Channel '" + name +
110 "' is not a to client channel");
113 connection = client.connectClientReceiver(
114 name, [
this](std::unique_ptr<SegmentedMessageData> &data) {
116 conn.getLogger().trace(
118 std::string &subsystem, std::string &msg,
119 std::unique_ptr<std::map<std::string, std::any>> &details) {
120 subsystem =
"cosim_read";
121 msg =
"Received message from channel '" + name +
"'";
122 details = std::make_unique<std::map<std::string, std::any>>();
124 (*details)[
"channel"] = name;
125 (*details)[
"data_size"] = flat.getSize();
126 (*details)[
"message_data"] = flat.toHex();
133 conn.getLogger().trace(
135 std::string &subsystem, std::string &msg,
136 std::unique_ptr<std::map<std::string, std::any>> &details) {
137 subsystem =
"cosim_read";
138 msg =
"Message from channel '" + name +
"' consumed";
147 conn.getLogger().debug(
"cosim_read",
"Disconnecting channel " + name);
149 connection->disconnect();
160 std::unique_ptr<RpcClient::ReadChannelConnection> connection;
173std::unique_ptr<AcceleratorConnection>
176 std::string host =
"localhost";
179 if ((colon = connectionString.find(
':')) != std::string::npos) {
180 portStr = connectionString.substr(colon + 1);
181 host = connectionString.substr(0, colon);
182 }
else if (connectionString.ends_with(
"cosim.cfg")) {
183 std::ifstream cfg(connectionString);
184 std::string line, key, value;
186 while (getline(cfg, line))
187 if ((colon = line.find(
":")) != std::string::npos) {
188 key = line.substr(0, colon);
189 value = line.substr(colon + 1);
192 else if (key ==
"host")
196 if (portStr.size() == 0)
197 throw std::runtime_error(
"port line not found in file");
198 }
else if (connectionString ==
"env") {
199 char *hostEnv = getenv(
"ESI_COSIM_HOST");
204 char *portEnv = getenv(
"ESI_COSIM_PORT");
208 throw std::runtime_error(
"ESI_COSIM_PORT environment variable not set");
210 throw std::runtime_error(
"Invalid connection std::string '" +
211 connectionString +
"'");
213 uint16_t port = stoul(portStr);
214 auto conn = make_unique<CosimAccelerator>(
ctxt, host, port);
239class CosimSysInfo :
public SysInfo {
249 :
SysInfo(conn), rpcClient(rpcClient) {
253 if (!rpcClient->
getChannelDesc(
"__cosim_cycle_count.arg", argDesc) ||
254 !rpcClient->
getChannelDesc(
"__cosim_cycle_count.result", resultDesc))
257 Context &ctxt = conn.getCtxt();
262 {{
"cycle", i64Type}, {
"freq", i64Type}}));
264 reqPort = std::make_unique<WriteCosimChannelPort>(
265 conn, *rpcClient, argDesc, i1Type,
"__cosim_cycle_count.arg");
266 respPort = std::make_unique<ReadCosimChannelPort>(
267 conn, *rpcClient, resultDesc, resultType,
"__cosim_cycle_count.result");
270 {{
"arg", BundleType::Direction::To, i1Type},
271 {
"result", BundleType::Direction::From, resultType}});
273 bundleType, *reqPort, *respPort));
277 uint32_t getEsiVersion()
const override {
return rpcClient->
getEsiVersion(); }
278 std::optional<uint64_t> getCycleCount()
const override {
281 return getCycleInfo().cycle;
283 std::optional<uint64_t> getCoreClockFrequency()
const override {
286 return getCycleInfo().freq;
289 std::vector<uint8_t> getCompressedManifest()
const override {
304 std::unique_ptr<WriteCosimChannelPort> reqPort;
305 std::unique_ptr<ReadCosimChannelPort> respPort;
306 std::unique_ptr<FuncService::Function> func;
308 CycleInfo getCycleInfo()
const {
310 std::future<MessageData> result = func->call(arg);
313 return *respMsg.
as<CycleInfo>();
319class CosimMMIO :
public MMIO {
327 if (!rpcClient->
getChannelDesc(
"__cosim_mmio_read_write.arg", cmdArg) ||
328 !rpcClient->
getChannelDesc(
"__cosim_mmio_read_write.result", cmdResp))
329 throw std::runtime_error(
"Could not find MMIO channels");
333 ctxt,
new StructType(cmdArg.
type, {{
"write", new BitsType(
"i1", 1)},
334 {
"offset", new UIntType(
"ui32", 32)},
335 {
"data", new BitsType(
"i64", 64)}}));
338 cmdArgPort = std::make_unique<WriteCosimChannelPort>(
339 conn, *rpcClient, cmdArg, cmdType,
"__cosim_mmio_read_write.arg");
340 cmdRespPort = std::make_unique<ReadCosimChannelPort>(
341 conn, *rpcClient, cmdResp, i64Type,
"__cosim_mmio_read_write.result");
343 "cosimMMIO", {{
"arg", BundleType::Direction::To, cmdType},
344 {
"result", BundleType::Direction::From, i64Type}});
346 *cmdArgPort, *cmdRespPort));
359 uint64_t
read(uint32_t addr)
const override {
360 MMIOCmd cmd{.data = 0, .offset =
addr, .write =
false};
362 std::lock_guard<std::mutex> g(mmioCmdLock);
363 std::future<MessageData> result = cmdMMIO->call(arg);
365 uint64_t ret = *result.get().as<uint64_t>();
366 conn.getLogger().trace(
367 [addr, ret](std::string &subsystem, std::string &msg,
368 std::unique_ptr<std::map<std::string, std::any>> &details) {
369 subsystem =
"cosim_mmio";
370 msg =
"MMIO[0x" +
toHex(addr) +
"] = 0x" +
toHex(ret);
375 void write(uint32_t addr, uint64_t data)
override {
376 conn.getLogger().trace(
378 data](std::string &subsystem, std::string &msg,
379 std::unique_ptr<std::map<std::string, std::any>> &details) {
380 subsystem =
"cosim_mmio";
381 msg =
"MMIO[0x" +
toHex(addr) +
"] <- 0x" +
toHex(data);
383 MMIOCmd cmd{.data =
data, .offset =
addr, .write =
true};
385 std::lock_guard<std::mutex> g(mmioCmdLock);
386 std::future<MessageData> result = cmdMMIO->call(arg);
399 std::unique_ptr<WriteCosimChannelPort> cmdArgPort;
400 std::unique_ptr<ReadCosimChannelPort> cmdRespPort;
401 std::unique_ptr<FuncService::Function> cmdMMIO;
405struct HostMemReadReq {
411using HostMemWriteResp = uint8_t;
422void putBits(uint8_t *buf,
size_t bitOff,
size_t width, uint64_t val) {
423 for (
size_t i = 0; i < width; ++i)
424 if ((val >> i) & 1ULL)
425 buf[(bitOff + i) >> 3] |=
static_cast<uint8_t
>(1u << ((bitOff + i) & 7));
427uint64_t getBits(
const uint8_t *buf,
size_t bitOff,
size_t width) {
429 for (
size_t i = 0; i < width; ++i)
430 if (buf[(bitOff + i) >> 3] & (1u << ((bitOff + i) & 7)))
440class HostMemReadRespFrame {
442 static constexpr size_t kMessageBits = 8 + 64 + 1;
443 static constexpr size_t kMessageBytes = (kMessageBits + 7) / 8;
445 HostMemReadRespFrame(uint8_t tag, uint64_t data,
bool last) {
446 putBits(bytes.data(), kLastOff, kLastW, last ? 1 : 0);
447 putBits(bytes.data(), kDataOff, kDataW, data);
448 putBits(bytes.data(), kTagOff, kTagW, tag);
451 uint8_t tag()
const {
return getBits(bytes.data(), kTagOff, kTagW); }
452 uint64_t
data()
const {
return getBits(bytes.data(), kDataOff, kDataW); }
453 bool last()
const {
return getBits(bytes.data(), kLastOff, kLastW) != 0; }
461 static constexpr size_t kLastW = 1, kLastOff = 0;
462 static constexpr size_t kDataW = 64, kDataOff = kLastOff + kLastW;
463 static constexpr size_t kTagW = 8, kTagOff = kDataOff + kDataW;
464 std::array<uint8_t, kMessageBytes> bytes{};
476class HostMemWriteReqFrame {
478 static constexpr size_t kNumItems = 8;
479 static constexpr size_t kMessageBits = 64 + 8 + kNumItems * 8 + 3 + 1;
480 static constexpr size_t kMessageBytes = (kMessageBits + 7) / 8;
482 explicit HostMemWriteReqFrame(
const uint8_t *raw) {
483 std::memcpy(bytes.data(), raw, kMessageBytes);
486 uint64_t address()
const {
return getBits(bytes.data(), kAddrOff, kAddrW); }
487 uint8_t tag()
const {
return getBits(bytes.data(), kTagOff, kTagW); }
488 uint8_t dataByte(
size_t i)
const {
489 return getBits(bytes.data(), kDataOff + 8 * i, 8);
492 unsigned validBytes()
const {
493 return static_cast<unsigned>(getBits(bytes.data(), kSizeOff, kSizeW)) + 1;
495 bool last()
const {
return getBits(bytes.data(), kLastOff, kLastW) != 0; }
498 static constexpr size_t kLastW = 1, kLastOff = 0;
499 static constexpr size_t kSizeW = 3, kSizeOff = kLastOff + kLastW;
500 static constexpr size_t kDataW = kNumItems * 8, kDataOff = kSizeOff + kSizeW;
501 static constexpr size_t kTagW = 8, kTagOff = kDataOff + kDataW;
502 static constexpr size_t kAddrW = 64, kAddrOff = kTagOff + kTagW;
503 std::array<uint8_t, kMessageBytes> bytes{};
510static constexpr uint32_t kPcieMaxReadRequestBytes = 64 * 4;
512class CosimHostMem :
public HostMem {
517 void start()
override {
529 if (!rpcClient->
getChannelDesc(
"__cosim_hostmem_read_req.data", readArg) ||
530 !rpcClient->
getChannelDesc(
"__cosim_hostmem_read_resp.data", readResp))
531 throw std::runtime_error(
"Could not find HostMem read channels");
534 ctxt,
new StructType(readResp.
type, {{
"tag", new UIntType(
"ui8", 8)},
535 {
"data", new BitsType(
"i64", 64)},
536 {
"last", new BitsType(
"i1", 1)}}));
539 {{
"address", new UIntType(
"ui64", 64)},
540 {
"length", new UIntType(
"ui32", 32)},
541 {
"tag", new UIntType(
"ui8", 8)}}));
545 readRespPort = std::make_unique<WriteCosimChannelPort>(
546 conn, *rpcClient, readResp, readRespType,
547 "__cosim_hostmem_read_resp.data");
548 readReqPort = std::make_unique<ReadCosimChannelPort>(
549 conn, *rpcClient, readArg, readReqType,
550 "__cosim_hostmem_read_req.data");
551 readReqPort->connect(
552 [
this](
const MessageData &req) {
return serviceRead(req); });
556 if (!rpcClient->
getChannelDesc(
"__cosim_hostmem_write.arg", writeArg) ||
557 !rpcClient->
getChannelDesc(
"__cosim_hostmem_write.result", writeResp))
558 throw std::runtime_error(
"Could not find HostMem write channels");
564 {{
"address", new UIntType(
"ui64", 64)},
565 {
"tag", new UIntType(
"ui8", 8)},
566 {
"data", new BitsType(
"i64", 64)},
567 {
"data_size", new UIntType(
"ui3", 3)},
568 {
"last", new BitsType(
"i1", 1)}}));
571 writeRespPort = std::make_unique<WriteCosimChannelPort>(
572 conn, *rpcClient, writeResp, writeRespType,
573 "__cosim_hostmem_write.result");
574 writeReqPort = std::make_unique<ReadCosimChannelPort>(
575 conn, *rpcClient, writeArg, writeReqType,
"__cosim_hostmem_write.arg");
581 bundleType, *writeRespPort,
583 write->connect([
this](
const MessageData &req) {
return serviceWrite(req); },
590 const HostMemReadReq *req = reqBytes.
as<HostMemReadReq>();
591 acc.getLogger().trace(
592 [&](std::string &subsystem, std::string &msg,
593 std::unique_ptr<std::map<std::string, std::any>> &details) {
594 subsystem =
"hostmem";
595 msg =
"Read request: addr=0x" +
toHex(req->address) +
596 " len=" + std::to_string(req->length) +
597 " tag=" + std::to_string(req->tag);
602 uint64_t *dataPtr =
reinterpret_cast<uint64_t *
>(req->address);
603 uint32_t numDataResps = (req->length + 7) / 8;
604 if (numDataResps == 0)
605 acc.getLogger().error(
607 std::format(
"Read request with length=0 from addr=0x{} tag={}. "
608 "Reads of length 0 are not valid and indicate a bug "
610 toHex(req->address), req->tag));
611 if (req->length > kPcieMaxReadRequestBytes)
612 acc.getLogger().error(
614 std::format(
"Read request length={} from addr=0x{} tag={} exceeds "
615 "the PCIe maximum read request size ({} bytes). The "
616 "requester must split reads larger than this into "
617 "multiple requests.",
618 req->length,
toHex(req->address), req->tag,
619 kPcieMaxReadRequestBytes));
620 uint32_t numResps = std::max(numDataResps, 1u);
621 for (uint32_t i = 0; i < numResps; ++i) {
622 uint64_t word = i < numDataResps ? dataPtr[i] : 0;
623 bool last = i + 1 == numResps;
624 HostMemReadRespFrame frame(req->tag, word, last);
625 acc.getLogger().trace(
626 [&](std::string &subsystem, std::string &msg,
627 std::unique_ptr<std::map<std::string, std::any>> &details) {
628 subsystem =
"HostMem";
629 msg =
"Read result: data=0x" +
toHex(word) +
630 " tag=" + std::to_string(req->tag) +
631 " last=" + std::to_string(last);
633 readRespPort->write(frame.toMessage());
641 if (reqBytes.
getSize() != HostMemWriteReqFrame::kMessageBytes)
642 throw std::runtime_error(
643 "HostMem write frame size mismatch. Size is " +
644 std::to_string(reqBytes.
getSize()) +
", expected " +
645 std::to_string(HostMemWriteReqFrame::kMessageBytes) +
".");
646 HostMemWriteReqFrame req(reqBytes.
getBytes());
647 acc.getLogger().trace(
648 [&](std::string &subsystem, std::string &msg,
649 std::unique_ptr<std::map<std::string, std::any>> &details) {
650 subsystem =
"hostmem";
651 msg =
"Write request: addr=0x" +
toHex(req.address()) +
652 " valid_bytes=" + std::to_string(req.validBytes()) +
653 " tag=" + std::to_string(req.tag()) +
654 " last=" + std::to_string(req.last());
656 uint8_t *dataPtr =
reinterpret_cast<uint8_t *
>(req.address());
657 unsigned validBytes = req.validBytes();
658 for (
unsigned i = 0; i < validBytes; ++i)
659 dataPtr[i] = req.dataByte(i);
660 HostMemWriteResp resp = req.tag();
664 struct CosimHostMemRegion :
public HostMemRegion {
665 CosimHostMemRegion(std::size_t size) {
667 memset(ptr, 0xFF, size);
670 virtual ~CosimHostMemRegion() { free(ptr); }
671 virtual void *getPtr()
const override {
return ptr; }
672 virtual std::size_t getSize()
const override {
return size; }
679 virtual std::unique_ptr<HostMemRegion>
681 auto ret = std::unique_ptr<HostMemRegion>(
new CosimHostMemRegion(size));
682 acc.getLogger().debug(
683 [&](std::string &subsystem, std::string &msg,
684 std::unique_ptr<std::map<std::string, std::any>> &details) {
685 subsystem =
"HostMem";
686 msg =
"Allocated host memory region at 0x" +
toHex(ret->getPtr()) +
687 " of size " + std::to_string(size);
691 virtual bool mapMemory(
void *ptr, std::size_t size,
695 virtual void unmapMemory(
void *ptr)
const override {}
709 std::unique_ptr<WriteCosimChannelPort> readRespPort;
710 std::unique_ptr<ReadCosimChannelPort> readReqPort;
711 std::unique_ptr<CallService::Callback>
read;
712 std::unique_ptr<WriteCosimChannelPort> writeRespPort;
713 std::unique_ptr<ReadCosimChannelPort> writeReqPort;
714 std::unique_ptr<CallService::Callback>
write;
727 if (prefix.size() > 0)
730 for (
auto client : clients) {
731 AppIDPath fullClientPath = prefix + client.relPath;
732 std::map<std::string, std::string> channelAssignments;
733 for (
auto assignment : client.channelAssignments)
734 if (assignment.second.type ==
"cosim")
735 channelAssignments[assignment.first] = std::any_cast<std::string>(
736 assignment.second.implOptions.at(
"name"));
742 const std::string &channelName,
744 const Type *type)
override;
748 std::map<AppIDPath, std::map<std::string, std::string>>
753std::unique_ptr<ChannelPort>
760 throw std::runtime_error(
"Could not find port for '" + idPath.
toStr() +
761 "." + channelName +
"'");
762 const std::map<std::string, std::string> &channelAssignments = f->second;
763 auto cosimChannelNameIter = channelAssignments.find(channelName);
764 if (cosimChannelNameIter == channelAssignments.end())
765 throw std::runtime_error(
"Could not find channel '" + idPath.
toStr() +
"." +
766 channelName +
"' in cosimulation");
771 if (!
conn.
rpcClient->getChannelDesc(cosimChannelNameIter->second, chDesc))
772 throw std::runtime_error(
"Could not find channel '" + idPath.
toStr() +
"." +
773 channelName +
"' in cosimulation");
775 std::unique_ptr<ChannelPort> port;
776 std::string fullChannelName = idPath.
toStr() +
"." + channelName;
778 port = std::make_unique<WriteCosimChannelPort>(
782 type, fullChannelName);
791 std::unique_ptr<Engine> engine =
nullptr;
792 if (engineTypeName ==
"cosim")
793 engine = std::make_unique<CosimEngine>(*
this, idPath, details, clients);
807 }
else if (svcType ==
typeid(
SysInfo)) {
810 return new CosimSysInfo(*
this,
rpcClient.get());
812 return new MMIOSysInfo(getService<services::MMIO>());
#define REGISTER_ACCELERATOR(Name, TAccelerator)
Abstract class representing a connection to an accelerator.
Context & getCtxt() const
void clearOwnedObjects()
Drop accelerator-owned objects before a derived backend destroys resources that those objects may ref...
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.
A concrete flat message backed by a single vector of bytes.
const uint8_t * getBytes() const
const T * as() const
Cast to a type.
size_t getSize() const
Get the size of the data in bytes.
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
Disconnect the channel.
bool invokeCallback(std::unique_ptr< SegmentedMessageData > &msg)
Invoke the currently registered callback.
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.
std::vector< MessageData > getMessageFrames(const MessageData &data)
Break a message into its frames.
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 client for the cosim RPC 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.