CIRCT 24.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 <array>
24#include <cstring>
25#include <format>
26#include <fstream>
27#include <iostream>
28#include <set>
29
30using namespace esi;
31using namespace esi::services;
32using namespace esi::backends::cosim;
33
34namespace {
35
36//===----------------------------------------------------------------------===//
37// WriteCosimChannelPort
38//===----------------------------------------------------------------------===//
39
40/// Cosim client implementation of a write channel port.
41class WriteCosimChannelPort : public WriteChannelPort {
42public:
43 WriteCosimChannelPort(AcceleratorConnection &conn, RpcClient &client,
44 const RpcClient::ChannelDesc &desc, const Type *type,
45 std::string name)
46 : WriteChannelPort(type), conn(conn), client(client), desc(desc),
47 name(std::move(name)) {}
48 ~WriteCosimChannelPort() = default;
49
50 void connectImpl(const ChannelPort::ConnectOptions &options) override {
51 if (desc.dir != RpcClient::ChannelDirection::ToServer)
52 throw std::runtime_error("Channel '" + name +
53 "' is not a to server channel");
54 }
55
56protected:
57 void writeImpl(const MessageData &data) override {
58 auto frames = getMessageFrames(data);
59 for (const auto &frame : frames) {
60 conn.getLogger().trace(
61 [this,
62 &data](std::string &subsystem, std::string &msg,
63 std::unique_ptr<std::map<std::string, std::any>> &details) {
64 subsystem = "cosim_write";
65 msg = "Writing message to channel '" + name + "'";
66 details = std::make_unique<std::map<std::string, std::any>>();
67 (*details)["channel"] = name;
68 (*details)["data_size"] = data.getSize();
69 (*details)["message_data"] = data.toHex();
70 });
71
72 client.writeToServer(name, frame);
73 }
74 }
75 bool tryWriteImpl(const MessageData &data) override {
76 // For simplicity, this implementation does not support backpressure and
77 // always returns true. A more complex implementation could track pending
78 // messages and return false if there are too many.
79 writeImpl(data);
80 return true;
81 }
82
83private:
85 RpcClient &client;
87 std::string name;
88};
89
90//===----------------------------------------------------------------------===//
91// ReadCosimChannelPort
92//===----------------------------------------------------------------------===//
93
94/// Cosim client implementation of a read channel port. The wire transport
95/// (see `CosimRpc`) delivers messages via callback, so this class just
96/// forwards them to the registered `ReadChannelPort` consumer.
97class ReadCosimChannelPort : public ReadChannelPort {
98public:
99 ReadCosimChannelPort(AcceleratorConnection &conn, RpcClient &client,
100 const RpcClient::ChannelDesc &desc, const Type *type,
101 std::string name)
102 : ReadChannelPort(type), conn(conn), client(client), desc(desc),
103 name(std::move(name)) {}
104
105 ~ReadCosimChannelPort() = default;
106
107 void connectImpl(const ChannelPort::ConnectOptions &options) override {
108 if (desc.dir != RpcClient::ChannelDirection::ToClient)
109 throw std::runtime_error("Channel '" + name +
110 "' is not a to client channel");
111
112 // Connect to the channel and set up callback.
113 connection = client.connectClientReceiver(
114 name, [this](std::unique_ptr<SegmentedMessageData> &data) {
115 // Add trace logging for the received message.
116 conn.getLogger().trace(
117 [this, &data](
118 std::string &subsystem, std::string &msg,
119 std::unique_ptr<std::map<std::string, std::any>> &details) {
120 subsystem = "cosim_read";
121 msg = "Received message from channel '" + name + "'";
122 details = std::make_unique<std::map<std::string, std::any>>();
123 MessageData flat = data->toMessageData();
124 (*details)["channel"] = name;
125 (*details)["data_size"] = flat.getSize();
126 (*details)["message_data"] = flat.toHex();
127 });
128
129 bool consumed = invokeCallback(data);
130
131 if (consumed) {
132 // Log the message consumption.
133 conn.getLogger().trace(
134 [this](
135 std::string &subsystem, std::string &msg,
136 std::unique_ptr<std::map<std::string, std::any>> &details) {
137 subsystem = "cosim_read";
138 msg = "Message from channel '" + name + "' consumed";
139 });
140 }
141
142 return consumed;
143 });
144 }
145
146 void disconnect() override {
147 conn.getLogger().debug("cosim_read", "Disconnecting channel " + name);
148 if (connection) {
149 connection->disconnect();
150 connection.reset();
151 }
153 }
154
155private:
157 RpcClient &client;
159 std::string name;
160 std::unique_ptr<RpcClient::ReadChannelConnection> connection;
161};
162
163} // anonymous namespace
164
165//===----------------------------------------------------------------------===//
166// CosimAccelerator
167//===----------------------------------------------------------------------===//
168
169/// Parse the connection std::string and instantiate the accelerator. Support
170/// the traditional 'host:port' syntax and a path to 'cosim.cfg' which is output
171/// by the cosimulation when it starts (which is useful when it chooses its own
172/// port).
173std::unique_ptr<AcceleratorConnection>
174CosimAccelerator::connect(Context &ctxt, std::string connectionString) {
175 std::string portStr;
176 std::string host = "localhost";
177
178 size_t colon;
179 if ((colon = connectionString.find(':')) != std::string::npos) {
180 portStr = connectionString.substr(colon + 1);
181 host = connectionString.substr(0, colon);
182 } else if (connectionString.ends_with("cosim.cfg")) {
183 std::ifstream cfg(connectionString);
184 std::string line, key, value;
185
186 while (getline(cfg, line))
187 if ((colon = line.find(":")) != std::string::npos) {
188 key = line.substr(0, colon);
189 value = line.substr(colon + 1);
190 if (key == "port")
191 portStr = value;
192 else if (key == "host")
193 host = value;
194 }
195
196 if (portStr.size() == 0)
197 throw std::runtime_error("port line not found in file");
198 } else if (connectionString == "env") {
199 char *hostEnv = getenv("ESI_COSIM_HOST");
200 if (hostEnv)
201 host = hostEnv;
202 else
203 host = "localhost";
204 char *portEnv = getenv("ESI_COSIM_PORT");
205 if (portEnv)
206 portStr = portEnv;
207 else
208 throw std::runtime_error("ESI_COSIM_PORT environment variable not set");
209 } else {
210 throw std::runtime_error("Invalid connection std::string '" +
211 connectionString + "'");
212 }
213 uint16_t port = stoul(portStr);
214 auto conn = make_unique<CosimAccelerator>(ctxt, host, port);
215
216 // Using the MMIO manifest method is really only for internal debugging, so it
217 // doesn't need to be part of the connection string.
218 char *manifestMethod = getenv("ESI_COSIM_MANIFEST_MMIO");
219 if (manifestMethod != nullptr)
220 conn->setManifestMethod(ManifestMethod::MMIO);
221
222 return conn;
223}
224
225/// Construct and connect to a cosim server.
226CosimAccelerator::CosimAccelerator(Context &ctxt, std::string hostname,
227 uint16_t port)
228 : AcceleratorConnection(ctxt) {
229 // Connect to the simulation.
230 rpcClient = std::make_unique<RpcClient>(getLogger(), hostname, port);
231}
237
238namespace {
239class CosimSysInfo : public SysInfo {
240#pragma pack(push, 1)
241 struct CycleInfo {
242 uint64_t freq;
243 uint64_t cycle;
244 };
245#pragma pack(pop)
246
247public:
248 CosimSysInfo(CosimAccelerator &conn, RpcClient *rpcClient)
249 : SysInfo(conn), rpcClient(rpcClient) {
250 // This is an optional interface; if the channels aren't present, we simply
251 // report no cycle/frequency information.
252 RpcClient::ChannelDesc argDesc, resultDesc;
253 if (!rpcClient->getChannelDesc("__cosim_cycle_count.arg", argDesc) ||
254 !rpcClient->getChannelDesc("__cosim_cycle_count.result", resultDesc))
255 return;
256
257 Context &ctxt = conn.getCtxt();
258 const esi::Type *i1Type = getType(ctxt, new BitsType("i1", 1));
259 const esi::Type *i64Type = getType(ctxt, new BitsType("i64", 64));
260 const esi::Type *resultType =
261 getType(ctxt, new StructType(resultDesc.type,
262 {{"cycle", i64Type}, {"freq", i64Type}}));
263
264 reqPort = std::make_unique<WriteCosimChannelPort>(
265 conn, *rpcClient, argDesc, i1Type, "__cosim_cycle_count.arg");
266 respPort = std::make_unique<ReadCosimChannelPort>(
267 conn, *rpcClient, resultDesc, resultType, "__cosim_cycle_count.result");
268 auto *bundleType =
269 new BundleType("cosimCycleCount",
270 {{"arg", BundleType::Direction::To, i1Type},
271 {"result", BundleType::Direction::From, resultType}});
272 func.reset(FuncService::Function::get(AppID("__cosim_cycle_count"),
273 bundleType, *reqPort, *respPort));
274 func->connect();
275 }
276
277 uint32_t getEsiVersion() const override { return rpcClient->getEsiVersion(); }
278 std::optional<uint64_t> getCycleCount() const override {
279 if (!func)
280 return std::nullopt;
281 return getCycleInfo().cycle;
282 }
283 std::optional<uint64_t> getCoreClockFrequency() const override {
284 if (!func)
285 return std::nullopt;
286 return getCycleInfo().freq;
287 }
288
289 std::vector<uint8_t> getCompressedManifest() const override {
290 return rpcClient->getCompressedManifest();
291 }
292
293private:
294 const esi::Type *getType(Context &ctxt, esi::Type *type) {
295 if (auto t = ctxt.getType(type->getID())) {
296 delete type;
297 return *t;
298 }
299 ctxt.registerType(type);
300 return type;
301 }
302
303 RpcClient *rpcClient;
304 std::unique_ptr<WriteCosimChannelPort> reqPort;
305 std::unique_ptr<ReadCosimChannelPort> respPort;
306 std::unique_ptr<FuncService::Function> func;
307
308 CycleInfo getCycleInfo() const {
309 MessageData arg({1}); // 1-bit trigger message
310 std::future<MessageData> result = func->call(arg);
311 result.wait();
312 MessageData respMsg = result.get();
313 return *respMsg.as<CycleInfo>();
314 }
315};
316} // namespace
317
318namespace {
319class CosimMMIO : public MMIO {
320public:
321 CosimMMIO(CosimAccelerator &conn, Context &ctxt, const AppIDPath &idPath,
322 RpcClient *rpcClient, const HWClientDetails &clients)
323 : MMIO(conn, idPath, clients) {
324 // We have to locate the channels ourselves since this service might be used
325 // to retrieve the manifest.
326 RpcClient::ChannelDesc cmdArg, cmdResp;
327 if (!rpcClient->getChannelDesc("__cosim_mmio_read_write.arg", cmdArg) ||
328 !rpcClient->getChannelDesc("__cosim_mmio_read_write.result", cmdResp))
329 throw std::runtime_error("Could not find MMIO channels");
330
331 const esi::Type *i64Type = getType(ctxt, new UIntType(cmdResp.type, 64));
332 const esi::Type *cmdType = getType(
333 ctxt, new StructType(cmdArg.type, {{"write", new BitsType("i1", 1)},
334 {"offset", new UIntType("ui32", 32)},
335 {"data", new BitsType("i64", 64)}}));
336
337 // Get ports, create the function, then connect to it.
338 cmdArgPort = std::make_unique<WriteCosimChannelPort>(
339 conn, *rpcClient, cmdArg, cmdType, "__cosim_mmio_read_write.arg");
340 cmdRespPort = std::make_unique<ReadCosimChannelPort>(
341 conn, *rpcClient, cmdResp, i64Type, "__cosim_mmio_read_write.result");
342 auto *bundleType = new BundleType(
343 "cosimMMIO", {{"arg", BundleType::Direction::To, cmdType},
344 {"result", BundleType::Direction::From, i64Type}});
345 cmdMMIO.reset(FuncService::Function::get(AppID("__cosim_mmio"), bundleType,
346 *cmdArgPort, *cmdRespPort));
347 cmdMMIO->connect();
348 }
349
350#pragma pack(push, 1)
351 struct MMIOCmd {
352 uint64_t data;
353 uint32_t offset;
354 bool write;
355 };
356#pragma pack(pop)
357
358 // Call the read function and wait for a response.
359 uint64_t read(uint32_t addr) const override {
360 MMIOCmd cmd{.data = 0, .offset = addr, .write = false};
361 auto arg = MessageData::from(cmd);
362 std::lock_guard<std::mutex> g(mmioCmdLock);
363 std::future<MessageData> result = cmdMMIO->call(arg);
364 result.wait();
365 uint64_t ret = *result.get().as<uint64_t>();
366 conn.getLogger().trace(
367 [addr, ret](std::string &subsystem, std::string &msg,
368 std::unique_ptr<std::map<std::string, std::any>> &details) {
369 subsystem = "cosim_mmio";
370 msg = "MMIO[0x" + toHex(addr) + "] = 0x" + toHex(ret);
371 });
372 return ret;
373 }
374
375 void write(uint32_t addr, uint64_t data) override {
376 conn.getLogger().trace(
377 [addr,
378 data](std::string &subsystem, std::string &msg,
379 std::unique_ptr<std::map<std::string, std::any>> &details) {
380 subsystem = "cosim_mmio";
381 msg = "MMIO[0x" + toHex(addr) + "] <- 0x" + toHex(data);
382 });
383 MMIOCmd cmd{.data = data, .offset = addr, .write = true};
384 auto arg = MessageData::from(cmd);
385 std::lock_guard<std::mutex> g(mmioCmdLock);
386 std::future<MessageData> result = cmdMMIO->call(arg);
387 result.wait();
388 }
389
390private:
391 const esi::Type *getType(Context &ctxt, esi::Type *type) {
392 if (auto t = ctxt.getType(type->getID())) {
393 delete type;
394 return *t;
395 }
396 ctxt.registerType(type);
397 return type;
398 }
399 std::unique_ptr<WriteCosimChannelPort> cmdArgPort;
400 std::unique_ptr<ReadCosimChannelPort> cmdRespPort;
401 std::unique_ptr<FuncService::Function> cmdMMIO;
402};
403
404#pragma pack(push, 1)
405struct HostMemReadReq {
406 uint8_t tag;
407 uint32_t length;
408 uint64_t address;
409};
410
411using HostMemWriteResp = uint8_t;
412#pragma pack(pop)
413
414// ESI lowers a parallel-window frame MSB-first (the first-declared field
415// occupies the highest bits) into a little-endian byte buffer; array elements
416// are packed least-index-first (element[0] in the low bits). The sub-byte
417// `last` / `data_size` fields make these frames bit-packed and not
418// byte-aligned, so -- to stay ABI-portable across toolchains (bit-field and
419// sub-byte `#pragma pack` layout is not guaranteed under MSVC) -- each frame is
420// a plain byte array whose bits are assembled/extracted explicitly with these
421// helpers. `bitOff` counts from the LSB (bit 0 of byte 0).
422void putBits(uint8_t *buf, size_t bitOff, size_t width, uint64_t val) {
423 for (size_t i = 0; i < width; ++i)
424 if ((val >> i) & 1ULL)
425 buf[(bitOff + i) >> 3] |= static_cast<uint8_t>(1u << ((bitOff + i) & 7));
426}
427uint64_t getBits(const uint8_t *buf, size_t bitOff, size_t width) {
428 uint64_t val = 0;
429 for (size_t i = 0; i < width; ++i)
430 if (buf[(bitOff + i) >> 3] & (1u << ((bitOff + i) & 7)))
431 val |= (1ULL << i);
432 return val;
433}
434
435// Read-response frame: one bus beat of a burst read. The client-facing read
436// response is a parallel window over struct{tag, data: list<i<HostMemWidth>>};
437// with one word per beat (num_items=1) the lowered frame is
438// struct{tag: ui8, data: i64, last: i1} (73 bits)
439// packed MSB-first: tag | data | last. `last` marks the final beat of a burst.
440class HostMemReadRespFrame {
441public:
442 static constexpr size_t kMessageBits = 8 + 64 + 1; // 73
443 static constexpr size_t kMessageBytes = (kMessageBits + 7) / 8; // 10
444
445 HostMemReadRespFrame(uint8_t tag, uint64_t data, bool last) {
446 putBits(bytes.data(), kLastOff, kLastW, last ? 1 : 0);
447 putBits(bytes.data(), kDataOff, kDataW, data);
448 putBits(bytes.data(), kTagOff, kTagW, tag);
449 }
450
451 uint8_t tag() const { return getBits(bytes.data(), kTagOff, kTagW); }
452 uint64_t data() const { return getBits(bytes.data(), kDataOff, kDataW); }
453 bool last() const { return getBits(bytes.data(), kLastOff, kLastW) != 0; }
454
455 MessageData toMessage() const {
456 return MessageData(bytes.data(), bytes.size());
457 }
458
459private:
460 // MSB-first field order => reverse (LSB-most) bit offsets.
461 static constexpr size_t kLastW = 1, kLastOff = 0; // bit 0
462 static constexpr size_t kDataW = 64, kDataOff = kLastOff + kLastW; // bit 1
463 static constexpr size_t kTagW = 8, kTagOff = kDataOff + kDataW; // bit 65
464 std::array<uint8_t, kMessageBytes> bytes{};
465};
466
467// Write-request frame: one bus beat of a burst write. The upstream write
468// request is a parallel window over struct{address, tag, data: list<i8>} with
469// num_items = the host-memory bus width in bytes (cosim HostMemWidth=64 => 8).
470// The lowered frame is
471// struct{address: ui64, tag: ui8, data: i8[8], data_size: ui3, last: i1}
472// (140 bits) packed MSB-first: address | tag | data[8] | data_size | last, with
473// data element[i] in ascending bits. `data_size` holds (valid_bytes - 1);
474// `last` marks the final beat. Received from the device, so construct from raw
475// bytes plus read accessors.
476class HostMemWriteReqFrame {
477public:
478 static constexpr size_t kNumItems = 8; // bus width in bytes (cosim: 64 / 8)
479 static constexpr size_t kMessageBits = 64 + 8 + kNumItems * 8 + 3 + 1; // 140
480 static constexpr size_t kMessageBytes = (kMessageBits + 7) / 8; // 18
481
482 explicit HostMemWriteReqFrame(const uint8_t *raw) {
483 std::memcpy(bytes.data(), raw, kMessageBytes);
484 }
485
486 uint64_t address() const { return getBits(bytes.data(), kAddrOff, kAddrW); }
487 uint8_t tag() const { return getBits(bytes.data(), kTagOff, kTagW); }
488 uint8_t dataByte(size_t i) const {
489 return getBits(bytes.data(), kDataOff + 8 * i, 8);
490 }
491 // Number of valid data bytes in this beat (data_size holds valid_bytes - 1).
492 unsigned validBytes() const {
493 return static_cast<unsigned>(getBits(bytes.data(), kSizeOff, kSizeW)) + 1;
494 }
495 bool last() const { return getBits(bytes.data(), kLastOff, kLastW) != 0; }
496
497private:
498 static constexpr size_t kLastW = 1, kLastOff = 0;
499 static constexpr size_t kSizeW = 3, kSizeOff = kLastOff + kLastW;
500 static constexpr size_t kDataW = kNumItems * 8, kDataOff = kSizeOff + kSizeW;
501 static constexpr size_t kTagW = 8, kTagOff = kDataOff + kDataW;
502 static constexpr size_t kAddrW = 64, kAddrOff = kTagOff + kTagW;
503 std::array<uint8_t, kMessageBytes> bytes{};
504};
505
506// PCIe caps a single memory read request at the Max_Read_Request_Size, whose
507// largest encoding (PCIe Gen 4 and earlier) is 4096 bytes, but root ports often
508// negotiate a smaller limit. Model a conservative 64-double-word (256-byte) cap
509// here: a read request larger than this is a protocol violation.
510static constexpr uint32_t kPcieMaxReadRequestBytes = 64 * 4;
511
512class CosimHostMem : public HostMem {
513public:
514 CosimHostMem(AcceleratorConnection &acc, Context &ctxt, RpcClient *rpcClient)
515 : HostMem(acc), acc(acc), ctxt(ctxt), rpcClient(rpcClient) {}
516
517 void start() override {
518 // We have to locate the channels ourselves since this service might be used
519 // to retrieve the manifest.
520
521 if (writeRespPort)
522 return;
523
524 // TODO: The types here are WRONG. They need to be wrapped in Channels! Fix
525 // this in a subsequent PR.
526
527 // Setup the read side callback.
528 RpcClient::ChannelDesc readArg, readResp;
529 if (!rpcClient->getChannelDesc("__cosim_hostmem_read_req.data", readArg) ||
530 !rpcClient->getChannelDesc("__cosim_hostmem_read_resp.data", readResp))
531 throw std::runtime_error("Could not find HostMem read channels");
532
533 const esi::Type *readRespType = getType(
534 ctxt, new StructType(readResp.type, {{"tag", new UIntType("ui8", 8)},
535 {"data", new BitsType("i64", 64)},
536 {"last", new BitsType("i1", 1)}}));
537 const esi::Type *readReqType =
538 getType(ctxt, new StructType(readArg.type,
539 {{"address", new UIntType("ui64", 64)},
540 {"length", new UIntType("ui32", 32)},
541 {"tag", new UIntType("ui8", 8)}}));
542
543 // Get ports. Unfortunately, we can't model this as a callback since there
544 // will sometimes be multiple responses per request.
545 readRespPort = std::make_unique<WriteCosimChannelPort>(
546 conn, *rpcClient, readResp, readRespType,
547 "__cosim_hostmem_read_resp.data");
548 readReqPort = std::make_unique<ReadCosimChannelPort>(
549 conn, *rpcClient, readArg, readReqType,
550 "__cosim_hostmem_read_req.data");
551 readReqPort->connect(
552 [this](const MessageData &req) { return serviceRead(req); });
553
554 // Setup the write side callback.
555 RpcClient::ChannelDesc writeArg, writeResp;
556 if (!rpcClient->getChannelDesc("__cosim_hostmem_write.arg", writeArg) ||
557 !rpcClient->getChannelDesc("__cosim_hostmem_write.result", writeResp))
558 throw std::runtime_error("Could not find HostMem write channels");
559
560 const esi::Type *writeRespType =
561 getType(ctxt, new UIntType(writeResp.type, 8));
562 const esi::Type *writeReqType =
563 getType(ctxt, new StructType(writeArg.type,
564 {{"address", new UIntType("ui64", 64)},
565 {"tag", new UIntType("ui8", 8)},
566 {"data", new BitsType("i64", 64)},
567 {"data_size", new UIntType("ui3", 3)},
568 {"last", new BitsType("i1", 1)}}));
569
570 // Get ports, create the function, then connect to it.
571 writeRespPort = std::make_unique<WriteCosimChannelPort>(
572 conn, *rpcClient, writeResp, writeRespType,
573 "__cosim_hostmem_write.result");
574 writeReqPort = std::make_unique<ReadCosimChannelPort>(
575 conn, *rpcClient, writeArg, writeReqType, "__cosim_hostmem_write.arg");
576 auto *bundleType = new BundleType(
577 "cosimHostMem",
578 {{"arg", BundleType::Direction::To, writeReqType},
579 {"result", BundleType::Direction::From, writeRespType}});
580 write.reset(CallService::Callback::get(acc, AppID("__cosim_hostmem_write"),
581 bundleType, *writeRespPort,
582 *writeReqPort));
583 write->connect([this](const MessageData &req) { return serviceWrite(req); },
584 true);
585 }
586
587 // Service the read request as a callback. Simply reads the data from the
588 // location specified. TODO: check that the memory has been mapped.
589 bool serviceRead(const MessageData &reqBytes) {
590 const HostMemReadReq *req = reqBytes.as<HostMemReadReq>();
591 acc.getLogger().trace(
592 [&](std::string &subsystem, std::string &msg,
593 std::unique_ptr<std::map<std::string, std::any>> &details) {
594 subsystem = "hostmem";
595 msg = "Read request: addr=0x" + toHex(req->address) +
596 " len=" + std::to_string(req->length) +
597 " tag=" + std::to_string(req->tag);
598 });
599 // Send one response per 8 bytes. Zero-length reads (e.g. void / zero-width
600 // types) indicates a bug in the hardware and we log an error, but we still
601 // send a single response.
602 uint64_t *dataPtr = reinterpret_cast<uint64_t *>(req->address);
603 uint32_t numDataResps = (req->length + 7) / 8;
604 if (numDataResps == 0)
605 acc.getLogger().error(
606 "hostmem",
607 std::format("Read request with length=0 from addr=0x{} tag={}. "
608 "Reads of length 0 are not valid and indicate a bug "
609 "in the requester.",
610 toHex(req->address), req->tag));
611 if (req->length > kPcieMaxReadRequestBytes)
612 acc.getLogger().error(
613 "hostmem",
614 std::format("Read request length={} from addr=0x{} tag={} exceeds "
615 "the PCIe maximum read request size ({} bytes). The "
616 "requester must split reads larger than this into "
617 "multiple requests.",
618 req->length, toHex(req->address), req->tag,
619 kPcieMaxReadRequestBytes));
620 uint32_t numResps = std::max(numDataResps, 1u);
621 for (uint32_t i = 0; i < numResps; ++i) {
622 uint64_t word = i < numDataResps ? dataPtr[i] : 0;
623 bool last = i + 1 == numResps;
624 HostMemReadRespFrame frame(req->tag, word, last);
625 acc.getLogger().trace(
626 [&](std::string &subsystem, std::string &msg,
627 std::unique_ptr<std::map<std::string, std::any>> &details) {
628 subsystem = "HostMem";
629 msg = "Read result: data=0x" + toHex(word) +
630 " tag=" + std::to_string(req->tag) +
631 " last=" + std::to_string(last);
632 });
633 readRespPort->write(frame.toMessage());
634 }
635 return true;
636 }
637
638 // Service a write request as a callback. Simply write the data to the
639 // location specified. TODO: check that the memory has been mapped.
640 MessageData serviceWrite(const MessageData &reqBytes) {
641 if (reqBytes.getSize() != HostMemWriteReqFrame::kMessageBytes)
642 throw std::runtime_error(
643 "HostMem write frame size mismatch. Size is " +
644 std::to_string(reqBytes.getSize()) + ", expected " +
645 std::to_string(HostMemWriteReqFrame::kMessageBytes) + ".");
646 HostMemWriteReqFrame req(reqBytes.getBytes());
647 acc.getLogger().trace(
648 [&](std::string &subsystem, std::string &msg,
649 std::unique_ptr<std::map<std::string, std::any>> &details) {
650 subsystem = "hostmem";
651 msg = "Write request: addr=0x" + toHex(req.address()) +
652 " valid_bytes=" + std::to_string(req.validBytes()) +
653 " tag=" + std::to_string(req.tag()) +
654 " last=" + std::to_string(req.last());
655 });
656 uint8_t *dataPtr = reinterpret_cast<uint8_t *>(req.address());
657 unsigned validBytes = req.validBytes();
658 for (unsigned i = 0; i < validBytes; ++i)
659 dataPtr[i] = req.dataByte(i);
660 HostMemWriteResp resp = req.tag();
661 return MessageData::from(resp);
662 }
663
664 struct CosimHostMemRegion : public HostMemRegion {
665 CosimHostMemRegion(std::size_t size) {
666 ptr = malloc(size);
667 memset(ptr, 0xFF, size);
668 this->size = size;
669 }
670 virtual ~CosimHostMemRegion() { free(ptr); }
671 virtual void *getPtr() const override { return ptr; }
672 virtual std::size_t getSize() const override { return size; }
673
674 private:
675 void *ptr;
676 std::size_t size;
677 };
678
679 virtual std::unique_ptr<HostMemRegion>
680 allocate(std::size_t size, HostMem::Options opts) const override {
681 auto ret = std::unique_ptr<HostMemRegion>(new CosimHostMemRegion(size));
682 acc.getLogger().debug(
683 [&](std::string &subsystem, std::string &msg,
684 std::unique_ptr<std::map<std::string, std::any>> &details) {
685 subsystem = "HostMem";
686 msg = "Allocated host memory region at 0x" + toHex(ret->getPtr()) +
687 " of size " + std::to_string(size);
688 });
689 return ret;
690 }
691 virtual bool mapMemory(void *ptr, std::size_t size,
692 HostMem::Options opts) const override {
693 return true;
694 }
695 virtual void unmapMemory(void *ptr) const override {}
696
697private:
698 const esi::Type *getType(Context &ctxt, esi::Type *type) {
699 if (auto t = ctxt.getType(type->getID())) {
700 delete type;
701 return *t;
702 }
703 ctxt.registerType(type);
704 return type;
705 }
707 Context &ctxt;
708 RpcClient *rpcClient;
709 std::unique_ptr<WriteCosimChannelPort> readRespPort;
710 std::unique_ptr<ReadCosimChannelPort> readReqPort;
711 std::unique_ptr<CallService::Callback> read;
712 std::unique_ptr<WriteCosimChannelPort> writeRespPort;
713 std::unique_ptr<ReadCosimChannelPort> writeReqPort;
714 std::unique_ptr<CallService::Callback> write;
715};
716} // namespace
717
718namespace esi::backends::cosim {
719/// Implement the magic cosim channel communication.
720class CosimEngine : public Engine {
721public:
723 const ServiceImplDetails &details, const HWClientDetails &clients)
724 : Engine(conn), conn(conn) {
725 // Compute our parents idPath path.
726 AppIDPath prefix = std::move(idPath);
727 if (prefix.size() > 0)
728 prefix.pop_back();
729
730 for (auto client : clients) {
731 AppIDPath fullClientPath = prefix + client.relPath;
732 std::map<std::string, std::string> channelAssignments;
733 for (auto assignment : client.channelAssignments)
734 if (assignment.second.type == "cosim")
735 channelAssignments[assignment.first] = std::any_cast<std::string>(
736 assignment.second.implOptions.at("name"));
737 clientChannelAssignments[fullClientPath] = std::move(channelAssignments);
738 }
739 }
740
741 std::unique_ptr<ChannelPort> createPort(AppIDPath idPath,
742 const std::string &channelName,
744 const Type *type) override;
745
746private:
748 std::map<AppIDPath, std::map<std::string, std::string>>
750};
751} // namespace esi::backends::cosim
752
753std::unique_ptr<ChannelPort>
754CosimEngine::createPort(AppIDPath idPath, const std::string &channelName,
755 BundleType::Direction dir, const Type *type) {
756
757 // Find the client details for the port at 'fullPath'.
758 auto f = clientChannelAssignments.find(idPath);
759 if (f == clientChannelAssignments.end())
760 throw std::runtime_error("Could not find port for '" + idPath.toStr() +
761 "." + channelName + "'");
762 const std::map<std::string, std::string> &channelAssignments = f->second;
763 auto cosimChannelNameIter = channelAssignments.find(channelName);
764 if (cosimChannelNameIter == channelAssignments.end())
765 throw std::runtime_error("Could not find channel '" + idPath.toStr() + "." +
766 channelName + "' in cosimulation");
767
768 // Get the endpoint, which may or may not exist. Construct the port.
769 // Everything is validated when the client calls 'connect()' on the port.
771 if (!conn.rpcClient->getChannelDesc(cosimChannelNameIter->second, chDesc))
772 throw std::runtime_error("Could not find channel '" + idPath.toStr() + "." +
773 channelName + "' in cosimulation");
774
775 std::unique_ptr<ChannelPort> port;
776 std::string fullChannelName = idPath.toStr() + "." + channelName;
777 if (BundlePort::isWrite(dir))
778 port = std::make_unique<WriteCosimChannelPort>(
779 conn, *conn.rpcClient, chDesc, type, fullChannelName);
780 else
781 port = std::make_unique<ReadCosimChannelPort>(conn, *conn.rpcClient, chDesc,
782 type, fullChannelName);
783 return port;
784}
785
786void CosimAccelerator::createEngine(const std::string &engineTypeName,
787 AppIDPath idPath,
788 const ServiceImplDetails &details,
789 const HWClientDetails &clients) {
790
791 std::unique_ptr<Engine> engine = nullptr;
792 if (engineTypeName == "cosim")
793 engine = std::make_unique<CosimEngine>(*this, idPath, details, clients);
794 else
795 engine = ::esi::registry::createEngine(*this, engineTypeName, idPath,
796 details, clients);
797 registerEngine(idPath, std::move(engine), clients);
798}
800 AppIDPath idPath, std::string implName,
801 const ServiceImplDetails &details,
802 const HWClientDetails &clients) {
803 if (svcType == typeid(services::MMIO)) {
804 return new CosimMMIO(*this, getCtxt(), idPath, rpcClient.get(), clients);
805 } else if (svcType == typeid(services::HostMem)) {
806 return new CosimHostMem(*this, getCtxt(), rpcClient.get());
807 } else if (svcType == typeid(SysInfo)) {
808 switch (manifestMethod) {
810 return new CosimSysInfo(*this, rpcClient.get());
812 return new MMIOSysInfo(getService<services::MMIO>());
813 }
814 }
815 return nullptr;
816}
817
821
#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 uint8_t * getBytes() const
Definition Common.h:166
const T * as() const
Cast to a type.
Definition Common.h:190
size_t getSize() const
Get the size of the data in bytes.
Definition Common.h:180
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:786
std::unique_ptr< RpcClient > rpcClient
Definition Cosim.h:65
void setManifestMethod(ManifestMethod method)
Definition Cosim.cpp:818
static std::unique_ptr< AcceleratorConnection > connect(Context &, std::string connectionString)
Parse the connection std::string and instantiate the accelerator.
Definition Cosim.cpp:174
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:799
CosimAccelerator(Context &, std::string hostname, uint16_t port)
Construct and connect to a cosim server.
Definition Cosim.cpp:226
std::set< std::unique_ptr< ChannelPort > > channels
Definition Cosim.h:69
Implement the magic cosim channel communication.
Definition Cosim.cpp:720
CosimEngine(CosimAccelerator &conn, AppIDPath idPath, const ServiceImplDetails &details, const HWClientDetails &clients)
Definition Cosim.cpp:722
std::map< AppIDPath, std::map< std::string, std::string > > clientChannelAssignments
Definition Cosim.cpp:749
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:754
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