CIRCT 23.0.0git
Loading...
Searching...
No Matches
Engines.cpp
Go to the documentation of this file.
1//===- Engines.cpp --------------------------------------------------------===//
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 (lib/dialect/ESI/runtime/cpp/).
12//
13//===----------------------------------------------------------------------===//
14
15#include "esi/Engines.h"
16#include "esi/Accelerator.h"
17
18#include <algorithm>
19#include <cstring>
20#include <queue>
21
22using namespace esi;
23
24//===----------------------------------------------------------------------===//
25// Unknown Engine
26//===----------------------------------------------------------------------===//
27
28namespace {
29/// Created by default when the DMA engine cannot be resolved. Throws the error
30/// upon trying to connect and creates ports which throw errors on their
31/// connection attempts.
32class UnknownEngine : public Engine {
33public:
34 UnknownEngine(AcceleratorConnection &conn, std::string engineName)
35 : Engine(conn), engineName(engineName) {}
36
37 void connect() override {
38 throw std::runtime_error("Unknown engine '" + engineName + "'");
39 }
40
41 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
42 const std::string &channelName,
44 const Type *type) override {
45 if (BundlePort::isWrite(dir))
46 return std::make_unique<UnknownWriteChannelPort>(
47 type,
48 "Unknown engine '" + engineName + "': cannot create write port");
49 else
50 return std::make_unique<UnknownReadChannelPort>(
51 type, "Unknown engine '" + engineName + "': cannot create read port");
52 }
53
54protected:
55 std::string engineName;
56};
57} // namespace
58
59//===----------------------------------------------------------------------===//
60// OneItemBuffersToHost engine
61//
62// Protocol:
63// 1) Host sends address of buffer address via MMIO write.
64// 2) Device writes data on channel with a byte '1' to said buffer address.
65// 3) Host polls the last byte in buffer for '1'.
66// 4) Data is copied out of buffer, last byte is set to '0', goto 1.
67//===----------------------------------------------------------------------===//
68
69namespace {
71class OneItemBuffersToHostReadPort : public ReadChannelPort {
72public:
73 /// Offset into the MMIO space to which the buffer pointer is written.
74 static constexpr size_t BufferPtrOffset = 8;
75
76 OneItemBuffersToHostReadPort(const Type *type, OneItemBuffersToHost *engine,
77 AppIDPath idPath, const std::string &channelName)
78 : ReadChannelPort(type), engine(engine), idPath(idPath),
79 channelName(channelName) {
80 // Use ceiling division so sub-byte types (e.g. SInt<4>) get at least 1
81 // byte of data space, matching the byte-aligned padding applied in the
82 // PyCDE DMA hardware (OneItemBuffersToHost pads client_data).
83 // +1 for the valid flag byte at the end of the buffer.
84 bufferSize =
85 utils::bitsToBytes(std::max(type->getBitWidth(), std::ptrdiff_t{8})) +
86 1;
87 }
88
89 // Write the location of the buffer to the MMIO space.
90 void writeBufferPtr();
91 // Connect allocate a buffer and prime the pump.
92 void connectImpl(const ChannelPort::ConnectOptions &options) override;
93 // Check for buffer fill.
94 bool pollImpl() override;
95
96 std::string identifier() const { return idPath.toStr() + "." + channelName; }
97
98protected:
99 // Size of buffer based on type.
100 size_t bufferSize;
101 // Owning engine.
102 OneItemBuffersToHost *engine;
103 // Single buffer.
104 std::unique_ptr<services::HostMem::HostMemRegion> buffer;
105 // Retain the current message across callback retries.
106 std::unique_ptr<SegmentedMessageData> pendingMessage;
107 // Number of times the poll function has been called.
108 uint64_t pollCount = 0;
109
110 // Identifing information.
111 AppIDPath idPath;
112 std::string channelName;
113};
114
115class OneItemBuffersToHost : public Engine {
116 friend class OneItemBuffersToHostReadPort;
117
118public:
120 const ServiceImplDetails &details)
121 : Engine(conn), thisPath(idPath) {
122 // Get the MMIO path but don't try to resolve it yet.
123 auto mmioIDIter = details.find("mmio");
124 if (mmioIDIter != details.end())
125 mmioID = std::any_cast<AppID>(mmioIDIter->second);
126 }
127
128 static std::unique_ptr<Engine> create(AcceleratorConnection &conn,
129 AppIDPath idPath,
130 const ServiceImplDetails &details,
131 const HWClientDetails &clients) {
132 return std::make_unique<OneItemBuffersToHost>(conn, idPath, details);
133 }
134
135 // Only throw errors on connect.
136 void connect() override;
137
138 // Create a real read port if requested. Create an error-throwing write port
139 // if requested.
140 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
141 const std::string &channelName,
143 const Type *type) override {
144 if (BundlePort::isWrite(dir))
145 return std::make_unique<UnknownWriteChannelPort>(
146 type, idPath.toStr() + "." + channelName +
147 " OneItemBuffersToHost: cannot create write port");
148 return std::make_unique<OneItemBuffersToHostReadPort>(type, this, idPath,
149 channelName);
150 }
151
152protected:
153 AppIDPath thisPath;
154 std::optional<AppID> mmioID;
156 services::HostMem *hostMem;
157};
158} // namespace
159
160void OneItemBuffersToHost::connect() {
161 // This is where we throw errors.
162 if (connected)
163 return;
164 if (!mmioID)
165 throw std::runtime_error("OneItemBuffersToHost: no mmio path specified");
166 hostMem = conn.getService<services::HostMem>();
167 if (!hostMem)
168 throw std::runtime_error("OneItemBuffersToHost: no host memory service");
169 hostMem->start();
170
171 // Resolve the MMIO port.
172 Accelerator &acc = conn.getAccelerator();
173 AppIDPath mmioPath = thisPath;
174 mmioPath.pop_back();
175 mmioPath.push_back(*mmioID);
176 AppIDPath lastPath;
177 BundlePort *port = acc.resolvePort(mmioPath, lastPath);
178 if (port == nullptr)
179 throw std::runtime_error(
180 thisPath.toStr() +
181 " OneItemBuffersToHost: could not find MMIO port at " +
182 mmioPath.toStr());
183 mmio = dynamic_cast<services::MMIO::MMIORegion *>(port);
184 if (!mmio)
185 throw std::runtime_error(
186 thisPath.toStr() +
187 " OneItemBuffersToHost: MMIO port is not an MMIO port");
188
189 // If we have a valid MMIO port, we can connect.
190 connected = true;
191}
192
193void OneItemBuffersToHostReadPort::writeBufferPtr() {
194 uint8_t *bufferData = reinterpret_cast<uint8_t *>(buffer->getPtr());
195 bufferData[bufferSize - 1] = 0;
196 // Write the *device-visible* address of the buffer to the MMIO space.
197 engine->mmio->write(BufferPtrOffset,
198 reinterpret_cast<uint64_t>(buffer->getDevicePtr()));
199}
200
201void OneItemBuffersToHostReadPort::connectImpl(
202 const ChannelPort::ConnectOptions &options) {
203 engine->connect();
204 buffer = engine->hostMem->allocate(bufferSize, {true, false});
205 writeBufferPtr();
206}
207
208bool OneItemBuffersToHostReadPort::pollImpl() {
209 Logger &logger = engine->conn.getLogger();
210
211 auto tryDeliverPending = [&]() {
212 logger.trace(
213 [&](std::string &subsystem, std::string &msg,
214 std::unique_ptr<std::map<std::string, std::any>> &details) {
215 subsystem = "OneItemBuffersToHost";
216 msg = "received message";
217 details = std::make_unique<std::map<std::string, std::any>>();
218 MessageData flat = pendingMessage->toMessageData();
219 (*details)["channel"] = identifier();
220 (*details)["data_size"] = flat.getSize();
221 (*details)["message_data"] = flat.toHex();
222 });
223
224 if (!invokeCallback(pendingMessage)) {
225 logger.trace(
226 [&](std::string &subsystem, std::string &msg,
227 std::unique_ptr<std::map<std::string, std::any>> &details) {
228 subsystem = "OneItemBuffersToHost";
229 msg = "callback rejected data";
230 details = std::make_unique<std::map<std::string, std::any>>();
231 (*details)["channel"] = identifier();
232 });
233 return false;
234 }
235
236 pendingMessage.reset();
237 writeBufferPtr();
238 logger.trace(
239 [this](std::string &subsystem, std::string &msg,
240 std::unique_ptr<std::map<std::string, std::any>> &details) {
241 subsystem = "OneItemBuffersToHost";
242 msg = "callback accepted data";
243 details = std::make_unique<std::map<std::string, std::any>>();
244 (*details)["channel"] = identifier();
245 });
246 return true;
247 };
248
249 if (pendingMessage)
250 return tryDeliverPending();
251
252 // Check to see if the buffer has been filled.
253 uint8_t *bufferData = reinterpret_cast<uint8_t *>(buffer->getPtr());
254 if (bufferData[bufferSize - 1] == 0)
255 return false;
256
257 // If it has, copy the data out and retain it until the consumer accepts it.
258 pendingMessage = std::make_unique<MessageData>(bufferData, bufferSize - 1);
259 return tryDeliverPending();
260}
261
262REGISTER_ENGINE("OneItemBuffersToHost", OneItemBuffersToHost);
263
264//===----------------------------------------------------------------------===//
265// OneItemBuffersFromHost engine
266//
267// Protocol:
268// 1) Host allocates two buffers: one for data and one for the completion byte.
269// 2) On write, if the completion byte is '0', the write fails since that
270// implies there's data currently in the buffer which the device has not yet
271// picked up. If the completion byte is '1', continue on.
272// 3) The host writes the data to the buffer and sets the completion byte to
273// '0'.
274// 4) The device reads the data into the data buffer and sets the completion
275// byte to '1'.
276// 5) Host sends address of buffer via MMIO write to register 0x8.
277// 6) Host sends address of completion buffer via MMIO write to register 0x10.
278// 7) Device reads data from said buffer address.
279// 8) Device writes '1' to the first byte of the completion buffer.
280//
281//===----------------------------------------------------------------------===//
282
283namespace {
284
285class OneItemBuffersFromHost;
286class OneItemBuffersFromHostWritePort : public WriteChannelPort {
287public:
288 /// Offset into the MMIO space to which the buffer pointer is written.
289 static constexpr size_t BufferPtrOffset = 8;
290 static constexpr size_t CompletionPtrOffset = 16;
291
292 OneItemBuffersFromHostWritePort(const Type *type,
293 OneItemBuffersFromHost *engine,
294 AppIDPath idPath,
295 const std::string &channelName)
296 : WriteChannelPort(type), engine(engine), idPath(idPath),
297 channelName(channelName) {}
298
299 // Connect allocate a buffer and prime the pump.
300 void connectImpl(const ChannelPort::ConnectOptions &options) override;
301
302 std::string identifier() const { return idPath.toStr() + "." + channelName; }
303
304protected:
305 void writeImpl(const MessageData &) override;
306 bool tryWriteImpl(const MessageData &data) override;
307
308 /// Break an oversized message into frame-sized pieces and enqueue them.
309 void enqueueFrames(const MessageData &data);
310
311 /// Poll for ability to send more data and send if able.
312 bool pollImpl() override;
313
314 // Owning engine.
316 // Single buffer.
317 std::unique_ptr<services::HostMem::HostMemRegion> data_buffer;
318 std::unique_ptr<services::HostMem::HostMemRegion> completion_buffer;
319
320 // Thread protection.
321 std::mutex bufferMutex;
322
323 // FIFO of frame-sized messages to send. When a message is larger than
324 // getFrameSizeBytes(), it's broken into frame-sized pieces and enqueued
325 // here.
326 std::queue<MessageData> pendingFrames;
327
328 // Identifing information.
329 AppIDPath idPath;
330 std::string channelName;
331};
332
333class OneItemBuffersFromHost : public Engine {
334 friend class OneItemBuffersFromHostWritePort;
335
336public:
338 const ServiceImplDetails &details)
339 : Engine(conn), thisPath(idPath) {
340 // Get the MMIO path but don't try to resolve it yet.
341 auto mmioIDIter = details.find("mmio");
342 if (mmioIDIter != details.end())
343 mmioID = std::any_cast<AppID>(mmioIDIter->second);
344 }
345
346 static std::unique_ptr<Engine> create(AcceleratorConnection &conn,
347 AppIDPath idPath,
348 const ServiceImplDetails &details,
349 const HWClientDetails &clients) {
350 return std::make_unique<OneItemBuffersFromHost>(conn, idPath, details);
351 }
352
353 // Only throw errors on connect.
354 void connect() override;
355
356 // Create a real write port if requested. Create an error-throwing read port
357 // if requested.
358 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
359 const std::string &channelName,
361 const Type *type) override {
362 if (!BundlePort::isWrite(dir))
363 return std::make_unique<UnknownReadChannelPort>(
364 type, idPath.toStr() + "." + channelName +
365 " OneItemBuffersFromHost: cannot create read port");
366 return std::make_unique<OneItemBuffersFromHostWritePort>(type, this, idPath,
367 channelName);
368 }
369
370protected:
371 AppIDPath thisPath;
372 std::optional<AppID> mmioID;
374 services::HostMem *hostMem;
375};
376
377} // namespace
378
379void OneItemBuffersFromHost::connect() {
380 // This is where we throw errors.
381 if (connected)
382 return;
383 if (!mmioID)
384 throw std::runtime_error("OneItemBuffersFromHost: no mmio path specified");
385 hostMem = conn.getService<services::HostMem>();
386 if (!hostMem)
387 throw std::runtime_error("OneItemBuffersFromHost: no host memory service");
388 hostMem->start();
389
390 // Resolve the MMIO port.
391 Accelerator &acc = conn.getAccelerator();
392 AppIDPath mmioPath = thisPath;
393 mmioPath.pop_back();
394 mmioPath.push_back(*mmioID);
395 AppIDPath lastPath;
396 BundlePort *port = acc.resolvePort(mmioPath, lastPath);
397 if (port == nullptr)
398 throw std::runtime_error(
399 thisPath.toStr() +
400 " OneItemBuffersFromHost: could not find MMIO port at " +
401 mmioPath.toStr());
402 mmio = dynamic_cast<services::MMIO::MMIORegion *>(port);
403 if (!mmio)
404 throw std::runtime_error(
405 thisPath.toStr() +
406 " OneItemBuffersFromHost: MMIO port is not an MMIO port");
407
408 // If we have a valid MMIO port, we can connect.
409 connected = true;
410}
411
412void OneItemBuffersFromHostWritePort::connectImpl(
413 const ChannelPort::ConnectOptions &options) {
414 engine->connect();
415 data_buffer = engine->hostMem->allocate(
416 std::max(getFrameSizeBytes(), (size_t)512), {false, false});
417 completion_buffer = engine->hostMem->allocate(512, {true, false});
418 // Set the last byte to '1' to indicate that the buffer is ready to be filled
419 // and sent to the device.
420 *static_cast<uint8_t *>(completion_buffer->getPtr()) = 1;
421}
422
423void OneItemBuffersFromHostWritePort::enqueueFrames(const MessageData &data) {
424 auto frames = getMessageFrames(data);
425 std::lock_guard<std::mutex> lock(bufferMutex);
426 for (const auto &frame : frames)
427 pendingFrames.push(frame);
428}
429
430void OneItemBuffersFromHostWritePort::writeImpl(const MessageData &data) {
431 while (!tryWriteImpl(data))
432 // Wait for the device to read the last data we sent.
433 std::this_thread::yield();
434}
435
436bool OneItemBuffersFromHostWritePort::tryWriteImpl(const MessageData &data) {
437 // We don't want to provide an infinite buffer, so limit it to one message.
438 {
439 std::lock_guard<std::mutex> lock(bufferMutex);
440 if (!pendingFrames.empty())
441 return false;
442 }
443
444 enqueueFrames(data);
445 return true;
446}
447
448bool OneItemBuffersFromHostWritePort::pollImpl() {
449 Logger &logger = engine->conn.getLogger();
450
451 // Check to see if the device is ready.
452 completion_buffer->flush();
453 uint8_t *completion =
454 reinterpret_cast<uint8_t *>(completion_buffer->getPtr());
455 if (*completion == 0)
456 return false;
457
458 std::lock_guard<std::mutex> lock(bufferMutex);
459
460 // Determine what to send: next frame from queue, or the original message.
461 const uint8_t *src;
462 size_t sendSize;
463 if (pendingFrames.empty())
464 return false;
465
466 MessageData frame = std::move(pendingFrames.front());
467 pendingFrames.pop();
468 src = frame.getBytes();
469 sendSize = frame.getSize();
470
471 void *bufferData = data_buffer->getPtr();
472 std::memcpy(bufferData, src, sendSize);
473 data_buffer->flush();
474 *completion = 0;
475 completion_buffer->flush();
476 engine->mmio->write(BufferPtrOffset,
477 reinterpret_cast<uint64_t>(data_buffer->getDevicePtr()));
478 engine->mmio->write(
479 CompletionPtrOffset,
480 reinterpret_cast<uint64_t>(completion_buffer->getDevicePtr()));
481
482 logger.trace([this, sendSize](
483 std::string &subsystem, std::string &msg,
484 std::unique_ptr<std::map<std::string, std::any>> &details) {
485 subsystem = "OneItemBuffersFromHost";
486 msg = "initiated transfer";
487 details = std::make_unique<std::map<std::string, std::any>>();
488 (*details)["channel"] = identifier();
489 (*details)["data_size"] = sendSize;
490 });
491
492 // We initiated a transfer, so return true to indicate that an action
493 // occurred.
494 return true;
495}
496
497REGISTER_ENGINE("OneItemBuffersFromHost", OneItemBuffersFromHost);
498
499//===----------------------------------------------------------------------===//
500// Engine / Bundle Engine Map
501//===----------------------------------------------------------------------===//
502
504 const std::string &channelName,
505 BundleType::Direction dir, const Type *type) {
506 auto portIter = ownedPorts.find(std::make_pair(idPath, channelName));
507 if (portIter != ownedPorts.end())
508 return *portIter->second;
509 std::unique_ptr<ChannelPort> port =
510 createPort(idPath, channelName, dir, type);
511 ChannelPort &ret = *port;
512 ownedPorts.emplace(std::make_pair(idPath, channelName), std::move(port));
513 return ret;
514}
515
517 const BundleType *bundleType) const {
518 PortMap ports;
519 for (auto [channelName, dir, type] : bundleType->getChannels()) {
520 auto engineIter = bundleEngineMap.find(channelName);
521 if (engineIter == bundleEngineMap.end())
522 continue;
523
524 ports.emplace(channelName, engineIter->second->requestPort(
525 idPath, channelName, dir, type));
526 }
527 return ports;
528}
529
530void BundleEngineMap::setEngine(const std::string &channelName,
531 Engine *engine) {
532 auto [it, inserted] = bundleEngineMap.try_emplace(channelName, engine);
533 if (!inserted)
534 throw std::runtime_error("Channel already exists in engine map");
535}
536
537//===----------------------------------------------------------------------===//
538// Registry
539//===----------------------------------------------------------------------===//
540
541namespace {
542class EngineRegistry {
543public:
544 static std::map<std::string, registry::internal::EngineCreate> &get() {
545 static EngineRegistry instance;
546 return instance.engineRegistry;
547 }
548
549private:
550 std::map<std::string, registry::internal::EngineCreate> engineRegistry;
551};
552} // namespace
553
554std::unique_ptr<Engine>
556 const std::string &dmaEngineName, AppIDPath idPath,
557 const ServiceImplDetails &details,
558 const HWClientDetails &clients) {
559 auto &reg = EngineRegistry::get();
560 auto it = reg.find(dmaEngineName);
561 if (it == reg.end())
562 return std::make_unique<UnknownEngine>(conn, dmaEngineName);
563 return it->second(conn, idPath, details, clients);
564}
565
566void registry::internal::registerEngine(const std::string &name,
567 EngineCreate create) {
568 auto tried = EngineRegistry::get().try_emplace(name, create);
569 if (!tried.second)
570 throw std::runtime_error("Engine already exists in registry");
571}
#define REGISTER_ENGINE(Name, TEngine)
Definition Engines.h:115
Abstract class representing a connection to an accelerator.
Definition Accelerator.h:89
Top level accelerator class.
Definition Accelerator.h:70
std::string toStr() const
Definition Manifest.cpp:814
PortMap requestPorts(const AppIDPath &idPath, const BundleType *bundleType) const
Request ports for all the channels in a bundle.
Definition Engines.cpp:516
std::map< std::string, Engine * > bundleEngineMap
Definition Engines.h:86
void setEngine(const std::string &channelName, Engine *engine)
Set a particlar engine for a particular channel.
Definition Engines.cpp:530
Services provide connections to 'bundles' – collections of named, unidirectional communication channe...
Definition Ports.h:611
Bundles represent a collection of channels.
Definition Types.h:104
const ChannelVector & getChannels() const
Definition Types.h:114
Unidirectional channels are the basic communication primitive between the host and accelerator.
Definition Ports.h:115
virtual bool pollImpl()
Method called by poll() to actually poll the channel if the channel is connected.
Definition Ports.h:300
virtual void connectImpl(const ConnectOptions &options)
Called by all connect methods to let backends initiate the underlying connections.
Definition Ports.h:304
Engines implement the actual channel communication between the host and the accelerator.
Definition Engines.h:42
virtual void connect()
Start the engine, if applicable.
Definition Engines.h:47
virtual ChannelPort & requestPort(AppIDPath idPath, const std::string &channelName, BundleType::Direction dir, const Type *type)
Get a port for a channel, from the cache if it exists or create it.
Definition Engines.cpp:503
virtual std::unique_ptr< ChannelPort > createPort(AppIDPath idPath, const std::string &channelName, BundleType::Direction dir, const Type *type)=0
Each engine needs to know how to create a ports.
std::map< std::pair< AppIDPath, std::string >, std::unique_ptr< ChannelPort > > ownedPorts
Definition Engines.h:68
void trace(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Log a trace message.
Definition Logging.h:106
A concrete flat message backed by a single vector of bytes.
Definition Common.h:155
const uint8_t * getBytes() const
Definition Common.h:166
size_t getSize() const
Get the size of the data in bytes.
Definition Common.h:180
MessageData toMessageData() const override
Flatten all segments into a standard MessageData.
Definition Common.h:213
A ChannelPort which reads data from the accelerator.
Definition Ports.h:453
Root class of the ESI type system.
Definition Types.h:36
virtual std::ptrdiff_t getBitWidth() const
Definition Types.h:43
A ChannelPort which sends data to the accelerator.
Definition Ports.h:308
A "slice" of some parent MMIO space.
Definition Services.h:181
connect(destination, source)
Definition support.py:39
std::function< std::unique_ptr< Engine >(AcceleratorConnection &conn, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)> EngineCreate
Engines can register themselves for pluggable functionality.
Definition Engines.h:104
void registerEngine(const std::string &name, EngineCreate create)
Definition Engines.cpp:566
std::unique_ptr< Engine > createEngine(AcceleratorConnection &conn, const std::string &dmaEngineName, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Create an engine by name.
Definition Engines.cpp:555
uint64_t bitsToBytes(uint64_t bits)
Compute ceil(bits/8).
Definition Utils.h:232
Definition esi.py:1
std::map< std::string, std::any > ServiceImplDetails
Definition Common.h:108
std::map< std::string, ChannelPort & > PortMap
Definition Ports.h:33
std::vector< HWClientDetail > HWClientDetails
Definition Common.h:107
OneItemBuffersFromHost(Type client_type)
Definition dma.py:126
OneItemBuffersToHost(Type client_type)
Definition dma.py:16