CIRCT  20.0.0git
Cosim.cpp
Go to the documentation of this file.
1 //===- Cosim.cpp - Connection to ESI simulation via GRPC ------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // DO NOT EDIT!
10 // This file is distributed as part of an ESI package. The source for this file
11 // should always be modified within CIRCT
12 // (lib/dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp).
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #include "esi/backends/Cosim.h"
17 #include "esi/Services.h"
18 #include "esi/Utils.h"
19 
20 #include "cosim.grpc.pb.h"
21 
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>
27 
28 #include <fstream>
29 #include <iostream>
30 #include <set>
31 
32 using namespace esi;
33 using namespace esi::cosim;
34 using namespace esi::services;
35 using namespace esi::backends::cosim;
36 
37 using grpc::Channel;
38 using grpc::ClientContext;
39 using grpc::ClientReader;
40 using grpc::ClientReaderWriter;
41 using grpc::ClientWriter;
42 using grpc::Status;
43 
44 static void checkStatus(Status s, const std::string &msg) {
45  if (!s.ok())
46  throw std::runtime_error(msg + ". Code " + to_string(s.error_code()) +
47  ": " + s.error_message() + " (" +
48  s.error_details() + ")");
49 }
50 
51 /// Hack around C++ not having a way to forward declare a nested class.
53  StubContainer(std::unique_ptr<ChannelServer::Stub> stub)
54  : stub(std::move(stub)) {}
55  std::unique_ptr<ChannelServer::Stub> stub;
56 
57  /// Get the type ID for a channel name.
58  bool getChannelDesc(const std::string &channelName,
59  esi::cosim::ChannelDesc &desc);
60 };
62 
63 /// Parse the connection std::string and instantiate the accelerator. Support
64 /// the traditional 'host:port' syntax and a path to 'cosim.cfg' which is output
65 /// by the cosimulation when it starts (which is useful when it chooses its own
66 /// port).
67 std::unique_ptr<AcceleratorConnection>
68 CosimAccelerator::connect(Context &ctxt, std::string connectionString) {
69  std::string portStr;
70  std::string host = "localhost";
71 
72  size_t colon;
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;
79 
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);
84  if (key == "port")
85  portStr = value;
86  else if (key == "host")
87  host = value;
88  }
89 
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");
94  if (hostEnv)
95  host = hostEnv;
96  else
97  host = "localhost";
98  char *portEnv = getenv("ESI_COSIM_PORT");
99  if (portEnv)
100  portStr = portEnv;
101  else
102  throw std::runtime_error("ESI_COSIM_PORT environment variable not set");
103  } else {
104  throw std::runtime_error("Invalid connection std::string '" +
105  connectionString + "'");
106  }
107  uint16_t port = stoul(portStr);
108  auto conn = make_unique<CosimAccelerator>(ctxt, host, port);
109 
110  // Using the MMIO manifest method is really only for internal debugging, so it
111  // doesn't need to be part of the connection string.
112  char *manifestMethod = getenv("ESI_COSIM_MANIFEST_MMIO");
113  if (manifestMethod != nullptr)
114  conn->setManifestMethod(ManifestMethod::MMIO);
115 
116  return conn;
117 }
118 
119 /// Construct and connect to a cosim server.
120 CosimAccelerator::CosimAccelerator(Context &ctxt, std::string hostname,
121  uint16_t port)
123  // Connect to the simulation.
124  auto channel = grpc::CreateChannel(hostname + ":" + std::to_string(port),
125  grpc::InsecureChannelCredentials());
126  rpcClient = new StubContainer(ChannelServer::NewStub(channel));
127 }
129  disconnect();
130  if (rpcClient)
131  delete rpcClient;
132  channels.clear();
133 }
134 
135 namespace {
136 class CosimSysInfo : public SysInfo {
137 public:
138  CosimSysInfo(ChannelServer::Stub *rpcClient) : rpcClient(rpcClient) {}
139 
140  uint32_t getEsiVersion() const override {
141  ::esi::cosim::Manifest response = getManifest();
142  return response.esi_version();
143  }
144 
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());
150  }
151 
152 private:
153  ::esi::cosim::Manifest getManifest() const {
154  ::esi::cosim::Manifest response;
155  // To get around the a race condition where the manifest may not be set yet,
156  // loop until it is. TODO: fix this with the DPI API change.
157  do {
158  ClientContext context;
159  VoidMessage arg;
160  Status s = rpcClient->GetManifest(&context, arg, &response);
161  checkStatus(s, "Failed to get manifest");
162  std::this_thread::sleep_for(std::chrono::milliseconds(10));
163  } while (response.esi_version() < 0);
164  return response;
165  }
166 
167  esi::cosim::ChannelServer::Stub *rpcClient;
168 };
169 } // namespace
170 
171 namespace {
172 /// Cosim client implementation of a write channel port.
173 class WriteCosimChannelPort : public WriteChannelPort {
174 public:
175  WriteCosimChannelPort(ChannelServer::Stub *rpcClient, const ChannelDesc &desc,
176  const Type *type, std::string name)
177  : WriteChannelPort(type), rpcClient(rpcClient), desc(desc), name(name) {}
178  ~WriteCosimChannelPort() = default;
179 
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);
189  }
190 
191  /// Send a write message to the server.
192  void write(const MessageData &data) override {
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());
204  }
205 
206  bool tryWrite(const MessageData &data) override {
207  write(data);
208  return true;
209  }
210 
211 protected:
212  ChannelServer::Stub *rpcClient;
213  /// The channel description as provided by the server.
214  ChannelDesc desc;
215  /// The name of the channel from the manifest.
216  std::string name;
217 };
218 } // namespace
219 
220 namespace {
221 /// Cosim client implementation of a read channel port. Since gRPC read protocol
222 /// streams messages back, this implementation is quite complex.
223 class ReadCosimChannelPort
224  : public ReadChannelPort,
225  public grpc::ClientReadReactor<esi::cosim::Message> {
226 public:
227  ReadCosimChannelPort(ChannelServer::Stub *rpcClient, const ChannelDesc &desc,
228  const Type *type, std::string name)
229  : ReadChannelPort(type), rpcClient(rpcClient), desc(desc), name(name),
230  context(nullptr) {}
231  virtual ~ReadCosimChannelPort() { disconnect(); }
232 
233  void connectImpl(std::optional<unsigned> bufferSize) override {
234  // Sanity checking.
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);
243 
244  // Initiate a stream of messages from the server.
245  context = std::make_unique<ClientContext>();
246  rpcClient->async()->ConnectToClientChannel(context.get(), &desc, this);
247  StartCall();
248  StartRead(&incomingMessage);
249  }
250 
251  /// Gets called when there's a new message from the server. It'll be stored in
252  /// `incomingMessage`.
253  void OnReadDone(bool ok) override {
254  if (!ok)
255  // This happens when we are disconnecting since we are canceling the call.
256  return;
257 
258  // Read the delivered message and push it onto the queue.
259  const std::string &messageString = incomingMessage.data();
260  MessageData data(reinterpret_cast<const uint8_t *>(messageString.data()),
261  messageString.size());
262  while (!callback(data))
263  // Blocking here could cause deadlocks in specific situations.
264  // TODO: Implement a way to handle this better.
265  std::this_thread::sleep_for(std::chrono::milliseconds(10));
266 
267  // Initiate the next read.
268  StartRead(&incomingMessage);
269  }
270 
271  /// Disconnect this channel from the server.
272  void disconnect() override {
273  if (!context)
274  return;
275  context->TryCancel();
276  context.reset();
278  }
279 
280 protected:
281  ChannelServer::Stub *rpcClient;
282  /// The channel description as provided by the server.
283  ChannelDesc desc;
284  /// The name of the channel from the manifest.
285  std::string name;
286 
287  std::unique_ptr<ClientContext> context;
288  /// Storage location for the incoming message.
289  esi::cosim::Message incomingMessage;
290 };
291 
292 } // namespace
293 
294 std::map<std::string, ChannelPort &> CosimAccelerator::requestChannelsFor(
295  AppIDPath idPath, const BundleType *bundleType, const ServiceTable &) {
296  std::map<std::string, ChannelPort &> channelResults;
297 
298  // Find the client details for the port at 'fullPath'.
299  auto f = clientChannelAssignments.find(idPath);
300  if (f == clientChannelAssignments.end())
301  return channelResults;
302  const std::map<std::string, std::string> &channelAssignments = f->second;
303 
304  // Each channel in a bundle has a separate cosim endpoint. Find them all.
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;
311 
312  // Get the endpoint, which may or may not exist. Construct the port.
313  // Everything is validated when the client calls 'connect()' on the port.
314  ChannelDesc chDesc;
315  if (!rpcClient->getChannelDesc(channelName, chDesc))
316  throw std::runtime_error("Could not find channel '" + channelName +
317  "' in cosimulation");
318 
319  ChannelPort *port;
320  if (BundlePort::isWrite(dir)) {
321  port = new WriteCosimChannelPort(rpcClient->stub.get(), chDesc, type,
322  channelName);
323  } else {
324  port = new ReadCosimChannelPort(rpcClient->stub.get(), chDesc, type,
325  channelName);
326  }
327  channels.emplace(port);
328  channelResults.emplace(name, *port);
329  }
330  return channelResults;
331 }
332 
333 /// Get the channel description for a channel name. Iterate through the list
334 /// each time. Since this will only be called a small number of times on a small
335 /// list, it's not worth doing anything fancy.
336 bool StubContainer::getChannelDesc(const std::string &channelName,
337  ChannelDesc &desc) {
338  ClientContext context;
339  VoidMessage arg;
340  ListOfChannels response;
341  Status s = stub->ListChannels(&context, arg, &response);
342  checkStatus(s, "Failed to list channels");
343  for (const auto &channel : response.channels())
344  if (channel.name() == channelName) {
345  desc = channel;
346  return true;
347  }
348  return false;
349 }
350 
351 namespace {
352 class CosimMMIO : public MMIO {
353 public:
354  CosimMMIO(Context &ctxt, StubContainer *rpcClient) {
355  // We have to locate the channels ourselves since this service might be used
356  // to retrieve the manifest.
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");
361 
362  const esi::Type *i64Type = getType(ctxt, new UIntType(cmdResp.type(), 64));
363  const esi::Type *cmdType =
364  getType(ctxt, new StructType(cmdArg.type(),
365  {{"write", new BitsType("i1", 1)},
366  {"offset", new UIntType("ui32", 32)},
367  {"data", new BitsType("i64", 64)}}));
368 
369  // Get ports, create the function, then connect to it.
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");
375  cmdMMIO.reset(FuncService::Function::get(AppID("__cosim_mmio"), *cmdArgPort,
376  *cmdRespPort));
377  cmdMMIO->connect();
378  }
379 
380 #pragma pack(push, 1)
381  struct MMIOCmd {
382  uint64_t data;
383  uint32_t offset;
384  bool write;
385  };
386 #pragma pack(pop)
387 
388  // Call the read function and wait for a response.
389  uint64_t read(uint32_t addr) const override {
390  MMIOCmd cmd{.offset = addr, .write = false};
391  auto arg = MessageData::from(cmd);
392  std::future<MessageData> result = cmdMMIO->call(arg);
393  result.wait();
394  return *result.get().as<uint64_t>();
395  }
396 
397  void write(uint32_t addr, uint64_t data) override {
398  MMIOCmd cmd{.data = data, .offset = addr, .write = true};
399  auto arg = MessageData::from(cmd);
400  std::future<MessageData> result = cmdMMIO->call(arg);
401  result.wait();
402  }
403 
404 private:
405  const esi::Type *getType(Context &ctxt, esi::Type *type) {
406  if (auto t = ctxt.getType(type->getID())) {
407  delete type;
408  return *t;
409  }
410  ctxt.registerType(type);
411  return type;
412  }
413  std::unique_ptr<WriteCosimChannelPort> cmdArgPort;
414  std::unique_ptr<ReadCosimChannelPort> cmdRespPort;
415  std::unique_ptr<FuncService::Function> cmdMMIO;
416 };
417 
418 #pragma pack(push, 1)
419 struct HostMemReadReq {
420  uint8_t tag;
421  uint32_t length;
422  uint64_t address;
423 };
424 
425 struct HostMemReadResp {
426  uint64_t data;
427  uint8_t tag;
428 };
429 #pragma pack(pop)
430 
431 class CosimHostMem : public HostMem {
432 public:
433  CosimHostMem(AcceleratorConnection &acc, Context &ctxt,
434  StubContainer *rpcClient)
435  : acc(acc), ctxt(ctxt), rpcClient(rpcClient) {}
436 
437  void start() override {
438  // We have to locate the channels ourselves since this service might be used
439  // to retrieve the manifest.
440 
441  // Setup the read side callback.
442  ChannelDesc readArg, readResp;
443  if (!rpcClient->getChannelDesc("__cosim_hostmem_read.arg", readArg) ||
444  !rpcClient->getChannelDesc("__cosim_hostmem_read.result", readResp))
445  throw std::runtime_error("Could not find HostMem channels");
446 
447  const esi::Type *readRespType =
448  getType(ctxt, new StructType(readResp.type(),
449  {{"tag", new UIntType("ui8", 8)},
450  {"data", new BitsType("i64", 64)}}));
451  const esi::Type *readReqType =
452  getType(ctxt, new StructType(readArg.type(),
453  {{"address", new UIntType("ui64", 64)},
454  {"length", new UIntType("ui32", 32)},
455  {"tag", new UIntType("ui8", 8)}}));
456 
457  // Get ports, create the function, then connect to it.
458  readRespPort = std::make_unique<WriteCosimChannelPort>(
459  rpcClient->stub.get(), readResp, readRespType,
460  "__cosim_hostmem_read.result");
461  readReqPort = std::make_unique<ReadCosimChannelPort>(
462  rpcClient->stub.get(), readArg, readReqType,
463  "__cosim_hostmem_read.arg");
464  read.reset(CallService::Callback::get(acc, AppID("__cosim_hostmem_read"),
465  *readRespPort, *readReqPort));
466  read->connect([this](const MessageData &req) { return serviceRead(req); },
467  true);
468  }
469 
470  // Service the read request as a callback. Simply reads the data from the
471  // location specified. TODO: check that the memory has been mapped.
472  MessageData serviceRead(const MessageData &reqBytes) {
473  const HostMemReadReq *req = reqBytes.as<HostMemReadReq>();
474  acc.getLogger().debug(
475  [&](std::string &subsystem, std::string &msg,
476  std::unique_ptr<std::map<std::string, std::any>> &details) {
477  subsystem = "HostMem";
478  msg = "Read request: addr=0x" + toHex(req->address) +
479  " len=" + std::to_string(req->length) +
480  " tag=" + std::to_string(req->tag);
481  });
482  uint64_t *dataPtr = reinterpret_cast<uint64_t *>(req->address);
483  HostMemReadResp resp{.data = *dataPtr, .tag = req->tag};
484  acc.getLogger().debug(
485  [&](std::string &subsystem, std::string &msg,
486  std::unique_ptr<std::map<std::string, std::any>> &details) {
487  subsystem = "HostMem";
488  msg = "Read result: data=0x" + toHex(resp.data) +
489  " tag=" + std::to_string(resp.tag);
490  });
491  return MessageData::from(resp);
492  }
493 
494  struct CosimHostMemRegion : public HostMemRegion {
495  CosimHostMemRegion(std::size_t size) {
496  ptr = malloc(size);
497  this->size = size;
498  }
499  virtual ~CosimHostMemRegion() { free(ptr); }
500  virtual void *getPtr() const override { return ptr; }
501  virtual std::size_t getSize() const override { return size; }
502 
503  private:
504  void *ptr;
505  std::size_t size;
506  };
507 
508  virtual std::unique_ptr<HostMemRegion>
509  allocate(std::size_t size, HostMem::Options opts) const override {
510  return std::unique_ptr<HostMemRegion>(new CosimHostMemRegion(size));
511  }
512  virtual bool mapMemory(void *ptr, std::size_t size,
513  HostMem::Options opts) const override {
514  return true;
515  }
516  virtual void unmapMemory(void *ptr) const override {}
517 
518 private:
519  const esi::Type *getType(Context &ctxt, esi::Type *type) {
520  if (auto t = ctxt.getType(type->getID())) {
521  delete type;
522  return *t;
523  }
524  ctxt.registerType(type);
525  return type;
526  }
528  Context &ctxt;
529  StubContainer *rpcClient;
530  std::unique_ptr<WriteCosimChannelPort> readRespPort;
531  std::unique_ptr<ReadCosimChannelPort> readReqPort;
532  std::unique_ptr<CallService::Callback> read;
533 };
534 
535 } // namespace
536 
537 Service *CosimAccelerator::createService(Service::Type svcType,
538  AppIDPath idPath, std::string implName,
539  const ServiceImplDetails &details,
540  const HWClientDetails &clients) {
541  // Compute our parents idPath path.
542  AppIDPath prefix = std::move(idPath);
543  if (prefix.size() > 0)
544  prefix.pop_back();
545 
546  // Get the channel assignments for each client.
547  for (auto client : clients) {
548  AppIDPath fullClientPath = prefix + client.relPath;
549  std::map<std::string, std::string> channelAssignments;
550  for (auto assignment : client.channelAssignments)
551  if (assignment.second.type == "cosim")
552  channelAssignments[assignment.first] = std::any_cast<std::string>(
553  assignment.second.implOptions.at("name"));
554  clientChannelAssignments[fullClientPath] = std::move(channelAssignments);
555  }
556 
557  if (svcType == typeid(services::MMIO)) {
558  return new CosimMMIO(getCtxt(), rpcClient);
559  } else if (svcType == typeid(services::HostMem)) {
560  return new CosimHostMem(*this, getCtxt(), rpcClient);
561  } else if (svcType == typeid(SysInfo)) {
562  switch (manifestMethod) {
563  case ManifestMethod::Cosim:
564  return new CosimSysInfo(rpcClient->stub.get());
566  return new MMIOSysInfo(getService<services::MMIO>());
567  }
568  } else if (svcType == typeid(CustomService) && implName == "cosim") {
569  return new CustomService(idPath, details, clients);
570  }
571  return nullptr;
572 }
573 
575  manifestMethod = method;
576 }
577 
assert(baseType &&"element must be base type")
esi::backends::cosim::CosimAccelerator::StubContainer StubContainer
Definition: Cosim.cpp:61
static void checkStatus(Status s, const std::string &msg)
Definition: Cosim.cpp:44
REGISTER_ACCELERATOR("cosim", backends::cosim::CosimAccelerator)
Abstract class representing a connection to an accelerator.
Definition: Accelerator.h:78
Context & getCtxt() const
Definition: Accelerator.h:82
virtual void disconnect()
Disconnect from the accelerator cleanly.
std::map< std::string, services::Service * > ServiceTable
Definition: Accelerator.h:93
std::string toStr() const
Definition: Manifest.cpp:708
static bool isWrite(BundleType::Direction bundleDir)
Compute the direction of a channel given the bundle direction and the bundle port's direction.
Definition: Ports.h:188
Bundles represent a collection of channels.
Definition: Types.h:44
const ChannelVector & getChannels() const
Definition: Types.h:54
Unidirectional channels are the basic communication primitive between the host and accelerator.
Definition: Ports.h:33
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Definition: Context.h:31
A logical chunk of data representing serialized data.
Definition: Common.h:103
const T * as() const
Cast to a type.
Definition: Common.h:119
static MessageData from(T &t)
Cast from a type to its raw bytes.
Definition: Common.h:129
A ChannelPort which reads data from the accelerator.
Definition: Ports.h:103
virtual void disconnect() override
Definition: Ports.h:108
Structs are an ordered collection of fields, each with a name and a type.
Definition: Types.h:130
Root class of the ESI type system.
Definition: Types.h:27
ID getID() const
Definition: Types.h:33
Unsigned integer.
Definition: Types.h:124
A ChannelPort which sends data to the accelerator.
Definition: Ports.h:74
Connect to an ESI simulation.
Definition: Cosim.h:37
std::map< AppIDPath, std::map< std::string, std::string > > clientChannelAssignments
Definition: Cosim.h:79
void setManifestMethod(ManifestMethod method)
Definition: Cosim.cpp:574
std::set< std::unique_ptr< ChannelPort > > channels
Definition: Cosim.h:76
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).
Definition: Cosim.cpp:294
A service for which there are no standard services registered.
Definition: Services.h:77
Implement the SysInfo API for a standard MMIO protocol.
Definition: Services.h:180
Parent class of all APIs modeled as 'services'.
Definition: Services.h:45
const std::type_info & Type
Definition: Services.h:47
Information about the Accelerator system.
Definition: Services.h:93
def connect(destination, source)
Definition: support.py:39
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
Definition: esi.py:1
std::map< std::string, std::any > ServiceImplDetails
Definition: Common.h:98
std::string toHex(uint32_t val)
Definition: Common.cpp:37
std::vector< HWClientDetail > HWClientDetails
Definition: Common.h:97
Hack around C++ not having a way to forward declare a nested class.
Definition: Cosim.cpp:52
std::unique_ptr< ChannelServer::Stub > stub
Definition: Cosim.cpp:55
bool getChannelDesc(const std::string &channelName, esi::cosim::ChannelDesc &desc)
Get the type ID for a channel name.
Definition: Cosim.cpp:336
StubContainer(std::unique_ptr< ChannelServer::Stub > stub)
Definition: Cosim.cpp:53
Options for allocating host memory.
Definition: Services.h:209