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 "cosim/CapnpThreads.h"
20 
21 #include <fstream>
22 #include <iostream>
23 #include <set>
24 
25 using namespace std;
26 
27 using namespace esi;
28 using namespace esi::services;
29 using namespace esi::backends::cosim;
30 
31 /// Parse the connection string and instantiate the accelerator. Support the
32 /// traditional 'host:port' syntax and a path to 'cosim.cfg' which is output by
33 /// the cosimulation when it starts (which is useful when it chooses its own
34 /// port).
35 unique_ptr<AcceleratorConnection>
36 CosimAccelerator::connect(Context &ctxt, string connectionString) {
37  string portStr;
38  string host = "localhost";
39 
40  size_t colon;
41  if ((colon = connectionString.find(':')) != string::npos) {
42  portStr = connectionString.substr(colon + 1);
43  host = connectionString.substr(0, colon);
44  } else if (connectionString.ends_with("cosim.cfg")) {
45  ifstream cfg(connectionString);
46  string line, key, value;
47 
48  while (getline(cfg, line))
49  if ((colon = line.find(":")) != string::npos) {
50  key = line.substr(0, colon);
51  value = line.substr(colon + 1);
52  if (key == "port")
53  portStr = value;
54  else if (key == "host")
55  host = value;
56  }
57 
58  if (portStr.size() == 0)
59  throw runtime_error("port line not found in file");
60  } else if (connectionString == "env") {
61  char *hostEnv = getenv("ESI_COSIM_HOST");
62  if (hostEnv)
63  host = hostEnv;
64  else
65  host = "localhost";
66  char *portEnv = getenv("ESI_COSIM_PORT");
67  if (portEnv)
68  portStr = portEnv;
69  else
70  throw runtime_error("ESI_COSIM_PORT environment variable not set");
71  } else {
72  throw runtime_error("Invalid connection string '" + connectionString + "'");
73  }
74  uint16_t port = stoul(portStr);
75  return make_unique<CosimAccelerator>(ctxt, host, port);
76 }
77 
78 /// Construct and connect to a cosim server.
79 CosimAccelerator::CosimAccelerator(Context &ctxt, string hostname,
80  uint16_t port)
82  // Connect to the simulation.
83  rpcClient = std::make_unique<esi::cosim::RpcClient>();
84  rpcClient->run(hostname, port);
85 }
86 
87 namespace {
88 class CosimMMIO : public MMIO {
89 public:
90  CosimMMIO(esi::cosim::LowLevel *lowLevel) : lowLevel(lowLevel) {}
91 
92  // Push the read request into the LowLevel capnp bridge and wait for the
93  // response.
94  uint32_t read(uint32_t addr) const override {
95  lowLevel->readReqs.push(addr);
96 
97  std::optional<std::pair<uint64_t, uint8_t>> resp;
98  while (resp = lowLevel->readResps.pop(), !resp.has_value())
99  std::this_thread::sleep_for(std::chrono::microseconds(10));
100  if (resp->second != 0)
101  throw runtime_error("MMIO read error" + to_string(resp->second));
102  return resp->first;
103  }
104 
105  // Push the write request into the LowLevel capnp bridge and wait for the ack
106  // or error.
107  void write(uint32_t addr, uint32_t data) override {
108  lowLevel->writeReqs.push(make_pair(addr, data));
109 
110  std::optional<uint8_t> resp;
111  while (resp = lowLevel->writeResps.pop(), !resp.has_value())
112  std::this_thread::sleep_for(std::chrono::microseconds(10));
113  if (*resp != 0)
114  throw runtime_error("MMIO write error" + to_string(*resp));
115  }
116 
117 private:
118  esi::cosim::LowLevel *lowLevel;
119 };
120 } // namespace
121 
122 namespace {
123 class CosimSysInfo : public SysInfo {
124 public:
125  CosimSysInfo(const std::unique_ptr<esi::cosim::RpcClient> &rpcClient)
126  : rpcClient(rpcClient) {}
127 
128  uint32_t getEsiVersion() const override {
129  unsigned int esiVersion;
130  std::vector<uint8_t> compressedManifest;
131  if (!rpcClient->getCompressedManifest(esiVersion, compressedManifest))
132  throw runtime_error("Could not get ESI version from cosim");
133  return esiVersion;
134  }
135 
136  vector<uint8_t> getCompressedManifest() const override {
137  unsigned int esiVersion;
138  std::vector<uint8_t> compressedManifest;
139  if (!rpcClient->getCompressedManifest(esiVersion, compressedManifest))
140  throw runtime_error("Could not get ESI version from cosim");
141  return compressedManifest;
142  }
143 
144 private:
145  const std::unique_ptr<esi::cosim::RpcClient> &rpcClient;
146 };
147 } // namespace
148 
149 namespace {
150 class WriteCosimChannelPort : public WriteChannelPort {
151 public:
152  WriteCosimChannelPort(esi::cosim::Endpoint *ep, const Type *type, string name)
153  : WriteChannelPort(type), ep(ep), name(name) {}
154  virtual ~WriteCosimChannelPort() = default;
155 
156  // TODO: Replace this with a request to connect to the capnp thread.
157  virtual void connect() override {
158  if (!ep)
159  throw runtime_error("Could not find channel '" + name +
160  "' in cosimulation");
161  if (ep->getSendTypeId() == "")
162  throw runtime_error("Channel '" + name + "' is not a read channel");
163  if (ep->getSendTypeId() != getType()->getID())
164  throw runtime_error("Channel '" + name + "' has wrong type. Expected " +
165  getType()->getID() + ", got " + ep->getSendTypeId());
166  ep->setInUse();
167  }
168  virtual void disconnect() override {
169  if (ep)
170  ep->returnForUse();
171  }
172  virtual void write(const MessageData &) override;
173 
174 protected:
176  string name;
177 };
178 } // namespace
179 
180 void WriteCosimChannelPort::write(const MessageData &data) {
181  ep->pushMessageToSim(make_unique<esi::MessageData>(data));
182 }
183 
184 namespace {
185 class ReadCosimChannelPort : public ReadChannelPort {
186 public:
187  ReadCosimChannelPort(esi::cosim::Endpoint *ep, const Type *type, string name)
188  : ReadChannelPort(type), ep(ep), name(name) {}
189  virtual ~ReadCosimChannelPort() = default;
190 
191  // TODO: Replace this with a request to connect to the capnp thread.
192  virtual void connect() override {
193  if (!ep)
194  throw runtime_error("Could not find channel '" + name +
195  "' in cosimulation");
196  if (ep->getRecvTypeId() == "")
197  throw runtime_error("Channel '" + name + "' is not a read channel");
198  if (ep->getRecvTypeId() != getType()->getID())
199  throw runtime_error("Channel '" + name + "' has wrong type. Expected " +
200  getType()->getID() + ", got " + ep->getRecvTypeId());
201  ep->setInUse();
202  }
203  virtual void disconnect() override {
204  if (ep)
205  ep->returnForUse();
206  }
207  virtual bool read(MessageData &) override;
208 
209 protected:
211  string name;
212 };
213 
214 } // namespace
215 
216 bool ReadCosimChannelPort::read(MessageData &data) {
218  if (!ep->getMessageToClient(msg))
219  return false;
220  data = *msg;
221  return true;
222 }
223 
224 map<string, ChannelPort &>
225 CosimAccelerator::requestChannelsFor(AppIDPath idPath,
226  const BundleType *bundleType) {
227  map<string, ChannelPort &> channelResults;
228 
229  // Find the client details for the port at 'fullPath'.
230  auto f = clientChannelAssignments.find(idPath);
231  if (f == clientChannelAssignments.end())
232  return channelResults;
233  const map<string, string> &channelAssignments = f->second;
234 
235  // Each channel in a bundle has a separate cosim endpoint. Find them all.
236  for (auto [name, dir, type] : bundleType->getChannels()) {
237  auto f = channelAssignments.find(name);
238  if (f == channelAssignments.end())
239  throw runtime_error("Could not find channel assignment for '" +
240  idPath.toStr() + "." + name + "'");
241  string channelName = f->second;
242 
243  // Get the endpoint, which may or may not exist. Construct the port.
244  // Everything is validated when the client calls 'connect()' on the port.
245  esi::cosim::Endpoint *ep = rpcClient->getEndpoint(channelName);
246  ChannelPort *port;
247  if (BundlePort::isWrite(dir))
248  port = new WriteCosimChannelPort(ep, type, channelName);
249  else
250  port = new ReadCosimChannelPort(ep, type, channelName);
251  channels.emplace(port);
252  channelResults.emplace(name, *port);
253  }
254  return channelResults;
255 }
256 
257 Service *CosimAccelerator::createService(Service::Type svcType,
258  AppIDPath idPath, std::string implName,
259  const ServiceImplDetails &details,
260  const HWClientDetails &clients) {
261  // Compute our parents idPath path.
262  AppIDPath prefix = std::move(idPath);
263  if (prefix.size() > 0)
264  prefix.pop_back();
265 
266  if (implName == "cosim") {
267  // Get the channel assignments for each client.
268  for (auto client : clients) {
269  AppIDPath fullClientPath = prefix + client.relPath;
270  map<string, string> channelAssignments;
271  for (auto assignment : any_cast<map<string, any>>(
272  client.implOptions.at("channel_assignments")))
273  channelAssignments[assignment.first] =
274  any_cast<string>(assignment.second);
275  clientChannelAssignments[fullClientPath] = std::move(channelAssignments);
276  }
277  }
278 
279  if (svcType == typeid(services::MMIO)) {
280  return new CosimMMIO(rpcClient->getLowLevel());
281  } else if (svcType == typeid(SysInfo)) {
282  switch (manifestMethod) {
283  case ManifestMethod::Cosim:
284  return new CosimSysInfo(rpcClient);
285  case ManifestMethod::MMIO:
286  return new MMIOSysInfo(getService<services::MMIO>());
287  }
288  } else if (svcType == typeid(CustomService) && implName == "cosim") {
289  return new CustomService(idPath, details, clients);
290  }
291  return nullptr;
292 }
293 
294 void CosimAccelerator::setManifestMethod(ManifestMethod method) {
295  manifestMethod = method;
296 }
297 
298 REGISTER_ACCELERATOR("cosim", backends::cosim::CosimAccelerator);
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
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
virtual void disconnect()
Definition: Ports.h:37
virtual void connect()
Definition: Ports.h:36
const Type * getType() const
Definition: Ports.h:39
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
virtual bool read(MessageData &)=0
Specify a buffer to read into.
Root class of the ESI type system.
Definition: Types.h:27
A ChannelPort which sends data to the accelerator.
Definition: Ports.h:46
virtual void write(const MessageData &)=0
A very basic write API. Will likely change for performance reasons.
Implements a bi-directional, thread-safe bridge between the RPC server and DPI functions.
Definition: Endpoint.h:35
std::unique_ptr< MessageData > MessageDataPtr
Representing messages as shared pointers to vectors may be a performance issue in the future but it i...
Definition: Endpoint.h:40
void pushMessageToSim(MessageDataPtr msg)
Queue message to the simulation.
Definition: Endpoint.h:58
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
virtual void write(uint32_t addr, uint32_t data)=0
virtual uint32_t read(uint32_t addr) const =0
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
virtual uint32_t getEsiVersion() const =0
Get the ESI version number to check version compatibility.
virtual std::vector< uint8_t > getCompressedManifest() const =0
Return the zlib compressed JSON system manifest.
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