CIRCT  20.0.0git
DpiEntryPoints.cpp
Go to the documentation of this file.
1 //===- DpiEntryPoints.cpp - ESI cosim DPI calls -----------------*- C++ -*-===//
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 // Cosim DPI function implementations. Mostly C-C++ gaskets to the C++
10 // RpcServer.
11 //
12 // These function signatures were generated by an HW simulator (see dpi.h) so
13 // we don't change them to be more rational here. The resulting code gets
14 // dynamically linked in and I'm concerned about maintaining binary
15 // compatibility with all the simulators.
16 //
17 //===----------------------------------------------------------------------===//
18 
19 #include "dpi.h"
20 #include "esi/Ports.h"
21 #include "esi/backends/RpcServer.h"
22 
23 #include <algorithm>
24 #include <cassert>
25 #include <cstdlib>
26 
27 using namespace esi;
28 using namespace esi::cosim;
29 
30 /// If non-null, log to this file. Protected by 'serverMutex`.
31 static FILE *logFile;
32 static std::unique_ptr<RpcServer> server = nullptr;
33 static std::mutex serverMutex;
34 
35 // ---- Helper functions ----
36 
37 /// Emit the contents of 'msg' to the log file in hex.
38 static void log(char *epId, bool toClient, const MessageData &msg) {
39  std::lock_guard<std::mutex> g(serverMutex);
40  if (!logFile)
41  return;
42 
43  fprintf(logFile, "[ep: %50s to: %4s]", epId, toClient ? "host" : "sim");
44  size_t msgSize = msg.getSize();
45  auto bytes = msg.getBytes();
46  for (size_t i = 0; i < msgSize; ++i) {
47  auto b = bytes[i];
48  // Separate 32-bit words.
49  if (i % 4 == 0 && i > 0)
50  fprintf(logFile, " ");
51  // Separate 64-bit words
52  if (i % 8 == 0 && i > 0)
53  fprintf(logFile, " ");
54  fprintf(logFile, " %02x", b);
55  }
56  fprintf(logFile, "\n");
57  fflush(logFile);
58 }
59 
60 /// Get the TCP port on which to listen. If the port isn't specified via an
61 /// environment variable, return 0 to allow automatic selection.
62 static int findPort() {
63  const char *portEnv = getenv("COSIM_PORT");
64  if (portEnv == nullptr) {
65  printf(
66  "[COSIM] RPC server port not found. Letting RPC server select one\n");
67  return 0;
68  }
69  printf("[COSIM] Opening RPC server on port %s\n", portEnv);
70  return std::strtoull(portEnv, nullptr, 10);
71 }
72 
73 /// Check that an array is an array of bytes and has some size.
74 // NOLINTNEXTLINE(misc-misplaced-const)
75 static int validateSvOpenArray(const svOpenArrayHandle data,
76  int expectedElemSize) {
77  if (svDimensions(data) != 1) {
78  printf("DPI-C: ERROR passed array argument that doesn't have expected 1D "
79  "dimensions\n");
80  return -1;
81  }
82  if (svGetArrayPtr(data) == NULL) {
83  printf("DPI-C: ERROR passed array argument that doesn't have C layout "
84  "(ptr==NULL)\n");
85  return -2;
86  }
87  int totalBytes = svSizeOfArray(data);
88  if (totalBytes == 0) {
89  printf("DPI-C: ERROR passed array argument that doesn't have C layout "
90  "(total_bytes==0)\n");
91  return -3;
92  }
93  int numElems = svSize(data, 1);
94  int elemSize = numElems == 0 ? 0 : (totalBytes / numElems);
95  if (numElems * expectedElemSize != totalBytes) {
96  printf("DPI-C: ERROR: passed array argument that doesn't have expected "
97  "element-size: expected=%d actual=%d numElems=%d totalBytes=%d\n",
98  expectedElemSize, elemSize, numElems, totalBytes);
99  return -4;
100  }
101  return 0;
102 }
103 
104 // ---- Traditional cosim DPI entry points ----
105 
106 // Lookups for registered ports. As a future optimization, change the DPI API to
107 // return a handle when registering wherein said handle is a pointer to a port.
108 std::map<std::string, ReadChannelPort &> readPorts;
109 std::map<ReadChannelPort *, std::future<MessageData>> readFutures;
110 std::map<std::string, WriteChannelPort &> writePorts;
111 
112 // Register simulated device endpoints.
113 // - return 0 on success, non-zero on failure (duplicate EP registered).
114 // TODO: Change this by breaking it in two functions, one for read and one for
115 // write. Also return the pointer as a handle.
116 DPI int sv2cCosimserverEpRegister(char *endpointId, char *fromHostTypeIdC,
117  int fromHostTypeSize, char *toHostTypeIdC,
118  int toHostTypeSize) {
119  // Ensure the server has been constructed.
121  std::string fromHostTypeId(fromHostTypeIdC), toHostTypeId(toHostTypeIdC);
122 
123  // Both only one type allowed.
124  if (!(fromHostTypeId.empty() ^ toHostTypeId.empty())) {
125  printf("ERROR: Only one of fromHostTypeId and toHostTypeId can be set!\n");
126  return -2;
127  }
128  if (readPorts.contains(endpointId)) {
129  printf("ERROR: Endpoint already registered!\n");
130  return -3;
131  }
132 
133  if (!fromHostTypeId.empty()) {
134  ReadChannelPort &port =
135  server->registerReadPort(endpointId, fromHostTypeId);
136  readPorts.emplace(endpointId, port);
137  readFutures.emplace(&port, port.readAsync());
138  } else {
139  writePorts.emplace(endpointId,
140  server->registerWritePort(endpointId, toHostTypeId));
141  }
142  return 0;
143 }
144 
145 // Attempt to recieve data from a client.
146 // - Returns negative when call failed (e.g. EP not registered).
147 // - If no message, return 0 with dataSize == 0.
148 // - Assumes buffer is large enough to contain entire message. Fails if not
149 // large enough. (In the future, will add support for getting the message
150 // into a fixed-size buffer over multiple calls.)
151 DPI int sv2cCosimserverEpTryGet(char *endpointId,
152  // NOLINTNEXTLINE(misc-misplaced-const)
153  const svOpenArrayHandle data,
154  unsigned int *dataSize) {
155  if (server == nullptr)
156  return -1;
157 
158  auto portIt = readPorts.find(endpointId);
159  if (portIt == readPorts.end()) {
160  fprintf(stderr, "Endpoint not found in registry!\n");
161  return -4;
162  }
163 
164  ReadChannelPort &port = portIt->second;
165  std::future<MessageData> &f = readFutures.at(&port);
166  // Poll for a message.
167  if (f.wait_for(std::chrono::milliseconds(0)) != std::future_status::ready) {
168  // No message.
169  *dataSize = 0;
170  return 0;
171  }
172  MessageData msg = f.get();
173  f = port.readAsync();
174  log(endpointId, false, msg);
175 
176  // Do the validation only if there's a message available. Since the
177  // simulator is going to poll up to every tick and there's not going to be
178  // a message most of the time, this is important for performance.
179  if (validateSvOpenArray(data, sizeof(int8_t)) != 0) {
180  printf("ERROR: DPI-func=%s line=%d event=invalid-sv-array\n", __func__,
181  __LINE__);
182  return -2;
183  }
184 
185  // Detect or verify size of buffer.
186  if (*dataSize == ~0u) {
187  *dataSize = svSizeOfArray(data);
188  } else if (*dataSize > (unsigned)svSizeOfArray(data)) {
189  printf("ERROR: DPI-func=%s line %d event=invalid-size (max %d)\n", __func__,
190  __LINE__, (unsigned)svSizeOfArray(data));
191  return -3;
192  }
193  // Verify it'll fit.
194  size_t msgSize = msg.getSize();
195  if (msgSize > *dataSize) {
196  printf("ERROR: Message size too big to fit in HW buffer\n");
197  return -5;
198  }
199 
200  // Copy the message data.
201  size_t i;
202  auto bytes = msg.getBytes();
203  for (i = 0; i < msgSize; ++i) {
204  auto b = bytes[i];
205  *(char *)svGetArrElemPtr1(data, i) = b;
206  }
207  // Zero out the rest of the buffer.
208  for (; i < *dataSize; ++i) {
209  *(char *)svGetArrElemPtr1(data, i) = 0;
210  }
211  // Set the output data size.
212  *dataSize = msg.getSize();
213  return 0;
214 }
215 
216 // Attempt to send data to a client.
217 // - return 0 on success, negative on failure (unregistered EP).
218 // - if dataSize is negative, attempt to dynamically determine the size of
219 // 'data'.
220 DPI int sv2cCosimserverEpTryPut(char *endpointId,
221  // NOLINTNEXTLINE(misc-misplaced-const)
222  const svOpenArrayHandle data, int dataSize) {
223  if (server == nullptr)
224  return -1;
225 
226  if (validateSvOpenArray(data, sizeof(int8_t)) != 0) {
227  printf("ERROR: DPI-func=%s line=%d event=invalid-sv-array\n", __func__,
228  __LINE__);
229  return -2;
230  }
231 
232  // Detect or verify size.
233  if (dataSize < 0) {
234  dataSize = svSizeOfArray(data);
235  } else if (dataSize > svSizeOfArray(data)) { // not enough data
236  printf("ERROR: DPI-func=%s line %d event=invalid-size limit %d array %d\n",
237  __func__, __LINE__, dataSize, svSizeOfArray(data));
238  return -3;
239  }
240 
241  // Copy the message data into 'blob'.
242  std::vector<uint8_t> dataVec(dataSize);
243  for (int i = 0; i < dataSize; ++i) {
244  dataVec[i] = *(char *)svGetArrElemPtr1(data, i);
245  }
246  auto blob = std::make_unique<esi::MessageData>(dataVec);
247 
248  // Queue the blob.
249  auto portIt = writePorts.find(endpointId);
250  if (portIt == writePorts.end()) {
251  fprintf(stderr, "Endpoint not found in registry!\n");
252  return -4;
253  }
254  log(endpointId, true, *blob);
255  WriteChannelPort &port = portIt->second;
256  port.write(*blob);
257  return 0;
258 }
259 
260 // Teardown cosimserver (disconnects from primary server port, stops connections
261 // from active clients).
263  std::lock_guard<std::mutex> g(serverMutex);
264  printf("[cosim] Tearing down RPC server.\n");
265  if (server != nullptr) {
266  server->stop();
267  server = nullptr;
268 
269  fclose(logFile);
270  logFile = nullptr;
271  }
272 }
273 
274 // Start cosimserver (spawns server for HW-initiated work, listens for
275 // connections from new SW-clients).
277  std::lock_guard<std::mutex> g(serverMutex);
278  if (server == nullptr) {
279  // Open log file if requested.
280  const char *logFN = getenv("COSIM_DEBUG_FILE");
281  if (logFN != nullptr) {
282  printf("[cosim] Opening debug log: %s\n", logFN);
283  logFile = fopen(logFN, "w");
284  }
285 
286  // Find the port and run.
287  printf("[cosim] Starting RPC server.\n");
288  server = std::make_unique<RpcServer>();
289  server->run(findPort());
290  }
291  return 0;
292 }
293 
294 // ---- Manifest DPI entry points ----
295 
296 DPI void
298  const svOpenArrayHandle compressedManifest) {
299  if (server == nullptr)
301 
302  if (validateSvOpenArray(compressedManifest, sizeof(int8_t)) != 0) {
303  printf("ERROR: DPI-func=%s line=%d event=invalid-sv-array\n", __func__,
304  __LINE__);
305  return;
306  }
307 
308  // Copy the message data into 'blob'.
309  int size = svSizeOfArray(compressedManifest);
310  std::vector<uint8_t> blob(size);
311  for (int i = 0; i < size; ++i) {
312  blob[size - i - 1] = *(char *)svGetArrElemPtr1(compressedManifest, i);
313  }
314  printf("[cosim] Setting manifest (esiVersion=%d, size=%d)\n", esiVersion,
315  size);
316  server->setManifest(esiVersion, blob);
317 }
318 
319 // ---- Low-level cosim DPI entry points ----
320 
321 // TODO: These had the shit broken outta them in the gRPC conversion. We're not
322 // actively using them at the moment, but they'll have to be revived again in
323 // the future.
324 
325 static bool mmioRegistered = false;
327  if (mmioRegistered) {
328  printf("ERROR: DPI MMIO master already registered!");
329  return -1;
330  }
332  mmioRegistered = true;
333  return 0;
334 }
335 
336 DPI int sv2cCosimserverMMIOReadTryGet(uint32_t *address) {
337  // assert(server);
338  // LowLevel *ll = server->getLowLevel();
339  // std::optional<int> reqAddress = ll->readReqs.pop();
340  // if (!reqAddress.has_value())
341  return -1;
342  // *address = reqAddress.value();
343  // ll->readsOutstanding++;
344  // return 0;
345 }
346 
347 DPI void sv2cCosimserverMMIOReadRespond(uint32_t data, char error) {
348  assert(false && "unimplemented");
349  // assert(server);
350  // LowLevel *ll = server->getLowLevel();
351  // if (ll->readsOutstanding == 0) {
352  // printf("ERROR: More read responses than requests! Not queuing
353  // response.\n"); return;
354  // }
355  // ll->readsOutstanding--;
356  // ll->readResps.push(data, error);
357 }
358 
360  assert(false && "unimplemented");
361  // assert(server);
362  // LowLevel *ll = server->getLowLevel();
363  // if (ll->writesOutstanding == 0) {
364  // printf(
365  // "ERROR: More write responses than requests! Not queuing
366  // response.\n");
367  // return;
368  // }
369  // ll->writesOutstanding--;
370  // ll->writeResps.push(error);
371 }
372 
373 DPI int sv2cCosimserverMMIOWriteTryGet(uint32_t *address, uint32_t *data) {
374  // assert(server);
375  // LowLevel *ll = server->getLowLevel();
376  // auto req = ll->writeReqs.pop();
377  // if (!req.has_value())
378  return -1;
379  // *address = req.value().first;
380  // *data = req.value().second;
381  // ll->writesOutstanding++;
382  // return 0;
383 }
assert(baseType &&"element must be base type")
static std::unique_ptr< RpcServer > server
std::map< std::string, ReadChannelPort & > readPorts
static FILE * logFile
If non-null, log to this file. Protected by 'serverMutex`.
DPI void sv2cCosimserverMMIOWriteRespond(char error)
std::map< std::string, WriteChannelPort & > writePorts
static int findPort()
Get the TCP port on which to listen.
DPI void sv2cCosimserverSetManifest(int esiVersion, const svOpenArrayHandle compressedManifest)
Set the system zlib-compressed manifest.
DPI int sv2cCosimserverMMIORegister()
Register an MMIO module. Just checks that there is only one instantiated.
static bool mmioRegistered
static void log(char *epId, bool toClient, const MessageData &msg)
Emit the contents of 'msg' to the log file in hex.
DPI void sv2cCosimserverFinish()
Shutdown the RPC server.
static int validateSvOpenArray(const svOpenArrayHandle data, int expectedElemSize)
Check that an array is an array of bytes and has some size.
static std::mutex serverMutex
std::map< ReadChannelPort *, std::future< MessageData > > readFutures
DPI int sv2cCosimserverEpTryPut(char *endpointId, const svOpenArrayHandle data, int dataSize)
Send a message to a client.
DPI int sv2cCosimserverEpRegister(char *endpointId, char *fromHostTypeIdC, int fromHostTypeSize, char *toHostTypeIdC, int toHostTypeSize)
Register an endpoint.
DPI int sv2cCosimserverInit()
Start the server.
DPI int sv2cCosimserverEpTryGet(char *endpointId, const svOpenArrayHandle data, unsigned int *dataSize)
Try to get a message from a client.
DPI int sv2cCosimserverMMIOWriteTryGet(uint32_t *address, uint32_t *data)
Write MMIO function pair.
DPI int sv2cCosimserverMMIOReadTryGet(uint32_t *address)
Read MMIO function pair.
DPI void sv2cCosimserverMMIOReadRespond(uint32_t data, char error)
int svSizeOfArray(const svOpenArrayHandle)
Definition: DummySvDpi.cpp:89
void * svGetArrElemPtr1(const svOpenArrayHandle, int indx1)
Definition: DummySvDpi.cpp:97
int svSize(const svOpenArrayHandle h, int d)
Definition: DummySvDpi.cpp:77
int svDimensions(const svOpenArrayHandle h)
Definition: DummySvDpi.cpp:81
void * svGetArrayPtr(const svOpenArrayHandle)
Definition: DummySvDpi.cpp:85
A logical chunk of data representing serialized data.
Definition: Common.h:103
size_t getSize() const
Get the size of the data in bytes.
Definition: Common.h:113
const uint8_t * getBytes() const
Definition: Common.h:111
A ChannelPort which reads data from the accelerator.
Definition: Ports.h:103
virtual std::future< MessageData > readAsync()
Asynchronous read.
Definition: Ports.cpp:77
A ChannelPort which sends data to the accelerator.
Definition: Ports.h:74
virtual void write(const MessageData &)=0
A very basic blocking write API.
#define DPI
Definition: dpi.h:23
Definition: esi.py:1
XXTERN typedef void * svOpenArrayHandle
Definition: svdpi.h:133