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