Loading [MathJax]/jax/output/HTML-CSS/config.js
CIRCT 21.0.0git
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Cosim.cpp
Go to the documentation of this file.
1//===- Cosim.cpp - Connection to ESI simulation via GRPC ------------------===//
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/Services.h"
19#include "esi/Utils.h"
20
21#include "cosim.grpc.pb.h"
22
23#include <grpc/grpc.h>
24#include <grpcpp/channel.h>
25#include <grpcpp/client_context.h>
26#include <grpcpp/create_channel.h>
27#include <grpcpp/security/credentials.h>
28
29#include <fstream>
30#include <iostream>
31#include <set>
32
33using namespace esi;
34using namespace esi::cosim;
35using namespace esi::services;
36using namespace esi::backends::cosim;
37
38using grpc::Channel;
39using grpc::ClientContext;
40using grpc::ClientReader;
41using grpc::ClientReaderWriter;
42using grpc::ClientWriter;
43using grpc::Status;
44
45static void checkStatus(Status s, const std::string &msg) {
46 if (!s.ok())
47 throw std::runtime_error(msg + ". Code " + to_string(s.error_code()) +
48 ": " + s.error_message() + " (" +
49 s.error_details() + ")");
50}
51
52/// Hack around C++ not having a way to forward declare a nested class.
54 StubContainer(std::unique_ptr<ChannelServer::Stub> stub)
55 : stub(std::move(stub)) {}
56 std::unique_ptr<ChannelServer::Stub> stub;
57
58 /// Get the type ID for a channel name.
59 bool getChannelDesc(const std::string &channelName,
60 esi::cosim::ChannelDesc &desc);
61};
63
64/// Parse the connection std::string and instantiate the accelerator. Support
65/// the traditional 'host:port' syntax and a path to 'cosim.cfg' which is output
66/// by the cosimulation when it starts (which is useful when it chooses its own
67/// port).
68std::unique_ptr<AcceleratorConnection>
69CosimAccelerator::connect(Context &ctxt, std::string connectionString) {
70 std::string portStr;
71 std::string host = "localhost";
72
73 size_t colon;
74 if ((colon = connectionString.find(':')) != std::string::npos) {
75 portStr = connectionString.substr(colon + 1);
76 host = connectionString.substr(0, colon);
77 } else if (connectionString.ends_with("cosim.cfg")) {
78 std::ifstream cfg(connectionString);
79 std::string line, key, value;
80
81 while (getline(cfg, line))
82 if ((colon = line.find(":")) != std::string::npos) {
83 key = line.substr(0, colon);
84 value = line.substr(colon + 1);
85 if (key == "port")
86 portStr = value;
87 else if (key == "host")
88 host = value;
89 }
90
91 if (portStr.size() == 0)
92 throw std::runtime_error("port line not found in file");
93 } else if (connectionString == "env") {
94 char *hostEnv = getenv("ESI_COSIM_HOST");
95 if (hostEnv)
96 host = hostEnv;
97 else
98 host = "localhost";
99 char *portEnv = getenv("ESI_COSIM_PORT");
100 if (portEnv)
101 portStr = portEnv;
102 else
103 throw std::runtime_error("ESI_COSIM_PORT environment variable not set");
104 } else {
105 throw std::runtime_error("Invalid connection std::string '" +
106 connectionString + "'");
107 }
108 uint16_t port = stoul(portStr);
109 auto conn = make_unique<CosimAccelerator>(ctxt, host, port);
110
111 // Using the MMIO manifest method is really only for internal debugging, so it
112 // doesn't need to be part of the connection string.
113 char *manifestMethod = getenv("ESI_COSIM_MANIFEST_MMIO");
114 if (manifestMethod != nullptr)
115 conn->setManifestMethod(ManifestMethod::MMIO);
116
117 return conn;
118}
119
120/// Construct and connect to a cosim server.
121CosimAccelerator::CosimAccelerator(Context &ctxt, std::string hostname,
122 uint16_t port)
123 : AcceleratorConnection(ctxt) {
124 // Connect to the simulation.
125 auto channel = grpc::CreateChannel(hostname + ":" + std::to_string(port),
126 grpc::InsecureChannelCredentials());
127 rpcClient = new StubContainer(ChannelServer::NewStub(channel));
128}
130 disconnect();
131 if (rpcClient)
132 delete rpcClient;
133 channels.clear();
134}
135
136namespace {
137class CosimSysInfo : public SysInfo {
138public:
139 CosimSysInfo(CosimAccelerator &conn, ChannelServer::Stub *rpcClient)
140 : SysInfo(conn), rpcClient(rpcClient) {}
141
142 uint32_t getEsiVersion() const override {
143 ::esi::cosim::Manifest response = getManifest();
144 return response.esi_version();
145 }
146
147 std::vector<uint8_t> getCompressedManifest() const override {
148 ::esi::cosim::Manifest response = getManifest();
149 std::string compressedManifestStr = response.compressed_manifest();
150 return std::vector<uint8_t>(compressedManifestStr.begin(),
151 compressedManifestStr.end());
152 }
153
154private:
155 ::esi::cosim::Manifest getManifest() const {
156 ::esi::cosim::Manifest response;
157 // To get around the a race condition where the manifest may not be set yet,
158 // loop until it is. TODO: fix this with the DPI API change.
159 do {
160 ClientContext context;
161 VoidMessage arg;
162 Status s = rpcClient->GetManifest(&context, arg, &response);
163 checkStatus(s, "Failed to get manifest");
164 std::this_thread::sleep_for(std::chrono::milliseconds(10));
165 } while (response.esi_version() < 0);
166 return response;
167 }
168
169 esi::cosim::ChannelServer::Stub *rpcClient;
170};
171} // namespace
172
173namespace {
174/// Cosim client implementation of a write channel port.
175class WriteCosimChannelPort : public WriteChannelPort {
176public:
177 WriteCosimChannelPort(AcceleratorConnection &conn,
178 ChannelServer::Stub *rpcClient, const ChannelDesc &desc,
179 const Type *type, std::string name)
180 : WriteChannelPort(type), conn(conn), rpcClient(rpcClient), desc(desc),
181 name(name) {}
182 ~WriteCosimChannelPort() = default;
183
184 void connectImpl(std::optional<unsigned> bufferSize) override {
185 if (desc.type() != getType()->getID())
186 throw std::runtime_error("Channel '" + name +
187 "' has wrong type. Expected " +
188 getType()->getID() + ", got " + desc.type());
189 if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_SERVER)
190 throw std::runtime_error("Channel '" + name +
191 "' is not a to server channel");
192 assert(desc.name() == name);
193 }
194
195 /// Send a write message to the server.
196 void write(const MessageData &data) override {
197 // Add trace logging before sending the message.
198 conn.getLogger().trace(
199 [this,
200 &data](std::string &subsystem, std::string &msg,
201 std::unique_ptr<std::map<std::string, std::any>> &details) {
202 subsystem = "cosim_write";
203 msg = "Writing message to channel '" + name + "'";
204 details = std::make_unique<std::map<std::string, std::any>>();
205 (*details)["channel"] = name;
206 (*details)["data_size"] = data.getSize();
207 (*details)["message_data"] = data.toHex();
208 });
209
210 ClientContext context;
211 AddressedMessage msg;
212 msg.set_channel_name(name);
213 msg.mutable_message()->set_data(data.getBytes(), data.getSize());
214 VoidMessage response;
215 grpc::Status sendStatus = rpcClient->SendToServer(&context, msg, &response);
216 if (!sendStatus.ok())
217 throw std::runtime_error("Failed to write to channel '" + name +
218 "': " + std::to_string(sendStatus.error_code()) +
219 " " + sendStatus.error_message() +
220 ". Details: " + sendStatus.error_details());
221 }
222
223 bool tryWrite(const MessageData &data) override {
224 write(data);
225 return true;
226 }
227
228protected:
230 ChannelServer::Stub *rpcClient;
231 /// The channel description as provided by the server.
232 ChannelDesc desc;
233 /// The name of the channel from the manifest.
234 std::string name;
235};
236} // namespace
237
238namespace {
239/// Cosim client implementation of a read channel port. Since gRPC read protocol
240/// streams messages back, this implementation is quite complex.
241class ReadCosimChannelPort
242 : public ReadChannelPort,
243 public grpc::ClientReadReactor<esi::cosim::Message> {
244public:
245 ReadCosimChannelPort(AcceleratorConnection &conn,
246 ChannelServer::Stub *rpcClient, const ChannelDesc &desc,
247 const Type *type, std::string name)
248 : ReadChannelPort(type), conn(conn), rpcClient(rpcClient), desc(desc),
249 name(name), context(nullptr) {}
250 virtual ~ReadCosimChannelPort() { disconnect(); }
251
252 void connectImpl(std::optional<unsigned> bufferSize) override {
253 // Sanity checking.
254 if (desc.type() != getType()->getID())
255 throw std::runtime_error("Channel '" + name +
256 "' has wrong type. Expected " +
257 getType()->getID() + ", got " + desc.type());
258 if (desc.dir() != ChannelDesc::Direction::ChannelDesc_Direction_TO_CLIENT)
259 throw std::runtime_error("Channel '" + name +
260 "' is not a to client channel");
261 assert(desc.name() == name);
262
263 // Initiate a stream of messages from the server.
264 if (context)
265 return;
266 context = new ClientContext();
267 rpcClient->async()->ConnectToClientChannel(context, &desc, this);
268 StartCall();
269 StartRead(&incomingMessage);
270 }
271
272 /// Gets called when there's a new message from the server. It'll be stored in
273 /// `incomingMessage`.
274 void OnReadDone(bool ok) override {
275 if (!ok)
276 // This happens when we are disconnecting since we are canceling the call.
277 return;
278
279 // Read the delivered message and push it onto the queue.
280 const std::string &messageString = incomingMessage.data();
281 MessageData data(reinterpret_cast<const uint8_t *>(messageString.data()),
282 messageString.size());
283
284 // Add trace logging for the received message.
285 conn.getLogger().trace(
286 [this,
287 &data](std::string &subsystem, std::string &msg,
288 std::unique_ptr<std::map<std::string, std::any>> &details) {
289 subsystem = "cosim_read";
290 msg = "Received message from channel '" + name + "'";
291 details = std::make_unique<std::map<std::string, std::any>>();
292 (*details)["channel"] = name;
293 (*details)["data_size"] = data.getSize();
294 (*details)["message_data"] = data.toHex();
295 });
296
297 while (!callback(data))
298 // Blocking here could cause deadlocks in specific situations.
299 // TODO: Implement a way to handle this better.
300 std::this_thread::sleep_for(std::chrono::milliseconds(10));
301
302 // Log the message consumption.
303 conn.getLogger().trace(
304 [this](std::string &subsystem, std::string &msg,
305 std::unique_ptr<std::map<std::string, std::any>> &details) {
306 subsystem = "cosim_read";
307 msg = "Message from channel '" + name + "' consumed";
308 });
309
310 // Initiate the next read.
311 StartRead(&incomingMessage);
312 }
313
314 /// Disconnect this channel from the server.
315 void disconnect() override {
316 Logger &logger = conn.getLogger();
317 logger.debug("cosim_read", "Disconnecting channel " + name);
318 if (!context)
319 return;
320 context->TryCancel();
321 // Don't delete the context since gRPC still hold a reference to it.
322 // TODO: figure out how to delete it.
324 }
325
326protected:
328 ChannelServer::Stub *rpcClient;
329 /// The channel description as provided by the server.
330 ChannelDesc desc;
331 /// The name of the channel from the manifest.
332 std::string name;
333
334 ClientContext *context;
335 /// Storage location for the incoming message.
336 esi::cosim::Message incomingMessage;
337};
338
339} // namespace
340
341/// Get the channel description for a channel name. Iterate through the list
342/// each time. Since this will only be called a small number of times on a small
343/// list, it's not worth doing anything fancy.
344bool StubContainer::getChannelDesc(const std::string &channelName,
345 ChannelDesc &desc) {
346 ClientContext context;
347 VoidMessage arg;
348 ListOfChannels response;
349 Status s = stub->ListChannels(&context, arg, &response);
350 checkStatus(s, "Failed to list channels");
351 for (const auto &channel : response.channels())
352 if (channel.name() == channelName) {
353 desc = channel;
354 return true;
355 }
356 return false;
357}
358
359namespace {
360class CosimMMIO : public MMIO {
361public:
362 CosimMMIO(CosimAccelerator &conn, Context &ctxt, StubContainer *rpcClient,
363 const HWClientDetails &clients)
364 : MMIO(conn, clients) {
365 // We have to locate the channels ourselves since this service might be used
366 // to retrieve the manifest.
367 ChannelDesc cmdArg, cmdResp;
368 if (!rpcClient->getChannelDesc("__cosim_mmio_read_write.arg", cmdArg) ||
369 !rpcClient->getChannelDesc("__cosim_mmio_read_write.result", cmdResp))
370 throw std::runtime_error("Could not find MMIO channels");
371
372 const esi::Type *i64Type = getType(ctxt, new UIntType(cmdResp.type(), 64));
373 const esi::Type *cmdType =
374 getType(ctxt, new StructType(cmdArg.type(),
375 {{"write", new BitsType("i1", 1)},
376 {"offset", new UIntType("ui32", 32)},
377 {"data", new BitsType("i64", 64)}}));
378
379 // Get ports, create the function, then connect to it.
380 cmdArgPort = std::make_unique<WriteCosimChannelPort>(
381 conn, rpcClient->stub.get(), cmdArg, cmdType,
382 "__cosim_mmio_read_write.arg");
383 cmdRespPort = std::make_unique<ReadCosimChannelPort>(
384 conn, rpcClient->stub.get(), cmdResp, i64Type,
385 "__cosim_mmio_read_write.result");
386 auto *bundleType = new BundleType(
387 "cosimMMIO", {{"arg", BundleType::Direction::To, cmdType},
388 {"result", BundleType::Direction::From, i64Type}});
389 cmdMMIO.reset(FuncService::Function::get(AppID("__cosim_mmio"), bundleType,
390 *cmdArgPort, *cmdRespPort));
391 cmdMMIO->connect();
392 }
393
394#pragma pack(push, 1)
395 struct MMIOCmd {
396 uint64_t data;
397 uint32_t offset;
398 bool write;
399 };
400#pragma pack(pop)
401
402 // Call the read function and wait for a response.
403 uint64_t read(uint32_t addr) const override {
404 MMIOCmd cmd{.offset = addr, .write = false};
405 auto arg = MessageData::from(cmd);
406 std::future<MessageData> result = cmdMMIO->call(arg);
407 result.wait();
408 uint64_t ret = *result.get().as<uint64_t>();
409 conn.getLogger().trace(
410 [addr, ret](std::string &subsystem, std::string &msg,
411 std::unique_ptr<std::map<std::string, std::any>> &details) {
412 subsystem = "cosim_mmio";
413 msg = "MMIO[0x" + toHex(addr) + "] = 0x" + toHex(ret);
414 });
415 return ret;
416 }
417
418 void write(uint32_t addr, uint64_t data) override {
419 conn.getLogger().trace(
420 [addr,
421 data](std::string &subsystem, std::string &msg,
422 std::unique_ptr<std::map<std::string, std::any>> &details) {
423 subsystem = "cosim_mmio";
424 msg = "MMIO[0x" + toHex(addr) + "] <- 0x" + toHex(data);
425 });
426 MMIOCmd cmd{.data = data, .offset = addr, .write = true};
427 auto arg = MessageData::from(cmd);
428 std::future<MessageData> result = cmdMMIO->call(arg);
429 result.wait();
430 }
431
432private:
433 const esi::Type *getType(Context &ctxt, esi::Type *type) {
434 if (auto t = ctxt.getType(type->getID())) {
435 delete type;
436 return *t;
437 }
438 ctxt.registerType(type);
439 return type;
440 }
441 std::unique_ptr<WriteCosimChannelPort> cmdArgPort;
442 std::unique_ptr<ReadCosimChannelPort> cmdRespPort;
443 std::unique_ptr<FuncService::Function> cmdMMIO;
444};
445
446#pragma pack(push, 1)
447struct HostMemReadReq {
448 uint8_t tag;
449 uint32_t length;
450 uint64_t address;
451};
452
453struct HostMemReadResp {
454 uint64_t data;
455 uint8_t tag;
456};
457
458struct HostMemWriteReq {
459 uint8_t valid_bytes;
460 uint64_t data;
461 uint8_t tag;
462 uint64_t address;
463};
464
465using HostMemWriteResp = uint8_t;
466#pragma pack(pop)
467
468class CosimHostMem : public HostMem {
469public:
470 CosimHostMem(AcceleratorConnection &acc, Context &ctxt,
471 StubContainer *rpcClient)
472 : HostMem(acc), acc(acc), ctxt(ctxt), rpcClient(rpcClient) {}
473
474 void start() override {
475 // We have to locate the channels ourselves since this service might be used
476 // to retrieve the manifest.
477
478 if (writeRespPort)
479 return;
480
481 // TODO: The types here are WRONG. They need to be wrapped in Channels! Fix
482 // this in a subsequent PR.
483
484 // Setup the read side callback.
485 ChannelDesc readArg, readResp;
486 if (!rpcClient->getChannelDesc("__cosim_hostmem_read_req.data", readArg) ||
487 !rpcClient->getChannelDesc("__cosim_hostmem_read_resp.data", readResp))
488 throw std::runtime_error("Could not find HostMem read channels");
489
490 const esi::Type *readRespType =
491 getType(ctxt, new StructType(readResp.type(),
492 {{"tag", new UIntType("ui8", 8)},
493 {"data", new BitsType("i64", 64)}}));
494 const esi::Type *readReqType =
495 getType(ctxt, new StructType(readArg.type(),
496 {{"address", new UIntType("ui64", 64)},
497 {"length", new UIntType("ui32", 32)},
498 {"tag", new UIntType("ui8", 8)}}));
499
500 // Get ports. Unfortunately, we can't model this as a callback since there
501 // will sometimes be multiple responses per request.
502 readRespPort = std::make_unique<WriteCosimChannelPort>(
503 conn, rpcClient->stub.get(), readResp, readRespType,
504 "__cosim_hostmem_read_resp.data");
505 readReqPort = std::make_unique<ReadCosimChannelPort>(
506 conn, rpcClient->stub.get(), readArg, readReqType,
507 "__cosim_hostmem_read_req.data");
508 readReqPort->connect(
509 [this](const MessageData &req) { return serviceRead(req); });
510
511 // Setup the write side callback.
512 ChannelDesc writeArg, writeResp;
513 if (!rpcClient->getChannelDesc("__cosim_hostmem_write.arg", writeArg) ||
514 !rpcClient->getChannelDesc("__cosim_hostmem_write.result", writeResp))
515 throw std::runtime_error("Could not find HostMem write channels");
516
517 const esi::Type *writeRespType =
518 getType(ctxt, new UIntType(writeResp.type(), 8));
519 const esi::Type *writeReqType =
520 getType(ctxt, new StructType(writeArg.type(),
521 {{"address", new UIntType("ui64", 64)},
522 {"tag", new UIntType("ui8", 8)},
523 {"data", new BitsType("i64", 64)}}));
524
525 // Get ports, create the function, then connect to it.
526 writeRespPort = std::make_unique<WriteCosimChannelPort>(
527 conn, rpcClient->stub.get(), writeResp, writeRespType,
528 "__cosim_hostmem_write.result");
529 writeReqPort = std::make_unique<ReadCosimChannelPort>(
530 conn, rpcClient->stub.get(), writeArg, writeReqType,
531 "__cosim_hostmem_write.arg");
532 auto *bundleType = new BundleType(
533 "cosimHostMem",
534 {{"arg", BundleType::Direction::To, writeReqType},
535 {"result", BundleType::Direction::From, writeRespType}});
536 write.reset(CallService::Callback::get(acc, AppID("__cosim_hostmem_write"),
537 bundleType, *writeRespPort,
538 *writeReqPort));
539 write->connect([this](const MessageData &req) { return serviceWrite(req); },
540 true);
541 }
542
543 // Service the read request as a callback. Simply reads the data from the
544 // location specified. TODO: check that the memory has been mapped.
545 bool serviceRead(const MessageData &reqBytes) {
546 const HostMemReadReq *req = reqBytes.as<HostMemReadReq>();
547 acc.getLogger().trace(
548 [&](std::string &subsystem, std::string &msg,
549 std::unique_ptr<std::map<std::string, std::any>> &details) {
550 subsystem = "hostmem";
551 msg = "Read request: addr=0x" + toHex(req->address) +
552 " len=" + std::to_string(req->length) +
553 " tag=" + std::to_string(req->tag);
554 });
555 // Send one response per 8 bytes.
556 uint64_t *dataPtr = reinterpret_cast<uint64_t *>(req->address);
557 for (uint32_t i = 0, e = (req->length + 7) / 8; i < e; ++i) {
558 HostMemReadResp resp{.data = dataPtr[i], .tag = req->tag};
559 acc.getLogger().trace(
560 [&](std::string &subsystem, std::string &msg,
561 std::unique_ptr<std::map<std::string, std::any>> &details) {
562 subsystem = "HostMem";
563 msg = "Read result: data=0x" + toHex(resp.data) +
564 " tag=" + std::to_string(resp.tag);
565 });
566 readRespPort->write(MessageData::from(resp));
567 }
568 return true;
569 }
570
571 // Service a write request as a callback. Simply write the data to the
572 // location specified. TODO: check that the memory has been mapped.
573 MessageData serviceWrite(const MessageData &reqBytes) {
574 const HostMemWriteReq *req = reqBytes.as<HostMemWriteReq>();
575 acc.getLogger().trace(
576 [&](std::string &subsystem, std::string &msg,
577 std::unique_ptr<std::map<std::string, std::any>> &details) {
578 subsystem = "hostmem";
579 msg = "Write request: addr=0x" + toHex(req->address) + " data=0x" +
580 toHex(req->data) +
581 " valid_bytes=" + std::to_string(req->valid_bytes) +
582 " tag=" + std::to_string(req->tag);
583 });
584 uint8_t *dataPtr = reinterpret_cast<uint8_t *>(req->address);
585 for (uint8_t i = 0; i < req->valid_bytes; ++i)
586 dataPtr[i] = (req->data >> (i * 8)) & 0xFF;
587 HostMemWriteResp resp = req->tag;
588 return MessageData::from(resp);
589 }
590
591 struct CosimHostMemRegion : public HostMemRegion {
592 CosimHostMemRegion(std::size_t size) {
593 ptr = malloc(size);
594 memset(ptr, 0xFF, size);
595 this->size = size;
596 }
597 virtual ~CosimHostMemRegion() { free(ptr); }
598 virtual void *getPtr() const override { return ptr; }
599 virtual std::size_t getSize() const override { return size; }
600
601 private:
602 void *ptr;
603 std::size_t size;
604 };
605
606 virtual std::unique_ptr<HostMemRegion>
607 allocate(std::size_t size, HostMem::Options opts) const override {
608 auto ret = std::unique_ptr<HostMemRegion>(new CosimHostMemRegion(size));
609 acc.getLogger().debug(
610 [&](std::string &subsystem, std::string &msg,
611 std::unique_ptr<std::map<std::string, std::any>> &details) {
612 subsystem = "HostMem";
613 msg = "Allocated host memory region at 0x" + toHex(ret->getPtr()) +
614 " of size " + std::to_string(size);
615 });
616 return ret;
617 }
618 virtual bool mapMemory(void *ptr, std::size_t size,
619 HostMem::Options opts) const override {
620 return true;
621 }
622 virtual void unmapMemory(void *ptr) const override {}
623
624private:
625 const esi::Type *getType(Context &ctxt, esi::Type *type) {
626 if (auto t = ctxt.getType(type->getID())) {
627 delete type;
628 return *t;
629 }
630 ctxt.registerType(type);
631 return type;
632 }
634 Context &ctxt;
635 StubContainer *rpcClient;
636 std::unique_ptr<WriteCosimChannelPort> readRespPort;
637 std::unique_ptr<ReadCosimChannelPort> readReqPort;
638 std::unique_ptr<CallService::Callback> read;
639 std::unique_ptr<WriteCosimChannelPort> writeRespPort;
640 std::unique_ptr<ReadCosimChannelPort> writeReqPort;
641 std::unique_ptr<CallService::Callback> write;
642};
643} // namespace
644
645namespace esi::backends::cosim {
646/// Implement the magic cosim channel communication.
647class CosimEngine : public Engine {
648public:
650 const ServiceImplDetails &details, const HWClientDetails &clients)
651 : Engine(conn), conn(conn) {
652 // Compute our parents idPath path.
653 AppIDPath prefix = std::move(idPath);
654 if (prefix.size() > 0)
655 prefix.pop_back();
656
657 for (auto client : clients) {
658 AppIDPath fullClientPath = prefix + client.relPath;
659 std::map<std::string, std::string> channelAssignments;
660 for (auto assignment : client.channelAssignments)
661 if (assignment.second.type == "cosim")
662 channelAssignments[assignment.first] = std::any_cast<std::string>(
663 assignment.second.implOptions.at("name"));
664 clientChannelAssignments[fullClientPath] = std::move(channelAssignments);
665 }
666 }
667
668 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
669 const std::string &channelName,
671 const Type *type) override;
672
673private:
675 std::map<AppIDPath, std::map<std::string, std::string>>
677};
678} // namespace esi::backends::cosim
679
680std::unique_ptr<ChannelPort>
681CosimEngine::createPort(AppIDPath idPath, const std::string &channelName,
682 BundleType::Direction dir, const Type *type) {
683
684 // Find the client details for the port at 'fullPath'.
685 auto f = clientChannelAssignments.find(idPath);
686 if (f == clientChannelAssignments.end())
687 throw std::runtime_error("Could not find port for '" + idPath.toStr() +
688 "." + channelName + "'");
689 const std::map<std::string, std::string> &channelAssignments = f->second;
690 auto cosimChannelNameIter = channelAssignments.find(channelName);
691 if (cosimChannelNameIter == channelAssignments.end())
692 throw std::runtime_error("Could not find channel '" + idPath.toStr() + "." +
693 channelName + "' in cosimulation");
694
695 // Get the endpoint, which may or may not exist. Construct the port.
696 // Everything is validated when the client calls 'connect()' on the port.
697 ChannelDesc chDesc;
698 if (!conn.rpcClient->getChannelDesc(cosimChannelNameIter->second, chDesc))
699 throw std::runtime_error("Could not find channel '" + idPath.toStr() + "." +
700 channelName + "' in cosimulation");
701
702 std::unique_ptr<ChannelPort> port;
703 std::string fullChannelName = idPath.toStr() + "." + channelName;
704 if (BundlePort::isWrite(dir))
705 port = std::make_unique<WriteCosimChannelPort>(
706 conn, conn.rpcClient->stub.get(), chDesc, type, fullChannelName);
707 else
708 port = std::make_unique<ReadCosimChannelPort>(
709 conn, conn.rpcClient->stub.get(), chDesc, type, fullChannelName);
710 return port;
711}
712
713void CosimAccelerator::createEngine(const std::string &engineTypeName,
714 AppIDPath idPath,
715 const ServiceImplDetails &details,
716 const HWClientDetails &clients) {
717
718 std::unique_ptr<Engine> engine = nullptr;
719 if (engineTypeName == "cosim")
720 engine = std::make_unique<CosimEngine>(*this, idPath, details, clients);
721 else
722 engine = ::esi::registry::createEngine(*this, engineTypeName, idPath,
723 details, clients);
724 registerEngine(idPath, std::move(engine), clients);
725}
727 AppIDPath idPath, std::string implName,
728 const ServiceImplDetails &details,
729 const HWClientDetails &clients) {
730 if (svcType == typeid(services::MMIO)) {
731 return new CosimMMIO(*this, getCtxt(), rpcClient, clients);
732 } else if (svcType == typeid(services::HostMem)) {
733 return new CosimHostMem(*this, getCtxt(), rpcClient);
734 } else if (svcType == typeid(SysInfo)) {
735 switch (manifestMethod) {
737 return new CosimSysInfo(*this, rpcClient->stub.get());
739 return new MMIOSysInfo(getService<services::MMIO>());
740 }
741 }
742 return nullptr;
743}
744
748
#define REGISTER_ACCELERATOR(Name, TAccelerator)
assert(baseType &&"element must be base type")
static void checkStatus(Status s, const std::string &msg)
Definition Cosim.cpp:45
Abstract class representing a connection to an accelerator.
Definition Accelerator.h:79
virtual void disconnect()
Disconnect from the accelerator cleanly.
Context & getCtxt() const
Definition Accelerator.h:83
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...
Logger & getLogger() const
Definition Accelerator.h:84
std::string toStr() const
Definition Manifest.cpp:731
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:230
Bundles represent a collection of channels.
Definition Types.h:44
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Definition Context.h:31
Engines implement the actual channel communication between the host and the accelerator.
Definition Engines.h:42
void debug(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report a debug message.
Definition Logging.h:83
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:103
const T * as() const
Cast to a type.
Definition Common.h:119
static MessageData from(T &t)
Cast from a type to its raw bytes.
Definition Common.h:129
A ChannelPort which reads data from the accelerator.
Definition Ports.h:124
virtual void disconnect() override
Definition Ports.h:129
Structs are an ordered collection of fields, each with a name and a type.
Definition Types.h:137
Root class of the ESI type system.
Definition Types.h:27
ID getID() const
Definition Types.h:33
Unsigned integer.
Definition Types.h:131
A ChannelPort which sends data to the accelerator.
Definition Ports.h:77
Connect to an ESI simulation.
Definition Cosim.h:38
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:713
void setManifestMethod(ManifestMethod method)
Definition Cosim.cpp:745
static std::unique_ptr< AcceleratorConnection > connect(Context &, std::string connectionString)
Parse the connection std::string and instantiate the accelerator.
Definition Cosim.cpp:69
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:726
CosimAccelerator(Context &, std::string hostname, uint16_t port)
Construct and connect to a cosim server.
Definition Cosim.cpp:121
std::set< std::unique_ptr< ChannelPort > > channels
Definition Cosim.h:76
Implement the magic cosim channel communication.
Definition Cosim.cpp:647
CosimEngine(CosimAccelerator &conn, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Definition Cosim.cpp:649
std::map< AppIDPath, std::map< std::string, std::string > > clientChannelAssignments
Definition Cosim.cpp:676
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:681
static Callback * get(AcceleratorConnection &acc, AppID id, BundleType *type, WriteChannelPort &result, ReadChannelPort &arg)
Definition Services.cpp:237
static Function * get(AppID id, BundleType *type, WriteChannelPort &arg, ReadChannelPort &result)
Definition Services.cpp:194
Implement the SysInfo API for a standard MMIO protocol.
Definition Services.h:184
Parent class of all APIs modeled as 'services'.
Definition Services.h:46
const std::type_info & Type
Definition Services.h:48
Information about the Accelerator system.
Definition Services.h:100
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:463
Definition esi.py:1
std::map< std::string, std::any > ServiceImplDetails
Definition Common.h:98
std::string toHex(void *val)
Definition Common.cpp:37
std::vector< HWClientDetail > HWClientDetails
Definition Common.h:97
Hack around C++ not having a way to forward declare a nested class.
Definition Cosim.cpp:53
std::unique_ptr< ChannelServer::Stub > stub
Definition Cosim.cpp:56
bool getChannelDesc(const std::string &channelName, esi::cosim::ChannelDesc &desc)
Get the type ID for a channel name.
Definition Cosim.cpp:344
StubContainer(std::unique_ptr< ChannelServer::Stub > stub)
Definition Cosim.cpp:54
Options for allocating host memory.
Definition Services.h:226