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 bufferSize = (type->getBitWidth() / 8) + 1;
81 }
82
83 // Write the location of the buffer to the MMIO space.
84 void writeBufferPtr();
85 // Connect allocate a buffer and prime the pump.
86 void connectImpl(const ChannelPort::ConnectOptions &options) override;
87 // Check for buffer fill.
88 bool pollImpl() override;
89
90 std::string identifier() const { return idPath.toStr() + "." + channelName; }
91
92protected:
93 // Size of buffer based on type.
94 size_t bufferSize;
95 // Owning engine.
97 // Single buffer.
98 std::unique_ptr<services::HostMem::HostMemRegion> buffer;
99 // Number of times the poll function has been called.
100 uint64_t pollCount = 0;
101
102 // Identifing information.
103 AppIDPath idPath;
104 std::string channelName;
105};
106
107class OneItemBuffersToHost : public Engine {
108 friend class OneItemBuffersToHostReadPort;
109
110public:
112 const ServiceImplDetails &details)
113 : Engine(conn), thisPath(idPath) {
114 // Get the MMIO path but don't try to resolve it yet.
115 auto mmioIDIter = details.find("mmio");
116 if (mmioIDIter != details.end())
117 mmioID = std::any_cast<AppID>(mmioIDIter->second);
118 }
119
120 static std::unique_ptr<Engine> create(AcceleratorConnection &conn,
121 AppIDPath idPath,
122 const ServiceImplDetails &details,
123 const HWClientDetails &clients) {
124 return std::make_unique<OneItemBuffersToHost>(conn, idPath, details);
125 }
126
127 // Only throw errors on connect.
128 void connect() override;
129
130 // Create a real read port if requested. Create an error-throwing write port
131 // if requested.
132 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
133 const std::string &channelName,
135 const Type *type) override {
136 if (BundlePort::isWrite(dir))
137 return std::make_unique<UnknownWriteChannelPort>(
138 type, idPath.toStr() + "." + channelName +
139 " OneItemBuffersToHost: cannot create write port");
140 return std::make_unique<OneItemBuffersToHostReadPort>(type, this, idPath,
141 channelName);
142 }
143
144protected:
145 AppIDPath thisPath;
146 std::optional<AppID> mmioID;
148 services::HostMem *hostMem;
149};
150} // namespace
151
152void OneItemBuffersToHost::connect() {
153 // This is where we throw errors.
154 if (connected)
155 return;
156 if (!mmioID)
157 throw std::runtime_error("OneItemBuffersToHost: no mmio path specified");
158 hostMem = conn.getService<services::HostMem>();
159 if (!hostMem)
160 throw std::runtime_error("OneItemBuffersToHost: no host memory service");
161 hostMem->start();
162
163 // Resolve the MMIO port.
164 Accelerator &acc = conn.getAccelerator();
165 AppIDPath mmioPath = thisPath;
166 mmioPath.pop_back();
167 mmioPath.push_back(*mmioID);
168 AppIDPath lastPath;
169 BundlePort *port = acc.resolvePort(mmioPath, lastPath);
170 if (port == nullptr)
171 throw std::runtime_error(
172 thisPath.toStr() +
173 " OneItemBuffersToHost: could not find MMIO port at " +
174 mmioPath.toStr());
175 mmio = dynamic_cast<services::MMIO::MMIORegion *>(port);
176 if (!mmio)
177 throw std::runtime_error(
178 thisPath.toStr() +
179 " OneItemBuffersToHost: MMIO port is not an MMIO port");
180
181 // If we have a valid MMIO port, we can connect.
182 connected = true;
183}
184
185void OneItemBuffersToHostReadPort::writeBufferPtr() {
186 uint8_t *bufferData = reinterpret_cast<uint8_t *>(buffer->getPtr());
187 bufferData[bufferSize - 1] = 0;
188 // Write the *device-visible* address of the buffer to the MMIO space.
189 engine->mmio->write(BufferPtrOffset,
190 reinterpret_cast<uint64_t>(buffer->getDevicePtr()));
191}
192
193void OneItemBuffersToHostReadPort::connectImpl(
194 const ChannelPort::ConnectOptions &options) {
195 engine->connect();
196 buffer = engine->hostMem->allocate(bufferSize, {true, false});
197 writeBufferPtr();
198}
199
200bool OneItemBuffersToHostReadPort::pollImpl() {
201 // Check to see if the buffer has been filled.
202 uint8_t *bufferData = reinterpret_cast<uint8_t *>(buffer->getPtr());
203 if (bufferData[bufferSize - 1] == 0)
204 return false;
205
206 Logger &logger = engine->conn.getLogger();
207
208 // If it has, copy the data out. If the consumer (callback) reports that it
209 // has accepted the data, re-use the buffer.
210 MessageData data(bufferData, bufferSize - 1);
211 logger.trace(
212 [this, data](std::string &subsystem, std::string &msg,
213 std::unique_ptr<std::map<std::string, std::any>> &details) {
214 subsystem = "OneItemBuffersToHost";
215 msg = "recieved message";
216 details = std::make_unique<std::map<std::string, std::any>>();
217 (*details)["channel"] = identifier();
218 (*details)["data_size"] = data.getSize();
219 (*details)["message_data"] = data.toHex();
220 });
221 if (callback(std::move(data))) {
222 writeBufferPtr();
223 logger.trace(
224 [this](std::string &subsystem, std::string &msg,
225 std::unique_ptr<std::map<std::string, std::any>> &details) {
226 subsystem = "OneItemBuffersToHost";
227 msg = "callback accepted data";
228 details = std::make_unique<std::map<std::string, std::any>>();
229 (*details)["channel"] = identifier();
230 });
231 return true;
232 }
233 logger.trace(
234 [this](std::string &subsystem, std::string &msg,
235 std::unique_ptr<std::map<std::string, std::any>> &details) {
236 subsystem = "OneItemBuffersToHost";
237 msg = "callback rejected data";
238 details = std::make_unique<std::map<std::string, std::any>>();
239 (*details)["channel"] = identifier();
240 });
241 return false;
242}
243
244REGISTER_ENGINE("OneItemBuffersToHost", OneItemBuffersToHost);
245
246//===----------------------------------------------------------------------===//
247// OneItemBuffersFromHost engine
248//
249// Protocol:
250// 1) Host allocates two buffers: one for data and one for the completion byte.
251// 2) On write, if the completion byte is '0', the write fails since that
252// implies there's data currently in the buffer which the device has not yet
253// picked up. If the completion byte is '1', continue on.
254// 3) The host writes the data to the buffer and sets the completion byte to
255// '0'.
256// 4) The device reads the data into the data buffer and sets the completion
257// byte to '1'.
258// 5) Host sends address of buffer via MMIO write to register 0x8.
259// 6) Host sends address of completion buffer via MMIO write to register 0x10.
260// 7) Device reads data from said buffer address.
261// 8) Device writes '1' to the first byte of the completion buffer.
262//
263//===----------------------------------------------------------------------===//
264
265namespace {
266
267class OneItemBuffersFromHost;
268class OneItemBuffersFromHostWritePort : public WriteChannelPort {
269public:
270 /// Offset into the MMIO space to which the buffer pointer is written.
271 static constexpr size_t BufferPtrOffset = 8;
272 static constexpr size_t CompletionPtrOffset = 16;
273
274 OneItemBuffersFromHostWritePort(const Type *type,
275 OneItemBuffersFromHost *engine,
276 AppIDPath idPath,
277 const std::string &channelName)
278 : WriteChannelPort(type), engine(engine), idPath(idPath),
279 channelName(channelName) {
280 frameSizeBytes = utils::bitsToBytes(type->getBitWidth());
281 }
282
283 // Connect allocate a buffer and prime the pump.
284 void connectImpl(const ChannelPort::ConnectOptions &options) override;
285
286 std::string identifier() const { return idPath.toStr() + "." + channelName; }
287
288protected:
289 void writeImpl(const MessageData &) override;
290 bool tryWriteImpl(const MessageData &data) override;
291
292 /// Break an oversized message into frame-sized pieces and enqueue them.
293 void enqueueFrames(const MessageData &data);
294
295 /// Poll for ability to send more data and send if able.
296 bool pollImpl() override;
297
298 // Size of buffer based on type.
299 size_t frameSizeBytes;
300 // Owning engine.
302 // Single buffer.
303 std::unique_ptr<services::HostMem::HostMemRegion> data_buffer;
304 std::unique_ptr<services::HostMem::HostMemRegion> completion_buffer;
305
306 // Thread protection.
307 std::mutex bufferMutex;
308
309 // FIFO of frame-sized messages to send. When a message is larger than
310 // frameSizeBytes, it's broken into frame-sized pieces and enqueued here.
311 std::queue<MessageData> pendingFrames;
312
313 // Identifing information.
314 AppIDPath idPath;
315 std::string channelName;
316};
317
318class OneItemBuffersFromHost : public Engine {
319 friend class OneItemBuffersFromHostWritePort;
320
321public:
323 const ServiceImplDetails &details)
324 : Engine(conn), thisPath(idPath) {
325 // Get the MMIO path but don't try to resolve it yet.
326 auto mmioIDIter = details.find("mmio");
327 if (mmioIDIter != details.end())
328 mmioID = std::any_cast<AppID>(mmioIDIter->second);
329 }
330
331 static std::unique_ptr<Engine> create(AcceleratorConnection &conn,
332 AppIDPath idPath,
333 const ServiceImplDetails &details,
334 const HWClientDetails &clients) {
335 return std::make_unique<OneItemBuffersFromHost>(conn, idPath, details);
336 }
337
338 // Only throw errors on connect.
339 void connect() override;
340
341 // Create a real write port if requested. Create an error-throwing read port
342 // if requested.
343 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
344 const std::string &channelName,
346 const Type *type) override {
347 if (!BundlePort::isWrite(dir))
348 return std::make_unique<UnknownReadChannelPort>(
349 type, idPath.toStr() + "." + channelName +
350 " OneItemBuffersFromHost: cannot create read port");
351 return std::make_unique<OneItemBuffersFromHostWritePort>(type, this, idPath,
352 channelName);
353 }
354
355protected:
356 AppIDPath thisPath;
357 std::optional<AppID> mmioID;
359 services::HostMem *hostMem;
360};
361
362} // namespace
363
364void OneItemBuffersFromHost::connect() {
365 // This is where we throw errors.
366 if (connected)
367 return;
368 if (!mmioID)
369 throw std::runtime_error("OneItemBuffersFromHost: no mmio path specified");
370 hostMem = conn.getService<services::HostMem>();
371 if (!hostMem)
372 throw std::runtime_error("OneItemBuffersFromHost: no host memory service");
373 hostMem->start();
374
375 // Resolve the MMIO port.
376 Accelerator &acc = conn.getAccelerator();
377 AppIDPath mmioPath = thisPath;
378 mmioPath.pop_back();
379 mmioPath.push_back(*mmioID);
380 AppIDPath lastPath;
381 BundlePort *port = acc.resolvePort(mmioPath, lastPath);
382 if (port == nullptr)
383 throw std::runtime_error(
384 thisPath.toStr() +
385 " OneItemBuffersFromHost: could not find MMIO port at " +
386 mmioPath.toStr());
387 mmio = dynamic_cast<services::MMIO::MMIORegion *>(port);
388 if (!mmio)
389 throw std::runtime_error(
390 thisPath.toStr() +
391 " OneItemBuffersFromHost: MMIO port is not an MMIO port");
392
393 // If we have a valid MMIO port, we can connect.
394 connected = true;
395}
396
397void OneItemBuffersFromHostWritePort::connectImpl(
398 const ChannelPort::ConnectOptions &options) {
399 engine->connect();
400 data_buffer = engine->hostMem->allocate(std::max(frameSizeBytes, (size_t)512),
401 {false, false});
402 completion_buffer = engine->hostMem->allocate(512, {true, false});
403 // Set the last byte to '1' to indicate that the buffer is ready to be filled
404 // and sent to the device.
405 *static_cast<uint8_t *>(completion_buffer->getPtr()) = 1;
406}
407
408void OneItemBuffersFromHostWritePort::enqueueFrames(const MessageData &data) {
409 auto frames = getMessageFrames(data);
410 std::lock_guard<std::mutex> lock(bufferMutex);
411 for (const auto &frame : frames)
412 pendingFrames.push(frame);
413}
414
415void OneItemBuffersFromHostWritePort::writeImpl(const MessageData &data) {
416 while (!tryWriteImpl(data))
417 // Wait for the device to read the last data we sent.
418 std::this_thread::yield();
419}
420
421bool OneItemBuffersFromHostWritePort::tryWriteImpl(const MessageData &data) {
422 // We don't want to provide an infinite buffer, so limit it to one message.
423 {
424 std::lock_guard<std::mutex> lock(bufferMutex);
425 if (!pendingFrames.empty())
426 return false;
427 }
428
429 enqueueFrames(data);
430 return true;
431}
432
433bool OneItemBuffersFromHostWritePort::pollImpl() {
434 Logger &logger = engine->conn.getLogger();
435
436 // Check to see if the device is ready.
437 completion_buffer->flush();
438 uint8_t *completion =
439 reinterpret_cast<uint8_t *>(completion_buffer->getPtr());
440 if (*completion == 0)
441 return false;
442
443 std::lock_guard<std::mutex> lock(bufferMutex);
444
445 // Determine what to send: next frame from queue, or the original message.
446 const uint8_t *src;
447 size_t sendSize;
448 if (pendingFrames.empty())
449 return false;
450
451 MessageData frame = std::move(pendingFrames.front());
452 pendingFrames.pop();
453 src = frame.getBytes();
454 sendSize = frame.getSize();
455
456 void *bufferData = data_buffer->getPtr();
457 std::memcpy(bufferData, src, sendSize);
458 data_buffer->flush();
459 *completion = 0;
460 completion_buffer->flush();
461 engine->mmio->write(BufferPtrOffset,
462 reinterpret_cast<uint64_t>(data_buffer->getDevicePtr()));
463 engine->mmio->write(
464 CompletionPtrOffset,
465 reinterpret_cast<uint64_t>(completion_buffer->getDevicePtr()));
466
467 logger.trace([this, sendSize](
468 std::string &subsystem, std::string &msg,
469 std::unique_ptr<std::map<std::string, std::any>> &details) {
470 subsystem = "OneItemBuffersFromHost";
471 msg = "initiated transfer";
472 details = std::make_unique<std::map<std::string, std::any>>();
473 (*details)["channel"] = identifier();
474 (*details)["data_size"] = sendSize;
475 });
476
477 // We initiated a transfer, so return true to indicate that an action
478 // occurred.
479 return true;
480}
481
482REGISTER_ENGINE("OneItemBuffersFromHost", OneItemBuffersFromHost);
483
484//===----------------------------------------------------------------------===//
485// Engine / Bundle Engine Map
486//===----------------------------------------------------------------------===//
487
489 const std::string &channelName,
490 BundleType::Direction dir, const Type *type) {
491 auto portIter = ownedPorts.find(std::make_pair(idPath, channelName));
492 if (portIter != ownedPorts.end())
493 return *portIter->second;
494 std::unique_ptr<ChannelPort> port =
495 createPort(idPath, channelName, dir, type);
496 ChannelPort &ret = *port;
497 ownedPorts.emplace(std::make_pair(idPath, channelName), std::move(port));
498 return ret;
499}
500
502 const BundleType *bundleType) const {
503 PortMap ports;
504 for (auto [channelName, dir, type] : bundleType->getChannels()) {
505 auto engineIter = bundleEngineMap.find(channelName);
506 if (engineIter == bundleEngineMap.end())
507 continue;
508
509 ports.emplace(channelName, engineIter->second->requestPort(
510 idPath, channelName, dir, type));
511 }
512 return ports;
513}
514
515void BundleEngineMap::setEngine(const std::string &channelName,
516 Engine *engine) {
517 auto [it, inserted] = bundleEngineMap.try_emplace(channelName, engine);
518 if (!inserted)
519 throw std::runtime_error("Channel already exists in engine map");
520}
521
522//===----------------------------------------------------------------------===//
523// Registry
524//===----------------------------------------------------------------------===//
525
526namespace {
527class EngineRegistry {
528public:
529 static std::map<std::string, registry::internal::EngineCreate> &get() {
530 static EngineRegistry instance;
531 return instance.engineRegistry;
532 }
533
534private:
535 std::map<std::string, registry::internal::EngineCreate> engineRegistry;
536};
537} // namespace
538
539std::unique_ptr<Engine>
541 const std::string &dmaEngineName, AppIDPath idPath,
542 const ServiceImplDetails &details,
543 const HWClientDetails &clients) {
544 auto &reg = EngineRegistry::get();
545 auto it = reg.find(dmaEngineName);
546 if (it == reg.end())
547 return std::make_unique<UnknownEngine>(conn, dmaEngineName);
548 return it->second(conn, idPath, details, clients);
549}
550
551void registry::internal::registerEngine(const std::string &name,
552 EngineCreate create) {
553 auto tried = EngineRegistry::get().try_emplace(name, create);
554 if (!tried.second)
555 throw std::runtime_error("Engine already exists in registry");
556}
#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:501
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:515
Services provide connections to 'bundles' – collections of named, unidirectional communication channe...
Definition Ports.h:456
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:36
virtual bool pollImpl()
Method called by poll() to actually poll the channel if the channel is connected.
Definition Ports.h:207
virtual void connectImpl(const ConnectOptions &options)
Called by all connect methods to let backends initiate the underlying connections.
Definition Ports.h:211
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:488
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 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:341
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:215
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:551
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:540
uint64_t bitsToBytes(uint64_t bits)
Compute ceil(bits/8).
Definition Utils.h:101
Definition esi.py:1
std::map< std::string, std::any > ServiceImplDetails
Definition Common.h:108
std::map< std::string, ChannelPort & > PortMap
Definition Ports.h:29
std::vector< HWClientDetail > HWClientDetails
Definition Common.h:107
OneItemBuffersFromHost(Type client_type)
Definition dma.py:106
OneItemBuffersToHost(Type client_type)
Definition dma.py:16