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