CIRCT 23.0.0git
Loading...
Searching...
No Matches
Cosim.cpp
Go to the documentation of this file.
1//===- Cosim.cpp - Connection to ESI simulation ---------------------------===//
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
12// (lib/dialect/ESI/runtime/cpp/lib/backends/Cosim.cpp).
13//
14//===----------------------------------------------------------------------===//
15
16#include "esi/backends/Cosim.h"
17#include "esi/Engines.h"
18#include "esi/Ports.h"
19#include "esi/Services.h"
20#include "esi/Utils.h"
22
23#include <cstring>
24#include <format>
25#include <fstream>
26#include <iostream>
27#include <set>
28
29using namespace esi;
30using namespace esi::services;
31using namespace esi::backends::cosim;
32
33namespace {
34
35//===----------------------------------------------------------------------===//
36// WriteCosimChannelPort
37//===----------------------------------------------------------------------===//
38
39/// Cosim client implementation of a write channel port.
40class WriteCosimChannelPort : public WriteChannelPort {
41public:
42 WriteCosimChannelPort(AcceleratorConnection &conn, RpcClient &client,
43 const RpcClient::ChannelDesc &desc, const Type *type,
44 std::string name)
45 : WriteChannelPort(type), conn(conn), client(client), desc(desc),
46 name(std::move(name)) {}
47 ~WriteCosimChannelPort() = default;
48
49 void connectImpl(const ChannelPort::ConnectOptions &options) override {
50 if (desc.dir != RpcClient::ChannelDirection::ToServer)
51 throw std::runtime_error("Channel '" + name +
52 "' is not a to server channel");
53 }
54
55protected:
56 void writeImpl(const MessageData &data) override {
57 auto frames = getMessageFrames(data);
58 for (const auto &frame : frames) {
59 conn.getLogger().trace(
60 [this,
61 &data](std::string &subsystem, std::string &msg,
62 std::unique_ptr<std::map<std::string, std::any>> &details) {
63 subsystem = "cosim_write";
64 msg = "Writing message to channel '" + name + "'";
65 details = std::make_unique<std::map<std::string, std::any>>();
66 (*details)["channel"] = name;
67 (*details)["data_size"] = data.getSize();
68 (*details)["message_data"] = data.toHex();
69 });
70
71 client.writeToServer(name, frame);
72 }
73 }
74 bool tryWriteImpl(const MessageData &data) override {
75 // For simplicity, this implementation does not support backpressure and
76 // always returns true. A more complex implementation could track pending
77 // messages and return false if there are too many.
78 writeImpl(data);
79 return true;
80 }
81
82private:
84 RpcClient &client;
86 std::string name;
87};
88
89//===----------------------------------------------------------------------===//
90// ReadCosimChannelPort
91//===----------------------------------------------------------------------===//
92
93/// Cosim client implementation of a read channel port. The wire transport
94/// (see `CosimRpc`) delivers messages via callback, so this class just
95/// forwards them to the registered `ReadChannelPort` consumer.
96class ReadCosimChannelPort : public ReadChannelPort {
97public:
98 ReadCosimChannelPort(AcceleratorConnection &conn, RpcClient &client,
99 const RpcClient::ChannelDesc &desc, const Type *type,
100 std::string name)
101 : ReadChannelPort(type), conn(conn), client(client), desc(desc),
102 name(std::move(name)) {}
103
104 ~ReadCosimChannelPort() = default;
105
106 void connectImpl(const ChannelPort::ConnectOptions &options) override {
107 if (desc.dir != RpcClient::ChannelDirection::ToClient)
108 throw std::runtime_error("Channel '" + name +
109 "' is not a to client channel");
110
111 // Connect to the channel and set up callback.
112 connection = client.connectClientReceiver(
113 name, [this](std::unique_ptr<SegmentedMessageData> &data) {
114 // Add trace logging for the received message.
115 conn.getLogger().trace(
116 [this, &data](
117 std::string &subsystem, std::string &msg,
118 std::unique_ptr<std::map<std::string, std::any>> &details) {
119 subsystem = "cosim_read";
120 msg = "Received message from channel '" + name + "'";
121 details = std::make_unique<std::map<std::string, std::any>>();
122 MessageData flat = data->toMessageData();
123 (*details)["channel"] = name;
124 (*details)["data_size"] = flat.getSize();
125 (*details)["message_data"] = flat.toHex();
126 });
127
128 bool consumed = invokeCallback(data);
129
130 if (consumed) {
131 // Log the message consumption.
132 conn.getLogger().trace(
133 [this](
134 std::string &subsystem, std::string &msg,
135 std::unique_ptr<std::map<std::string, std::any>> &details) {
136 subsystem = "cosim_read";
137 msg = "Message from channel '" + name + "' consumed";
138 });
139 }
140
141 return consumed;
142 });
143 }
144
145 void disconnect() override {
146 conn.getLogger().debug("cosim_read", "Disconnecting channel " + name);
147 if (connection) {
148 connection->disconnect();
149 connection.reset();
150 }
152 }
153
154private:
156 RpcClient &client;
158 std::string name;
159 std::unique_ptr<RpcClient::ReadChannelConnection> connection;
160};
161
162} // anonymous namespace
163
164//===----------------------------------------------------------------------===//
165// CosimAccelerator
166//===----------------------------------------------------------------------===//
167
168/// Parse the connection std::string and instantiate the accelerator. Support
169/// the traditional 'host:port' syntax and a path to 'cosim.cfg' which is output
170/// by the cosimulation when it starts (which is useful when it chooses its own
171/// port).
172std::unique_ptr<AcceleratorConnection>
173CosimAccelerator::connect(Context &ctxt, std::string connectionString) {
174 std::string portStr;
175 std::string host = "localhost";
176
177 size_t colon;
178 if ((colon = connectionString.find(':')) != std::string::npos) {
179 portStr = connectionString.substr(colon + 1);
180 host = connectionString.substr(0, colon);
181 } else if (connectionString.ends_with("cosim.cfg")) {
182 std::ifstream cfg(connectionString);
183 std::string line, key, value;
184
185 while (getline(cfg, line))
186 if ((colon = line.find(":")) != std::string::npos) {
187 key = line.substr(0, colon);
188 value = line.substr(colon + 1);
189 if (key == "port")
190 portStr = value;
191 else if (key == "host")
192 host = value;
193 }
194
195 if (portStr.size() == 0)
196 throw std::runtime_error("port line not found in file");
197 } else if (connectionString == "env") {
198 char *hostEnv = getenv("ESI_COSIM_HOST");
199 if (hostEnv)
200 host = hostEnv;
201 else
202 host = "localhost";
203 char *portEnv = getenv("ESI_COSIM_PORT");
204 if (portEnv)
205 portStr = portEnv;
206 else
207 throw std::runtime_error("ESI_COSIM_PORT environment variable not set");
208 } else {
209 throw std::runtime_error("Invalid connection std::string '" +
210 connectionString + "'");
211 }
212 uint16_t port = stoul(portStr);
213 auto conn = make_unique<CosimAccelerator>(ctxt, host, port);
214
215 // Using the MMIO manifest method is really only for internal debugging, so it
216 // doesn't need to be part of the connection string.
217 char *manifestMethod = getenv("ESI_COSIM_MANIFEST_MMIO");
218 if (manifestMethod != nullptr)
219 conn->setManifestMethod(ManifestMethod::MMIO);
220
221 return conn;
222}
223
224/// Construct and connect to a cosim server.
225CosimAccelerator::CosimAccelerator(Context &ctxt, std::string hostname,
226 uint16_t port)
227 : AcceleratorConnection(ctxt) {
228 // Connect to the simulation.
229 rpcClient = std::make_unique<RpcClient>(getLogger(), hostname, port);
230}
236
237namespace {
238class CosimSysInfo : public SysInfo {
239#pragma pack(push, 1)
240 struct CycleInfo {
241 uint64_t freq;
242 uint64_t cycle;
243 };
244#pragma pack(pop)
245
246public:
247 CosimSysInfo(CosimAccelerator &conn, RpcClient *rpcClient)
248 : SysInfo(conn), rpcClient(rpcClient) {
249 // This is an optional interface; if the channels aren't present, we simply
250 // report no cycle/frequency information.
251 RpcClient::ChannelDesc argDesc, resultDesc;
252 if (!rpcClient->getChannelDesc("__cosim_cycle_count.arg", argDesc) ||
253 !rpcClient->getChannelDesc("__cosim_cycle_count.result", resultDesc))
254 return;
255
256 Context &ctxt = conn.getCtxt();
257 const esi::Type *i1Type = getType(ctxt, new BitsType("i1", 1));
258 const esi::Type *i64Type = getType(ctxt, new BitsType("i64", 64));
259 const esi::Type *resultType =
260 getType(ctxt, new StructType(resultDesc.type,
261 {{"cycle", i64Type}, {"freq", i64Type}}));
262
263 reqPort = std::make_unique<WriteCosimChannelPort>(
264 conn, *rpcClient, argDesc, i1Type, "__cosim_cycle_count.arg");
265 respPort = std::make_unique<ReadCosimChannelPort>(
266 conn, *rpcClient, resultDesc, resultType, "__cosim_cycle_count.result");
267 auto *bundleType =
268 new BundleType("cosimCycleCount",
269 {{"arg", BundleType::Direction::To, i1Type},
270 {"result", BundleType::Direction::From, resultType}});
271 func.reset(FuncService::Function::get(AppID("__cosim_cycle_count"),
272 bundleType, *reqPort, *respPort));
273 func->connect();
274 }
275
276 uint32_t getEsiVersion() const override { return rpcClient->getEsiVersion(); }
277 std::optional<uint64_t> getCycleCount() const override {
278 if (!func)
279 return std::nullopt;
280 return getCycleInfo().cycle;
281 }
282 std::optional<uint64_t> getCoreClockFrequency() const override {
283 if (!func)
284 return std::nullopt;
285 return getCycleInfo().freq;
286 }
287
288 std::vector<uint8_t> getCompressedManifest() const override {
289 return rpcClient->getCompressedManifest();
290 }
291
292private:
293 const esi::Type *getType(Context &ctxt, esi::Type *type) {
294 if (auto t = ctxt.getType(type->getID())) {
295 delete type;
296 return *t;
297 }
298 ctxt.registerType(type);
299 return type;
300 }
301
302 RpcClient *rpcClient;
303 std::unique_ptr<WriteCosimChannelPort> reqPort;
304 std::unique_ptr<ReadCosimChannelPort> respPort;
305 std::unique_ptr<FuncService::Function> func;
306
307 CycleInfo getCycleInfo() const {
308 MessageData arg({1}); // 1-bit trigger message
309 std::future<MessageData> result = func->call(arg);
310 result.wait();
311 MessageData respMsg = result.get();
312 return *respMsg.as<CycleInfo>();
313 }
314};
315} // namespace
316
317namespace {
318class CosimMMIO : public MMIO {
319public:
320 CosimMMIO(CosimAccelerator &conn, Context &ctxt, const AppIDPath &idPath,
321 RpcClient *rpcClient, const HWClientDetails &clients)
322 : MMIO(conn, idPath, clients) {
323 // We have to locate the channels ourselves since this service might be used
324 // to retrieve the manifest.
325 RpcClient::ChannelDesc cmdArg, cmdResp;
326 if (!rpcClient->getChannelDesc("__cosim_mmio_read_write.arg", cmdArg) ||
327 !rpcClient->getChannelDesc("__cosim_mmio_read_write.result", cmdResp))
328 throw std::runtime_error("Could not find MMIO channels");
329
330 const esi::Type *i64Type = getType(ctxt, new UIntType(cmdResp.type, 64));
331 const esi::Type *cmdType = getType(
332 ctxt, new StructType(cmdArg.type, {{"write", new BitsType("i1", 1)},
333 {"offset", new UIntType("ui32", 32)},
334 {"data", new BitsType("i64", 64)}}));
335
336 // Get ports, create the function, then connect to it.
337 cmdArgPort = std::make_unique<WriteCosimChannelPort>(
338 conn, *rpcClient, cmdArg, cmdType, "__cosim_mmio_read_write.arg");
339 cmdRespPort = std::make_unique<ReadCosimChannelPort>(
340 conn, *rpcClient, cmdResp, i64Type, "__cosim_mmio_read_write.result");
341 auto *bundleType = new BundleType(
342 "cosimMMIO", {{"arg", BundleType::Direction::To, cmdType},
343 {"result", BundleType::Direction::From, i64Type}});
344 cmdMMIO.reset(FuncService::Function::get(AppID("__cosim_mmio"), bundleType,
345 *cmdArgPort, *cmdRespPort));
346 cmdMMIO->connect();
347 }
348
349#pragma pack(push, 1)
350 struct MMIOCmd {
351 uint64_t data;
352 uint32_t offset;
353 bool write;
354 };
355#pragma pack(pop)
356
357 // Call the read function and wait for a response.
358 uint64_t read(uint32_t addr) const override {
359 MMIOCmd cmd{.data = 0, .offset = addr, .write = false};
360 auto arg = MessageData::from(cmd);
361 std::lock_guard<std::mutex> g(mmioCmdLock);
362 std::future<MessageData> result = cmdMMIO->call(arg);
363 result.wait();
364 uint64_t ret = *result.get().as<uint64_t>();
365 conn.getLogger().trace(
366 [addr, ret](std::string &subsystem, std::string &msg,
367 std::unique_ptr<std::map<std::string, std::any>> &details) {
368 subsystem = "cosim_mmio";
369 msg = "MMIO[0x" + toHex(addr) + "] = 0x" + toHex(ret);
370 });
371 return ret;
372 }
373
374 void write(uint32_t addr, uint64_t data) override {
375 conn.getLogger().trace(
376 [addr,
377 data](std::string &subsystem, std::string &msg,
378 std::unique_ptr<std::map<std::string, std::any>> &details) {
379 subsystem = "cosim_mmio";
380 msg = "MMIO[0x" + toHex(addr) + "] <- 0x" + toHex(data);
381 });
382 MMIOCmd cmd{.data = data, .offset = addr, .write = true};
383 auto arg = MessageData::from(cmd);
384 std::lock_guard<std::mutex> g(mmioCmdLock);
385 std::future<MessageData> result = cmdMMIO->call(arg);
386 result.wait();
387 }
388
389private:
390 const esi::Type *getType(Context &ctxt, esi::Type *type) {
391 if (auto t = ctxt.getType(type->getID())) {
392 delete type;
393 return *t;
394 }
395 ctxt.registerType(type);
396 return type;
397 }
398 std::unique_ptr<WriteCosimChannelPort> cmdArgPort;
399 std::unique_ptr<ReadCosimChannelPort> cmdRespPort;
400 std::unique_ptr<FuncService::Function> cmdMMIO;
401};
402
403#pragma pack(push, 1)
404struct HostMemReadReq {
405 uint8_t tag;
406 uint32_t length;
407 uint64_t address;
408};
409
410struct HostMemReadResp {
411 uint64_t data;
412 uint8_t tag;
413};
414
415struct HostMemWriteReq {
416 uint8_t valid_bytes;
417 uint64_t data;
418 uint8_t tag;
419 uint64_t address;
420};
421
422using HostMemWriteResp = uint8_t;
423#pragma pack(pop)
424
425class CosimHostMem : public HostMem {
426public:
427 CosimHostMem(AcceleratorConnection &acc, Context &ctxt, RpcClient *rpcClient)
428 : HostMem(acc), acc(acc), ctxt(ctxt), rpcClient(rpcClient) {}
429
430 void start() override {
431 // We have to locate the channels ourselves since this service might be used
432 // to retrieve the manifest.
433
434 if (writeRespPort)
435 return;
436
437 // TODO: The types here are WRONG. They need to be wrapped in Channels! Fix
438 // this in a subsequent PR.
439
440 // Setup the read side callback.
441 RpcClient::ChannelDesc readArg, readResp;
442 if (!rpcClient->getChannelDesc("__cosim_hostmem_read_req.data", readArg) ||
443 !rpcClient->getChannelDesc("__cosim_hostmem_read_resp.data", readResp))
444 throw std::runtime_error("Could not find HostMem read channels");
445
446 const esi::Type *readRespType =
447 getType(ctxt, new StructType(readResp.type,
448 {{"tag", new UIntType("ui8", 8)},
449 {"data", new BitsType("i64", 64)}}));
450 const esi::Type *readReqType =
451 getType(ctxt, new StructType(readArg.type,
452 {{"address", new UIntType("ui64", 64)},
453 {"length", new UIntType("ui32", 32)},
454 {"tag", new UIntType("ui8", 8)}}));
455
456 // Get ports. Unfortunately, we can't model this as a callback since there
457 // will sometimes be multiple responses per request.
458 readRespPort = std::make_unique<WriteCosimChannelPort>(
459 conn, *rpcClient, readResp, readRespType,
460 "__cosim_hostmem_read_resp.data");
461 readReqPort = std::make_unique<ReadCosimChannelPort>(
462 conn, *rpcClient, readArg, readReqType,
463 "__cosim_hostmem_read_req.data");
464 readReqPort->connect(
465 [this](const MessageData &req) { return serviceRead(req); });
466
467 // Setup the write side callback.
468 RpcClient::ChannelDesc writeArg, writeResp;
469 if (!rpcClient->getChannelDesc("__cosim_hostmem_write.arg", writeArg) ||
470 !rpcClient->getChannelDesc("__cosim_hostmem_write.result", writeResp))
471 throw std::runtime_error("Could not find HostMem write channels");
472
473 const esi::Type *writeRespType =
474 getType(ctxt, new UIntType(writeResp.type, 8));
475 const esi::Type *writeReqType =
476 getType(ctxt, new StructType(writeArg.type,
477 {{"address", new UIntType("ui64", 64)},
478 {"tag", new UIntType("ui8", 8)},
479 {"data", new BitsType("i64", 64)}}));
480
481 // Get ports, create the function, then connect to it.
482 writeRespPort = std::make_unique<WriteCosimChannelPort>(
483 conn, *rpcClient, writeResp, writeRespType,
484 "__cosim_hostmem_write.result");
485 writeReqPort = std::make_unique<ReadCosimChannelPort>(
486 conn, *rpcClient, writeArg, writeReqType, "__cosim_hostmem_write.arg");
487 auto *bundleType = new BundleType(
488 "cosimHostMem",
489 {{"arg", BundleType::Direction::To, writeReqType},
490 {"result", BundleType::Direction::From, writeRespType}});
491 write.reset(CallService::Callback::get(acc, AppID("__cosim_hostmem_write"),
492 bundleType, *writeRespPort,
493 *writeReqPort));
494 write->connect([this](const MessageData &req) { return serviceWrite(req); },
495 true);
496 }
497
498 // Service the read request as a callback. Simply reads the data from the
499 // location specified. TODO: check that the memory has been mapped.
500 bool serviceRead(const MessageData &reqBytes) {
501 const HostMemReadReq *req = reqBytes.as<HostMemReadReq>();
502 acc.getLogger().trace(
503 [&](std::string &subsystem, std::string &msg,
504 std::unique_ptr<std::map<std::string, std::any>> &details) {
505 subsystem = "hostmem";
506 msg = "Read request: addr=0x" + toHex(req->address) +
507 " len=" + std::to_string(req->length) +
508 " tag=" + std::to_string(req->tag);
509 });
510 // Send one response per 8 bytes. Zero-length reads (e.g. void / zero-width
511 // types) indicates a bug in the hardware and we log an error, but we still
512 // send a single response.
513 uint64_t *dataPtr = reinterpret_cast<uint64_t *>(req->address);
514 uint32_t numDataResps = (req->length + 7) / 8;
515 if (numDataResps == 0)
516 acc.getLogger().error(
517 "hostmem",
518 std::format("Read request with length=0 from addr=0x{} tag={}. "
519 "Reads of length 0 are not valid and indicate a bug "
520 "in the requester.",
521 toHex(req->address), req->tag));
522 uint32_t numResps = std::max(numDataResps, 1u);
523 for (uint32_t i = 0; i < numResps; ++i) {
524 HostMemReadResp resp{.data = i < numDataResps ? dataPtr[i] : 0,
525 .tag = req->tag};
526 acc.getLogger().trace(
527 [&](std::string &subsystem, std::string &msg,
528 std::unique_ptr<std::map<std::string, std::any>> &details) {
529 subsystem = "HostMem";
530 msg = "Read result: data=0x" + toHex(resp.data) +
531 " tag=" + std::to_string(resp.tag);
532 });
533 readRespPort->write(MessageData::from(resp));
534 }
535 return true;
536 }
537
538 // Service a write request as a callback. Simply write the data to the
539 // location specified. TODO: check that the memory has been mapped.
540 MessageData serviceWrite(const MessageData &reqBytes) {
541 const HostMemWriteReq *req = reqBytes.as<HostMemWriteReq>();
542 acc.getLogger().trace(
543 [&](std::string &subsystem, std::string &msg,
544 std::unique_ptr<std::map<std::string, std::any>> &details) {
545 subsystem = "hostmem";
546 msg = "Write request: addr=0x" + toHex(req->address) + " data=0x" +
547 toHex(req->data) +
548 " valid_bytes=" + std::to_string(req->valid_bytes) +
549 " tag=" + std::to_string(req->tag);
550 });
551 uint8_t *dataPtr = reinterpret_cast<uint8_t *>(req->address);
552 for (uint8_t i = 0; i < req->valid_bytes; ++i)
553 dataPtr[i] = (req->data >> (i * 8)) & 0xFF;
554 HostMemWriteResp resp = req->tag;
555 return MessageData::from(resp);
556 }
557
558 struct CosimHostMemRegion : public HostMemRegion {
559 CosimHostMemRegion(std::size_t size) {
560 ptr = malloc(size);
561 memset(ptr, 0xFF, size);
562 this->size = size;
563 }
564 virtual ~CosimHostMemRegion() { free(ptr); }
565 virtual void *getPtr() const override { return ptr; }
566 virtual std::size_t getSize() const override { return size; }
567
568 private:
569 void *ptr;
570 std::size_t size;
571 };
572
573 virtual std::unique_ptr<HostMemRegion>
574 allocate(std::size_t size, HostMem::Options opts) const override {
575 auto ret = std::unique_ptr<HostMemRegion>(new CosimHostMemRegion(size));
576 acc.getLogger().debug(
577 [&](std::string &subsystem, std::string &msg,
578 std::unique_ptr<std::map<std::string, std::any>> &details) {
579 subsystem = "HostMem";
580 msg = "Allocated host memory region at 0x" + toHex(ret->getPtr()) +
581 " of size " + std::to_string(size);
582 });
583 return ret;
584 }
585 virtual bool mapMemory(void *ptr, std::size_t size,
586 HostMem::Options opts) const override {
587 return true;
588 }
589 virtual void unmapMemory(void *ptr) const override {}
590
591private:
592 const esi::Type *getType(Context &ctxt, esi::Type *type) {
593 if (auto t = ctxt.getType(type->getID())) {
594 delete type;
595 return *t;
596 }
597 ctxt.registerType(type);
598 return type;
599 }
601 Context &ctxt;
602 RpcClient *rpcClient;
603 std::unique_ptr<WriteCosimChannelPort> readRespPort;
604 std::unique_ptr<ReadCosimChannelPort> readReqPort;
605 std::unique_ptr<CallService::Callback> read;
606 std::unique_ptr<WriteCosimChannelPort> writeRespPort;
607 std::unique_ptr<ReadCosimChannelPort> writeReqPort;
608 std::unique_ptr<CallService::Callback> write;
609};
610} // namespace
611
612namespace esi::backends::cosim {
613/// Implement the magic cosim channel communication.
614class CosimEngine : public Engine {
615public:
617 const ServiceImplDetails &details, const HWClientDetails &clients)
618 : Engine(conn), conn(conn) {
619 // Compute our parents idPath path.
620 AppIDPath prefix = std::move(idPath);
621 if (prefix.size() > 0)
622 prefix.pop_back();
623
624 for (auto client : clients) {
625 AppIDPath fullClientPath = prefix + client.relPath;
626 std::map<std::string, std::string> channelAssignments;
627 for (auto assignment : client.channelAssignments)
628 if (assignment.second.type == "cosim")
629 channelAssignments[assignment.first] = std::any_cast<std::string>(
630 assignment.second.implOptions.at("name"));
631 clientChannelAssignments[fullClientPath] = std::move(channelAssignments);
632 }
633 }
634
635 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
636 const std::string &channelName,
638 const Type *type) override;
639
640private:
642 std::map<AppIDPath, std::map<std::string, std::string>>
644};
645} // namespace esi::backends::cosim
646
647std::unique_ptr<ChannelPort>
648CosimEngine::createPort(AppIDPath idPath, const std::string &channelName,
649 BundleType::Direction dir, const Type *type) {
650
651 // Find the client details for the port at 'fullPath'.
652 auto f = clientChannelAssignments.find(idPath);
653 if (f == clientChannelAssignments.end())
654 throw std::runtime_error("Could not find port for '" + idPath.toStr() +
655 "." + channelName + "'");
656 const std::map<std::string, std::string> &channelAssignments = f->second;
657 auto cosimChannelNameIter = channelAssignments.find(channelName);
658 if (cosimChannelNameIter == channelAssignments.end())
659 throw std::runtime_error("Could not find channel '" + idPath.toStr() + "." +
660 channelName + "' in cosimulation");
661
662 // Get the endpoint, which may or may not exist. Construct the port.
663 // Everything is validated when the client calls 'connect()' on the port.
665 if (!conn.rpcClient->getChannelDesc(cosimChannelNameIter->second, chDesc))
666 throw std::runtime_error("Could not find channel '" + idPath.toStr() + "." +
667 channelName + "' in cosimulation");
668
669 std::unique_ptr<ChannelPort> port;
670 std::string fullChannelName = idPath.toStr() + "." + channelName;
671 if (BundlePort::isWrite(dir))
672 port = std::make_unique<WriteCosimChannelPort>(
673 conn, *conn.rpcClient, chDesc, type, fullChannelName);
674 else
675 port = std::make_unique<ReadCosimChannelPort>(conn, *conn.rpcClient, chDesc,
676 type, fullChannelName);
677 return port;
678}
679
680void CosimAccelerator::createEngine(const std::string &engineTypeName,
681 AppIDPath idPath,
682 const ServiceImplDetails &details,
683 const HWClientDetails &clients) {
684
685 std::unique_ptr<Engine> engine = nullptr;
686 if (engineTypeName == "cosim")
687 engine = std::make_unique<CosimEngine>(*this, idPath, details, clients);
688 else
689 engine = ::esi::registry::createEngine(*this, engineTypeName, idPath,
690 details, clients);
691 registerEngine(idPath, std::move(engine), clients);
692}
694 AppIDPath idPath, std::string implName,
695 const ServiceImplDetails &details,
696 const HWClientDetails &clients) {
697 if (svcType == typeid(services::MMIO)) {
698 return new CosimMMIO(*this, getCtxt(), idPath, rpcClient.get(), clients);
699 } else if (svcType == typeid(services::HostMem)) {
700 return new CosimHostMem(*this, getCtxt(), rpcClient.get());
701 } else if (svcType == typeid(SysInfo)) {
702 switch (manifestMethod) {
704 return new CosimSysInfo(*this, rpcClient.get());
706 return new MMIOSysInfo(getService<services::MMIO>());
707 }
708 }
709 return nullptr;
710}
711
715
#define REGISTER_ACCELERATOR(Name, TAccelerator)
Abstract class representing a connection to an accelerator.
Context & getCtxt() const
void clearOwnedObjects()
Drop accelerator-owned objects before a derived backend destroys resources that those objects may ref...
Context & ctxt
ESI accelerator context.
void registerEngine(AppIDPath idPath, std::unique_ptr< Engine > engine, const HWClientDetails &clients)
If createEngine is overridden, this method should be called to register the engine and all of the cha...
virtual void disconnect()
Disconnect from the accelerator cleanly.
Logger & getLogger() const
std::string toStr() const
Definition Manifest.cpp:814
Bits are just an array of bits.
Definition Types.h:206
static bool isWrite(BundleType::Direction bundleDir)
Compute the direction of a channel given the bundle direction and the bundle port's direction.
Definition Ports.h:615
Bundles represent a collection of channels.
Definition Types.h:104
virtual void connectImpl(const ConnectOptions &options)
Called by all connect methods to let backends initiate the underlying connections.
Definition Ports.h:304
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Definition Context.h:34
std::optional< const Type * > getType(Type::ID id) const
Resolve a type id to the type.
Definition Context.h:50
void registerType(Type *type)
Register a type with the context. Takes ownership of the pointer type.
Definition Context.cpp:33
Engines implement the actual channel communication between the host and the accelerator.
Definition Engines.h:42
A concrete flat message backed by a single vector of bytes.
Definition Common.h:155
const T * as() const
Cast to a type.
Definition Common.h:190
static MessageData from(T &t)
Cast from a type to its raw bytes.
Definition Common.h:200
A ChannelPort which reads data from the accelerator.
Definition Ports.h:453
virtual void disconnect() override
Disconnect the channel.
Definition Ports.cpp:70
bool invokeCallback(std::unique_ptr< SegmentedMessageData > &msg)
Invoke the currently registered callback.
Definition Ports.cpp:87
Structs are an ordered collection of fields, each with a name and a type.
Definition Types.h:246
Root class of the ESI type system.
Definition Types.h:36
ID getID() const
Definition Types.h:42
Unsigned integer.
Definition Types.h:235
A ChannelPort which sends data to the accelerator.
Definition Ports.h:308
virtual bool tryWriteImpl(const MessageData &data)=0
Implementation for tryWrite(). Subclasses must implement this.
std::vector< MessageData > getMessageFrames(const MessageData &data)
Break a message into its frames.
Definition Ports.cpp:680
virtual void writeImpl(const MessageData &)=0
Implementation for write(). Subclasses must implement this.
Connect to an ESI simulation.
Definition Cosim.h:36
void createEngine(const std::string &engineTypeName, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients) override
Create a new engine for channel communication with the accelerator.
Definition Cosim.cpp:680
std::unique_ptr< RpcClient > rpcClient
Definition Cosim.h:65
void setManifestMethod(ManifestMethod method)
Definition Cosim.cpp:712
static std::unique_ptr< AcceleratorConnection > connect(Context &, std::string connectionString)
Parse the connection std::string and instantiate the accelerator.
Definition Cosim.cpp:173
virtual Service * createService(Service::Type service, AppIDPath path, std::string implName, const ServiceImplDetails &details, const HWClientDetails &clients) override
Called by getServiceImpl exclusively.
Definition Cosim.cpp:693
CosimAccelerator(Context &, std::string hostname, uint16_t port)
Construct and connect to a cosim server.
Definition Cosim.cpp:225
std::set< std::unique_ptr< ChannelPort > > channels
Definition Cosim.h:69
Implement the magic cosim channel communication.
Definition Cosim.cpp:614
CosimEngine(CosimAccelerator &conn, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Definition Cosim.cpp:616
std::map< AppIDPath, std::map< std::string, std::string > > clientChannelAssignments
Definition Cosim.cpp:643
std::unique_ptr< ChannelPort > createPort(AppIDPath idPath, const std::string &channelName, BundleType::Direction dir, const Type *type) override
Each engine needs to know how to create a ports.
Definition Cosim.cpp:648
A client for the cosim RPC server.
Definition RpcClient.h:34
std::vector< uint8_t > getCompressedManifest() const
Get the compressed manifest from the server.
uint32_t getEsiVersion() const
Get the ESI version from the manifest.
bool getChannelDesc(const std::string &channelName, ChannelDesc &desc) const
Get the channel description for a channel name.
static Callback * get(AcceleratorConnection &acc, AppID id, const BundleType *type, WriteChannelPort &result, ReadChannelPort &arg)
Definition Services.cpp:335
static Function * get(AppID id, BundleType *type, WriteChannelPort &arg, ReadChannelPort &result)
Definition Services.cpp:286
Implement the SysInfo API for a standard MMIO protocol.
Definition Services.h:213
Parent class of all APIs modeled as 'services'.
Definition Services.h:59
const std::type_info & Type
Definition Services.h:61
Information about the Accelerator system.
Definition Services.h:113
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
Definition esi.py:1
std::map< std::string, std::any > ServiceImplDetails
Definition Common.h:108
std::string toHex(void *val)
Definition Common.cpp:37
std::vector< HWClientDetail > HWClientDetails
Definition Common.h:107
write(addr, data)
Definition xrt_cosim.py:30
read(addr)
Definition xrt_cosim.py:23
Description of a channel from the server.
Definition RpcClient.h:53
Options for allocating host memory.
Definition Services.h:261