CIRCT 24.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,
43 BundleType::Direction dir,
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 {
70class OneItemBuffersToHost;
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:
119 OneItemBuffersToHost(AcceleratorConnection &conn, AppIDPath idPath,
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,
142 BundleType::Direction dir,
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;
155 services::MMIO::MMIORegion *mmio;
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 if (!buffer)
206 throw std::runtime_error("OneItemBuffersToHost: failed to allocate " +
207 std::to_string(bufferSize) +
208 " bytes of host memory");
209 writeBufferPtr();
210}
211
212bool OneItemBuffersToHostReadPort::pollImpl() {
213 Logger &logger = engine->conn.getLogger();
214
215 auto tryDeliverPending = [&]() {
216 logger.trace(
217 [&](std::string &subsystem, std::string &msg,
218 std::unique_ptr<std::map<std::string, std::any>> &details) {
219 subsystem = "OneItemBuffersToHost";
220 msg = "received message";
221 details = std::make_unique<std::map<std::string, std::any>>();
222 MessageData flat = pendingMessage->toMessageData();
223 (*details)["channel"] = identifier();
224 (*details)["data_size"] = flat.getSize();
225 (*details)["message_data"] = flat.toHex();
226 });
227
228 if (!invokeCallback(pendingMessage)) {
229 logger.trace(
230 [&](std::string &subsystem, std::string &msg,
231 std::unique_ptr<std::map<std::string, std::any>> &details) {
232 subsystem = "OneItemBuffersToHost";
233 msg = "callback rejected data";
234 details = std::make_unique<std::map<std::string, std::any>>();
235 (*details)["channel"] = identifier();
236 });
237 return false;
238 }
239
240 pendingMessage.reset();
241 writeBufferPtr();
242 logger.trace(
243 [this](std::string &subsystem, std::string &msg,
244 std::unique_ptr<std::map<std::string, std::any>> &details) {
245 subsystem = "OneItemBuffersToHost";
246 msg = "callback accepted data";
247 details = std::make_unique<std::map<std::string, std::any>>();
248 (*details)["channel"] = identifier();
249 });
250 return true;
251 };
252
253 if (pendingMessage)
254 return tryDeliverPending();
255
256 // Check to see if the buffer has been filled.
257 uint8_t *bufferData = reinterpret_cast<uint8_t *>(buffer->getPtr());
258 if (bufferData[bufferSize - 1] == 0)
259 return false;
260
261 // If it has, copy the data out and retain it until the consumer accepts it.
262 pendingMessage = std::make_unique<MessageData>(bufferData, bufferSize - 1);
263 return tryDeliverPending();
264}
265
266REGISTER_ENGINE("OneItemBuffersToHost", OneItemBuffersToHost);
267
268//===----------------------------------------------------------------------===//
269// OneItemBuffersFromHost engine
270//
271// Protocol:
272// 1) Host allocates two buffers: one for data and one for the completion byte.
273// 2) On write, if the completion byte is '0', the write fails since that
274// implies there's data currently in the buffer which the device has not yet
275// picked up. If the completion byte is '1', continue on.
276// 3) The host writes the data to the buffer and sets the completion byte to
277// '0'.
278// 4) The device reads the data into the data buffer and sets the completion
279// byte to '1'.
280// 5) Host sends address of buffer via MMIO write to register 0x8.
281// 6) Host sends address of completion buffer via MMIO write to register 0x10.
282// 7) Device reads data from said buffer address.
283// 8) Device writes '1' to the first byte of the completion buffer.
284//
285//===----------------------------------------------------------------------===//
286
287namespace {
288
289class OneItemBuffersFromHost;
290class OneItemBuffersFromHostWritePort : public WriteChannelPort {
291public:
292 /// Offset into the MMIO space to which the buffer pointer is written.
293 static constexpr size_t BufferPtrOffset = 8;
294 static constexpr size_t CompletionPtrOffset = 16;
295
296 OneItemBuffersFromHostWritePort(const Type *type,
297 OneItemBuffersFromHost *engine,
298 AppIDPath idPath,
299 const std::string &channelName)
300 : WriteChannelPort(type), engine(engine), idPath(idPath),
301 channelName(channelName) {}
302
303 // Connect allocate a buffer and prime the pump.
304 void connectImpl(const ChannelPort::ConnectOptions &options) override;
305
306 std::string identifier() const { return idPath.toStr() + "." + channelName; }
307
308protected:
309 void writeImpl(const MessageData &) override;
310 bool tryWriteImpl(const MessageData &data) override;
311
312 /// Break an oversized message into frame-sized pieces and enqueue them.
313 void enqueueFrames(const MessageData &data);
314
315 /// Poll for ability to send more data and send if able.
316 bool pollImpl() override;
317
318 // Owning engine.
320 // Single buffer.
321 std::unique_ptr<services::HostMem::HostMemRegion> data_buffer;
322 std::unique_ptr<services::HostMem::HostMemRegion> completion_buffer;
323
324 // Thread protection.
325 std::mutex bufferMutex;
326
327 // FIFO of frame-sized messages to send. When a message is larger than
328 // getFrameSizeBytes(), it's broken into frame-sized pieces and enqueued
329 // here.
330 std::queue<MessageData> pendingFrames;
331
332 // Identifing information.
333 AppIDPath idPath;
334 std::string channelName;
335};
336
337class OneItemBuffersFromHost : public Engine {
338 friend class OneItemBuffersFromHostWritePort;
339
340public:
341 OneItemBuffersFromHost(AcceleratorConnection &conn, AppIDPath idPath,
342 const ServiceImplDetails &details)
343 : Engine(conn), thisPath(idPath) {
344 // Get the MMIO path but don't try to resolve it yet.
345 auto mmioIDIter = details.find("mmio");
346 if (mmioIDIter != details.end())
347 mmioID = std::any_cast<AppID>(mmioIDIter->second);
348 }
349
350 static std::unique_ptr<Engine> create(AcceleratorConnection &conn,
351 AppIDPath idPath,
352 const ServiceImplDetails &details,
353 const HWClientDetails &clients) {
354 return std::make_unique<OneItemBuffersFromHost>(conn, idPath, details);
355 }
356
357 // Only throw errors on connect.
358 void connect() override;
359
360 // Create a real write port if requested. Create an error-throwing read port
361 // if requested.
362 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
363 const std::string &channelName,
364 BundleType::Direction dir,
365 const Type *type) override {
366 if (!BundlePort::isWrite(dir))
367 return std::make_unique<UnknownReadChannelPort>(
368 type, idPath.toStr() + "." + channelName +
369 " OneItemBuffersFromHost: cannot create read port");
370 return std::make_unique<OneItemBuffersFromHostWritePort>(type, this, idPath,
371 channelName);
372 }
373
374protected:
375 AppIDPath thisPath;
376 std::optional<AppID> mmioID;
377 services::MMIO::MMIORegion *mmio;
378 services::HostMem *hostMem;
379};
380
381} // namespace
382
383void OneItemBuffersFromHost::connect() {
384 // This is where we throw errors.
385 if (connected)
386 return;
387 if (!mmioID)
388 throw std::runtime_error("OneItemBuffersFromHost: no mmio path specified");
389 hostMem = conn.getService<services::HostMem>();
390 if (!hostMem)
391 throw std::runtime_error("OneItemBuffersFromHost: no host memory service");
392 hostMem->start();
393
394 // Resolve the MMIO port.
395 Accelerator &acc = conn.getAccelerator();
396 AppIDPath mmioPath = thisPath;
397 mmioPath.pop_back();
398 mmioPath.push_back(*mmioID);
399 AppIDPath lastPath;
400 BundlePort *port = acc.resolvePort(mmioPath, lastPath);
401 if (port == nullptr)
402 throw std::runtime_error(
403 thisPath.toStr() +
404 " OneItemBuffersFromHost: could not find MMIO port at " +
405 mmioPath.toStr());
406 mmio = dynamic_cast<services::MMIO::MMIORegion *>(port);
407 if (!mmio)
408 throw std::runtime_error(
409 thisPath.toStr() +
410 " OneItemBuffersFromHost: MMIO port is not an MMIO port");
411
412 // If we have a valid MMIO port, we can connect.
413 connected = true;
414}
415
416void OneItemBuffersFromHostWritePort::connectImpl(
417 const ChannelPort::ConnectOptions &options) {
418 engine->connect();
419 data_buffer = engine->hostMem->allocate(
420 std::max(getFrameSizeBytes(), (size_t)512), {false, false});
421 completion_buffer = engine->hostMem->allocate(512, {true, false});
422 if (!data_buffer || !completion_buffer)
423 throw std::runtime_error(
424 "OneItemBuffersFromHost: failed to allocate host memory");
425 // Set the last byte to '1' to indicate that the buffer is ready to be filled
426 // and sent to the device.
427 *static_cast<uint8_t *>(completion_buffer->getPtr()) = 1;
428}
429
430void OneItemBuffersFromHostWritePort::enqueueFrames(const MessageData &data) {
431 auto frames = getMessageFrames(data);
432 std::lock_guard<std::mutex> lock(bufferMutex);
433 for (const auto &frame : frames)
434 pendingFrames.push(frame);
435}
436
437void OneItemBuffersFromHostWritePort::writeImpl(const MessageData &data) {
438 while (!tryWriteImpl(data))
439 // Wait for the device to read the last data we sent.
440 std::this_thread::yield();
441}
442
443bool OneItemBuffersFromHostWritePort::tryWriteImpl(const MessageData &data) {
444 // We don't want to provide an infinite buffer, so limit it to one message.
445 {
446 std::lock_guard<std::mutex> lock(bufferMutex);
447 if (!pendingFrames.empty())
448 return false;
449 }
450
451 enqueueFrames(data);
452 return true;
453}
454
455bool OneItemBuffersFromHostWritePort::pollImpl() {
456 Logger &logger = engine->conn.getLogger();
457
458 // Check to see if the device is ready.
459 completion_buffer->flush();
460 uint8_t *completion =
461 reinterpret_cast<uint8_t *>(completion_buffer->getPtr());
462 if (*completion == 0)
463 return false;
464
465 std::lock_guard<std::mutex> lock(bufferMutex);
466
467 // Determine what to send: next frame from queue, or the original message.
468 const uint8_t *src;
469 size_t sendSize;
470 if (pendingFrames.empty())
471 return false;
472
473 MessageData frame = std::move(pendingFrames.front());
474 pendingFrames.pop();
475 src = frame.getBytes();
476 sendSize = frame.getSize();
477
478 void *bufferData = data_buffer->getPtr();
479 std::memcpy(bufferData, src, sendSize);
480 data_buffer->flush();
481 *completion = 0;
482 completion_buffer->flush();
483 engine->mmio->write(BufferPtrOffset,
484 reinterpret_cast<uint64_t>(data_buffer->getDevicePtr()));
485 engine->mmio->write(
486 CompletionPtrOffset,
487 reinterpret_cast<uint64_t>(completion_buffer->getDevicePtr()));
488
489 logger.trace([this, sendSize](
490 std::string &subsystem, std::string &msg,
491 std::unique_ptr<std::map<std::string, std::any>> &details) {
492 subsystem = "OneItemBuffersFromHost";
493 msg = "initiated transfer";
494 details = std::make_unique<std::map<std::string, std::any>>();
495 (*details)["channel"] = identifier();
496 (*details)["data_size"] = sendSize;
497 });
498
499 // We initiated a transfer, so return true to indicate that an action
500 // occurred.
501 return true;
502}
503
504REGISTER_ENGINE("OneItemBuffersFromHost", OneItemBuffersFromHost);
505
506//===----------------------------------------------------------------------===//
507// Engine / Bundle Engine Map
508//===----------------------------------------------------------------------===//
509
511 const std::string &channelName,
512 BundleType::Direction dir, const Type *type) {
513 auto portIter = ownedPorts.find(std::make_pair(idPath, channelName));
514 if (portIter != ownedPorts.end())
515 return *portIter->second;
516 std::unique_ptr<ChannelPort> port =
517 createPort(idPath, channelName, dir, type);
518 ChannelPort &ret = *port;
519 ownedPorts.emplace(std::make_pair(idPath, channelName), std::move(port));
520 return ret;
521}
522
524 const BundleType *bundleType) const {
525 PortMap ports;
526 for (auto [channelName, dir, type] : bundleType->getChannels()) {
527 auto engineIter = bundleEngineMap.find(channelName);
528 if (engineIter == bundleEngineMap.end())
529 continue;
530
531 ports.emplace(channelName, engineIter->second->requestPort(
532 idPath, channelName, dir, type));
533 }
534 return ports;
535}
536
537void BundleEngineMap::setEngine(const std::string &channelName,
538 Engine *engine) {
539 auto [it, inserted] = bundleEngineMap.try_emplace(channelName, engine);
540 if (!inserted)
541 throw std::runtime_error("Channel already exists in engine map");
542}
543
544//===----------------------------------------------------------------------===//
545// Registry
546//===----------------------------------------------------------------------===//
547
548namespace {
549class EngineRegistry {
550public:
551 static std::map<std::string, registry::internal::EngineCreate> &get() {
552 static EngineRegistry instance;
553 return instance.engineRegistry;
554 }
555
556private:
557 std::map<std::string, registry::internal::EngineCreate> engineRegistry;
558};
559} // namespace
560
561std::unique_ptr<Engine>
563 const std::string &dmaEngineName, AppIDPath idPath,
564 const ServiceImplDetails &details,
565 const HWClientDetails &clients) {
566 auto &reg = EngineRegistry::get();
567 auto it = reg.find(dmaEngineName);
568 if (it == reg.end())
569 return std::make_unique<UnknownEngine>(conn, dmaEngineName);
570 return it->second(conn, idPath, details, clients);
571}
572
573void registry::internal::registerEngine(const std::string &name,
574 EngineCreate create) {
575 auto tried = EngineRegistry::get().try_emplace(name, create);
576 if (!tried.second)
577 throw std::runtime_error("Engine already exists in registry");
578}
#define REGISTER_ENGINE(Name, TEngine)
Definition Engines.h:118
Abstract class representing a connection to an accelerator.
std::string toStr() const
Definition Manifest.cpp:816
PortMap requestPorts(const AppIDPath &idPath, const BundleType *bundleType) const
Request ports for all the channels in a bundle.
Definition Engines.cpp:523
std::map< std::string, Engine * > bundleEngineMap
Definition Engines.h:89
void setEngine(const std::string &channelName, Engine *engine)
Set a particlar engine for a particular channel.
Definition Engines.cpp:537
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
Engines implement the actual channel communication between the host and the accelerator.
Definition Engines.h:42
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:510
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:71
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
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
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
virtual void start()
In cases where necessary, enable host memory services.
Definition Services.h:292
virtual std::unique_ptr< HostMemRegion > allocate(std::size_t size, Options opts) const override=0
Allocate a region of host memory in accelerator accessible address space.
virtual void write(uint32_t addr, uint64_t data)
Write a 64-bit value to this region, not the global address space.
Definition Services.cpp:137
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:105
void registerEngine(const std::string &name, EngineCreate create)
Definition Engines.cpp:573
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:562
uint64_t bitsToBytes(uint64_t bits)
Compute ceil(bits/8).
Definition Utils.h:232
Definition esi.py:1
std::map< std::string, ChannelPort & > PortMap
Definition Ports.h:33
std::vector< HWClientDetail > HWClientDetails
Definition Common.h:108
std::map< std::string, std::any > ServiceImplDetails
Definition Common.h:109
OneItemBuffersFromHost(Type client_type)
Definition dma.py:126
OneItemBuffersToHost(Type client_type)
Definition dma.py:16