CIRCT 24.0.0git
Loading...
Searching...
No Matches
esitester.cpp
Go to the documentation of this file.
1//===- esitester.cpp - ESI accelerator test/example tool ------------------===//
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 runtime package. The source for
11// this file should always be modified within CIRCT
12// (lib/dialect/ESI/runtime/cpp/tools/esitester.cpp).
13//
14//===----------------------------------------------------------------------===//
15//
16// This application isn't a utility so much as a test driver for an ESI system.
17// It is also useful as an example of how to use the ESI C++ API. esiquery.cpp
18// is also useful as an example.
19//
20//===----------------------------------------------------------------------===//
21
22#include "esi/Accelerator.h"
23#include "esi/CLI.h"
24#include "esi/Manifest.h"
25#include "esi/Services.h"
26#include "esi/TypedPorts.h"
27
28#include <algorithm>
29#include <atomic>
30#include <chrono>
31#include <cstdlib>
32#include <cstring>
33#include <future>
34#include <iostream>
35#include <map>
36#include <memory>
37#include <random>
38#include <span>
39#include <sstream>
40#include <stdexcept>
41#include <vector>
42
43using namespace esi;
44
45// Forward declarations of test functions.
47 uint32_t iterations);
49 const std::vector<uint32_t> &widths, bool write,
50 bool read);
52 uint32_t xferCount,
53 const std::vector<uint32_t> &widths, bool read,
54 bool write);
56 const std::vector<uint32_t> &widths, bool read, bool write);
58 const std::vector<uint32_t> &widths,
59 uint32_t xferCount, bool read, bool write,
60 bool checkData);
61static uint8_t esitesterDataByte(uint32_t index, size_t byte);
62static uint8_t enginePatternByte(uint32_t index, size_t byte, size_t bitWidth);
64 uint32_t iterations, bool pipeline);
66 Accelerator *, uint32_t width,
67 uint32_t xferCount, bool read,
68 bool write);
70 uint32_t addAmt, uint32_t numItems);
72 uint32_t addAmt, uint32_t numItems);
74 uint32_t xTrans, uint32_t yTrans,
75 uint32_t numCoords);
77 uint32_t xTrans, uint32_t yTrans,
78 uint32_t numCoords, size_t batchSizeLimit);
80 uint32_t xTrans, uint32_t yTrans,
81 uint32_t numCoords);
83 uint32_t iterations);
85
86// Default widths and default widths string for CLI help text.
87constexpr std::array<uint32_t, 8> defaultWidths = {24, 32, 64, 72,
88 128, 256, 512, 534};
89static std::string defaultWidthsStr() {
90 std::string s;
91 for (size_t i = 0; i < defaultWidths.size(); ++i) {
92 s += std::to_string(defaultWidths[i]);
93 if (i + 1 < defaultWidths.size())
94 s += ",";
95 }
96 return s;
97}
98
99// Helper to format bandwidth with appropriate units.
100static std::string formatBandwidth(double bytesPerSec) {
101 const char *unit = "B/s";
102 double value = bytesPerSec;
103 if (bytesPerSec >= 1e9) {
104 unit = "GB/s";
105 value = bytesPerSec / 1e9;
106 } else if (bytesPerSec >= 1e6) {
107 unit = "MB/s";
108 value = bytesPerSec / 1e6;
109 } else if (bytesPerSec >= 1e3) {
110 unit = "KB/s";
111 value = bytesPerSec / 1e3;
112 }
113 std::ostringstream oss;
114 oss.setf(std::ios::fixed);
115 oss.precision(2);
116 oss << value << " " << unit;
117 return oss.str();
118}
119
120// Human-readable size from bytes.
121static std::string humanBytes(uint64_t bytes) {
122 const char *units[] = {"B", "KB", "MB", "GB", "TB"};
123 double v = (double)bytes;
124 int u = 0;
125 while (v >= 1024.0 && u < 4) {
126 v /= 1024.0;
127 ++u;
128 }
129 std::ostringstream oss;
130 oss.setf(std::ios::fixed);
131 oss.precision(u == 0 ? 0 : 2);
132 oss << v << " " << units[u];
133 return oss.str();
134}
135
136// Human-readable time from microseconds.
137static std::string humanTimeUS(uint64_t us) {
138 if (us < 1000)
139 return std::to_string(us) + " us";
140 double ms = us / 1000.0;
141 if (ms < 1000.0) {
142 std::ostringstream oss;
143 oss.setf(std::ios::fixed);
144 oss.precision(ms < 10.0 ? 2 : (ms < 100.0 ? 1 : 0));
145 oss << ms << " ms";
146 return oss.str();
147 }
148 double sec = ms / 1000.0;
149 std::ostringstream oss;
150 oss.setf(std::ios::fixed);
151 oss.precision(sec < 10.0 ? 3 : 2);
152 oss << sec << " s";
153 return oss.str();
154}
155
156// MSVC does not implement std::aligned_malloc, even though it's part of the
157// C++17 standard. Provide a compatibility layer.
158static void *alignedAllocCompat(std::size_t alignment, std::size_t size) {
159#if defined(_MSC_VER)
160 void *ptr = _aligned_malloc(size, alignment);
161 if (!ptr)
162 throw std::bad_alloc();
163 return ptr;
164#else
165 void *ptr = std::aligned_alloc(alignment, size);
166 if (!ptr)
167 throw std::bad_alloc();
168 return ptr;
169#endif
170}
171
172static void alignedFreeCompat(void *ptr) {
173#if defined(_MSC_VER)
174 _aligned_free(ptr);
175#else
176 std::free(ptr);
177#endif
178}
179
180int main(int argc, const char *argv[]) {
181 CliParser cli("esitester");
182 cli.description("Test an ESI system running the ESI tester image.");
183 cli.require_subcommand(1);
184
185 CLI::App *callback_test =
186 cli.add_subcommand("callback", "initiate callback test");
187 uint32_t cb_iters = 1;
188 callback_test->add_option("-i,--iters", cb_iters,
189 "Number of iterations to run");
190
191 CLI::App *hostmemtestSub =
192 cli.add_subcommand("hostmem", "Run the host memory test");
193 bool hmRead = false;
194 bool hmWrite = false;
195 std::vector<uint32_t> hostmemWidths(defaultWidths.begin(),
196 defaultWidths.end());
197 hostmemtestSub->add_flag("-w,--write", hmWrite,
198 "Enable host memory write test");
199 hostmemtestSub->add_flag("-r,--read", hmRead, "Enable host memory read test");
200 hostmemtestSub->add_option(
201 "--widths", hostmemWidths,
202 "Hostmem test widths (default: " + defaultWidthsStr() + ")");
203
204 CLI::App *dmatestSub = cli.add_subcommand("dma", "Run the DMA test");
205 bool dmaRead = false;
206 bool dmaWrite = false;
207 std::vector<uint32_t> dmaWidths(defaultWidths.begin(), defaultWidths.end());
208 dmatestSub->add_flag("-w,--write", dmaWrite, "Enable dma write test");
209 dmatestSub->add_flag("-r,--read", dmaRead, "Enable dma read test");
210 dmatestSub->add_option("--widths", dmaWidths,
211 "DMA test widths (default: " + defaultWidthsStr() +
212 ")");
213
214 CLI::App *bandwidthSub =
215 cli.add_subcommand("bandwidth", "Run the bandwidth test");
216 uint32_t xferCount = 1000;
217 bandwidthSub->add_option("-c,--count", xferCount,
218 "Number of transfers to perform");
219 bool bandwidthRead = false;
220 bool bandwidthWrite = false;
221 bool bandwidthCheckData = false;
222 std::vector<uint32_t> bandwidthWidths(defaultWidths.begin(),
223 defaultWidths.end());
224 bandwidthSub->add_option("--widths", bandwidthWidths,
225 "Width of the transfers to perform (default: " +
226 defaultWidthsStr() + ")");
227 bandwidthSub->add_flag("-w,--write", bandwidthWrite,
228 "Enable bandwidth write");
229 bandwidthSub->add_flag("-r,--read", bandwidthRead, "Enable bandwidth read");
230 bandwidthSub->add_flag("--check-data", bandwidthCheckData,
231 "Verify every transferred payload byte");
232
233 CLI::App *hostmembwSub =
234 cli.add_subcommand("hostmembw", "Run the host memory bandwidth test");
235 uint32_t hmBwCount = 1000;
236 bool hmBwRead = false;
237 bool hmBwWrite = false;
238 std::vector<uint32_t> hmBwWidths(defaultWidths.begin(), defaultWidths.end());
239 hostmembwSub->add_option("-c,--count", hmBwCount,
240 "Number of hostmem transfers");
241 hostmembwSub->add_option(
242 "--widths", hmBwWidths,
243 "Hostmem bandwidth widths (default: " + defaultWidthsStr() + ")");
244 hostmembwSub->add_flag("-w,--write", hmBwWrite,
245 "Measure hostmem write bandwidth");
246 hostmembwSub->add_flag("-r,--read", hmBwRead,
247 "Measure hostmem read bandwidth");
248
249 CLI::App *loopbackSub =
250 cli.add_subcommand("loopback", "Test LoopbackInOutAdd function service");
251 uint32_t loopbackIters = 10;
252 bool loopbackPipeline = false;
253 loopbackSub->add_option("-i,--iters", loopbackIters,
254 "Number of function invocations (default 10)");
255 loopbackSub->add_flag("-p,--pipeline", loopbackPipeline,
256 "Pipeline all calls then collect results");
257
258 CLI::App *aggBwSub = cli.add_subcommand(
259 "aggbandwidth",
260 "Aggregate hostmem bandwidth across four units (readmem*, writemem*)");
261 uint32_t aggWidth = 512;
262 uint32_t aggCount = 1000;
263 bool aggRead = false;
264 bool aggWrite = false;
265 aggBwSub->add_option(
266 "--width", aggWidth,
267 "Bit width (default 512; other widths ignored if absent)");
268 aggBwSub->add_option("-c,--count", aggCount, "Flits per unit (default 1000)");
269 aggBwSub->add_flag("-r,--read", aggRead, "Include read units");
270 aggBwSub->add_flag("-w,--write", aggWrite, "Include write units");
271
272 CLI::App *streamingAddSub = cli.add_subcommand(
273 "streaming_add", "Test StreamingAdder function service with list input");
274 uint32_t streamingAddAmt = 5;
275 uint32_t streamingNumItems = 5;
276 bool streamingTranslate = false;
277 streamingAddSub->add_option("-a,--add", streamingAddAmt,
278 "Amount to add to each element (default 5)");
279 streamingAddSub->add_option("-n,--num-items", streamingNumItems,
280 "Number of random items in the list (default 5)");
281 streamingAddSub->add_flag("-t,--translate", streamingTranslate,
282 "Use message translation (list translation)");
283
284 CLI::App *coordTranslateSub = cli.add_subcommand(
285 "translate_coords",
286 "Test CoordTranslator function service with list of coordinates");
287 uint32_t coordXTrans = 10;
288 uint32_t coordYTrans = 20;
289 uint32_t coordNumItems = 5;
290 coordTranslateSub->add_option("-x,--x-translation", coordXTrans,
291 "X translation amount (default 10)");
292 coordTranslateSub->add_option("-y,--y-translation", coordYTrans,
293 "Y translation amount (default 20)");
294 coordTranslateSub->add_option("-n,--num-coords", coordNumItems,
295 "Number of random coordinates (default 5)");
296
297 CLI::App *serialCoordTranslateSub = cli.add_subcommand(
298 "serial_coords",
299 "Test SerialCoordTranslator function service with list of coordinates");
300 uint32_t serialBatchSize = 240;
301 serialCoordTranslateSub->add_option("-x,--x-translation", coordXTrans,
302 "X translation amount (default 10)");
303 serialCoordTranslateSub->add_option("-y,--y-translation", coordYTrans,
304 "Y translation amount (default 20)");
305 serialCoordTranslateSub->add_option(
306 "-n,--num-coords", coordNumItems,
307 "Number of random coordinates (default 5)");
308 serialCoordTranslateSub
309 ->add_option("-b,--batch-size", serialBatchSize,
310 "Coordinates per header (default 240, max 65535)")
311 ->check(CLI::Range(1u, 0xFFFFu));
312
313 CLI::App *autoSerialCoordTranslateSub = cli.add_subcommand(
314 "auto_serial_coords",
315 "Test AutoSerialCoordTranslator (uses ListWindowToParallel/Serial "
316 "converters under the hood)");
317 uint32_t autoCoordXTrans = 10;
318 uint32_t autoCoordYTrans = 20;
319 uint32_t autoCoordNumItems = 5;
320 autoSerialCoordTranslateSub->add_option("-x,--x-translation", autoCoordXTrans,
321 "X translation amount (default 10)");
322 autoSerialCoordTranslateSub->add_option("-y,--y-translation", autoCoordYTrans,
323 "Y translation amount (default 20)");
324 autoSerialCoordTranslateSub->add_option(
325 "-n,--num-coords", autoCoordNumItems,
326 "Number of random coordinates (default 5)");
327
328 CLI::App *channelTestSub = cli.add_subcommand(
329 "channel", "Test ChannelService to_host and from_host");
330 uint32_t channelIters = 10;
331 channelTestSub->add_option("-i,--iters", channelIters,
332 "Number of loopback iterations (default 10)");
333
334 CLI::App *resetSub = cli.add_subcommand(
335 "reset", "Test the design reset feature (telemetry clears after reset)");
336
337 if (int rc = cli.esiParse(argc, argv))
338 return rc;
339 if (!cli.get_help_ptr()->empty())
340 return 0;
341
342 Context &ctxt = cli.getContext();
343 AcceleratorConnection *acc = cli.connect();
344 try {
345 const auto &info = *acc->getService<services::SysInfo>();
346 ctxt.getLogger().info("esitester", "Connected to accelerator.");
347 Manifest manifest(ctxt, info.getJsonManifest());
348 Accelerator *accel = manifest.buildAccelerator(*acc);
349 ctxt.getLogger().info("esitester", "Built accelerator.");
350 acc->getServiceThread()->addPoll(*accel);
351
352 if (*callback_test) {
353 callbackTest(acc, accel, cb_iters);
354 } else if (*hostmemtestSub) {
355 hostmemTest(acc, accel, hostmemWidths, hmWrite, hmRead);
356 } else if (*loopbackSub) {
357 loopbackAddTest(acc, accel, loopbackIters, loopbackPipeline);
358 } else if (*dmatestSub) {
359 dmaTest(acc, accel, dmaWidths, dmaRead, dmaWrite);
360 } else if (*bandwidthSub) {
361 bandwidthTest(acc, accel, bandwidthWidths, xferCount, bandwidthRead,
362 bandwidthWrite, bandwidthCheckData);
363 } else if (*hostmembwSub) {
364 hostmemBandwidthTest(acc, accel, hmBwCount, hmBwWidths, hmBwRead,
365 hmBwWrite);
366 } else if (*aggBwSub) {
367 aggregateHostmemBandwidthTest(acc, accel, aggWidth, aggCount, aggRead,
368 aggWrite);
369 } else if (*streamingAddSub) {
370 if (streamingTranslate)
371 streamingAddTranslatedTest(acc, accel, streamingAddAmt,
372 streamingNumItems);
373 else
374 streamingAddTest(acc, accel, streamingAddAmt, streamingNumItems);
375 } else if (*coordTranslateSub) {
376 coordTranslateTest(acc, accel, coordXTrans, coordYTrans, coordNumItems);
377 } else if (*serialCoordTranslateSub) {
378 serialCoordTranslateTest(acc, accel, coordXTrans, coordYTrans,
379 coordNumItems, serialBatchSize);
380 } else if (*autoSerialCoordTranslateSub) {
381 autoSerialCoordTranslateTest(acc, accel, autoCoordXTrans, autoCoordYTrans,
382 autoCoordNumItems);
383 } else if (*channelTestSub) {
384 channelTest(acc, accel, channelIters);
385 } else if (*resetSub) {
386 resetTest(acc, accel);
387 }
388
389 acc->disconnect();
390 } catch (std::exception &e) {
391 ctxt.getLogger().error("esitester", e.what());
392 acc->disconnect();
393 return -1;
394 }
395 std::cout << "Exiting successfully\n";
396 return 0;
397}
398
400 uint32_t iterations) {
401 auto cb_test = accel->getChildren().find(AppID("cb_test"));
402 if (cb_test == accel->getChildren().end())
403 throw std::runtime_error("No cb_test child found in accelerator");
404 auto &ports = cb_test->second->getPorts();
405 auto cmd_port = ports.find(AppID("cmd"));
406 if (cmd_port == ports.end())
407 throw std::runtime_error("No cmd port found in cb_test child");
408 auto *cmdMMIO = cmd_port->second.getAs<services::MMIO::MMIORegion>();
409 if (!cmdMMIO)
410 throw std::runtime_error("cb_test cmd port is not MMIO");
411
412 auto f = ports.find(AppID("cb"));
413 if (f == ports.end())
414 throw std::runtime_error("No cb port found in accelerator");
415
416 auto *callPort = f->second.getAs<services::CallService::Callback>();
417 if (!callPort)
418 throw std::runtime_error("cb port is not a CallService::Callback");
419
420 std::atomic<uint32_t> callbackCount = 0;
421 callPort->connect(
422 [conn, &callbackCount](const MessageData &data) mutable -> MessageData {
423 conn->getLogger().debug(
424 [&](std::string &subsystem, std::string &msg,
425 std::unique_ptr<std::map<std::string, std::any>> &details) {
426 subsystem = "ESITESTER";
427 msg = "Received callback";
428 details = std::make_unique<std::map<std::string, std::any>>();
429 details->emplace("data", data);
430 });
431 std::cout << "callback: " << *data.as<uint64_t>() << std::endl;
432 callbackCount.fetch_add(1);
433 return MessageData();
434 },
435 true);
436
437 for (uint32_t i = 0; i < iterations; ++i) {
438 conn->getLogger().info("esitester", "Issuing callback command iteration " +
439 std::to_string(i) + "/" +
440 std::to_string(iterations));
441 cmdMMIO->write(0x10, i); // Command the callback
442 // Wait up to 1 second for the callback to be invoked.
443 for (uint32_t wait = 0; wait < 1000; ++wait) {
444 if (callbackCount.load() > i)
445 break;
446 std::this_thread::sleep_for(std::chrono::milliseconds(1));
447 }
448 if (callbackCount.load() <= i)
449 throw std::runtime_error("Callback test failed. No callback received");
450 }
451}
452
454 uint64_t address, uint64_t flits) {
455 struct RegisterExpectation {
456 uint32_t offset;
457 uint64_t expected;
458 const char *name;
459 };
460 const RegisterExpectation expectations[] = {
461 {0x00, 0, "flits_left"},
462 {0x08, address, "start_addr"},
463 {0x10, flits, "flits_total"},
464 };
465 for (const auto &[offset, expected, name] : expectations) {
466 uint64_t actual = mmio.read(offset);
467 if (actual != expected)
468 throw std::runtime_error("BurstCommand MMIO readback for " +
469 std::string(name) + " failed: expected " +
470 toHex(expected) + ", got " + toHex(actual));
471 }
472}
473
474/// Test the hostmem write functionality.
477 uint32_t width) {
478 std::cout << "Running hostmem WRITE test with width " << width << std::endl;
479 uint64_t *dataPtr = static_cast<uint64_t *>(region.getPtr());
480 auto check = [&](bool print) {
481 bool ret = true;
482 for (size_t i = 0; i < 9; ++i) {
483 if (print)
484 printf("[write] dataPtr[%zu] = 0x%016lx\n", i, dataPtr[i]);
485 if (i < (width + 63) / 64 && dataPtr[i] == 0xFFFFFFFFFFFFFFFFull)
486 ret = false;
487 }
488 return ret;
489 };
490
491 auto writeMemChildIter = acc->getChildren().find(AppID("writemem", width));
492 if (writeMemChildIter == acc->getChildren().end())
493 throw std::runtime_error(
494 "hostmem write test failed. No writemem child found");
495 auto &writeMemPorts = writeMemChildIter->second->getPorts();
496
497 // The MMIO command surface lives in a nested 'mmio[width]' submodule
498 // (BurstCommand -> MmioRegistry), exposing its 'cmd' port at
499 // writemem[width]/mmio[width]/cmd.
500 AppIDPath cmdPath;
501 BundlePort *cmdPortBundle = acc->resolvePort(
502 {AppID("writemem", width), AppID("mmio", width), AppID("cmd")}, cmdPath);
503 if (!cmdPortBundle)
504 throw std::runtime_error(
505 "hostmem write test failed. No mmio[width]/cmd MMIO port");
506 auto *cmdMMIO = cmdPortBundle->getAs<services::MMIO::MMIORegion>();
507 if (!cmdMMIO)
508 throw std::runtime_error(
509 "hostmem write test failed. mmio[width]/cmd port not MMIO");
510
511 auto issuedPortIter = writeMemPorts.find(AppID("addrCmdIssued"));
512 if (issuedPortIter == writeMemPorts.end())
513 throw std::runtime_error(
514 "hostmem write test failed. addrCmdIssued missing");
515 auto *addrCmdIssuedPort =
516 issuedPortIter->second.getAs<services::TelemetryService::Metric>();
517 if (!addrCmdIssuedPort)
518 throw std::runtime_error(
519 "hostmem write test failed. addrCmdIssued not telemetry");
520 addrCmdIssuedPort->connect();
521
522 auto responsesPortIter = writeMemPorts.find(AppID("addrCmdResponses"));
523 if (responsesPortIter == writeMemPorts.end())
524 throw std::runtime_error(
525 "hostmem write test failed. addrCmdResponses missing");
526 auto *addrCmdResponsesPort =
527 responsesPortIter->second.getAs<services::TelemetryService::Metric>();
528 if (!addrCmdResponsesPort)
529 throw std::runtime_error(
530 "hostmem write test failed. addrCmdResponses not telemetry");
531 addrCmdResponsesPort->connect();
532
533 for (size_t i = 0, e = 9; i < e; ++i)
534 dataPtr[i] = 0xFFFFFFFFFFFFFFFFull;
535 region.flush();
536 uint64_t devPtr = reinterpret_cast<uint64_t>(region.getDevicePtr());
537 cmdMMIO->write(0x08, devPtr);
538 cmdMMIO->write(0x10, 1);
539 checkBurstCommandRegisters(*cmdMMIO, devPtr, 1);
540 cmdMMIO->write(0x18, 1);
541 bool done = false;
542 for (int i = 0; i < 100; ++i) {
543 auto issued = addrCmdIssuedPort->readInt();
544 auto responses = addrCmdResponsesPort->readInt();
545 if (issued == 1 && responses == 1) {
546 done = true;
547 break;
548 }
549 std::this_thread::sleep_for(std::chrono::microseconds(100));
550 }
551 if (!done) {
552 check(true);
553 throw std::runtime_error("hostmem write test (" + std::to_string(width) +
554 " bits) timeout waiting for completion");
555 }
556 if (!check(true))
557 throw std::runtime_error("hostmem write test failed (" +
558 std::to_string(width) + " bits)");
559}
560
563 uint32_t width) {
564 std::cout << "Running hostmem READ test with width " << width << std::endl;
565 auto readMemChildIter = acc->getChildren().find(AppID("readmem", width));
566 if (readMemChildIter == acc->getChildren().end())
567 throw std::runtime_error(
568 "hostmem read test failed. No readmem child found");
569
570 auto &readMemPorts = readMemChildIter->second->getPorts();
571 // The MMIO command surface lives in a nested 'mmio[width]' submodule
572 // (BurstCommand -> MmioRegistry), exposing its 'cmd' port at
573 // readmem[width]/mmio[width]/cmd.
574 AppIDPath addrCmdPath;
575 BundlePort *addrCmdPortBundle = acc->resolvePort(
576 {AppID("readmem", width), AppID("mmio", width), AppID("cmd")},
577 addrCmdPath);
578 if (!addrCmdPortBundle)
579 throw std::runtime_error(
580 "hostmem read test failed. No mmio[width]/cmd MMIO port");
581 auto *addrCmdMMIO = addrCmdPortBundle->getAs<services::MMIO::MMIORegion>();
582 if (!addrCmdMMIO)
583 throw std::runtime_error(
584 "hostmem read test failed. mmio[width]/cmd port not MMIO");
585
586 auto lastReadPortIter = readMemPorts.find(AppID("lastReadLSB"));
587 if (lastReadPortIter == readMemPorts.end())
588 throw std::runtime_error("hostmem read test failed. lastReadLSB missing");
589 auto *lastReadPort =
590 lastReadPortIter->second.getAs<services::TelemetryService::Metric>();
591 if (!lastReadPort)
592 throw std::runtime_error(
593 "hostmem read test failed. lastReadLSB not telemetry");
594 lastReadPort->connect();
595
596 auto issuedPortIter = readMemPorts.find(AppID("addrCmdIssued"));
597 if (issuedPortIter == readMemPorts.end())
598 throw std::runtime_error("hostmem read test failed. addrCmdIssued missing");
599 auto *addrCmdIssuedPort =
600 issuedPortIter->second.getAs<services::TelemetryService::Metric>();
601 if (!addrCmdIssuedPort)
602 throw std::runtime_error(
603 "hostmem read test failed. addrCmdIssued not telemetry");
604 addrCmdIssuedPort->connect();
605
606 auto responsesPortIter = readMemPorts.find(AppID("addrCmdResponses"));
607 if (responsesPortIter == readMemPorts.end())
608 throw std::runtime_error(
609 "hostmem read test failed. addrCmdResponses missing");
610 auto *addrCmdResponsesPort =
611 responsesPortIter->second.getAs<services::TelemetryService::Metric>();
612 if (!addrCmdResponsesPort)
613 throw std::runtime_error(
614 "hostmem read test failed. addrCmdResponses not telemetry");
615 addrCmdResponsesPort->connect();
616
617 for (size_t i = 0; i < 8; ++i) {
618 auto *dataPtr = static_cast<uint64_t *>(region.getPtr());
619 dataPtr[0] = 0x12345678ull << i;
620 dataPtr[1] = 0xDEADBEEFull << i;
621 region.flush();
622 uint64_t devPtr = reinterpret_cast<uint64_t>(region.getDevicePtr());
623 addrCmdMMIO->write(0x08, devPtr);
624 addrCmdMMIO->write(0x10, 1);
625 checkBurstCommandRegisters(*addrCmdMMIO, devPtr, 1);
626 addrCmdMMIO->write(0x18, 1);
627 bool done = false;
628 for (int waitLoop = 0; waitLoop < 100; ++waitLoop) {
629 auto issued = addrCmdIssuedPort->readInt();
630 auto responses = addrCmdResponsesPort->readInt();
631 if (issued == 1 && responses == 1) {
632 done = true;
633 break;
634 }
635 std::this_thread::sleep_for(std::chrono::milliseconds(10));
636 }
637 if (!done)
638 throw std::runtime_error("hostmem read (" + std::to_string(width) +
639 " bits) timeout waiting for completion");
640 uint64_t captured = lastReadPort->readInt();
641 uint64_t expected = dataPtr[0];
642 if (width < 64)
643 expected &= ((1ull << width) - 1);
644 if (captured != expected)
645 throw std::runtime_error("hostmem read test (" + std::to_string(width) +
646 " bits) failed. Expected " +
647 esi::toHex(expected) + ", got " +
648 esi::toHex(captured));
649 }
650}
651
653 const std::vector<uint32_t> &widths, bool write,
654 bool read) {
655 // Enable the host memory service.
656 auto hostmem = conn->getService<services::HostMem>();
657 hostmem->start();
658 auto scratchRegion = hostmem->allocate(/*size(bytes)=*/1024 * 1024,
659 /*memOpts=*/{.writeable = true});
660 if (!scratchRegion)
661 throw std::runtime_error("hostmem test: failed to allocate host memory");
662 uint64_t *dataPtr = static_cast<uint64_t *>(scratchRegion->getPtr());
663 conn->getLogger().info("esitester",
664 "Running host memory test with region size " +
665 std::to_string(scratchRegion->getSize()) +
666 " bytes at 0x" + toHex(dataPtr));
667 for (size_t i = 0; i < scratchRegion->getSize() / 8; ++i)
668 dataPtr[i] = 0;
669 scratchRegion->flush();
670
671 bool passed = true;
672 for (size_t width : widths) {
673 try {
674 if (write)
675 hostmemWriteTest(acc, *scratchRegion, width);
676 if (read)
677 hostmemReadTest(acc, *scratchRegion, width);
678 } catch (std::exception &e) {
679 conn->getLogger().error("esitester", "Hostmem test failed for width " +
680 std::to_string(width) + ": " +
681 e.what());
682 passed = false;
683 }
684 }
685 if (!passed)
686 throw std::runtime_error("Hostmem test failed");
687 std::cout << "Hostmem test passed" << std::endl;
688}
689
691 size_t width) {
692 Logger &logger = conn->getLogger();
693 logger.info("esitester",
694 "== Running DMA read test with width " + std::to_string(width));
695 AppIDPath lastPath;
696 BundlePort *toHostMMIOPort =
697 acc->resolvePort({AppID("tohostdma", width), AppID("cmd")}, lastPath);
698 if (!toHostMMIOPort)
699 throw std::runtime_error("dma read test failed. No tohostdma[" +
700 std::to_string(width) + "] found");
701 auto *toHostMMIO = toHostMMIOPort->getAs<services::MMIO::MMIORegion>();
702 if (!toHostMMIO)
703 throw std::runtime_error("dma read test failed. MMIO port is not MMIO");
704 lastPath.clear();
705 BundlePort *outPortBundle =
706 acc->resolvePort({AppID("tohostdma", width), AppID("out")}, lastPath);
707 ReadChannelPort &outPort = outPortBundle->getRawRead("data");
708 outPort.connect();
709
710 size_t xferCount = 24;
711 MessageData data;
712 toHostMMIO->write(0, xferCount);
713 const size_t wireBytes = (width + 7) / 8;
714 for (size_t index = 0; index < xferCount; ++index) {
715 outPort.read(data);
716 if (data.getSize() != wireBytes)
717 throw std::runtime_error("dma read test failed. Expected " +
718 std::to_string(wireBytes) + " bytes, got " +
719 std::to_string(data.getSize()));
720 for (size_t byte = 0; byte < wireBytes; ++byte) {
721 uint8_t expected =
722 enginePatternByte(static_cast<uint32_t>(index), byte, width);
723 if (data.getBytes()[byte] != expected)
724 throw std::runtime_error(
725 "dma read test failed. Data mismatch at item " +
726 std::to_string(index) + " byte " + std::to_string(byte) +
727 ": expected " + toHex(expected) + ", got " +
728 toHex(data.getBytes()[byte]));
729 }
730 logger.debug("esitester",
731 "Payload [" + std::to_string(index) + "] = 0x" + data.toHex());
732 }
733 outPort.disconnect();
734 std::cout << " DMA read test for " << width << " bits passed" << std::endl;
735}
736
738 size_t width) {
739 Logger &logger = conn->getLogger();
740 logger.info("esitester",
741 "Running DMA write test with width " + std::to_string(width));
742 AppIDPath lastPath;
743 BundlePort *fromHostMMIOPort =
744 acc->resolvePort({AppID("fromhostdma", width), AppID("cmd")}, lastPath);
745 if (!fromHostMMIOPort)
746 throw std::runtime_error("dma read test for " + toString(width) +
747 " bits failed. No fromhostdma[" +
748 std::to_string(width) + "] found");
749 auto *fromHostMMIO = fromHostMMIOPort->getAs<services::MMIO::MMIORegion>();
750 if (!fromHostMMIO)
751 throw std::runtime_error("dma write test for " + toString(width) +
752 " bits failed. MMIO port is not MMIO");
753 lastPath.clear();
754 BundlePort *outPortBundle =
755 acc->resolvePort({AppID("fromhostdma", width), AppID("in")}, lastPath);
756 if (!outPortBundle)
757 throw std::runtime_error("dma write test for " + toString(width) +
758 " bits failed. No out port found");
759 WriteChannelPort &writePort = outPortBundle->getRawWrite("data");
760 writePort.connect();
761
762 size_t xferCount = 24;
763 std::vector<uint8_t> data((width + 7) / 8, 0);
764 fromHostMMIO->read(8);
765 fromHostMMIO->write(0, xferCount);
766 for (size_t i = 1; i < xferCount + 1; ++i) {
767 data[0] = i;
768 bool successWrite;
769 size_t attempts = 0;
770 do {
771 successWrite = writePort.tryWrite(MessageData(data.data(), data.size()));
772 if (!successWrite) {
773 std::this_thread::sleep_for(std::chrono::milliseconds(10));
774 }
775 } while (!successWrite && ++attempts < 100);
776 if (!successWrite)
777 throw std::runtime_error("dma write test for " + toString(width) +
778 " bits failed. Write failed");
779 uint64_t lastReadMMIO;
780 for (size_t a = 0; a < 20; ++a) {
781 lastReadMMIO = fromHostMMIO->read(8);
782 if (lastReadMMIO == i)
783 break;
784 std::this_thread::sleep_for(std::chrono::milliseconds(10));
785 if (a >= 19)
786 throw std::runtime_error("dma write for " + toString(width) +
787 " bits test failed. Read from MMIO failed");
788 }
789 }
790 writePort.disconnect();
791 std::cout << " DMA write test for " << width << " bits passed" << std::endl;
792}
793
795 const std::vector<uint32_t> &widths, bool read,
796 bool write) {
797 bool success = true;
798 if (write)
799 for (size_t width : widths)
800 try {
801 dmaWriteTest(conn, acc, width);
802 } catch (std::exception &e) {
803 success = false;
804 std::cerr << "DMA write test for " << width
805 << " bits failed: " << e.what() << std::endl;
806 }
807 if (read)
808 for (size_t width : widths)
809 dmaReadTest(conn, acc, width);
810 if (!success)
811 throw std::runtime_error("DMA test failed");
812 std::cout << "DMA test passed" << std::endl;
813}
814
815//
816// DMA bandwidth test
817
818static size_t engineWireBytes(size_t bitWidth) { return (bitWidth + 7) / 8; }
819
820static uint8_t enginePatternByte(uint32_t index, size_t byte, size_t bitWidth) {
821 uint8_t value = esitesterDataByte(index, byte);
822 const size_t tailBits = bitWidth % 8;
823 if (tailBits != 0 && byte + 1 == engineWireBytes(bitWidth))
824 value &= (uint8_t(1) << tailBits) - 1;
825 return value;
826}
827
828static std::vector<uint8_t> enginePatternBytes(uint32_t index,
829 size_t bitWidth) {
830 std::vector<uint8_t> bytes(engineWireBytes(bitWidth));
831 for (size_t byte = 0; byte < bytes.size(); ++byte)
832 bytes[byte] = enginePatternByte(index, byte, bitWidth);
833 return bytes;
834}
835
836static uint64_t enginePayloadFold(uint32_t index, size_t bitWidth) {
837 const size_t numChunks = (bitWidth + 63) / 64;
838 uint64_t fold = 0;
839 for (size_t chunkIndex = 0; chunkIndex < numChunks; ++chunkIndex) {
840 uint64_t chunk = 0;
841 for (size_t byteInChunk = 0; byteInChunk < 8; ++byteInChunk) {
842 const size_t byteIndex = chunkIndex * 8 + byteInChunk;
843 if (byteIndex < engineWireBytes(bitWidth))
844 chunk |= uint64_t(enginePatternByte(index, byteIndex, bitWidth))
845 << (8 * byteInChunk);
846 }
847 const unsigned rotate = (8 * chunkIndex) % 64;
848 fold ^= rotate ? ((chunk << rotate) | (chunk >> (64 - rotate))) : chunk;
849 }
850 return fold;
851}
852
854 size_t width, size_t xferCount, bool checkData) {
855
856 AppIDPath lastPath;
857 BundlePort *toHostMMIOPort =
858 acc->resolvePort({AppID("tohostdma", width), AppID("cmd")}, lastPath);
859 if (!toHostMMIOPort)
860 throw std::runtime_error("bandwidth test failed. No tohostdma[" +
861 std::to_string(width) + "] found");
862 auto *toHostMMIO = toHostMMIOPort->getAs<services::MMIO::MMIORegion>();
863 if (!toHostMMIO)
864 throw std::runtime_error("bandwidth test failed. MMIO port is not MMIO");
865 lastPath.clear();
866 BundlePort *outPortBundle =
867 acc->resolvePort({AppID("tohostdma", width), AppID("out")}, lastPath);
868 ReadChannelPort &outPort = outPortBundle->getRawRead("data");
869 outPort.connect();
870
871 Logger &logger = conn->getLogger();
872 logger.info("esitester", "Starting read bandwidth test with " +
873 std::to_string(xferCount) + " x " +
874 std::to_string(width) + " bit transfers");
875 MessageData data;
876 size_t sizeMismatches = 0;
877 size_t dataMismatches = 0;
878 size_t firstMismatchItem = 0;
879 size_t firstMismatchByte = 0;
880 uint8_t firstExpected = 0;
881 uint8_t firstActual = 0;
882 auto start = std::chrono::high_resolution_clock::now();
883 toHostMMIO->write(0, xferCount);
884 for (size_t index = 0; index < xferCount; ++index) {
885 outPort.read(data);
886 if (checkData) {
887 const size_t wireBytes = engineWireBytes(width);
888 if (data.getSize() != wireBytes) {
889 ++sizeMismatches;
890 } else {
891 for (size_t byte = 0; byte < wireBytes; ++byte) {
892 const uint8_t expected =
893 enginePatternByte(static_cast<uint32_t>(index), byte, width);
894 if (data.getBytes()[byte] != expected) {
895 if (dataMismatches == 0) {
896 firstMismatchItem = index;
897 firstMismatchByte = byte;
898 firstExpected = expected;
899 firstActual = data.getBytes()[byte];
900 }
901 ++dataMismatches;
902 }
903 }
904 }
905 }
906 logger.debug(
907 [index,
908 &data](std::string &subsystem, std::string &msg,
909 std::unique_ptr<std::map<std::string, std::any>> &details) {
910 subsystem = "esitester";
911 msg = "Payload [" + std::to_string(index) + "] = 0x" + data.toHex();
912 });
913 }
914 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
915 std::chrono::high_resolution_clock::now() - start);
916 outPort.disconnect();
917 if (checkData && sizeMismatches != 0)
918 throw std::runtime_error(
919 "bandwidth read data mismatch: " + std::to_string(sizeMismatches) +
920 " payload(s) had the wrong wire size");
921 if (checkData && dataMismatches != 0)
922 throw std::runtime_error(
923 "bandwidth read data mismatch: " + std::to_string(dataMismatches) +
924 " bytes wrong; first at item " + std::to_string(firstMismatchItem) +
925 " byte " + std::to_string(firstMismatchByte) + ": expected " +
926 toHex(firstExpected) + ", got " + toHex(firstActual));
927 double bytesPerSec =
928 (double)xferCount * (width / 8.0) * 1e6 / (double)duration.count();
929 logger.info("esitester",
930 " Bandwidth test: " + std::to_string(xferCount) + " x " +
931 std::to_string(width) + " bit transfers in " +
932 std::to_string(duration.count()) + " microseconds");
933 logger.info("esitester", " bandwidth: " + formatBandwidth(bytesPerSec));
934 if (checkData)
935 logger.info("esitester", " data integrity: passed");
936}
937
939 size_t width, size_t xferCount, bool checkData) {
940
941 AppIDPath lastPath;
942 BundlePort *fromHostMMIOPort =
943 acc->resolvePort({AppID("fromhostdma", width), AppID("cmd")}, lastPath);
944 if (!fromHostMMIOPort)
945 throw std::runtime_error("bandwidth test failed. No fromhostdma[" +
946 std::to_string(width) + "] found");
947 auto *fromHostMMIO = fromHostMMIOPort->getAs<services::MMIO::MMIORegion>();
948 if (!fromHostMMIO)
949 throw std::runtime_error("bandwidth test failed. MMIO port is not MMIO");
950 services::TelemetryService::Metric *checksumPort = nullptr;
951 if (checkData) {
952 auto fromHostChild = acc->getChildren().find(AppID("fromhostdma", width));
953 if (fromHostChild == acc->getChildren().end())
954 throw std::runtime_error("bandwidth test failed. No fromhostdma[" +
955 std::to_string(width) + "] found");
956 auto checksumIter =
957 fromHostChild->second->getPorts().find(AppID("fromHostChecksum"));
958 if (checksumIter == fromHostChild->second->getPorts().end())
959 throw std::runtime_error(
960 "bandwidth write data check failed. fromHostChecksum missing");
961 checksumPort =
962 checksumIter->second.getAs<services::TelemetryService::Metric>();
963 if (!checksumPort)
964 throw std::runtime_error("bandwidth write data check failed. "
965 "fromHostChecksum not telemetry");
966 checksumPort->connect();
967 }
968 lastPath.clear();
969 BundlePort *inPortBundle =
970 acc->resolvePort({AppID("fromhostdma", width), AppID("in")}, lastPath);
971 WriteChannelPort &outPort = inPortBundle->getRawWrite("data");
972 outPort.connect();
973
974 Logger &logger = conn->getLogger();
975 logger.info("esitester", "Starting write bandwidth test with " +
976 std::to_string(xferCount) + " x " +
977 std::to_string(width) + " bit transfers");
978 std::vector<uint8_t> dataVec(engineWireBytes(width));
979 for (size_t i = 0; i < dataVec.size(); ++i)
980 dataVec[i] = i;
981 MessageData data(dataVec);
982 uint64_t expectedChecksum = 0;
983 auto start = std::chrono::high_resolution_clock::now();
984 fromHostMMIO->read(8);
985 fromHostMMIO->write(0, xferCount);
986 for (size_t index = 0; index < xferCount; ++index) {
987 if (checkData) {
988 data =
989 MessageData(enginePatternBytes(static_cast<uint32_t>(index), width));
990 expectedChecksum ^=
991 enginePayloadFold(static_cast<uint32_t>(index), width);
992 }
993 outPort.write(data);
994 logger.debug(
995 [index,
996 &data](std::string &subsystem, std::string &msg,
997 std::unique_ptr<std::map<std::string, std::any>> &details) {
998 subsystem = "esitester";
999 msg = "Payload [" + std::to_string(index) + "] = 0x" + data.toHex();
1000 });
1001 }
1002 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
1003 std::chrono::high_resolution_clock::now() - start);
1004 if (checkData) {
1005 std::vector<uint8_t> expectedLast;
1006 if (xferCount != 0)
1007 expectedLast =
1008 enginePatternBytes(static_cast<uint32_t>(xferCount - 1), width);
1009 uint64_t expectedLastValue = 0;
1010 if (!expectedLast.empty())
1011 std::memcpy(&expectedLastValue, expectedLast.data(),
1012 std::min(expectedLast.size(), sizeof(expectedLastValue)));
1013 bool complete = xferCount == 0;
1014 uint64_t lastReadValue = 0;
1015 for (size_t attempt = 0; !complete && attempt < 5000; ++attempt) {
1016 lastReadValue = fromHostMMIO->read(8);
1017 if (lastReadValue == expectedLastValue) {
1018 complete = true;
1019 break;
1020 }
1021 std::this_thread::sleep_for(std::chrono::milliseconds(1));
1022 }
1023 if (!complete)
1024 throw std::runtime_error(
1025 "bandwidth write data check timed out waiting for completion: "
1026 "expected final payload " +
1027 toHex(expectedLastValue) + ", got " + toHex(lastReadValue));
1028 const uint64_t actualChecksum = checksumPort->readInt();
1029 if (actualChecksum != expectedChecksum)
1030 throw std::runtime_error(
1031 "bandwidth write data mismatch: checksum expected " +
1032 toHex(expectedChecksum) + ", got " + toHex(actualChecksum));
1033 }
1034 if (checkData)
1035 outPort.disconnect();
1036 double bytesPerSec =
1037 (double)xferCount * (width / 8.0) * 1e6 / (double)duration.count();
1038 logger.info("esitester",
1039 " Bandwidth test: " + std::to_string(xferCount) + " x " +
1040 std::to_string(width) + " bit transfers in " +
1041 std::to_string(duration.count()) + " microseconds");
1042 logger.info("esitester", " bandwidth: " + formatBandwidth(bytesPerSec));
1043 if (checkData)
1044 logger.info("esitester", " data integrity: passed");
1045}
1046
1048 const std::vector<uint32_t> &widths,
1049 uint32_t xferCount, bool read, bool write,
1050 bool checkData) {
1051 if (read)
1052 for (uint32_t w : widths)
1053 bandwidthReadTest(conn, acc, w, xferCount, checkData);
1054 if (write)
1055 for (uint32_t w : widths)
1056 bandwidthWriteTest(conn, acc, w, xferCount, checkData);
1057}
1058
1059// Fixed 64-bit seed for the hostmem burst data pattern; must match
1060// _ESITESTER_SEQ_SEED in esiaccel/esitester.py.
1061static constexpr uint64_t kEsitesterSeqSeed = 0x5A5A5A5A5A5A5A5AULL;
1062
1063// Byte j of element i in the hostmem burst data pattern: tile (seed ^ i)
1064// across the element's bytes and XOR each byte with a distinct per-position
1065// mask so every byte is unique. Must match WriteMem/ReadMem in
1066// esiaccel/esitester.py.
1067static inline uint8_t esitesterDataByte(uint32_t i, size_t j) {
1068 uint64_t seq = (uint64_t)i ^ kEsitesterSeqSeed;
1069 return (uint8_t)(seq >> (8 * (j % 8))) ^ (uint8_t)(j * 0x9D);
1070}
1071
1072static size_t hostmemWireBytes(uint32_t width) { return (width + 7) / 8; }
1073
1074static uint8_t esitesterHostmemByte(uint32_t index, size_t byte,
1075 uint32_t width) {
1076 uint8_t value = esitesterDataByte(index, byte);
1077 const size_t tailBits = width % 8;
1078 if (tailBits != 0 && byte + 1 == hostmemWireBytes(width))
1079 value &= (uint8_t(1) << tailBits) - 1;
1080 return value;
1081}
1082
1083// Fold element i's `width` bits into its 64-bit readChecksum contribution:
1084// XOR each 64-bit chunk with a per-chunk rotate so word/byte misplacement
1085// doesn't cancel. Must match ReadMem's readChecksum in esiaccel/esitester.py.
1086static inline uint64_t esitesterElemFold(uint32_t i, uint32_t width) {
1087 size_t numBytes = hostmemWireBytes(width);
1088 size_t numChunks = (width + 63) / 64;
1089 uint64_t fold = 0;
1090 for (size_t c = 0; c < numChunks; ++c) {
1091 uint64_t chunk = 0;
1092 for (size_t b = 0; b < 8; ++b) {
1093 size_t j = 8 * c + b;
1094 if (j < numBytes)
1095 chunk |= (uint64_t)esitesterHostmemByte(i, j, width) << (8 * b);
1096 }
1097 unsigned r = (8 * c) % 64;
1098 fold ^= r ? ((chunk << r) | (chunk >> (64 - r))) : chunk;
1099 }
1100 return fold;
1101}
1102
1103//
1104// Hostmem bandwidth test
1105//
1106
1107static void
1110 uint32_t width, uint32_t xferCount) {
1111 Logger &logger = conn->getLogger();
1112 logger.info("esitester", "Starting hostmem WRITE bandwidth test: " +
1113 std::to_string(xferCount) + " x " +
1114 std::to_string(width) + " bits");
1115
1116 auto writeMemChildIter = acc->getChildren().find(AppID("writemem", width));
1117 if (writeMemChildIter == acc->getChildren().end())
1118 throw std::runtime_error("hostmem write bandwidth: writemem child missing");
1119 auto &writeMemPorts = writeMemChildIter->second->getPorts();
1120
1121 // MMIO command surface and cycle telemetry live in nested BurstCommand
1122 // submodules: MMIO at writemem[width]/mmio[width]/cmd and the active-cycle
1123 // metric at writemem[width]/addrCmdResp/cycles.
1124 AppIDPath cmdPath;
1125 BundlePort *cmdPortBundle = acc->resolvePort(
1126 {AppID("writemem", width), AppID("mmio", width), AppID("cmd")}, cmdPath);
1127 if (!cmdPortBundle)
1128 throw std::runtime_error("hostmem write bandwidth: cmd MMIO missing");
1129 auto *cmdMMIO = cmdPortBundle->getAs<services::MMIO::MMIORegion>();
1130 if (!cmdMMIO)
1131 throw std::runtime_error("hostmem write bandwidth: cmd not MMIO");
1132
1133 AppIDPath cyclePath;
1134 BundlePort *cyclePortBundle = acc->resolvePort(
1135 {AppID("writemem", width), AppID("addrCmdResp"), AppID("cycles")},
1136 cyclePath);
1137 auto issuedIter = writeMemPorts.find(AppID("addrCmdIssued"));
1138 auto respIter = writeMemPorts.find(AppID("addrCmdResponses"));
1139 if (issuedIter == writeMemPorts.end() || respIter == writeMemPorts.end() ||
1140 !cyclePortBundle)
1141 throw std::runtime_error("hostmem write bandwidth: telemetry missing");
1142 auto *issuedPort =
1143 issuedIter->second.getAs<services::TelemetryService::Metric>();
1144 auto *respPort = respIter->second.getAs<services::TelemetryService::Metric>();
1145 auto *cyclePort =
1146 cyclePortBundle->getAs<services::TelemetryService::Metric>();
1147 if (!issuedPort || !respPort || !cyclePort)
1148 throw std::runtime_error(
1149 "hostmem write bandwidth: telemetry type mismatch");
1150
1151 issuedPort->connect();
1152 respPort->connect();
1153 cyclePort->connect();
1154
1155 // Initialize pattern (optional).
1156 uint64_t *dataPtr = static_cast<uint64_t *>(region.getPtr());
1157 size_t words = region.getSize() / 8;
1158 for (size_t i = 0; i < words; ++i)
1159 dataPtr[i] = i + 0xA5A50000;
1160 region.flush();
1161
1162 auto start = std::chrono::high_resolution_clock::now();
1163 // Fire off xferCount write commands (one flit each).
1164 uint64_t devPtr = reinterpret_cast<uint64_t>(region.getDevicePtr());
1165 cmdMMIO->write(0x08, devPtr); // address
1166 cmdMMIO->write(0x10, xferCount); // flits
1167 cmdMMIO->write(0x18, 1); // start
1168
1169 // Wait for responses counter to reach target.
1170 bool completed = false;
1171 for (int wait = 0; wait < 100000; ++wait) {
1172 uint64_t respNow = respPort->readInt();
1173 if (respNow == xferCount) {
1174 completed = true;
1175 break;
1176 }
1177 std::this_thread::sleep_for(std::chrono::microseconds(50));
1178 }
1179 if (!completed)
1180 throw std::runtime_error("hostmem write bandwidth timeout");
1181 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
1182 std::chrono::high_resolution_clock::now() - start);
1183 double bytesPerSec =
1184 (double)xferCount * (width / 8.0) * 1e6 / (double)duration.count();
1185 uint64_t cycles = cyclePort->readInt();
1186 double bytesPerCycle = (double)xferCount * (width / 8.0) / (double)cycles;
1187 std::cout << "[WRITE] Hostmem bandwidth (" << std::to_string(width)
1188 << "): " << formatBandwidth(bytesPerSec) << " "
1189 << std::to_string(xferCount) << " flits in "
1190 << std::to_string(duration.count()) << " us, "
1191 << std::to_string(cycles) << " cycles, " << bytesPerCycle
1192 << " bytes/cycle" << std::endl;
1193
1194 // Data integrity: WriteMem wrote the byte-level pattern (esitesterDataByte)
1195 // into element i. The host-memory layout must be contiguous and backend-
1196 // width-independent, so element i occupies ceil(width/8) bytes at that
1197 // stride; verify every byte.
1198 uint8_t *bytePtr = static_cast<uint8_t *>(region.getPtr());
1199 size_t elemBytes = hostmemWireBytes(width);
1200 size_t mismatches = 0;
1201 uint32_t firstMismatch = 0;
1202 size_t firstByte = 0;
1203 uint8_t firstExpected = 0, firstActual = 0;
1204 for (uint32_t i = 0; i < xferCount; ++i) {
1205 for (size_t j = 0; j < elemBytes; ++j) {
1206 uint8_t expected = esitesterHostmemByte(i, j, width);
1207 uint8_t actual = bytePtr[(size_t)i * elemBytes + j];
1208 if (actual != expected) {
1209 if (mismatches == 0) {
1210 firstMismatch = i;
1211 firstByte = j;
1212 firstExpected = expected;
1213 firstActual = actual;
1214 }
1215 ++mismatches;
1216 }
1217 }
1218 }
1219 if (mismatches != 0) {
1220 char eb[8], gb[8];
1221 std::snprintf(eb, sizeof(eb), "0x%02x", firstExpected);
1222 std::snprintf(gb, sizeof(gb), "0x%02x", firstActual);
1223 throw std::runtime_error(
1224 "hostmem write bandwidth data mismatch: " + std::to_string(mismatches) +
1225 " bytes wrong; first at element " + std::to_string(firstMismatch) +
1226 " byte " + std::to_string(firstByte) + " expected=" + eb +
1227 " got=" + gb);
1228 }
1229}
1230
1231static void
1234 uint32_t width, uint32_t xferCount) {
1235 Logger &logger = conn->getLogger();
1236 logger.info("esitester", "Starting hostmem READ bandwidth test: " +
1237 std::to_string(xferCount) + " x " +
1238 std::to_string(width) + " bits");
1239
1240 auto readMemChildIter = acc->getChildren().find(AppID("readmem", width));
1241 if (readMemChildIter == acc->getChildren().end())
1242 throw std::runtime_error("hostmem read bandwidth: readmem child missing");
1243 auto &readMemPorts = readMemChildIter->second->getPorts();
1244
1245 // MMIO at readmem[width]/mmio[width]/cmd; active-cycle metric at
1246 // readmem[width]/addrCmdResp/cycles (nested BurstCommand submodules).
1247 AppIDPath cmdPath;
1248 BundlePort *cmdPortBundle = acc->resolvePort(
1249 {AppID("readmem", width), AppID("mmio", width), AppID("cmd")}, cmdPath);
1250 if (!cmdPortBundle)
1251 throw std::runtime_error("hostmem read bandwidth: cmd MMIO missing");
1252 auto *cmdMMIO = cmdPortBundle->getAs<services::MMIO::MMIORegion>();
1253 if (!cmdMMIO)
1254 throw std::runtime_error("hostmem read bandwidth: cmd not MMIO");
1255
1256 AppIDPath cyclePath;
1257 BundlePort *cyclePortBundle = acc->resolvePort(
1258 {AppID("readmem", width), AppID("addrCmdResp"), AppID("cycles")},
1259 cyclePath);
1260 auto issuedIter = readMemPorts.find(AppID("addrCmdIssued"));
1261 auto respIter = readMemPorts.find(AppID("addrCmdResponses"));
1262 auto checksumIter = readMemPorts.find(AppID("readChecksum"));
1263 if (issuedIter == readMemPorts.end() || respIter == readMemPorts.end() ||
1264 checksumIter == readMemPorts.end() || !cyclePortBundle)
1265 throw std::runtime_error("hostmem read bandwidth: telemetry missing");
1266 auto *issuedPort =
1267 issuedIter->second.getAs<services::TelemetryService::Metric>();
1268 auto *respPort = respIter->second.getAs<services::TelemetryService::Metric>();
1269 auto *checksumPort =
1270 checksumIter->second.getAs<services::TelemetryService::Metric>();
1271 auto *cycleCntPort =
1272 cyclePortBundle->getAs<services::TelemetryService::Metric>();
1273 if (!issuedPort || !respPort || !checksumPort || !cycleCntPort)
1274 throw std::runtime_error("hostmem read bandwidth: telemetry type mismatch");
1275 issuedPort->connect();
1276 respPort->connect();
1277 checksumPort->connect();
1278 cycleCntPort->connect();
1279
1280 // Lay out the read data contiguously (natural ceil(width/8)-byte stride)
1281 // using the byte-level pattern for every byte; ReadMem folds each received
1282 // element into readChecksum, which must match this host-side fold if the
1283 // read fetched the right bytes.
1284 uint8_t *bytePtr = static_cast<uint8_t *>(region.getPtr());
1285 size_t elemBytes = hostmemWireBytes(width);
1286 uint64_t expectedChecksum = 0;
1287 for (uint32_t i = 0; i < xferCount; ++i) {
1288 for (size_t j = 0; j < elemBytes; ++j)
1289 bytePtr[(size_t)i * elemBytes + j] = esitesterHostmemByte(i, j, width);
1290 expectedChecksum ^= esitesterElemFold(i, width);
1291 }
1292 region.flush();
1293 uint64_t devPtr = reinterpret_cast<uint64_t>(region.getDevicePtr());
1294 auto start = std::chrono::high_resolution_clock::now();
1295
1296 cmdMMIO->write(0x08, devPtr);
1297 cmdMMIO->write(0x10, xferCount);
1298 cmdMMIO->write(0x18, 1);
1299
1300 bool timeout = true;
1301 for (int wait = 0; wait < 100000; ++wait) {
1302 uint64_t respNow = respPort->readInt();
1303 if (respNow == xferCount) {
1304 timeout = false;
1305 break;
1306 }
1307 std::this_thread::sleep_for(std::chrono::microseconds(50));
1308 }
1309 if (timeout)
1310 throw std::runtime_error("hostmem read bandwidth timeout");
1311 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
1312 std::chrono::high_resolution_clock::now() - start);
1313 double bytesPerSec =
1314 (double)xferCount * (width / 8.0) * 1e6 / (double)duration.count();
1315 uint64_t cycles = cycleCntPort->readInt();
1316 double bytesPerCycle = (double)xferCount * (width / 8.0) / (double)cycles;
1317 std::cout << "[ READ] Hostmem bandwidth (" << width
1318 << "): " << formatBandwidth(bytesPerSec) << ", " << xferCount
1319 << " flits in " << duration.count() << " us, " << cycles
1320 << " cycles, " << bytesPerCycle << " bytes/cycle" << std::endl;
1321
1322 uint64_t gotChecksum = checksumPort->readInt();
1323 if (gotChecksum != expectedChecksum) {
1324 char eb[24], gb[24];
1325 std::snprintf(eb, sizeof(eb), "0x%016llx",
1326 (unsigned long long)expectedChecksum);
1327 std::snprintf(gb, sizeof(gb), "0x%016llx", (unsigned long long)gotChecksum);
1328 throw std::runtime_error(
1329 std::string(
1330 "hostmem read bandwidth data mismatch: checksum expected ") +
1331 eb + " got " + gb);
1332 }
1333}
1334
1336 uint32_t xferCount,
1337 const std::vector<uint32_t> &widths, bool read,
1338 bool write) {
1339 auto hostmemSvc = conn->getService<services::HostMem>();
1340 hostmemSvc->start();
1341 auto region = hostmemSvc->allocate(/*size(bytes)=*/1024 * 1024 * 1024,
1342 /*memOpts=*/{.writeable = true});
1343 if (!region)
1344 throw std::runtime_error(
1345 "hostmem bandwidth test: failed to allocate host memory");
1346 for (uint32_t w : widths) {
1347 if (write)
1348 hostmemWriteBandwidthTest(conn, acc, *region, w, xferCount);
1349 if (read)
1350 hostmemReadBandwidthTest(conn, acc, *region, w, xferCount);
1351 }
1352}
1353
1355 uint32_t iterations, bool pipeline) {
1356 Logger &logger = conn->getLogger();
1357 auto loopbackChild = accel->getChildren().find(AppID("loopback"));
1358 if (loopbackChild == accel->getChildren().end())
1359 throw std::runtime_error("Loopback test: no 'loopback' child");
1360 auto &ports = loopbackChild->second->getPorts();
1361 auto addIter = ports.find(AppID("add"));
1362 if (addIter == ports.end())
1363 throw std::runtime_error("Loopback test: no 'add' port");
1364
1365 // Use FuncService::Func instead of raw channels.
1366 auto *funcPort = addIter->second.getAs<services::FuncService::Function>();
1367 if (!funcPort)
1368 throw std::runtime_error(
1369 "Loopback test: 'add' port not a FuncService::Function");
1370 funcPort->connect();
1371 if (iterations == 0) {
1372 logger.info("esitester", "Loopback add test: 0 iterations (skipped)");
1373 return;
1374 }
1375 std::mt19937_64 rng(0xC0FFEE);
1376 std::uniform_int_distribution<uint32_t> dist(0, (1u << 24) - 1);
1377
1378 if (!pipeline) {
1379 auto start = std::chrono::high_resolution_clock::now();
1380 for (uint32_t i = 0; i < iterations; ++i) {
1381 uint32_t argVal = dist(rng);
1382 uint32_t expected = (argVal + 11) & 0xFFFF;
1383 uint8_t argBytes[3] = {
1384 static_cast<uint8_t>(argVal & 0xFF),
1385 static_cast<uint8_t>((argVal >> 8) & 0xFF),
1386 static_cast<uint8_t>((argVal >> 16) & 0xFF),
1387 };
1388 MessageData argMsg(argBytes, 3);
1389 MessageData resMsg = funcPort->call(argMsg).get();
1390 uint16_t got = *resMsg.as<uint16_t>();
1391 std::cout << "[loopback] i=" << i << " arg=0x" << esi::toHex(argVal)
1392 << " got=0x" << esi::toHex(got) << " exp=0x"
1393 << esi::toHex(expected) << std::endl;
1394 if (got != expected)
1395 throw std::runtime_error("Loopback mismatch (non-pipelined)");
1396 }
1397 auto end = std::chrono::high_resolution_clock::now();
1398 auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start)
1399 .count();
1400 double callsPerSec = (double)iterations * 1e6 / (double)us;
1401 logger.info("esitester", "Loopback add test passed (non-pipelined, " +
1402 std::to_string(iterations) + " calls, " +
1403 std::to_string(us) + " us, " +
1404 std::to_string(callsPerSec) + " calls/s)");
1405 } else {
1406 // Pipelined mode: launch all calls first, then collect.
1407 std::vector<std::future<MessageData>> futures;
1408 futures.reserve(iterations);
1409 std::vector<uint32_t> expectedVals;
1410 expectedVals.reserve(iterations);
1411
1412 auto issueStart = std::chrono::high_resolution_clock::now();
1413 for (uint32_t i = 0; i < iterations; ++i) {
1414 uint32_t argVal = dist(rng);
1415 uint32_t expected = (argVal + 11) & 0xFFFF;
1416 uint8_t argBytes[3] = {
1417 static_cast<uint8_t>(argVal & 0xFF),
1418 static_cast<uint8_t>((argVal >> 8) & 0xFF),
1419 static_cast<uint8_t>((argVal >> 16) & 0xFF),
1420 };
1421 futures.emplace_back(funcPort->call(MessageData(argBytes, 3)));
1422 expectedVals.emplace_back(expected);
1423 }
1424 auto issueEnd = std::chrono::high_resolution_clock::now();
1425
1426 for (uint32_t i = 0; i < iterations; ++i) {
1427 MessageData resMsg = futures[i].get();
1428 uint16_t got = *resMsg.as<uint16_t>();
1429 uint16_t exp = (uint16_t)expectedVals[i];
1430 std::cout << "[loopback-pipelined] i=" << i << " got=0x"
1431 << esi::toHex(got) << " exp=0x" << esi::toHex(exp) << std::endl;
1432 if (got != exp)
1433 throw std::runtime_error("Loopback mismatch (pipelined) idx=" +
1434 std::to_string(i));
1435 }
1436 auto collectEnd = std::chrono::high_resolution_clock::now();
1437
1438 auto issueUs = std::chrono::duration_cast<std::chrono::microseconds>(
1439 issueEnd - issueStart)
1440 .count();
1441 auto totalUs = std::chrono::duration_cast<std::chrono::microseconds>(
1442 collectEnd - issueStart)
1443 .count();
1444
1445 double issueRate = (double)iterations * 1e6 / (double)issueUs;
1446 double completionRate = (double)iterations * 1e6 / (double)totalUs;
1447
1448 logger.info("esitester", "Loopback add test passed (pipelined). Issued " +
1449 std::to_string(iterations) + " in " +
1450 std::to_string(issueUs) + " us (" +
1451 std::to_string(issueRate) +
1452 " calls/s), total " + std::to_string(totalUs) +
1453 " us (" + std::to_string(completionRate) +
1454 " calls/s effective)");
1455 }
1456}
1457
1458// Exercise the design reset feature using existing telemetry. Run a hostmem
1459// write operation on the 'writemem' module, which increments its
1460// 'addrCmdResponses' telemetry counter. Confirm the telemetry advanced,
1461// request a design reset, then confirm the telemetry has been cleared back to
1462// zero (the counters live in the user design which the reset clears).
1463static void resetTest(AcceleratorConnection *conn, Accelerator *accel) {
1464 Logger &logger = conn->getLogger();
1465 constexpr uint32_t width = 64;
1466
1467 // Run an existing test that increments telemetry. The hostmem write test
1468 // bumps the writemem module's 'addrCmdResponses' counter.
1469 hostmemTest(conn, accel, {width}, /*write=*/true, /*read=*/false);
1470
1471 // Grab the writemem module's response telemetry counter to observe the
1472 // reset.
1473 auto writeMemChildIter = accel->getChildren().find(AppID("writemem", width));
1474 if (writeMemChildIter == accel->getChildren().end())
1475 throw std::runtime_error("Reset test: no 'writemem' child");
1476 auto &ports = writeMemChildIter->second->getPorts();
1477 auto respIter = ports.find(AppID("addrCmdResponses"));
1478 if (respIter == ports.end())
1479 throw std::runtime_error(
1480 "Reset test: no 'addrCmdResponses' telemetry port");
1481 auto *respMetric =
1482 respIter->second.getAs<services::TelemetryService::Metric>();
1483 if (!respMetric)
1484 throw std::runtime_error("Reset test: 'addrCmdResponses' not telemetry");
1485 respMetric->connect();
1486
1487 uint64_t before = respMetric->readInt();
1488 std::cout << "[reset] telemetry addrCmdResponses before reset = " << before
1489 << std::endl;
1490 if (before == 0)
1491 throw std::runtime_error(
1492 "Reset test: telemetry was not incremented by the hostmem write");
1493
1494 // Request a design reset.
1495 logger.info("esitester", "Requesting design reset");
1496 if (!conn->reset())
1497 throw std::runtime_error("Reset test: reset() reported failure");
1498 std::cout << "[reset] reset requested" << std::endl;
1499
1500 // The reset is asserted a fixed number of cycles after the request (to let
1501 // in-flight transactions drain), so poll the telemetry until it clears.
1502 uint64_t after = before;
1503 constexpr int maxPolls = 1000000;
1504 for (int polls = 0; polls < maxPolls; ++polls) {
1505 after = respMetric->readInt();
1506 if (after == 0)
1507 break;
1508 std::this_thread::sleep_for(std::chrono::microseconds(1));
1509 }
1510 std::cout << "[reset] telemetry addrCmdResponses after reset = " << after
1511 << std::endl;
1512 if (after != 0)
1513 throw std::runtime_error(
1514 "Reset test: telemetry was not cleared by the reset (got " +
1515 std::to_string(after) + ")");
1516
1517 std::cout << "Reset test passed" << std::endl;
1518}
1519
1521 Accelerator *acc, uint32_t width,
1522 uint32_t xferCount, bool read,
1523 bool write) {
1524 Logger &logger = conn->getLogger();
1525 if (!read && !write) {
1526 std::cout << "aggbandwidth: nothing to do (enable --read and/or --write)\n";
1527 return;
1528 }
1529 logger.info(
1530 "esitester",
1531 "Aggregate hostmem bandwidth start width=" + std::to_string(width) +
1532 " count=" + std::to_string(xferCount) +
1533 " read=" + (read ? "Y" : "N") + " write=" + (write ? "Y" : "N"));
1534
1535 auto hostmemSvc = conn->getService<services::HostMem>();
1536 hostmemSvc->start();
1537
1538 struct Unit {
1539 std::string prefix;
1540 bool isRead = false;
1541 bool isWrite = false;
1542 std::unique_ptr<esi::services::HostMem::HostMemRegion> region;
1543 services::TelemetryService::Metric *resp = nullptr;
1544 services::TelemetryService::Metric *cycles = nullptr;
1545 services::MMIO::MMIORegion *cmd = nullptr;
1546 bool launched = false;
1547 bool done = false;
1548 uint64_t bytes = 0;
1549 uint64_t duration_us = 0;
1550 uint64_t cycleCount = 0;
1551 std::chrono::high_resolution_clock::time_point start;
1552 };
1553 std::vector<Unit> units;
1554 const std::vector<std::string> readPrefixes = {"readmem", "readmem_0",
1555 "readmem_1", "readmem_2"};
1556 const std::vector<std::string> writePrefixes = {"writemem", "writemem_0",
1557 "writemem_1", "writemem_2"};
1558
1559 // Size each unit's region to the actual transfer (min 1 MiB) rather than a
1560 // fixed 1 GiB, so aggregating many units stays memory-bounded.
1561 size_t strideBytes = ((width + 31) / 32) * 4;
1562 size_t neededBytes = static_cast<size_t>(xferCount) * strideBytes;
1563 size_t regionBytes = neededBytes < (1u << 20) ? (1u << 20) : neededBytes;
1564
1565 auto addUnits = [&](const std::vector<std::string> &pref, bool doRead,
1566 bool doWrite) {
1567 for (auto &p : pref) {
1568 AppID id(p, width);
1569 auto childIt = acc->getChildren().find(id);
1570 if (childIt == acc->getChildren().end())
1571 continue; // silently skip missing variants
1572 auto &ports = childIt->second->getPorts();
1573 auto respIt = ports.find(AppID("addrCmdResponses"));
1574 // MMIO ('cmd') and the cycle metric are nested inside BurstCommand
1575 // submodules: <unit>/mmio[width]/cmd and <unit>/addrCmdResp/cycles.
1576 AppIDPath cmdPath, cycPath;
1577 BundlePort *cmdBundle =
1578 acc->resolvePort({id, AppID("mmio", width), AppID("cmd")}, cmdPath);
1579 BundlePort *cycBundle = acc->resolvePort(
1580 {id, AppID("addrCmdResp"), AppID("cycles")}, cycPath);
1581 if (respIt == ports.end() || !cmdBundle || !cycBundle)
1582 continue;
1583 auto *cmd = cmdBundle->getAs<services::MMIO::MMIORegion>();
1584 auto *resp = respIt->second.getAs<services::TelemetryService::Metric>();
1585 auto *cyc = cycBundle->getAs<services::TelemetryService::Metric>();
1586 if (!cmd || !resp || !cyc)
1587 continue;
1588 resp->connect();
1589 cyc->connect();
1590 Unit u;
1591 u.prefix = p;
1592 u.isRead = doRead;
1593 u.isWrite = doWrite;
1594 u.region = hostmemSvc->allocate(regionBytes, {.writeable = true});
1595 if (!u.region)
1596 throw std::runtime_error(
1597 "hostmem bandwidth test: failed to allocate host memory");
1598 // Init pattern.
1599 uint64_t *ptr = static_cast<uint64_t *>(u.region->getPtr());
1600 size_t words = u.region->getSize() / 8;
1601 for (size_t i = 0; i < words; ++i)
1602 ptr[i] =
1603 (p[0] == 'w' ? (0xA5A500000000ull + i) : (0xCAFEBABE0000ull + i));
1604 u.region->flush();
1605 u.cmd = cmd;
1606 u.resp = resp;
1607 u.cycles = cyc;
1608 u.bytes = uint64_t(xferCount) * (width / 8);
1609 units.emplace_back(std::move(u));
1610 }
1611 };
1612 if (read)
1613 addUnits(readPrefixes, true, false);
1614 if (write)
1615 addUnits(writePrefixes, false, true);
1616 if (units.empty()) {
1617 std::cout << "aggbandwidth: no matching units present for width " << width
1618 << "\n";
1619 return;
1620 }
1621
1622 auto wallStart = std::chrono::high_resolution_clock::now();
1623 // Launch sequentially.
1624 for (auto &u : units) {
1625 uint64_t devPtr = reinterpret_cast<uint64_t>(u.region->getDevicePtr());
1626 u.cmd->write(0x08, devPtr);
1627 u.cmd->write(0x10, xferCount);
1628 u.cmd->write(0x18, 1);
1629 u.start = std::chrono::high_resolution_clock::now();
1630 u.launched = true;
1631 }
1632
1633 // Poll all until complete.
1634 const uint64_t timeoutLoops = 200000; // ~10s at 50us sleep
1635 uint64_t loops = 0;
1636 while (true) {
1637 bool allDone = true;
1638 for (auto &u : units) {
1639 if (u.done)
1640 continue;
1641 if (u.resp->readInt() == xferCount) {
1642 auto end = std::chrono::high_resolution_clock::now();
1643 u.duration_us =
1644 std::chrono::duration_cast<std::chrono::microseconds>(end - u.start)
1645 .count();
1646 u.cycleCount = u.cycles->readInt();
1647 u.done = true;
1648 } else {
1649 allDone = false;
1650 }
1651 }
1652 if (allDone)
1653 break;
1654 if (++loops >= timeoutLoops)
1655 throw std::runtime_error("aggbandwidth: timeout");
1656 std::this_thread::sleep_for(std::chrono::microseconds(50));
1657 }
1658 auto wallUs = std::chrono::duration_cast<std::chrono::microseconds>(
1659 std::chrono::high_resolution_clock::now() - wallStart)
1660 .count();
1661
1662 uint64_t totalBytes = 0;
1663 uint64_t totalReadBytes = 0;
1664 uint64_t totalWriteBytes = 0;
1665 for (auto &u : units) {
1666 totalBytes += u.bytes;
1667 if (u.isRead)
1668 totalReadBytes += u.bytes;
1669 if (u.isWrite)
1670 totalWriteBytes += u.bytes;
1671 double unitBps = (double)u.bytes * 1e6 / (double)u.duration_us;
1672 std::cout << "[agg-unit] " << u.prefix << "[" << width << "] "
1673 << (u.isRead ? "READ" : (u.isWrite ? "WRITE" : "UNK"))
1674 << " bytes=" << humanBytes(u.bytes) << " (" << u.bytes << " B)"
1675 << " time=" << humanTimeUS(u.duration_us) << " (" << u.duration_us
1676 << " us) cycles=" << u.cycleCount
1677 << " throughput=" << formatBandwidth(unitBps) << std::endl;
1678 }
1679 // Compute aggregate bandwidths as total size / total wall time (not sum of
1680 // unit throughputs).
1681 double aggReadBps =
1682 totalReadBytes ? (double)totalReadBytes * 1e6 / (double)wallUs : 0.0;
1683 double aggWriteBps =
1684 totalWriteBytes ? (double)totalWriteBytes * 1e6 / (double)wallUs : 0.0;
1685 double aggCombinedBps =
1686 totalBytes ? (double)totalBytes * 1e6 / (double)wallUs : 0.0;
1687
1688 std::cout << "[agg-total] units=" << units.size()
1689 << " read_bytes=" << humanBytes(totalReadBytes) << " ("
1690 << totalReadBytes << " B)"
1691 << " read_bw=" << formatBandwidth(aggReadBps)
1692 << " write_bytes=" << humanBytes(totalWriteBytes) << " ("
1693 << totalWriteBytes << " B)"
1694 << " write_bw=" << formatBandwidth(aggWriteBps)
1695 << " combined_bytes=" << humanBytes(totalBytes) << " ("
1696 << totalBytes << " B)"
1697 << " combined_bw=" << formatBandwidth(aggCombinedBps)
1698 << " wall_time=" << humanTimeUS(wallUs) << " (" << wallUs << " us)"
1699 << std::endl;
1700 logger.info("esitester", "Aggregate hostmem bandwidth test complete");
1701}
1702
1703/// Packed struct representing a parallel window argument for StreamingAdder.
1704/// Layout in SystemVerilog (so it must be reversed in C):
1705/// { add_amt: UInt(32), input: UInt(32), last: UInt(8) }
1706#pragma pack(push, 1)
1708 uint8_t last;
1709 uint32_t input;
1710 uint32_t addAmt;
1711};
1712#pragma pack(pop)
1713static_assert(sizeof(StreamingAddArg) == 9,
1714 "StreamingAddArg must be 9 bytes packed");
1715
1716/// Packed struct representing a parallel window result for StreamingAdder.
1717/// Layout in SystemVerilog (so it must be reversed in C):
1718/// { data: UInt(32), last: UInt(8) }
1719#pragma pack(push, 1)
1721 uint8_t last;
1722 uint32_t data;
1723};
1724#pragma pack(pop)
1725static_assert(sizeof(StreamingAddResult) == 5,
1726 "StreamingAddResult must be 5 bytes packed");
1727
1728/// Test the StreamingAdder module. This module takes a struct containing
1729/// an add_amt and a list of uint32s, adds add_amt to each element, and
1730/// returns the resulting list. The data is streamed using windowed types.
1732 uint32_t addAmt, uint32_t numItems) {
1733 Logger &logger = conn->getLogger();
1734 logger.info("esitester", "Starting streaming add test with add_amt=" +
1735 std::to_string(addAmt) +
1736 ", num_items=" + std::to_string(numItems));
1737
1738 // Generate random input data.
1739 std::mt19937 rng(0xDEADBEEF);
1740 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
1741 std::vector<uint32_t> inputData;
1742 inputData.reserve(numItems);
1743 for (uint32_t i = 0; i < numItems; ++i)
1744 inputData.push_back(dist(rng));
1745
1746 // Find the streaming_adder child.
1747 auto streamingAdderChild =
1748 accel->getChildren().find(AppID("streaming_adder"));
1749 if (streamingAdderChild == accel->getChildren().end())
1750 throw std::runtime_error(
1751 "Streaming add test: no 'streaming_adder' child found");
1752
1753 auto &ports = streamingAdderChild->second->getPorts();
1754 auto addIter = ports.find(AppID("streaming_add"));
1755 if (addIter == ports.end())
1756 throw std::runtime_error(
1757 "Streaming add test: no 'streaming_add' port found");
1758
1759 // Get the raw read/write channel ports for the windowed function.
1760 // The argument channel expects parallel windowed data where each message
1761 // contains: struct { add_amt: UInt(32), input: UInt(32), last: bool }
1762 WriteChannelPort &argPort = addIter->second.getRawWrite("arg");
1763 ReadChannelPort &resultPort = addIter->second.getRawRead("result");
1764
1765 argPort.connect(ChannelPort::ConnectOptions(std::nullopt, false));
1766 resultPort.connect(ChannelPort::ConnectOptions(std::nullopt, false));
1767
1768 // Send each list element with add_amt repeated in every message.
1769 for (size_t i = 0; i < inputData.size(); ++i) {
1770 StreamingAddArg arg;
1771 arg.addAmt = addAmt;
1772 arg.input = inputData[i];
1773 arg.last = (i == inputData.size() - 1) ? 1 : 0;
1774 argPort.write(
1775 MessageData(reinterpret_cast<const uint8_t *>(&arg), sizeof(arg)));
1776 logger.debug("esitester", "Sent {add_amt=" + std::to_string(arg.addAmt) +
1777 ", input=" + std::to_string(arg.input) +
1778 ", last=" + (arg.last ? "true" : "false") +
1779 "}");
1780 }
1781
1782 // Read the result list (also windowed).
1783 std::vector<uint32_t> results;
1784 bool lastSeen = false;
1785 while (!lastSeen) {
1786 MessageData resMsg;
1787 resultPort.read(resMsg);
1788 if (resMsg.getSize() < sizeof(StreamingAddResult))
1789 throw std::runtime_error(
1790 "Streaming add test: unexpected result message size");
1791
1792 const auto *res =
1793 reinterpret_cast<const StreamingAddResult *>(resMsg.getBytes());
1794 lastSeen = res->last != 0;
1795 results.push_back(res->data);
1796 logger.debug("esitester", "Received result=" + std::to_string(res->data) +
1797 " (last=" + (lastSeen ? "true" : "false") +
1798 ")");
1799 }
1800
1801 // Verify results.
1802 if (results.size() != inputData.size())
1803 throw std::runtime_error(
1804 "Streaming add test: result size mismatch. Expected " +
1805 std::to_string(inputData.size()) + ", got " +
1806 std::to_string(results.size()));
1807
1808 bool passed = true;
1809 std::cout << "Streaming add test results:" << std::endl;
1810 for (size_t i = 0; i < inputData.size(); ++i) {
1811 uint32_t expected = inputData[i] + addAmt;
1812 std::cout << " input[" << i << "]=" << inputData[i] << " + " << addAmt
1813 << " = " << results[i] << " (expected " << expected << ")";
1814 if (results[i] != expected) {
1815 std::cout << " MISMATCH!";
1816 passed = false;
1817 }
1818 std::cout << std::endl;
1819 }
1820
1821 argPort.disconnect();
1822 resultPort.disconnect();
1823
1824 if (!passed)
1825 throw std::runtime_error("Streaming add test failed: result mismatch");
1826
1827 logger.info("esitester", "Streaming add test passed");
1828 std::cout << "Streaming add test passed" << std::endl;
1829}
1830
1831/// Test the StreamingAdder module using message translation.
1832/// This version uses the list translation support where the message format is:
1833/// Argument: { add_amt (4 bytes), input_length (8 bytes), input_data[] }
1834/// Result: { data_length (8 bytes), data[] }
1835/// The translation layer automatically converts between this format and the
1836/// parallel windowed frames used by the hardware.
1837
1838/// Translated argument struct for StreamingAdder.
1839/// Memory layout (standard C struct ordering, fields in declaration order):
1840/// ESI type: struct { add_amt: UInt(32), input: List<UInt(32)> }
1841/// becomes host struct:
1842/// { input_length (size_t, 8 bytes on 64-bit), add_amt (uint32_t),
1843/// input_data[] }
1844/// Note: The translation layer handles the conversion between this C struct
1845/// layout and the hardware's SystemVerilog frame format.
1846/// Note: size_t is used for list lengths, so this format is platform-dependent.
1847#pragma pack(push, 1)
1850 uint32_t addAmt;
1851 // Trailing array data follows immediately after the struct in memory.
1852 // Use inputData() accessor to access it.
1853
1854 /// Get pointer to trailing input data array.
1855 uint32_t *inputData() { return reinterpret_cast<uint32_t *>(this + 1); }
1856 const uint32_t *inputData() const {
1857 return reinterpret_cast<const uint32_t *>(this + 1);
1858 }
1859 /// Get span view of input data (requires inputLength to be set first).
1860 std::span<uint32_t> inputDataSpan() { return {inputData(), inputLength}; }
1861 std::span<const uint32_t> inputDataSpan() const {
1862 return {inputData(), inputLength};
1863 }
1864
1865 static size_t allocSize(size_t numItems) {
1866 return sizeof(StreamingAddTranslatedArg) + numItems * sizeof(uint32_t);
1867 }
1868};
1869#pragma pack(pop)
1870
1871/// Translated result struct for StreamingAdder.
1872/// Memory layout:
1873/// struct { data: List<UInt(32)> }
1874/// becomes:
1875/// { data_length (size_t, 8 bytes on 64-bit), data[] }
1876#pragma pack(push, 1)
1879 // Trailing array data follows immediately after the struct in memory.
1880
1881 /// Get pointer to trailing result data array.
1882 uint32_t *data() { return reinterpret_cast<uint32_t *>(this + 1); }
1883 const uint32_t *data() const {
1884 return reinterpret_cast<const uint32_t *>(this + 1);
1885 }
1886 /// Get span view of result data (requires dataLength to be set first).
1887 std::span<uint32_t> dataSpan() { return {data(), dataLength}; }
1888 std::span<const uint32_t> dataSpan() const { return {data(), dataLength}; }
1889
1890 static size_t allocSize(size_t numItems) {
1891 return sizeof(StreamingAddTranslatedResult) + numItems * sizeof(uint32_t);
1892 }
1893};
1894#pragma pack(pop)
1895
1897 Accelerator *accel, uint32_t addAmt,
1898 uint32_t numItems) {
1899 Logger &logger = conn->getLogger();
1900 logger.info("esitester",
1901 "Starting streaming add test (translated) with add_amt=" +
1902 std::to_string(addAmt) +
1903 ", num_items=" + std::to_string(numItems));
1904
1905 // Generate random input data.
1906 std::mt19937 rng(0xDEADBEEF);
1907 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
1908 std::vector<uint32_t> inputData;
1909 inputData.reserve(numItems);
1910 for (uint32_t i = 0; i < numItems; ++i)
1911 inputData.push_back(dist(rng));
1912
1913 // Find the streaming_adder child.
1914 auto streamingAdderChild =
1915 accel->getChildren().find(AppID("streaming_adder"));
1916 if (streamingAdderChild == accel->getChildren().end())
1917 throw std::runtime_error(
1918 "Streaming add test: no 'streaming_adder' child found");
1919
1920 auto &ports = streamingAdderChild->second->getPorts();
1921 auto addIter = ports.find(AppID("streaming_add"));
1922 if (addIter == ports.end())
1923 throw std::runtime_error(
1924 "Streaming add test: no 'streaming_add' port found");
1925
1926 // Get the raw read/write channel ports with translation enabled (default).
1927 WriteChannelPort &argPort = addIter->second.getRawWrite("arg");
1928 ReadChannelPort &resultPort = addIter->second.getRawRead("result");
1929
1930 // Connect with translation enabled (the default).
1931 argPort.connect();
1932 resultPort.connect();
1933
1934 // Allocate the argument struct with proper alignment for the struct
1935 // members. We use aligned_alloc to ensure the buffer meets alignment
1936 // requirements.
1937 size_t argSize = StreamingAddTranslatedArg::allocSize(numItems);
1938 constexpr size_t alignment = alignof(StreamingAddTranslatedArg);
1939 // aligned_alloc requires size to be a multiple of alignment
1940 size_t allocSize = ((argSize + alignment - 1) / alignment) * alignment;
1941 void *argRaw = alignedAllocCompat(alignment, allocSize);
1942 if (!argRaw)
1943 throw std::bad_alloc();
1944 auto argDeleter = [](void *p) { alignedFreeCompat(p); };
1945 std::unique_ptr<void, decltype(argDeleter)> argBuffer(argRaw, argDeleter);
1946 auto *arg = static_cast<StreamingAddTranslatedArg *>(argRaw);
1947 arg->inputLength = numItems;
1948 arg->addAmt = addAmt;
1949 for (uint32_t i = 0; i < numItems; ++i)
1950 arg->inputData()[i] = inputData[i];
1951
1952 logger.debug("esitester",
1953 "Sending translated argument: " + std::to_string(argSize) +
1954 " bytes, list_length=" + std::to_string(arg->inputLength) +
1955 ", add_amt=" + std::to_string(arg->addAmt));
1956
1957 // Send the complete message - translation will split it into frames.
1958 argPort.write(MessageData(reinterpret_cast<const uint8_t *>(arg), argSize));
1959 // argBuffer automatically freed when it goes out of scope
1960
1961 // Read the translated result.
1962 MessageData resMsg;
1963 resultPort.read(resMsg);
1964
1965 logger.debug("esitester", "Received translated result: " +
1966 std::to_string(resMsg.getSize()) + " bytes");
1967
1968 if (resMsg.getSize() < sizeof(StreamingAddTranslatedResult))
1969 throw std::runtime_error(
1970 "Streaming add test (translated): result too small");
1971
1972 const auto *result =
1973 reinterpret_cast<const StreamingAddTranslatedResult *>(resMsg.getBytes());
1974
1975 if (resMsg.getSize() <
1976 StreamingAddTranslatedResult::allocSize(result->dataLength))
1977 throw std::runtime_error(
1978 "Streaming add test (translated): result data truncated");
1979
1980 // Verify results.
1981 if (result->dataLength != inputData.size())
1982 throw std::runtime_error(
1983 "Streaming add test (translated): result size mismatch. Expected " +
1984 std::to_string(inputData.size()) + ", got " +
1985 std::to_string(result->dataLength));
1986
1987 bool passed = true;
1988 std::cout << "Streaming add test results:" << std::endl;
1989 for (size_t i = 0; i < inputData.size(); ++i) {
1990 uint32_t expected = inputData[i] + addAmt;
1991 std::cout << " input[" << i << "]=" << inputData[i] << " + " << addAmt
1992 << " = " << result->data()[i] << " (expected " << expected << ")";
1993 if (result->data()[i] != expected) {
1994 std::cout << " MISMATCH!";
1995 passed = false;
1996 }
1997 std::cout << std::endl;
1998 }
1999
2000 argPort.disconnect();
2001 resultPort.disconnect();
2002
2003 if (!passed)
2004 throw std::runtime_error(
2005 "Streaming add test (translated) failed: result mismatch");
2006
2007 logger.info("esitester", "Streaming add test passed (translated)");
2008 std::cout << "Streaming add test passed" << std::endl;
2009}
2010
2011/// Test the CoordTranslator module using message translation.
2012/// This version uses the list translation support where the message format is:
2013/// Argument: { x_translation, y_translation, coords_length, coords[] }
2014/// Result: { coords_length, coords[] }
2015/// Each coord is a struct { x, y }.
2016
2017/// Coordinate struct for CoordTranslator.
2018/// SV ordering means y comes before x in memory.
2019#pragma pack(push, 1)
2020struct Coord {
2021 uint32_t y; // SV ordering: last declared field first in memory
2022 uint32_t x;
2023};
2024#pragma pack(pop)
2025static_assert(sizeof(Coord) == 8, "Coord must be 8 bytes packed");
2026
2027/// Translated argument struct for CoordTranslator.
2028/// Memory layout (standard C struct ordering):
2029/// ESI type: struct { x_translation: UInt(32), y_translation: UInt(32),
2030/// coords: List<struct{x, y}> }
2031/// becomes host struct:
2032/// { coords_length (size_t, 8 bytes on 64-bit), y_translation (uint32_t),
2033/// x_translation (uint32_t), coords[] }
2034/// Note: Fields are in reverse order due to SV struct ordering.
2035/// Note: size_t is used for list lengths, so this format is platform-dependent.
2036#pragma pack(push, 1)
2039 uint32_t yTranslation; // SV ordering: last declared field first in memory
2041 // Trailing array data follows immediately after the struct in memory.
2042
2043 /// Get pointer to trailing coords array.
2044 Coord *coords() { return reinterpret_cast<Coord *>(this + 1); }
2045 const Coord *coords() const {
2046 return reinterpret_cast<const Coord *>(this + 1);
2047 }
2048 /// Get span view of coords (requires coordsLength to be set first).
2049 std::span<Coord> coordsSpan() { return {coords(), coordsLength}; }
2050 std::span<const Coord> coordsSpan() const { return {coords(), coordsLength}; }
2051
2052 static size_t allocSize(size_t numCoords) {
2053 return sizeof(CoordTranslateArg) + numCoords * sizeof(Coord);
2054 }
2055};
2056#pragma pack(pop)
2057
2058/// Translated result struct for CoordTranslator.
2059/// Memory layout:
2060/// ESI type: List<struct{x, y}>
2061/// becomes host struct:
2062/// { coords_length (size_t, 8 bytes on 64-bit), coords[] }
2063#pragma pack(push, 1)
2066 // Trailing array data follows immediately after the struct in memory.
2067
2068 /// Get pointer to trailing coords array.
2069 Coord *coords() { return reinterpret_cast<Coord *>(this + 1); }
2070 const Coord *coords() const {
2071 return reinterpret_cast<const Coord *>(this + 1);
2072 }
2073 /// Get span view of coords (requires coordsLength to be set first).
2074 std::span<Coord> coordsSpan() { return {coords(), coordsLength}; }
2075 std::span<const Coord> coordsSpan() const { return {coords(), coordsLength}; }
2076
2077 static size_t allocSize(size_t numCoords) {
2078 return sizeof(CoordTranslateResult) + numCoords * sizeof(Coord);
2079 }
2080};
2081#pragma pack(pop)
2082
2084 uint32_t xTrans, uint32_t yTrans,
2085 uint32_t numCoords) {
2086 Logger &logger = conn->getLogger();
2087 logger.info("esitester", "Starting coord translate test with x_trans=" +
2088 std::to_string(xTrans) +
2089 ", y_trans=" + std::to_string(yTrans) +
2090 ", num_coords=" + std::to_string(numCoords));
2091
2092 // Generate random input coordinates.
2093 // Note: Coord struct has y before x due to SV ordering, but we generate
2094 // and display as (x, y) for human readability.
2095 std::mt19937 rng(0xDEADBEEF);
2096 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
2097 std::vector<Coord> inputCoords;
2098 inputCoords.reserve(numCoords);
2099 for (uint32_t i = 0; i < numCoords; ++i) {
2100 Coord c;
2101 c.x = dist(rng);
2102 c.y = dist(rng);
2103 inputCoords.push_back(c);
2104 }
2105
2106 // Find the coord_translator child.
2107 auto coordTranslatorChild =
2108 accel->getChildren().find(AppID("coord_translator"));
2109 if (coordTranslatorChild == accel->getChildren().end())
2110 throw std::runtime_error(
2111 "Coord translate test: no 'coord_translator' child found");
2112
2113 auto &ports = coordTranslatorChild->second->getPorts();
2114 auto translateIter = ports.find(AppID("translate_coords"));
2115 if (translateIter == ports.end())
2116 throw std::runtime_error(
2117 "Coord translate test: no 'translate_coords' port found");
2118
2119 // Use FuncService::Function which handles connection and translation.
2120 auto *funcPort =
2121 translateIter->second.getAs<services::FuncService::Function>();
2122 if (!funcPort)
2123 throw std::runtime_error(
2124 "Coord translate test: 'translate_coords' port not a "
2125 "FuncService::Function");
2126 funcPort->connect();
2127
2128 // Allocate the argument struct with proper alignment for the struct
2129 // members.
2130 size_t argSize = CoordTranslateArg::allocSize(numCoords);
2131 constexpr size_t alignment = alignof(CoordTranslateArg);
2132 // aligned_alloc requires size to be a multiple of alignment
2133 size_t allocSize = ((argSize + alignment - 1) / alignment) * alignment;
2134 void *argRaw = alignedAllocCompat(alignment, allocSize);
2135 if (!argRaw)
2136 throw std::bad_alloc();
2137 auto argDeleter = [](void *p) { alignedFreeCompat(p); };
2138 std::unique_ptr<void, decltype(argDeleter)> argBuffer(argRaw, argDeleter);
2139 auto *arg = static_cast<CoordTranslateArg *>(argRaw);
2140 arg->coordsLength = numCoords;
2141 arg->xTranslation = xTrans;
2142 arg->yTranslation = yTrans;
2143 for (uint32_t i = 0; i < numCoords; ++i)
2144 arg->coords()[i] = inputCoords[i];
2145
2146 logger.debug(
2147 "esitester",
2148 "Sending coord translate argument: " + std::to_string(argSize) +
2149 " bytes, coords_length=" + std::to_string(arg->coordsLength) +
2150 ", x_trans=" + std::to_string(arg->xTranslation) +
2151 ", y_trans=" + std::to_string(arg->yTranslation));
2152
2153 // Call the function - translation happens automatically.
2154 MessageData resMsg =
2155 funcPort
2156 ->call(MessageData(reinterpret_cast<const uint8_t *>(arg), argSize))
2157 .get();
2158 // argBuffer automatically freed when it goes out of scope
2159
2160 logger.debug("esitester", "Received coord translate result: " +
2161 std::to_string(resMsg.getSize()) + " bytes");
2162
2163 if (resMsg.getSize() < sizeof(CoordTranslateResult))
2164 throw std::runtime_error("Coord translate test: result too small");
2165
2166 const auto *result =
2167 reinterpret_cast<const CoordTranslateResult *>(resMsg.getBytes());
2168
2169 if (resMsg.getSize() < CoordTranslateResult::allocSize(result->coordsLength))
2170 throw std::runtime_error("Coord translate test: result data truncated");
2171
2172 // Verify results.
2173 if (result->coordsLength != inputCoords.size())
2174 throw std::runtime_error(
2175 "Coord translate test: result size mismatch. Expected " +
2176 std::to_string(inputCoords.size()) + ", got " +
2177 std::to_string(result->coordsLength));
2178
2179 bool passed = true;
2180 std::cout << "Coord translate test results:" << std::endl;
2181 for (size_t i = 0; i < inputCoords.size(); ++i) {
2182 uint32_t expectedX = inputCoords[i].x + xTrans;
2183 uint32_t expectedY = inputCoords[i].y + yTrans;
2184 std::cout << " coord[" << i << "]=(" << inputCoords[i].x << ","
2185 << inputCoords[i].y << ") + (" << xTrans << "," << yTrans
2186 << ") = (" << result->coords()[i].x << ","
2187 << result->coords()[i].y << ")";
2188 if (result->coords()[i].x != expectedX ||
2189 result->coords()[i].y != expectedY) {
2190 std::cout << " MISMATCH! (expected (" << expectedX << "," << expectedY
2191 << "))";
2192 passed = false;
2193 }
2194 std::cout << std::endl;
2195 }
2196
2197 if (!passed)
2198 throw std::runtime_error("Coord translate test failed: result mismatch");
2199
2200 logger.info("esitester", "Coord translate test passed");
2201 std::cout << "Coord translate test passed" << std::endl;
2202}
2203
2204//
2205// SerialCoordTranslator test
2206//
2207
2208#pragma pack(push, 1)
2210 uint16_t coordsCount;
2213};
2214static_assert(sizeof(SerialCoordHeader) == 10, "Size mismatch");
2216 SerialCoordData(uint32_t x, uint32_t y) : y(y), x(x), _pad_tail(0) {}
2217 uint32_t y;
2218 uint32_t x;
2219 uint16_t _pad_tail;
2220};
2221static_assert(sizeof(SerialCoordData) == sizeof(SerialCoordHeader),
2222 "Size mismatch");
2223#pragma pack(pop)
2224
2225// Note: this application is intended to test hardware. As such, we need
2226// to be able to send batches. So this is not the typical way one would define
2227// a message struct. It's closer to a streaming style.
2229private:
2231 std::vector<SerialCoordData> coords;
2233
2234public:
2236 header.coordsCount = 0;
2237 header.xTranslation = 0;
2238 header.yTranslation = 0;
2239 // The footer is a count==0 header that terminates the list per the ESI
2240 // bulk-transfer serial encoding. Static fields are constant within a
2241 // list so the footer's translation values are irrelevant; zero them.
2242 footer.coordsCount = 0;
2243 footer.xTranslation = 0;
2244 footer.yTranslation = 0;
2245 }
2246 void yTranslation(uint32_t yTrans) { header.yTranslation = yTrans; }
2247 uint32_t yTranslation() const { return header.yTranslation; }
2248 void xTranslation(uint32_t xTrans) { header.xTranslation = xTrans; }
2249 uint32_t xTranslation() const { return header.xTranslation; }
2250 void appendCoord(uint32_t x, uint32_t y) {
2251 coords.emplace_back(x, y);
2252 header.coordsCount = (uint16_t)coords.size();
2253 }
2254 const std::vector<SerialCoordData> &getCoords() const { return coords; }
2255
2256 size_t numSegments() const override { return 3; }
2257 Segment segment(size_t idx) const override {
2258 if (idx == 0)
2259 return {reinterpret_cast<const uint8_t *>(&header), sizeof(header)};
2260 else if (idx == 1)
2261 return {reinterpret_cast<const uint8_t *>(coords.data()),
2262 coords.size() * sizeof(SerialCoordData)};
2263 else if (idx == 2)
2264 return {reinterpret_cast<const uint8_t *>(&footer), sizeof(footer)};
2265 else
2266 throw std::out_of_range("SerialCoordInput: invalid segment index");
2267 }
2268};
2269
2270// Like SerialCoordInput but without the trailing count==0 terminator. Used
2271// when streaming multiple bursts that together comprise a single logical
2272// list; the caller is responsible for sending a separate terminator burst
2273// (a SerialCoordBurst with count==0 and no data).
2275private:
2277 std::vector<SerialCoordData> coords;
2278
2279public:
2281 header.coordsCount = 0;
2282 header.xTranslation = 0;
2283 header.yTranslation = 0;
2284 }
2285 void yTranslation(uint32_t yTrans) { header.yTranslation = yTrans; }
2286 void xTranslation(uint32_t xTrans) { header.xTranslation = xTrans; }
2287 void appendCoord(uint32_t x, uint32_t y) {
2288 coords.emplace_back(x, y);
2289 header.coordsCount = (uint16_t)coords.size();
2290 }
2291
2292 size_t numSegments() const override { return 2; }
2293 Segment segment(size_t idx) const override {
2294 if (idx == 0)
2295 return {reinterpret_cast<const uint8_t *>(&header), sizeof(header)};
2296 else if (idx == 1)
2297 return {reinterpret_cast<const uint8_t *>(coords.data()),
2298 coords.size() * sizeof(SerialCoordData)};
2299 else
2300 throw std::out_of_range("SerialCoordBurst: invalid segment index");
2301 }
2302};
2303
2304#pragma pack(push, 1)
2306 uint16_t coordsCount;
2307 uint8_t _pad[6];
2308};
2310 uint32_t y;
2311 uint32_t x;
2312};
2317#pragma pack(pop)
2318static_assert(sizeof(SerialCoordOutputFrame) == 8, "Size mismatch");
2319
2320/// Deserialized result batch from the serial coord translator. The
2321/// TypeDeserializer accumulates header+data frame sequences until the
2322/// zero-count footer header, then emits the complete coordinate list.
2324 std::vector<Coord> coords;
2325
2327 : public QueuedDecodeTypeDeserializer<SerialCoordOutputBatch> {
2328 public:
2332
2334 : Base(std::move(output)) {}
2335
2336 private:
2337 DecodedOutputs decode(std::unique_ptr<SegmentedMessageData> &msg) override {
2338 DecodedOutputs decoded;
2339
2340 MessageData scratch;
2341 const MessageData &flat =
2343 const uint8_t *bytes = flat.getBytes();
2344 size_t size = flat.getSize();
2345 constexpr size_t frameSize = sizeof(SerialCoordOutputFrame);
2346
2347 size_t offset = 0;
2348 while (offset < size) {
2349 size_t needed = frameSize - partialFrameBytes.size();
2350 size_t chunkSize = std::min(needed, size - offset);
2351 partialFrameBytes.insert(partialFrameBytes.end(), bytes + offset,
2352 bytes + offset + chunkSize);
2353 offset += chunkSize;
2354
2355 if (partialFrameBytes.size() != frameSize)
2356 break;
2357
2359 std::memcpy(&frame, partialFrameBytes.data(), frameSize);
2360 partialFrameBytes.clear();
2361
2362 if (remainingCoords == 0) {
2363 // Header frame.
2364 uint16_t batchCount = frame.header.coordsCount;
2365 if (batchCount == 0) {
2366 // Footer: end of list. Emit accumulated coordinates.
2367 auto batch = std::make_unique<SerialCoordOutputBatch>();
2368 batch->coords = std::move(accumulated);
2369 accumulated.clear();
2370 decoded.push_back(std::move(batch));
2371 msg.reset();
2372 return decoded;
2373 }
2374 remainingCoords = batchCount;
2375 continue;
2376 }
2377 // Data frame.
2378 accumulated.push_back({frame.data.y, frame.data.x});
2380 }
2381
2382 msg.reset();
2383 return decoded;
2384 }
2385
2386 std::vector<Coord> accumulated;
2387 std::vector<uint8_t> partialFrameBytes;
2389 };
2390};
2391
2393 Accelerator *accel, uint32_t xTrans,
2394 uint32_t yTrans, uint32_t numCoords,
2395 size_t batchSizeLimit) {
2396 Logger &logger = conn->getLogger();
2397 logger.info("esitester", "Starting Serial coord translate test");
2398
2399 // Generate random coordinates.
2400 std::mt19937 rng(0xDEADBEEF);
2401 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
2402 std::vector<Coord> inputCoords;
2403 inputCoords.reserve(numCoords);
2404 for (uint32_t i = 0; i < numCoords; ++i)
2405 inputCoords.push_back({dist(rng), dist(rng)});
2406
2407 auto child = accel->getChildren().find(AppID("coord_translator_serial"));
2408 if (child == accel->getChildren().end())
2409 throw std::runtime_error("Serial coord translate test: no "
2410 "'coord_translator_serial' child found");
2411
2412 auto &ports = child->second->getPorts();
2413 auto portIter = ports.find(AppID("translate_coords_serial"));
2414 if (portIter == ports.end())
2415 throw std::runtime_error("Serial coord translate test: no "
2416 "'translate_coords_serial' port found");
2417
2418 TypedWritePort<SerialCoordBurst, /*SkipTypeCheck=*/true> argPort(
2419 portIter->second.getRawWrite("arg"));
2420 // Use the raw read port so we can verify the multi-burst output framing
2421 // explicitly rather than relying on the typed deserializer to accumulate
2422 // frames until the terminator.
2423 ReadChannelPort &resultRaw = portIter->second.getRawRead("result");
2424
2425 argPort.connect(ChannelPort::ConnectOptions(std::nullopt, false));
2426 // Use an unlimited read queue so the device output isn't stalled by a full
2427 // queue while we're still writing. With raw reads (translateMessage=false),
2428 // each output frame becomes its own queued message, so the default 32-msg
2429 // limit can be hit easily on a multi-burst run.
2430 resultRaw.connect(ChannelPort::ConnectOptions(/*bufferSize=*/0,
2431 /*translateMessage=*/false));
2432
2433 size_t sent = 0;
2434 while (sent < numCoords) {
2435 size_t batchSize = std::min(batchSizeLimit, numCoords - sent);
2436
2437 // Send Header. Only the first header needs the translation values, test
2438 // the subsequent ones with zero translation to verify that the hardware
2439 // correctly applies the first header's translation to the whole list.
2440 auto batch = std::make_unique<SerialCoordBurst>();
2441 batch->xTranslation(sent == 0 ? xTrans : 0);
2442 batch->yTranslation(sent == 0 ? yTrans : 0);
2443 // Send Data
2444 for (size_t i = 0; i < batchSize; ++i) {
2445 batch->appendCoord(inputCoords[sent + i].x, inputCoords[sent + i].y);
2446 }
2447 argPort.write(batch);
2448 sent += batchSize;
2449 }
2450 // Send final header with count=0 to signal end of input.
2451 auto footerBurst = std::make_unique<SerialCoordBurst>();
2452 argPort.write(footerBurst);
2453
2454 // Read raw output frames, walking the bulk-transfer wire format: zero or
2455 // more (HDR(N) + N data frames) sequences followed by a single HDR(0)
2456 // terminator. Each `read()` returns whatever the transport layer has
2457 // available, which is not guaranteed to align with frame boundaries
2458 // (e.g., DMA channel engines may coalesce or split across frames). So
2459 // we accumulate bytes into a buffer and only consume whole frames.
2460 constexpr size_t frameSize = sizeof(SerialCoordOutputFrame);
2461 std::vector<uint8_t> rxBuf;
2462 auto readFrame = [&](SerialCoordOutputFrame &out) {
2463 while (rxBuf.size() < frameSize) {
2464 MessageData data;
2465 resultRaw.read(data);
2466 rxBuf.insert(rxBuf.end(), data.getBytes(),
2467 data.getBytes() + data.getSize());
2468 }
2469 std::memcpy(&out, rxBuf.data(), frameSize);
2470 rxBuf.erase(rxBuf.begin(), rxBuf.begin() + frameSize);
2471 };
2472
2473 std::vector<Coord> results;
2474 results.reserve(numCoords);
2475 while (true) {
2477 readFrame(hdr);
2478 uint16_t batchCount = hdr.header.coordsCount;
2479 if (batchCount == 0)
2480 break;
2481 for (uint16_t i = 0; i < batchCount; ++i) {
2482 SerialCoordOutputFrame frame{};
2483 readFrame(frame);
2484 results.push_back({frame.data.y, frame.data.x});
2485 }
2486 }
2487
2488 // Verify
2489 bool passed = true;
2490 std::cout << "Serial coord translate test results:" << std::endl;
2491 if (results.size() != inputCoords.size()) {
2492 std::cout << "Result size mismatch. Expected " << inputCoords.size()
2493 << ", got " << results.size() << std::endl;
2494 passed = false;
2495 }
2496 for (size_t i = 0; i < std::min(inputCoords.size(), results.size()); ++i) {
2497 uint32_t expX = inputCoords[i].x + xTrans;
2498 uint32_t expY = inputCoords[i].y + yTrans;
2499 std::cout << " coord[" << i << "]=(" << inputCoords[i].x << ","
2500 << inputCoords[i].y << ") + (" << xTrans << "," << yTrans
2501 << ") = (" << results[i].x << "," << results[i].y
2502 << ") (expected (" << expX << "," << expY << "))";
2503 if (results[i].x != expX || results[i].y != expY) {
2504 std::cout << " MISMATCH!";
2505 passed = false;
2506 }
2507 std::cout << std::endl;
2508 }
2509
2510 argPort.disconnect();
2511 resultRaw.disconnect();
2512
2513 if (!passed)
2514 throw std::runtime_error("Serial coord translate test failed");
2515
2516 logger.info("esitester", "Serial coord translate test passed");
2517 std::cout << "Serial coord translate test passed" << std::endl;
2518}
2519
2520//
2521// AutoSerialCoordTranslator test
2522//
2523// The hardware module pipes the input through ListWindowToParallel ->
2524// per-coordinate translation -> ListWindowToSerial. The conversion modules
2525// emit one or more bulk transfers per call (each `header(count>0)` followed
2526// by `count` data frames) terminated by a `header(count==0)` footer per the
2527// ESI WindowField serial-encoding spec. This test:
2528// * Sends exactly one input batch: header(numCoords) + numCoords data
2529// frames + header(0) footer.
2530// * Reads back: a sequence of one-or-more `header(count>0) + count data`
2531// bursts terminated by `header(0)`. Use raw frame reads since the
2532// canonical `SerialCoordOutputBatch` deserializer hasn't been wired in
2533// for the converter pair.
2534//
2536 Accelerator *accel, uint32_t xTrans,
2537 uint32_t yTrans, uint32_t numCoords) {
2538 Logger &logger = conn->getLogger();
2539 logger.info("esitester", "Starting Auto serial coord translate test");
2540
2541 // Generate random coordinates.
2542 std::mt19937 rng(0xDEADBEEF);
2543 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
2544 std::vector<Coord> inputCoords;
2545 inputCoords.reserve(numCoords);
2546 for (uint32_t i = 0; i < numCoords; ++i)
2547 inputCoords.push_back({dist(rng), dist(rng)});
2548
2549 auto child = accel->getChildren().find(AppID("coord_translator_auto_serial"));
2550 if (child == accel->getChildren().end())
2551 throw std::runtime_error("Auto serial coord translate test: no "
2552 "'coord_translator_auto_serial' child found");
2553
2554 auto &ports = child->second->getPorts();
2555 auto portIter = ports.find(AppID("translate_coords_auto_serial"));
2556 if (portIter == ports.end())
2557 throw std::runtime_error("Auto serial coord translate test: no "
2558 "'translate_coords_auto_serial' port found");
2559
2560 // Reuse SerialCoordInput: the input wire format is identical (header with
2561 // x/y_translation+count, followed by data frames each carrying one coord).
2562 TypedWritePort<SerialCoordInput, /*SkipTypeCheck=*/true> argPort(
2563 portIter->second.getRawWrite("arg"));
2564 argPort.connect(ChannelPort::ConnectOptions(std::nullopt, false));
2565
2566 // Use the raw read port for results: read one header frame then numCoords
2567 // data frames as raw `SerialCoordOutputFrame`-shaped messages. Disable
2568 // window-message translation so we get one frame per `read()` instead of
2569 // assembled higher-level messages.
2570 ReadChannelPort &resultRaw = portIter->second.getRawRead("result");
2571 // Use an unlimited read queue so the device output isn't stalled by a full
2572 // queue while we're still writing. With raw reads (translateMessage=false),
2573 // each output frame becomes its own queued message, so the default 32-msg
2574 // limit can be hit easily on a multi-frame run.
2575 resultRaw.connect(ChannelPort::ConnectOptions(/*bufferSize=*/0,
2576 /*translateMessage=*/false));
2577
2578 // Send a single header+data burst.
2579 auto batch = std::make_unique<SerialCoordInput>();
2580 batch->xTranslation(xTrans);
2581 batch->yTranslation(yTrans);
2582 for (uint32_t i = 0; i < numCoords; ++i)
2583 batch->appendCoord(inputCoords[i].x, inputCoords[i].y);
2584 argPort.write(batch);
2585
2586 // Helper: read one raw frame, accumulating bytes across `read()` calls
2587 // since transports such as DMA channel engines do not guarantee that
2588 // each `read()` returns exactly one frame.
2589 constexpr size_t frameSize = sizeof(SerialCoordOutputFrame);
2590 std::vector<uint8_t> rxBuf;
2591 auto readFrame = [&](SerialCoordOutputFrame &out) {
2592 while (rxBuf.size() < frameSize) {
2593 MessageData data;
2594 resultRaw.read(data);
2595 rxBuf.insert(rxBuf.end(), data.getBytes(),
2596 data.getBytes() + data.getSize());
2597 }
2598 std::memcpy(&out, rxBuf.data(), frameSize);
2599 rxBuf.erase(rxBuf.begin(), rxBuf.begin() + frameSize);
2600 };
2601
2602 // Read a sequence of one-or-more `header(count>0) + count data` bursts
2603 // followed by a `header(count==0)` terminator footer. Total data items
2604 // received across all bursts must equal numCoords.
2605 std::vector<Coord> results;
2606 results.reserve(numCoords);
2607 while (true) {
2609 readFrame(hdr);
2610 uint16_t burstCount = hdr.header.coordsCount;
2611 if (burstCount == 0)
2612 break;
2613 if (results.size() + burstCount > numCoords)
2614 throw std::runtime_error("Auto serial coord translate test: bursts "
2615 "overflow expected total " +
2616 std::to_string(numCoords));
2617 for (uint32_t i = 0; i < burstCount; ++i) {
2618 SerialCoordOutputFrame frame{};
2619 readFrame(frame);
2620 results.push_back({frame.data.y, frame.data.x});
2621 }
2622 }
2623 if (results.size() != numCoords)
2624 throw std::runtime_error("Auto serial coord translate test: got " +
2625 std::to_string(results.size()) +
2626 " coords across all bursts " + "(expected " +
2627 std::to_string(numCoords) + ")");
2628
2629 argPort.disconnect();
2630 resultRaw.disconnect();
2631
2632 bool passed = true;
2633 std::cout << "Auto serial coord translate test results:" << std::endl;
2634 for (size_t i = 0; i < inputCoords.size(); ++i) {
2635 uint32_t expX = inputCoords[i].x + xTrans;
2636 uint32_t expY = inputCoords[i].y + yTrans;
2637 std::cout << " coord[" << i << "]=(" << inputCoords[i].x << ","
2638 << inputCoords[i].y << ") + (" << xTrans << "," << yTrans
2639 << ") = (" << results[i].x << "," << results[i].y
2640 << ") (expected (" << expX << "," << expY << "))";
2641 if (results[i].x != expX || results[i].y != expY) {
2642 std::cout << " MISMATCH!";
2643 passed = false;
2644 }
2645 std::cout << std::endl;
2646 }
2647
2648 if (!passed)
2649 throw std::runtime_error("Auto serial coord translate test failed");
2650
2651 logger.info("esitester", "Auto serial coord translate test passed");
2652 std::cout << "Auto serial coord translate test passed" << std::endl;
2653}
2654
2656 uint32_t iterations) {
2657 Logger &logger = conn->getLogger();
2658
2659 auto channelChild = accel->getChildren().find(AppID("channel_test"));
2660 if (channelChild == accel->getChildren().end())
2661 throw std::runtime_error("Channel test: no 'channel_test' child");
2662 auto &ports = channelChild->second->getPorts();
2663
2664 // --- Get the MMIO port to trigger the producer ---
2665 auto cmdIter = ports.find(AppID("cmd"));
2666 if (cmdIter == ports.end())
2667 throw std::runtime_error("Channel test: no 'cmd' port");
2668 auto *cmdMMIO = cmdIter->second.getAs<services::MMIO::MMIORegion>();
2669 if (!cmdMMIO)
2670 throw std::runtime_error("Channel test: 'cmd' is not MMIO");
2671
2672 // --- Get the producer to_host port ---
2673 auto producerIter = ports.find(AppID("producer"));
2674 if (producerIter == ports.end())
2675 throw std::runtime_error("Channel test: no 'producer' port");
2676 auto *producerPort =
2677 producerIter->second.getAs<services::ChannelService::ToHost>();
2678 if (!producerPort)
2679 throw std::runtime_error(
2680 "Channel test: 'producer' is not a ChannelService::ToHost");
2681 producerPort->connect();
2682
2683 // --- Test to_host: MMIO-triggered incrementing values ---
2684 // Write the number of values to send at offset 0x0.
2685 cmdMMIO->write(0x0, iterations);
2686
2687 for (uint32_t i = 0; i < iterations; ++i) {
2688 MessageData recvData = producerPort->read().get();
2689 uint32_t got = *recvData.as<uint32_t>();
2690 std::cout << "[channel] producer i=" << i << " got=" << got << std::endl;
2691 if (got != i)
2692 throw std::runtime_error("Channel producer: expected " +
2693 std::to_string(i) + ", got " +
2694 std::to_string(got));
2695 }
2696 logger.info("esitester", "Channel test: producer passed (" +
2697 std::to_string(iterations) +
2698 " incrementing values)");
2699
2700 // --- Test from_host -> to_host loopback ---
2701 auto loopbackInIter = ports.find(AppID("loopback_in"));
2702 if (loopbackInIter == ports.end())
2703 throw std::runtime_error("Channel test: no 'loopback_in' port");
2704 auto *fromHostPort =
2705 loopbackInIter->second.getAs<services::ChannelService::FromHost>();
2706 if (!fromHostPort)
2707 throw std::runtime_error(
2708 "Channel test: 'loopback_in' is not a ChannelService::FromHost");
2709 fromHostPort->connect();
2710
2711 auto loopbackOutIter = ports.find(AppID("loopback_out"));
2712 if (loopbackOutIter == ports.end())
2713 throw std::runtime_error("Channel test: no 'loopback_out' port");
2714 auto *loopbackOutPort =
2715 loopbackOutIter->second.getAs<services::ChannelService::ToHost>();
2716 if (!loopbackOutPort)
2717 throw std::runtime_error(
2718 "Channel test: 'loopback_out' is not a ChannelService::ToHost");
2719 loopbackOutPort->connect();
2720
2721 std::mt19937_64 rng(0xDEADBEEF);
2722 std::uniform_int_distribution<uint32_t> dist(0, UINT32_MAX);
2723
2724 for (uint32_t i = 0; i < iterations; ++i) {
2725 uint32_t sendVal = dist(rng);
2726 fromHostPort->write(MessageData::from(sendVal));
2727 MessageData recvData = loopbackOutPort->read().get();
2728 uint32_t recvVal = *recvData.as<uint32_t>();
2729 std::cout << "[channel] loopback i=" << i << " sent=0x"
2730 << esi::toHex(sendVal) << " recv=0x" << esi::toHex(recvVal)
2731 << std::endl;
2732 if (recvVal != sendVal)
2733 throw std::runtime_error("Channel loopback mismatch at i=" +
2734 std::to_string(i));
2735 }
2736
2737 logger.info("esitester", "Channel test: loopback passed (" +
2738 std::to_string(iterations) + " iterations)");
2739 std::cout << "Channel test passed" << std::endl;
2740}
static void print(TypedAttr val, llvm::raw_ostream &os)
return success()
QueuedDecodeTypeDeserializer< SerialCoordOutputBatch > Base
DecodedOutputs decode(std::unique_ptr< SegmentedMessageData > &msg) override
Decode one raw message into zero or more typed outputs.
Abstract class representing a connection to an accelerator.
Top level accelerator class.
Definition Accelerator.h:85
T * getAs() const
Cast this Bundle port to a subclass which is actually useful.
Definition Ports.h:665
Common options and code for ESI runtime tools.
Definition CLI.h:29
Context & getContext()
Get the context.
Definition CLI.h:69
AcceleratorConnection * connect()
Connect to the accelerator using the specified backend and connection.
Definition CLI.h:66
int esiParse(int argc, const char **argv)
Run the parser.
Definition CLI.h:52
AcceleratorConnections, Accelerators, and Manifests must all share a context.
Definition Context.h:34
Logger & getLogger()
Definition Context.h:69
const std::map< AppID, Instance * > & getChildren() const
Access the module's children by ID.
Definition Design.h:71
virtual void error(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report an error.
Definition Logging.h:64
virtual void info(const std::string &subsystem, const std::string &msg, const std::map< std::string, std::any > *details=nullptr)
Report an informational message.
Definition Logging.h:75
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
Class to parse a manifest.
Definition Manifest.h:39
Accelerator * buildAccelerator(AcceleratorConnection &acc) const
A concrete flat message backed by a single vector of bytes.
Definition Common.h:197
const uint8_t * getBytes() const
Definition Common.h:208
const T * as() const
Cast to a type.
Definition Common.h:232
size_t getSize() const
Get the size of the data in bytes.
Definition Common.h:222
static MessageData from(T &t)
Cast from a type to its raw bytes.
Definition Common.h:242
detail::TypedReadOwnedCallback< SerialCoordOutputBatch > OutputCallback
Definition TypedPorts.h:241
std::vector< std::unique_ptr< SerialCoordOutputBatch > > DecodedOutputs
Definition TypedPorts.h:242
A ChannelPort which reads data from the accelerator.
Definition Ports.h:479
virtual void connect(ReadCallback callback, const ConnectOptions &options={})
Definition Ports.cpp:140
virtual void disconnect() override
Disconnect the channel.
Definition Ports.cpp:70
virtual void read(MessageData &outData)
Specify a buffer to read into.
Definition Ports.h:543
Abstract multi-segment message.
Definition Common.h:151
void connect(const ChannelPort::ConnectOptions &opts={std::nullopt, false})
Definition TypedPorts.h:619
void write(const T &data)
Definition TypedPorts.h:629
A ChannelPort which sends data to the accelerator.
Definition Ports.h:308
virtual void disconnect() override
Definition Ports.h:322
void write(const MessageData &data)
A very basic blocking write API.
Definition Ports.h:327
bool tryWrite(const MessageData &data)
A basic non-blocking write API.
Definition Ports.h:358
virtual void connect(const ConnectOptions &options={}) override
Set up a connection to the accelerator.
Definition Ports.h:312
A function call which gets attached to a service port.
Definition Services.h:437
A port which writes data to the accelerator (from_host).
Definition Services.h:347
A port which reads data from the accelerator (to_host).
Definition Services.h:323
A function call which gets attached to a service port.
Definition Services.h:385
virtual void start()
In cases where necessary, enable host memory services.
Definition Services.h:292
services::HostMemRegion HostMemRegion
RAII memory region for host memory. See services::HostMemRegion.
Definition Services.h:289
A "slice" of some parent MMIO space.
Definition Services.h:187
virtual uint64_t read(uint32_t addr) const
Read a 64-bit value from this region, not the global address space.
Definition Services.cpp:132
Information about the Accelerator system.
Definition Services.h:113
A telemetry port which gets attached to a service port.
Definition Services.h:502
void connect()
Connect to a particular telemetry port. Offset should be non-nullopt.
Definition Services.cpp:481
int main()
static void * alignedAllocCompat(std::size_t alignment, std::size_t size)
static void bandwidthReadTest(AcceleratorConnection *conn, Accelerator *acc, size_t width, size_t xferCount, bool checkData)
static void hostmemWriteTest(Accelerator *acc, esi::services::HostMem::HostMemRegion &region, uint32_t width)
Test the hostmem write functionality.
static void aggregateHostmemBandwidthTest(AcceleratorConnection *, Accelerator *, uint32_t width, uint32_t xferCount, bool read, bool write)
static void dmaTest(AcceleratorConnection *, Accelerator *, const std::vector< uint32_t > &widths, bool read, bool write)
static void hostmemBandwidthTest(AcceleratorConnection *conn, Accelerator *acc, uint32_t xferCount, const std::vector< uint32_t > &widths, bool read, bool write)
static void callbackTest(AcceleratorConnection *, Accelerator *, uint32_t iterations)
static uint64_t enginePayloadFold(uint32_t index, size_t bitWidth)
static void serialCoordTranslateTest(AcceleratorConnection *, Accelerator *, uint32_t xTrans, uint32_t yTrans, uint32_t numCoords, size_t batchSizeLimit)
static void bandwidthTest(AcceleratorConnection *, Accelerator *, const std::vector< uint32_t > &widths, uint32_t xferCount, bool read, bool write, bool checkData)
static std::vector< uint8_t > enginePatternBytes(uint32_t index, size_t bitWidth)
static void hostmemReadBandwidthTest(AcceleratorConnection *conn, Accelerator *acc, esi::services::HostMem::HostMemRegion &region, uint32_t width, uint32_t xferCount)
static void channelTest(AcceleratorConnection *, Accelerator *, uint32_t iterations)
static std::string formatBandwidth(double bytesPerSec)
static void autoSerialCoordTranslateTest(AcceleratorConnection *, Accelerator *, uint32_t xTrans, uint32_t yTrans, uint32_t numCoords)
static void hostmemWriteBandwidthTest(AcceleratorConnection *conn, Accelerator *acc, esi::services::HostMem::HostMemRegion &region, uint32_t width, uint32_t xferCount)
static void alignedFreeCompat(void *ptr)
static void dmaWriteTest(AcceleratorConnection *conn, Accelerator *acc, size_t width)
static uint8_t esitesterHostmemByte(uint32_t index, size_t byte, uint32_t width)
static void bandwidthWriteTest(AcceleratorConnection *conn, Accelerator *acc, size_t width, size_t xferCount, bool checkData)
static size_t hostmemWireBytes(uint32_t width)
static std::string humanBytes(uint64_t bytes)
static void checkBurstCommandRegisters(services::MMIO::MMIORegion &mmio, uint64_t address, uint64_t flits)
static uint8_t esitesterDataByte(uint32_t index, size_t byte)
static uint64_t esitesterElemFold(uint32_t i, uint32_t width)
static void streamingAddTest(AcceleratorConnection *, Accelerator *, uint32_t addAmt, uint32_t numItems)
Test the StreamingAdder module.
static void loopbackAddTest(AcceleratorConnection *, Accelerator *, uint32_t iterations, bool pipeline)
static void dmaReadTest(AcceleratorConnection *conn, Accelerator *acc, size_t width)
static void streamingAddTranslatedTest(AcceleratorConnection *, Accelerator *, uint32_t addAmt, uint32_t numItems)
static void hostmemTest(AcceleratorConnection *, Accelerator *, const std::vector< uint32_t > &widths, bool write, bool read)
static std::string humanTimeUS(uint64_t us)
static void coordTranslateTest(AcceleratorConnection *, Accelerator *, uint32_t xTrans, uint32_t yTrans, uint32_t numCoords)
static void resetTest(AcceleratorConnection *, Accelerator *)
constexpr std::array< uint32_t, 8 > defaultWidths
Definition esitester.cpp:87
static uint8_t enginePatternByte(uint32_t index, size_t byte, size_t bitWidth)
static size_t engineWireBytes(size_t bitWidth)
static std::string defaultWidthsStr()
Definition esitester.cpp:89
static constexpr uint64_t kEsitesterSeqSeed
static void hostmemReadTest(Accelerator *acc, esi::services::HostMem::HostMemRegion &region, uint32_t width)
const MessageData & getMessageDataRef(const SegmentedMessageData &msg, MessageData &scratch)
Definition TypedPorts.h:152
Definition esi.py:1
std::string toString(const std::any &a)
'Stringify' a std::any. This is used to log std::any values by some loggers.
Definition Logging.cpp:132
std::string toHex(void *val)
Definition Common.cpp:38
Definition seq.py:1
Translated argument struct for CoordTranslator.
std::span< const Coord > coordsSpan() const
const Coord * coords() const
static size_t allocSize(size_t numCoords)
Coord * coords()
Get pointer to trailing coords array.
std::span< Coord > coordsSpan()
Get span view of coords (requires coordsLength to be set first).
Translated result struct for CoordTranslator.
static size_t allocSize(size_t numCoords)
std::span< Coord > coordsSpan()
Get span view of coords (requires coordsLength to be set first).
const Coord * coords() const
Coord * coords()
Get pointer to trailing coords array.
std::span< const Coord > coordsSpan() const
Test the CoordTranslator module using message translation.
uint32_t x
uint32_t y
void yTranslation(uint32_t yTrans)
void appendCoord(uint32_t x, uint32_t y)
std::vector< SerialCoordData > coords
void xTranslation(uint32_t xTrans)
SerialCoordHeader header
Segment segment(size_t idx) const override
Get a segment by index. Throws if the segment has been take()n.
size_t numSegments() const override
Number of segments in the message.
SerialCoordData(uint32_t x, uint32_t y)
size_t numSegments() const override
Number of segments in the message.
void appendCoord(uint32_t x, uint32_t y)
uint32_t yTranslation() const
SerialCoordHeader header
void yTranslation(uint32_t yTrans)
SerialCoordHeader footer
uint32_t xTranslation() const
Segment segment(size_t idx) const override
Get a segment by index. Throws if the segment has been take()n.
const std::vector< SerialCoordData > & getCoords() const
void xTranslation(uint32_t xTrans)
std::vector< SerialCoordData > coords
Deserialized result batch from the serial coord translator.
std::vector< Coord > coords
Packed struct representing a parallel window argument for StreamingAdder.
Packed struct representing a parallel window result for StreamingAdder.
Test the StreamingAdder module using message translation.
uint32_t * inputData()
Get pointer to trailing input data array.
static size_t allocSize(size_t numItems)
std::span< uint32_t > inputDataSpan()
Get span view of input data (requires inputLength to be set first).
std::span< const uint32_t > inputDataSpan() const
const uint32_t * inputData() const
Translated result struct for StreamingAdder.
uint32_t * data()
Get pointer to trailing result data array.
std::span< uint32_t > dataSpan()
Get span view of result data (requires dataLength to be set first).
static size_t allocSize(size_t numItems)
std::span< const uint32_t > dataSpan() const
const uint32_t * data() const
A contiguous, non-owning view of bytes within a SegmentedMessageData.
Definition Common.h:122
virtual std::size_t getSize() const =0
virtual void * getPtr() const =0
Get a pointer to the host memory.
virtual void flush()
Flush the memory region to ensure that the device sees the latest contents.
Definition Services.h:256
virtual void * getDevicePtr() const
Sometimes the pointer the device sees is different from the pointer the host sees.
Definition Services.h:243
SerialCoordOutputData data
SerialCoordOutputHeader header