CIRCT  19.0.0git
Cosim.cpp
Go to the documentation of this file.
1 //===- Cosim.cpp - Connection to ESI simulation via capnp RPC -------------===//
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 
19 #include "CosimDpi.capnp.h"
20 #include <capnp/ez-rpc.h>
21 
22 #include <fstream>
23 #include <iostream>
24 #include <set>
25 
26 using namespace std;
27 
28 using namespace esi;
29 using namespace esi::services;
30 using namespace esi::backends::cosim;
31 
32 /// Parse the connection string and instantiate the accelerator. Support the
33 /// traditional 'host:port' syntax and a path to 'cosim.cfg' which is output by
34 /// the cosimulation when it starts (which is useful when it chooses its own
35 /// port).
36 unique_ptr<AcceleratorConnection>
37 CosimAccelerator::connect(Context &ctxt, string connectionString) {
38  string portStr;
39  string host = "localhost";
40 
41  size_t colon;
42  if ((colon = connectionString.find(':')) != string::npos) {
43  portStr = connectionString.substr(colon + 1);
44  host = connectionString.substr(0, colon);
45  } else if (connectionString.ends_with("cosim.cfg")) {
46  ifstream cfg(connectionString);
47  string line, key, value;
48 
49  while (getline(cfg, line))
50  if ((colon = line.find(":")) != string::npos) {
51  key = line.substr(0, colon);
52  value = line.substr(colon + 1);
53  if (key == "port")
54  portStr = value;
55  else if (key == "host")
56  host = value;
57  }
58 
59  if (portStr.size() == 0)
60  throw runtime_error("port line not found in file");
61  } else if (connectionString == "env") {
62  char *hostEnv = getenv("ESI_COSIM_HOST");
63  if (hostEnv)
64  host = hostEnv;
65  else
66  host = "localhost";
67  char *portEnv = getenv("ESI_COSIM_PORT");
68  if (portEnv)
69  portStr = portEnv;
70  else
71  throw runtime_error("ESI_COSIM_PORT environment variable not set");
72  } else {
73  throw runtime_error("Invalid connection string '" + connectionString + "'");
74  }
75  uint16_t port = stoul(portStr);
76  return make_unique<CosimAccelerator>(ctxt, host, port);
77 }
78 
79 namespace {
80 class CosimChannelPort;
81 }
82 
84  capnp::EzRpcClient rpcClient;
85  kj::WaitScope &waitScope;
86  CosimDpiServer::Client cosim;
87  EsiLowLevel::Client lowLevel;
88 
89  // We own all channels connected to rpcClient since their lifetime is tied to
90  // rpcClient.
91  set<unique_ptr<ChannelPort>> channels;
92 
93  // Map from client path to channel assignments for that client.
94  map<AppIDPath, map<string, string>> clientChannelAssignments;
95 
96  Impl(string hostname, uint16_t port)
97  : rpcClient(hostname, port), waitScope(rpcClient.getWaitScope()),
98  cosim(rpcClient.getMain<CosimDpiServer>()), lowLevel(nullptr) {
99  auto llReq = cosim.openLowLevelRequest();
100  auto llPromise = llReq.send();
101  lowLevel = llPromise.wait(waitScope).getLowLevel();
102  }
103  ~Impl();
104 
105  /// Request the host side channel ports for a particular instance (identified
106  /// by the AppID path). For convenience, provide the bundle type and direction
107  /// of the bundle port.
108  std::map<std::string, ChannelPort &> requestChannelsFor(AppIDPath,
109  const BundleType *);
110 };
111 
112 /// Construct and connect to a cosim server.
113 CosimAccelerator::CosimAccelerator(Context &ctxt, string hostname,
114  uint16_t port)
116  impl = make_unique<Impl>(hostname, port);
117 }
118 
119 namespace {
120 class CosimMMIO : public MMIO {
121 public:
122  CosimMMIO(EsiLowLevel::Client &llClient, kj::WaitScope &waitScope)
123  : llClient(llClient), waitScope(waitScope) {}
124 
125  uint32_t read(uint32_t addr) const override {
126  auto req = llClient.readMMIORequest();
127  req.setAddress(addr);
128  return req.send().wait(waitScope).getData();
129  }
130  void write(uint32_t addr, uint32_t data) override {
131  auto req = llClient.writeMMIORequest();
132  req.setAddress(addr);
133  req.setData(data);
134  req.send().wait(waitScope);
135  }
136 
137 private:
138  EsiLowLevel::Client &llClient;
139  kj::WaitScope &waitScope;
140 };
141 } // namespace
142 
143 namespace {
144 class CosimSysInfo : public SysInfo {
145 public:
146  CosimSysInfo(CosimDpiServer::Client &client, kj::WaitScope &waitScope)
147  : client(client), waitScope(waitScope) {}
148 
149  uint32_t getEsiVersion() const override {
150  auto maniResp =
151  client.getCompressedManifestRequest().send().wait(waitScope);
152  return maniResp.getVersion();
153  }
154 
155  vector<uint8_t> getCompressedManifest() const override {
156  auto maniResp =
157  client.getCompressedManifestRequest().send().wait(waitScope);
158  capnp::Data::Reader data = maniResp.getCompressedManifest();
159  return vector<uint8_t>(data.begin(), data.end());
160  }
161 
162 private:
163  CosimDpiServer::Client &client;
164  kj::WaitScope &waitScope;
165 };
166 } // namespace
167 
168 namespace {
169 /// Parent class for read and write channel ports.
170 class CosimChannelPort {
171 public:
172  CosimChannelPort(CosimAccelerator::Impl &impl, string name)
173  : impl(impl), name(name), isConnected(false), ep(nullptr) {}
174  virtual ~CosimChannelPort() {}
175 
176  void connect();
177  void disconnect();
178  void write(const MessageData &);
179  bool read(MessageData &);
180 
181 protected:
183  string name;
184  bool isConnected;
185  EsiDpiEndpoint::Client ep;
186 };
187 } // namespace
188 
189 void CosimChannelPort::write(const MessageData &data) {
190  if (!isConnected)
191  throw runtime_error("Cannot write to a channel port that is not connected");
192 
193  auto req = ep.sendFromHostRequest();
194  req.setMsg(capnp::Data::Reader(data.getBytes(), data.getSize()));
195  req.send().wait(impl.waitScope);
196 }
197 
198 bool CosimChannelPort::read(MessageData &data) {
199  auto req = ep.recvToHostRequest();
200  auto resp = req.send().wait(impl.waitScope);
201  if (!resp.getHasData())
202  return false;
203  capnp::Data::Reader msg = resp.getResp();
204  size_t size = msg.size();
205  std::vector<uint8_t> vec(size);
206  memcpy(vec.data(), msg.begin(), size);
207  data = MessageData(vec);
208  return true;
209 }
210 
212  // Make sure all channels are disconnected before rpcClient gets deconstructed
213  // or it'll throw an exception.
214  for (auto &ccp : channels)
215  ccp->disconnect();
216 }
217 
219  if (isConnected)
220  return;
221 
222  // Linear search through the list of cosim endpoints. Slow, but good enough as
223  // connect isn't expected to be called often.
224  auto listResp = impl.cosim.listRequest().send().wait(impl.waitScope);
225  for (auto iface : listResp.getIfaces())
226  if (iface.getEndpointID() == name) {
227  auto openReq = impl.cosim.openRequest();
228  openReq.setIface(iface);
229  auto openResp = openReq.send().wait(impl.waitScope);
230  ep = openResp.getEndpoint();
231  isConnected = true;
232  return;
233  }
234  throw runtime_error("Could not find channel '" + name + "' in cosimulation");
235 }
236 
237 void CosimChannelPort::disconnect() {
238  if (!isConnected)
239  return;
240  ep.closeRequest().send().wait(impl.waitScope);
241  ep = nullptr;
242  isConnected = false;
243 }
244 
245 namespace {
246 class WriteCosimChannelPort : public WriteChannelPort {
247 public:
248  WriteCosimChannelPort(CosimAccelerator::Impl &impl, const Type *type,
249  string name)
250  : WriteChannelPort(type),
251  cosim(make_unique<CosimChannelPort>(impl, name)) {}
252 
253  virtual ~WriteCosimChannelPort() = default;
254 
255  virtual void connect() override { cosim->connect(); }
256  virtual void disconnect() override { cosim->disconnect(); }
257  virtual void write(const MessageData &) override;
258 
259 protected:
260  std::unique_ptr<CosimChannelPort> cosim;
261 };
262 } // namespace
263 
264 void WriteCosimChannelPort::write(const MessageData &data) {
265  cosim->write(data);
266 }
267 
268 namespace {
269 class ReadCosimChannelPort : public ReadChannelPort {
270 public:
271  ReadCosimChannelPort(CosimAccelerator::Impl &impl, const Type *type,
272  string name)
273  : ReadChannelPort(type), cosim(new CosimChannelPort(impl, name)) {}
274 
275  virtual ~ReadCosimChannelPort() = default;
276 
277  virtual void connect() override { cosim->connect(); }
278  virtual void disconnect() override { cosim->disconnect(); }
279  virtual bool read(MessageData &) override;
280 
281 protected:
282  std::unique_ptr<CosimChannelPort> cosim;
283 };
284 
285 } // namespace
286 
287 bool ReadCosimChannelPort::read(MessageData &data) { return cosim->read(data); }
288 
289 map<string, ChannelPort &>
291  const BundleType *bundleType) {
292  map<string, ChannelPort &> channelResults;
293 
294  // Find the client details for the port at 'fullPath'.
295  auto f = clientChannelAssignments.find(idPath);
296  if (f == clientChannelAssignments.end())
297  return channelResults;
298  const map<string, string> &channelAssignments = f->second;
299 
300  // Each channel in a bundle has a separate cosim endpoint. Find them all.
301  for (auto [name, dir, type] : bundleType->getChannels()) {
302  auto f = channelAssignments.find(name);
303  if (f == channelAssignments.end())
304  throw runtime_error("Could not find channel assignment for '" +
305  idPath.toStr() + "." + name + "'");
306  string channelName = f->second;
307 
308  ChannelPort *port;
309  if (BundlePort::isWrite(dir))
310  port = new WriteCosimChannelPort(*this, type, channelName);
311  else
312  port = new ReadCosimChannelPort(*this, type, channelName);
313  channels.emplace(port);
314  channelResults.emplace(name, *port);
315  }
316  return channelResults;
317 }
318 
319 map<string, ChannelPort &>
321  const BundleType *bundleType) {
322  return impl->requestChannelsFor(idPath, bundleType);
323 }
325  AppIDPath idPath, std::string implName,
326  const ServiceImplDetails &details,
327  const HWClientDetails &clients) {
328  // Compute our parents idPath path.
329  AppIDPath prefix = std::move(idPath);
330  if (prefix.size() > 0)
331  prefix.pop_back();
332 
333  if (implName == "cosim") {
334  // Get the channel assignments for each client.
335  for (auto client : clients) {
336  AppIDPath fullClientPath = prefix + client.relPath;
337  map<string, string> channelAssignments;
338  for (auto assignment : any_cast<map<string, any>>(
339  client.implOptions.at("channel_assignments")))
340  channelAssignments[assignment.first] =
341  any_cast<string>(assignment.second);
342  impl->clientChannelAssignments[fullClientPath] =
343  std::move(channelAssignments);
344  }
345  }
346 
347  if (svcType == typeid(services::MMIO)) {
348  return new CosimMMIO(impl->lowLevel, impl->waitScope);
349  } else if (svcType == typeid(SysInfo)) {
350  switch (manifestMethod) {
351  case ManifestMethod::Cosim:
352  return new CosimSysInfo(impl->cosim, impl->waitScope);
353  case ManifestMethod::MMIO:
354  return new MMIOSysInfo(getService<services::MMIO>());
355  }
356  } else if (svcType == typeid(CustomService) && implName == "cosim") {
357  return new CustomService(idPath, details, clients);
358  }
359  return nullptr;
360 }
361 
363  manifestMethod = method;
364 }
365 
REGISTER_ACCELERATOR("cosim", backends::cosim::CosimAccelerator)
Abstract class representing a connection to an accelerator.
Definition: Accelerator.h:75
std::string toStr() const
Definition: Manifest.cpp:602
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:78
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:31
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Definition: Context.h:30
A logical chunk of data representing serialized data.
Definition: Common.h:85
A ChannelPort which reads data from the accelerator.
Definition: Ports.h:55
Root class of the ESI type system.
Definition: Types.h:27
A ChannelPort which sends data to the accelerator.
Definition: Ports.h:46
Connect to an ESI simulation.
Definition: Cosim.h:32
virtual std::map< std::string, ChannelPort & > requestChannelsFor(AppIDPath, const BundleType *) override
Request the host side channel ports for a particular instance (identified by the AppID path).
Definition: Cosim.cpp:320
void setManifestMethod(ManifestMethod method)
Definition: Cosim.cpp:362
virtual Service * createService(Service::Type service, AppIDPath path, std::string implName, const ServiceImplDetails &details, const HWClientDetails &clients) override
Called by getServiceImpl exclusively.
Definition: Cosim.cpp:324
std::unique_ptr< Impl > impl
Definition: Cosim.h:61
A service for which there are no standard services registered.
Definition: Services.h:61
Implement the SysInfo API for a standard MMIO protocol.
Definition: Services.h:102
Parent class of all APIs modeled as 'services'.
Definition: Services.h:42
const std::type_info & Type
Definition: Services.h:44
Information about the Accelerator system.
Definition: Services.h:77
def connect(destination, source)
Definition: support.py:37
Definition: esi.py:1
std::map< std::string, std::any > ServiceImplDetails
Definition: Common.h:80
std::vector< HWClientDetail > HWClientDetails
Definition: Common.h:79
std::map< std::string, ChannelPort & > requestChannelsFor(AppIDPath, const BundleType *)
Request the host side channel ports for a particular instance (identified by the AppID path).
Definition: Cosim.cpp:290
set< unique_ptr< ChannelPort > > channels
Definition: Cosim.cpp:91
Impl(string hostname, uint16_t port)
Definition: Cosim.cpp:96
map< AppIDPath, map< string, string > > clientChannelAssignments
Definition: Cosim.cpp:94