CIRCT 24.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 // The message has now been handed to the simulation. Return its credit so
200 // the client may send another.
201 server->messageConsumed(port);
202 log(endpointId, false, msg);
203
204 // Do the validation only if there's a message available. Since the
205 // simulator is going to poll up to every tick and there's not going to be
206 // a message most of the time, this is important for performance.
207 if (validateSvOpenArray(data, sizeof(int8_t)) != 0) {
208 getLogger().error("cosim",
209 std::format("DPI-func={} line={} event=invalid-sv-array",
210 __func__, __LINE__));
211 return -2;
212 }
213
214 // Detect or verify size of buffer.
215 if (*dataSize == ~0u) {
216 *dataSize = svSizeOfArray(data);
217 } else if (*dataSize > (unsigned)svSizeOfArray(data)) {
219 "cosim",
220 std::format("DPI-func={} line={} event=invalid-size (max {})", __func__,
221 __LINE__, (unsigned)svSizeOfArray(data)));
222 return -3;
223 }
224 // Verify it'll fit.
225 size_t msgSize = msg.getSize();
226 if (msgSize > *dataSize) {
227 getLogger().error("cosim", "Message size too big to fit in HW buffer");
228 return -5;
229 }
230
231 // Copy the message data.
232 size_t i;
233 auto bytes = msg.getBytes();
234 for (i = 0; i < msgSize; ++i) {
235 auto b = bytes[i];
236 *(char *)svGetArrElemPtr1(data, i) = b;
237 }
238 // Zero out the rest of the buffer.
239 for (; i < *dataSize; ++i) {
240 *(char *)svGetArrElemPtr1(data, i) = 0;
241 }
242 // Set the output data size.
243 *dataSize = msg.getSize();
244 return 0;
245}
246
247// Attempt to send data to a client.
248// - return 0 on success, negative on failure (unregistered EP), positive if
249// the endpoint's queue is full (data not accepted; retry later).
250// - if dataSize is negative, attempt to dynamically determine the size of
251// 'data'.
252DPI int sv2cCosimserverEpTryPut(char *endpointId,
253 // NOLINTNEXTLINE(misc-misplaced-const)
254 const svOpenArrayHandle data, int dataSize) {
255 if (server == nullptr)
256 return -1;
257
258 if (validateSvOpenArray(data, sizeof(int8_t)) != 0) {
259 getLogger().error("cosim",
260 std::format("DPI-func={} line={} event=invalid-sv-array",
261 __func__, __LINE__));
262 return -2;
263 }
264
265 // Detect or verify size.
266 if (dataSize < 0) {
267 dataSize = svSizeOfArray(data);
268 } else if (dataSize > svSizeOfArray(data)) { // not enough data
270 "cosim",
271 std::format("DPI-func={} line={} event=invalid-size limit={} array={}",
272 __func__, __LINE__, dataSize, svSizeOfArray(data)));
273 return -3;
274 }
275
276 // Copy the message data into 'blob'.
277 std::vector<uint8_t> dataVec(dataSize);
278 for (int i = 0; i < dataSize; ++i) {
279 dataVec[i] = *(char *)svGetArrElemPtr1(data, i);
280 }
281 auto blob = std::make_unique<esi::MessageData>(dataVec);
282
283 // Queue the blob.
284 auto portIt = writePorts.find(endpointId);
285 if (portIt == writePorts.end()) {
286 getLogger().error("cosim", "Endpoint not found in registry!");
287 return -4;
288 }
289 WriteChannelPort &port = portIt->second;
290 // Never block the simulator thread: if the channel is at its flow-control
291 // in-flight limit, tell the RTL to hold the data and retry.
292 if (!port.tryWrite(*blob))
293 return 1;
294 log(endpointId, true, *blob);
295 return 0;
296}
297
298// Teardown cosimserver (disconnects from primary server port, stops connections
299// from active clients).
301 std::lock_guard<std::mutex> g(serverMutex);
302 getLogger().info("cosim", "Tearing down RPC server.");
303 if (server != nullptr) {
304 server->stop();
305 server = nullptr;
306
307 fclose(logFile);
308 logFile = nullptr;
309 }
310}
311
312// Start cosimserver (spawns server for HW-initiated work, listens for
313// connections from new SW-clients).
315 std::lock_guard<std::mutex> g(serverMutex);
316 if (context == nullptr)
318
319 if (server == nullptr) {
320 // Open log file if requested.
321 const char *logFN = getenv("COSIM_DEBUG_FILE");
322 if (logFN != nullptr) {
323 getLogger().info("cosim", std::format("Opening debug log: {}", logFN));
324 logFile = fopen(logFN, "w");
325 }
326
327 // Find the port and run.
328 getLogger().info("cosim", "Starting RPC server.");
329 server = std::make_unique<RpcServer>(*context);
330 server->run(findPort());
331 }
332 return 0;
333}
334
335// ---- Manifest DPI entry points ----
336
337DPI void
339 const svOpenArrayHandle compressedManifest) {
340 if (server == nullptr)
342
343 if (validateSvOpenArray(compressedManifest, sizeof(int8_t)) != 0) {
344 getLogger().error("cosim",
345 std::format("DPI-func={} line={} event=invalid-sv-array",
346 __func__, __LINE__));
347 return;
348 }
349
350 // Copy the message data into 'blob'.
351 int size = svSizeOfArray(compressedManifest);
352 std::vector<uint8_t> blob(size);
353 for (int i = 0; i < size; ++i) {
354 blob[size - i - 1] = *(char *)svGetArrElemPtr1(compressedManifest, i);
355 }
356 getLogger().info("cosim",
357 std::format("Setting manifest (esiVersion={}, size={})",
358 esiVersion, size));
359 server->setManifest(esiVersion, blob);
360}
361
362// ---- Low-level cosim DPI entry points ----
363
364// TODO: These had the shit broken outta them in the gRPC conversion. We're not
365// actively using them at the moment, but they'll have to be revived again in
366// the future.
367
368static bool mmioRegistered = false;
370 if (mmioRegistered) {
371 getLogger().error("cosim", "DPI MMIO master already registered!");
372 return -1;
373 }
375 mmioRegistered = true;
376 return 0;
377}
378
379DPI int sv2cCosimserverMMIOReadTryGet(uint32_t *address) {
380 // assert(server);
381 // LowLevel *ll = server->getLowLevel();
382 // std::optional<int> reqAddress = ll->readReqs.pop();
383 // if (!reqAddress.has_value())
384 return -1;
385 // *address = reqAddress.value();
386 // ll->readsOutstanding++;
387 // return 0;
388}
389
390DPI void sv2cCosimserverMMIOReadRespond(uint32_t data, char error) {
391 assert(false && "unimplemented");
392 // assert(server);
393 // LowLevel *ll = server->getLowLevel();
394 // if (ll->readsOutstanding == 0) {
395 // printf("ERROR: More read responses than requests! Not queuing
396 // response.\n"); return;
397 // }
398 // ll->readsOutstanding--;
399 // ll->readResps.push(data, error);
400}
401
403 assert(false && "unimplemented");
404 // assert(server);
405 // LowLevel *ll = server->getLowLevel();
406 // if (ll->writesOutstanding == 0) {
407 // printf(
408 // "ERROR: More write responses than requests! Not queuing
409 // response.\n");
410 // return;
411 // }
412 // ll->writesOutstanding--;
413 // ll->writeResps.push(error);
414}
415
416DPI int sv2cCosimserverMMIOWriteTryGet(uint32_t *address, uint32_t *data) {
417 // assert(server);
418 // LowLevel *ll = server->getLowLevel();
419 // auto req = ll->writeReqs.pop();
420 // if (!req.has_value())
421 return -1;
422 // *address = req.value().first;
423 // *data = req.value().second;
424 // ll->writesOutstanding++;
425 // return 0;
426}
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)
A logger that writes to the console. Includes color support.
Definition Logging.h:205
static std::unique_ptr< Context > withLogger(Args &&...args)
Create a context with a specific logger type.
Definition Context.h:45
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 concrete flat message backed by a single vector of bytes.
Definition Common.h:197
const uint8_t * getBytes() const
Definition Common.h:208
size_t getSize() const
Get the size of the data in bytes.
Definition Common.h:222
A ChannelPort which reads data from the accelerator.
Definition Ports.h:479
virtual void connect(ReadCallback callback, const ConnectOptions &options={})
Definition Ports.cpp:140
virtual std::future< MessageData > readAsync()
Asynchronous polling read.
Definition Ports.cpp:225
A ChannelPort which sends data to the accelerator.
Definition Ports.h:308
bool tryWrite(const MessageData &data)
A basic non-blocking write API.
Definition Ports.h:358
virtual void connect(const ConnectOptions &options={}) override
Set up a connection to the accelerator.
Definition Ports.h:312
#define DPI
Definition dpi.h:23
Definition esi.py:1
XXTERN int svSizeOfArray(const svOpenArrayHandle)
XXTERN int svDimensions(const svOpenArrayHandle h)
XXTERN void * svGetArrElemPtr1(const svOpenArrayHandle, int indx1)
XXTERN int svSize(const svOpenArrayHandle h, int d)
XXTERN void * svGetArrayPtr(const svOpenArrayHandle)
XXTERN typedef void * svOpenArrayHandle
Definition svdpi.h:133