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 uint64_t *dataPtr = static_cast<uint64_t *>(scratchRegion->getPtr());
661 conn->getLogger().info("esitester",
662 "Running host memory test with region size " +
663 std::to_string(scratchRegion->getSize()) +
664 " bytes at 0x" + toHex(dataPtr));
665 for (size_t i = 0; i < scratchRegion->getSize() / 8; ++i)
666 dataPtr[i] = 0;
667 scratchRegion->flush();
668
669 bool passed = true;
670 for (size_t width : widths) {
671 try {
672 if (write)
673 hostmemWriteTest(acc, *scratchRegion, width);
674 if (read)
675 hostmemReadTest(acc, *scratchRegion, width);
676 } catch (std::exception &e) {
677 conn->getLogger().error("esitester", "Hostmem test failed for width " +
678 std::to_string(width) + ": " +
679 e.what());
680 passed = false;
681 }
682 }
683 if (!passed)
684 throw std::runtime_error("Hostmem test failed");
685 std::cout << "Hostmem test passed" << std::endl;
686}
687
689 size_t width) {
690 Logger &logger = conn->getLogger();
691 logger.info("esitester",
692 "== Running DMA read test with width " + std::to_string(width));
693 AppIDPath lastPath;
694 BundlePort *toHostMMIOPort =
695 acc->resolvePort({AppID("tohostdma", width), AppID("cmd")}, lastPath);
696 if (!toHostMMIOPort)
697 throw std::runtime_error("dma read test failed. No tohostdma[" +
698 std::to_string(width) + "] found");
699 auto *toHostMMIO = toHostMMIOPort->getAs<services::MMIO::MMIORegion>();
700 if (!toHostMMIO)
701 throw std::runtime_error("dma read test failed. MMIO port is not MMIO");
702 lastPath.clear();
703 BundlePort *outPortBundle =
704 acc->resolvePort({AppID("tohostdma", width), AppID("out")}, lastPath);
705 ReadChannelPort &outPort = outPortBundle->getRawRead("data");
706 outPort.connect();
707
708 size_t xferCount = 24;
709 MessageData data;
710 toHostMMIO->write(0, xferCount);
711 const size_t wireBytes = (width + 7) / 8;
712 for (size_t index = 0; index < xferCount; ++index) {
713 outPort.read(data);
714 if (data.getSize() != wireBytes)
715 throw std::runtime_error("dma read test failed. Expected " +
716 std::to_string(wireBytes) + " bytes, got " +
717 std::to_string(data.getSize()));
718 for (size_t byte = 0; byte < wireBytes; ++byte) {
719 uint8_t expected =
720 enginePatternByte(static_cast<uint32_t>(index), byte, width);
721 if (data.getBytes()[byte] != expected)
722 throw std::runtime_error(
723 "dma read test failed. Data mismatch at item " +
724 std::to_string(index) + " byte " + std::to_string(byte) +
725 ": expected " + toHex(expected) + ", got " +
726 toHex(data.getBytes()[byte]));
727 }
728 logger.debug("esitester",
729 "Payload [" + std::to_string(index) + "] = 0x" + data.toHex());
730 }
731 outPort.disconnect();
732 std::cout << " DMA read test for " << width << " bits passed" << std::endl;
733}
734
736 size_t width) {
737 Logger &logger = conn->getLogger();
738 logger.info("esitester",
739 "Running DMA write test with width " + std::to_string(width));
740 AppIDPath lastPath;
741 BundlePort *fromHostMMIOPort =
742 acc->resolvePort({AppID("fromhostdma", width), AppID("cmd")}, lastPath);
743 if (!fromHostMMIOPort)
744 throw std::runtime_error("dma read test for " + toString(width) +
745 " bits failed. No fromhostdma[" +
746 std::to_string(width) + "] found");
747 auto *fromHostMMIO = fromHostMMIOPort->getAs<services::MMIO::MMIORegion>();
748 if (!fromHostMMIO)
749 throw std::runtime_error("dma write test for " + toString(width) +
750 " bits failed. MMIO port is not MMIO");
751 lastPath.clear();
752 BundlePort *outPortBundle =
753 acc->resolvePort({AppID("fromhostdma", width), AppID("in")}, lastPath);
754 if (!outPortBundle)
755 throw std::runtime_error("dma write test for " + toString(width) +
756 " bits failed. No out port found");
757 WriteChannelPort &writePort = outPortBundle->getRawWrite("data");
758 writePort.connect();
759
760 size_t xferCount = 24;
761 std::vector<uint8_t> data((width + 7) / 8, 0);
762 fromHostMMIO->read(8);
763 fromHostMMIO->write(0, xferCount);
764 for (size_t i = 1; i < xferCount + 1; ++i) {
765 data[0] = i;
766 bool successWrite;
767 size_t attempts = 0;
768 do {
769 successWrite = writePort.tryWrite(MessageData(data.data(), data.size()));
770 if (!successWrite) {
771 std::this_thread::sleep_for(std::chrono::milliseconds(10));
772 }
773 } while (!successWrite && ++attempts < 100);
774 if (!successWrite)
775 throw std::runtime_error("dma write test for " + toString(width) +
776 " bits failed. Write failed");
777 uint64_t lastReadMMIO;
778 for (size_t a = 0; a < 20; ++a) {
779 lastReadMMIO = fromHostMMIO->read(8);
780 if (lastReadMMIO == i)
781 break;
782 std::this_thread::sleep_for(std::chrono::milliseconds(10));
783 if (a >= 19)
784 throw std::runtime_error("dma write for " + toString(width) +
785 " bits test failed. Read from MMIO failed");
786 }
787 }
788 writePort.disconnect();
789 std::cout << " DMA write test for " << width << " bits passed" << std::endl;
790}
791
793 const std::vector<uint32_t> &widths, bool read,
794 bool write) {
795 bool success = true;
796 if (write)
797 for (size_t width : widths)
798 try {
799 dmaWriteTest(conn, acc, width);
800 } catch (std::exception &e) {
801 success = false;
802 std::cerr << "DMA write test for " << width
803 << " bits failed: " << e.what() << std::endl;
804 }
805 if (read)
806 for (size_t width : widths)
807 dmaReadTest(conn, acc, width);
808 if (!success)
809 throw std::runtime_error("DMA test failed");
810 std::cout << "DMA test passed" << std::endl;
811}
812
813//
814// DMA bandwidth test
815
816static size_t engineWireBytes(size_t bitWidth) { return (bitWidth + 7) / 8; }
817
818static uint8_t enginePatternByte(uint32_t index, size_t byte, size_t bitWidth) {
819 uint8_t value = esitesterDataByte(index, byte);
820 const size_t tailBits = bitWidth % 8;
821 if (tailBits != 0 && byte + 1 == engineWireBytes(bitWidth))
822 value &= (uint8_t(1) << tailBits) - 1;
823 return value;
824}
825
826static std::vector<uint8_t> enginePatternBytes(uint32_t index,
827 size_t bitWidth) {
828 std::vector<uint8_t> bytes(engineWireBytes(bitWidth));
829 for (size_t byte = 0; byte < bytes.size(); ++byte)
830 bytes[byte] = enginePatternByte(index, byte, bitWidth);
831 return bytes;
832}
833
834static uint64_t enginePayloadFold(uint32_t index, size_t bitWidth) {
835 const size_t numChunks = (bitWidth + 63) / 64;
836 uint64_t fold = 0;
837 for (size_t chunkIndex = 0; chunkIndex < numChunks; ++chunkIndex) {
838 uint64_t chunk = 0;
839 for (size_t byteInChunk = 0; byteInChunk < 8; ++byteInChunk) {
840 const size_t byteIndex = chunkIndex * 8 + byteInChunk;
841 if (byteIndex < engineWireBytes(bitWidth))
842 chunk |= uint64_t(enginePatternByte(index, byteIndex, bitWidth))
843 << (8 * byteInChunk);
844 }
845 const unsigned rotate = (8 * chunkIndex) % 64;
846 fold ^= rotate ? ((chunk << rotate) | (chunk >> (64 - rotate))) : chunk;
847 }
848 return fold;
849}
850
852 size_t width, size_t xferCount, bool checkData) {
853
854 AppIDPath lastPath;
855 BundlePort *toHostMMIOPort =
856 acc->resolvePort({AppID("tohostdma", width), AppID("cmd")}, lastPath);
857 if (!toHostMMIOPort)
858 throw std::runtime_error("bandwidth test failed. No tohostdma[" +
859 std::to_string(width) + "] found");
860 auto *toHostMMIO = toHostMMIOPort->getAs<services::MMIO::MMIORegion>();
861 if (!toHostMMIO)
862 throw std::runtime_error("bandwidth test failed. MMIO port is not MMIO");
863 lastPath.clear();
864 BundlePort *outPortBundle =
865 acc->resolvePort({AppID("tohostdma", width), AppID("out")}, lastPath);
866 ReadChannelPort &outPort = outPortBundle->getRawRead("data");
867 outPort.connect();
868
869 Logger &logger = conn->getLogger();
870 logger.info("esitester", "Starting read bandwidth test with " +
871 std::to_string(xferCount) + " x " +
872 std::to_string(width) + " bit transfers");
873 MessageData data;
874 size_t sizeMismatches = 0;
875 size_t dataMismatches = 0;
876 size_t firstMismatchItem = 0;
877 size_t firstMismatchByte = 0;
878 uint8_t firstExpected = 0;
879 uint8_t firstActual = 0;
880 auto start = std::chrono::high_resolution_clock::now();
881 toHostMMIO->write(0, xferCount);
882 for (size_t index = 0; index < xferCount; ++index) {
883 outPort.read(data);
884 if (checkData) {
885 const size_t wireBytes = engineWireBytes(width);
886 if (data.getSize() != wireBytes) {
887 ++sizeMismatches;
888 } else {
889 for (size_t byte = 0; byte < wireBytes; ++byte) {
890 const uint8_t expected =
891 enginePatternByte(static_cast<uint32_t>(index), byte, width);
892 if (data.getBytes()[byte] != expected) {
893 if (dataMismatches == 0) {
894 firstMismatchItem = index;
895 firstMismatchByte = byte;
896 firstExpected = expected;
897 firstActual = data.getBytes()[byte];
898 }
899 ++dataMismatches;
900 }
901 }
902 }
903 }
904 logger.debug(
905 [index,
906 &data](std::string &subsystem, std::string &msg,
907 std::unique_ptr<std::map<std::string, std::any>> &details) {
908 subsystem = "esitester";
909 msg = "Payload [" + std::to_string(index) + "] = 0x" + data.toHex();
910 });
911 }
912 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
913 std::chrono::high_resolution_clock::now() - start);
914 outPort.disconnect();
915 if (checkData && sizeMismatches != 0)
916 throw std::runtime_error(
917 "bandwidth read data mismatch: " + std::to_string(sizeMismatches) +
918 " payload(s) had the wrong wire size");
919 if (checkData && dataMismatches != 0)
920 throw std::runtime_error(
921 "bandwidth read data mismatch: " + std::to_string(dataMismatches) +
922 " bytes wrong; first at item " + std::to_string(firstMismatchItem) +
923 " byte " + std::to_string(firstMismatchByte) + ": expected " +
924 toHex(firstExpected) + ", got " + toHex(firstActual));
925 double bytesPerSec =
926 (double)xferCount * (width / 8.0) * 1e6 / (double)duration.count();
927 logger.info("esitester",
928 " Bandwidth test: " + std::to_string(xferCount) + " x " +
929 std::to_string(width) + " bit transfers in " +
930 std::to_string(duration.count()) + " microseconds");
931 logger.info("esitester", " bandwidth: " + formatBandwidth(bytesPerSec));
932 if (checkData)
933 logger.info("esitester", " data integrity: passed");
934}
935
937 size_t width, size_t xferCount, bool checkData) {
938
939 AppIDPath lastPath;
940 BundlePort *fromHostMMIOPort =
941 acc->resolvePort({AppID("fromhostdma", width), AppID("cmd")}, lastPath);
942 if (!fromHostMMIOPort)
943 throw std::runtime_error("bandwidth test failed. No fromhostdma[" +
944 std::to_string(width) + "] found");
945 auto *fromHostMMIO = fromHostMMIOPort->getAs<services::MMIO::MMIORegion>();
946 if (!fromHostMMIO)
947 throw std::runtime_error("bandwidth test failed. MMIO port is not MMIO");
948 services::TelemetryService::Metric *checksumPort = nullptr;
949 if (checkData) {
950 auto fromHostChild = acc->getChildren().find(AppID("fromhostdma", width));
951 if (fromHostChild == acc->getChildren().end())
952 throw std::runtime_error("bandwidth test failed. No fromhostdma[" +
953 std::to_string(width) + "] found");
954 auto checksumIter =
955 fromHostChild->second->getPorts().find(AppID("fromHostChecksum"));
956 if (checksumIter == fromHostChild->second->getPorts().end())
957 throw std::runtime_error(
958 "bandwidth write data check failed. fromHostChecksum missing");
959 checksumPort =
960 checksumIter->second.getAs<services::TelemetryService::Metric>();
961 if (!checksumPort)
962 throw std::runtime_error("bandwidth write data check failed. "
963 "fromHostChecksum not telemetry");
964 checksumPort->connect();
965 }
966 lastPath.clear();
967 BundlePort *inPortBundle =
968 acc->resolvePort({AppID("fromhostdma", width), AppID("in")}, lastPath);
969 WriteChannelPort &outPort = inPortBundle->getRawWrite("data");
970 outPort.connect();
971
972 Logger &logger = conn->getLogger();
973 logger.info("esitester", "Starting write bandwidth test with " +
974 std::to_string(xferCount) + " x " +
975 std::to_string(width) + " bit transfers");
976 std::vector<uint8_t> dataVec(engineWireBytes(width));
977 for (size_t i = 0; i < dataVec.size(); ++i)
978 dataVec[i] = i;
979 MessageData data(dataVec);
980 uint64_t expectedChecksum = 0;
981 auto start = std::chrono::high_resolution_clock::now();
982 fromHostMMIO->read(8);
983 fromHostMMIO->write(0, xferCount);
984 for (size_t index = 0; index < xferCount; ++index) {
985 if (checkData) {
986 data =
987 MessageData(enginePatternBytes(static_cast<uint32_t>(index), width));
988 expectedChecksum ^=
989 enginePayloadFold(static_cast<uint32_t>(index), width);
990 }
991 outPort.write(data);
992 logger.debug(
993 [index,
994 &data](std::string &subsystem, std::string &msg,
995 std::unique_ptr<std::map<std::string, std::any>> &details) {
996 subsystem = "esitester";
997 msg = "Payload [" + std::to_string(index) + "] = 0x" + data.toHex();
998 });
999 }
1000 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
1001 std::chrono::high_resolution_clock::now() - start);
1002 if (checkData) {
1003 std::vector<uint8_t> expectedLast;
1004 if (xferCount != 0)
1005 expectedLast =
1006 enginePatternBytes(static_cast<uint32_t>(xferCount - 1), width);
1007 uint64_t expectedLastValue = 0;
1008 if (!expectedLast.empty())
1009 std::memcpy(&expectedLastValue, expectedLast.data(),
1010 std::min(expectedLast.size(), sizeof(expectedLastValue)));
1011 bool complete = xferCount == 0;
1012 uint64_t lastReadValue = 0;
1013 for (size_t attempt = 0; !complete && attempt < 5000; ++attempt) {
1014 lastReadValue = fromHostMMIO->read(8);
1015 if (lastReadValue == expectedLastValue) {
1016 complete = true;
1017 break;
1018 }
1019 std::this_thread::sleep_for(std::chrono::milliseconds(1));
1020 }
1021 if (!complete)
1022 throw std::runtime_error(
1023 "bandwidth write data check timed out waiting for completion: "
1024 "expected final payload " +
1025 toHex(expectedLastValue) + ", got " + toHex(lastReadValue));
1026 const uint64_t actualChecksum = checksumPort->readInt();
1027 if (actualChecksum != expectedChecksum)
1028 throw std::runtime_error(
1029 "bandwidth write data mismatch: checksum expected " +
1030 toHex(expectedChecksum) + ", got " + toHex(actualChecksum));
1031 }
1032 if (checkData)
1033 outPort.disconnect();
1034 double bytesPerSec =
1035 (double)xferCount * (width / 8.0) * 1e6 / (double)duration.count();
1036 logger.info("esitester",
1037 " Bandwidth test: " + std::to_string(xferCount) + " x " +
1038 std::to_string(width) + " bit transfers in " +
1039 std::to_string(duration.count()) + " microseconds");
1040 logger.info("esitester", " bandwidth: " + formatBandwidth(bytesPerSec));
1041 if (checkData)
1042 logger.info("esitester", " data integrity: passed");
1043}
1044
1046 const std::vector<uint32_t> &widths,
1047 uint32_t xferCount, bool read, bool write,
1048 bool checkData) {
1049 if (read)
1050 for (uint32_t w : widths)
1051 bandwidthReadTest(conn, acc, w, xferCount, checkData);
1052 if (write)
1053 for (uint32_t w : widths)
1054 bandwidthWriteTest(conn, acc, w, xferCount, checkData);
1055}
1056
1057// Fixed 64-bit seed for the hostmem burst data pattern; must match
1058// _ESITESTER_SEQ_SEED in esiaccel/esitester.py.
1059static constexpr uint64_t kEsitesterSeqSeed = 0x5A5A5A5A5A5A5A5AULL;
1060
1061// Byte j of element i in the hostmem burst data pattern: tile (seed ^ i)
1062// across the element's bytes and XOR each byte with a distinct per-position
1063// mask so every byte is unique. Must match WriteMem/ReadMem in
1064// esiaccel/esitester.py.
1065static inline uint8_t esitesterDataByte(uint32_t i, size_t j) {
1066 uint64_t seq = (uint64_t)i ^ kEsitesterSeqSeed;
1067 return (uint8_t)(seq >> (8 * (j % 8))) ^ (uint8_t)(j * 0x9D);
1068}
1069
1070static size_t hostmemWireBytes(uint32_t width) { return (width + 7) / 8; }
1071
1072static uint8_t esitesterHostmemByte(uint32_t index, size_t byte,
1073 uint32_t width) {
1074 uint8_t value = esitesterDataByte(index, byte);
1075 const size_t tailBits = width % 8;
1076 if (tailBits != 0 && byte + 1 == hostmemWireBytes(width))
1077 value &= (uint8_t(1) << tailBits) - 1;
1078 return value;
1079}
1080
1081// Fold element i's `width` bits into its 64-bit readChecksum contribution:
1082// XOR each 64-bit chunk with a per-chunk rotate so word/byte misplacement
1083// doesn't cancel. Must match ReadMem's readChecksum in esiaccel/esitester.py.
1084static inline uint64_t esitesterElemFold(uint32_t i, uint32_t width) {
1085 size_t numBytes = hostmemWireBytes(width);
1086 size_t numChunks = (width + 63) / 64;
1087 uint64_t fold = 0;
1088 for (size_t c = 0; c < numChunks; ++c) {
1089 uint64_t chunk = 0;
1090 for (size_t b = 0; b < 8; ++b) {
1091 size_t j = 8 * c + b;
1092 if (j < numBytes)
1093 chunk |= (uint64_t)esitesterHostmemByte(i, j, width) << (8 * b);
1094 }
1095 unsigned r = (8 * c) % 64;
1096 fold ^= r ? ((chunk << r) | (chunk >> (64 - r))) : chunk;
1097 }
1098 return fold;
1099}
1100
1101//
1102// Hostmem bandwidth test
1103//
1104
1105static void
1108 uint32_t width, uint32_t xferCount) {
1109 Logger &logger = conn->getLogger();
1110 logger.info("esitester", "Starting hostmem WRITE bandwidth test: " +
1111 std::to_string(xferCount) + " x " +
1112 std::to_string(width) + " bits");
1113
1114 auto writeMemChildIter = acc->getChildren().find(AppID("writemem", width));
1115 if (writeMemChildIter == acc->getChildren().end())
1116 throw std::runtime_error("hostmem write bandwidth: writemem child missing");
1117 auto &writeMemPorts = writeMemChildIter->second->getPorts();
1118
1119 // MMIO command surface and cycle telemetry live in nested BurstCommand
1120 // submodules: MMIO at writemem[width]/mmio[width]/cmd and the active-cycle
1121 // metric at writemem[width]/addrCmdResp/cycles.
1122 AppIDPath cmdPath;
1123 BundlePort *cmdPortBundle = acc->resolvePort(
1124 {AppID("writemem", width), AppID("mmio", width), AppID("cmd")}, cmdPath);
1125 if (!cmdPortBundle)
1126 throw std::runtime_error("hostmem write bandwidth: cmd MMIO missing");
1127 auto *cmdMMIO = cmdPortBundle->getAs<services::MMIO::MMIORegion>();
1128 if (!cmdMMIO)
1129 throw std::runtime_error("hostmem write bandwidth: cmd not MMIO");
1130
1131 AppIDPath cyclePath;
1132 BundlePort *cyclePortBundle = acc->resolvePort(
1133 {AppID("writemem", width), AppID("addrCmdResp"), AppID("cycles")},
1134 cyclePath);
1135 auto issuedIter = writeMemPorts.find(AppID("addrCmdIssued"));
1136 auto respIter = writeMemPorts.find(AppID("addrCmdResponses"));
1137 if (issuedIter == writeMemPorts.end() || respIter == writeMemPorts.end() ||
1138 !cyclePortBundle)
1139 throw std::runtime_error("hostmem write bandwidth: telemetry missing");
1140 auto *issuedPort =
1141 issuedIter->second.getAs<services::TelemetryService::Metric>();
1142 auto *respPort = respIter->second.getAs<services::TelemetryService::Metric>();
1143 auto *cyclePort =
1144 cyclePortBundle->getAs<services::TelemetryService::Metric>();
1145 if (!issuedPort || !respPort || !cyclePort)
1146 throw std::runtime_error(
1147 "hostmem write bandwidth: telemetry type mismatch");
1148
1149 issuedPort->connect();
1150 respPort->connect();
1151 cyclePort->connect();
1152
1153 // Initialize pattern (optional).
1154 uint64_t *dataPtr = static_cast<uint64_t *>(region.getPtr());
1155 size_t words = region.getSize() / 8;
1156 for (size_t i = 0; i < words; ++i)
1157 dataPtr[i] = i + 0xA5A50000;
1158 region.flush();
1159
1160 auto start = std::chrono::high_resolution_clock::now();
1161 // Fire off xferCount write commands (one flit each).
1162 uint64_t devPtr = reinterpret_cast<uint64_t>(region.getDevicePtr());
1163 cmdMMIO->write(0x08, devPtr); // address
1164 cmdMMIO->write(0x10, xferCount); // flits
1165 cmdMMIO->write(0x18, 1); // start
1166
1167 // Wait for responses counter to reach target.
1168 bool completed = false;
1169 for (int wait = 0; wait < 100000; ++wait) {
1170 uint64_t respNow = respPort->readInt();
1171 if (respNow == xferCount) {
1172 completed = true;
1173 break;
1174 }
1175 std::this_thread::sleep_for(std::chrono::microseconds(50));
1176 }
1177 if (!completed)
1178 throw std::runtime_error("hostmem write bandwidth timeout");
1179 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
1180 std::chrono::high_resolution_clock::now() - start);
1181 double bytesPerSec =
1182 (double)xferCount * (width / 8.0) * 1e6 / (double)duration.count();
1183 uint64_t cycles = cyclePort->readInt();
1184 double bytesPerCycle = (double)xferCount * (width / 8.0) / (double)cycles;
1185 std::cout << "[WRITE] Hostmem bandwidth (" << std::to_string(width)
1186 << "): " << formatBandwidth(bytesPerSec) << " "
1187 << std::to_string(xferCount) << " flits in "
1188 << std::to_string(duration.count()) << " us, "
1189 << std::to_string(cycles) << " cycles, " << bytesPerCycle
1190 << " bytes/cycle" << std::endl;
1191
1192 // Data integrity: WriteMem wrote the byte-level pattern (esitesterDataByte)
1193 // into element i. The host-memory layout must be contiguous and backend-
1194 // width-independent, so element i occupies ceil(width/8) bytes at that
1195 // stride; verify every byte.
1196 uint8_t *bytePtr = static_cast<uint8_t *>(region.getPtr());
1197 size_t elemBytes = hostmemWireBytes(width);
1198 size_t mismatches = 0;
1199 uint32_t firstMismatch = 0;
1200 size_t firstByte = 0;
1201 uint8_t firstExpected = 0, firstActual = 0;
1202 for (uint32_t i = 0; i < xferCount; ++i) {
1203 for (size_t j = 0; j < elemBytes; ++j) {
1204 uint8_t expected = esitesterHostmemByte(i, j, width);
1205 uint8_t actual = bytePtr[(size_t)i * elemBytes + j];
1206 if (actual != expected) {
1207 if (mismatches == 0) {
1208 firstMismatch = i;
1209 firstByte = j;
1210 firstExpected = expected;
1211 firstActual = actual;
1212 }
1213 ++mismatches;
1214 }
1215 }
1216 }
1217 if (mismatches != 0) {
1218 char eb[8], gb[8];
1219 std::snprintf(eb, sizeof(eb), "0x%02x", firstExpected);
1220 std::snprintf(gb, sizeof(gb), "0x%02x", firstActual);
1221 throw std::runtime_error(
1222 "hostmem write bandwidth data mismatch: " + std::to_string(mismatches) +
1223 " bytes wrong; first at element " + std::to_string(firstMismatch) +
1224 " byte " + std::to_string(firstByte) + " expected=" + eb +
1225 " got=" + gb);
1226 }
1227}
1228
1229static void
1232 uint32_t width, uint32_t xferCount) {
1233 Logger &logger = conn->getLogger();
1234 logger.info("esitester", "Starting hostmem READ bandwidth test: " +
1235 std::to_string(xferCount) + " x " +
1236 std::to_string(width) + " bits");
1237
1238 auto readMemChildIter = acc->getChildren().find(AppID("readmem", width));
1239 if (readMemChildIter == acc->getChildren().end())
1240 throw std::runtime_error("hostmem read bandwidth: readmem child missing");
1241 auto &readMemPorts = readMemChildIter->second->getPorts();
1242
1243 // MMIO at readmem[width]/mmio[width]/cmd; active-cycle metric at
1244 // readmem[width]/addrCmdResp/cycles (nested BurstCommand submodules).
1245 AppIDPath cmdPath;
1246 BundlePort *cmdPortBundle = acc->resolvePort(
1247 {AppID("readmem", width), AppID("mmio", width), AppID("cmd")}, cmdPath);
1248 if (!cmdPortBundle)
1249 throw std::runtime_error("hostmem read bandwidth: cmd MMIO missing");
1250 auto *cmdMMIO = cmdPortBundle->getAs<services::MMIO::MMIORegion>();
1251 if (!cmdMMIO)
1252 throw std::runtime_error("hostmem read bandwidth: cmd not MMIO");
1253
1254 AppIDPath cyclePath;
1255 BundlePort *cyclePortBundle = acc->resolvePort(
1256 {AppID("readmem", width), AppID("addrCmdResp"), AppID("cycles")},
1257 cyclePath);
1258 auto issuedIter = readMemPorts.find(AppID("addrCmdIssued"));
1259 auto respIter = readMemPorts.find(AppID("addrCmdResponses"));
1260 auto checksumIter = readMemPorts.find(AppID("readChecksum"));
1261 if (issuedIter == readMemPorts.end() || respIter == readMemPorts.end() ||
1262 checksumIter == readMemPorts.end() || !cyclePortBundle)
1263 throw std::runtime_error("hostmem read bandwidth: telemetry missing");
1264 auto *issuedPort =
1265 issuedIter->second.getAs<services::TelemetryService::Metric>();
1266 auto *respPort = respIter->second.getAs<services::TelemetryService::Metric>();
1267 auto *checksumPort =
1268 checksumIter->second.getAs<services::TelemetryService::Metric>();
1269 auto *cycleCntPort =
1270 cyclePortBundle->getAs<services::TelemetryService::Metric>();
1271 if (!issuedPort || !respPort || !checksumPort || !cycleCntPort)
1272 throw std::runtime_error("hostmem read bandwidth: telemetry type mismatch");
1273 issuedPort->connect();
1274 respPort->connect();
1275 checksumPort->connect();
1276 cycleCntPort->connect();
1277
1278 // Lay out the read data contiguously (natural ceil(width/8)-byte stride)
1279 // using the byte-level pattern for every byte; ReadMem folds each received
1280 // element into readChecksum, which must match this host-side fold if the
1281 // read fetched the right bytes.
1282 uint8_t *bytePtr = static_cast<uint8_t *>(region.getPtr());
1283 size_t elemBytes = hostmemWireBytes(width);
1284 uint64_t expectedChecksum = 0;
1285 for (uint32_t i = 0; i < xferCount; ++i) {
1286 for (size_t j = 0; j < elemBytes; ++j)
1287 bytePtr[(size_t)i * elemBytes + j] = esitesterHostmemByte(i, j, width);
1288 expectedChecksum ^= esitesterElemFold(i, width);
1289 }
1290 region.flush();
1291 uint64_t devPtr = reinterpret_cast<uint64_t>(region.getDevicePtr());
1292 auto start = std::chrono::high_resolution_clock::now();
1293
1294 cmdMMIO->write(0x08, devPtr);
1295 cmdMMIO->write(0x10, xferCount);
1296 cmdMMIO->write(0x18, 1);
1297
1298 bool timeout = true;
1299 for (int wait = 0; wait < 100000; ++wait) {
1300 uint64_t respNow = respPort->readInt();
1301 if (respNow == xferCount) {
1302 timeout = false;
1303 break;
1304 }
1305 std::this_thread::sleep_for(std::chrono::microseconds(50));
1306 }
1307 if (timeout)
1308 throw std::runtime_error("hostmem read bandwidth timeout");
1309 auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
1310 std::chrono::high_resolution_clock::now() - start);
1311 double bytesPerSec =
1312 (double)xferCount * (width / 8.0) * 1e6 / (double)duration.count();
1313 uint64_t cycles = cycleCntPort->readInt();
1314 double bytesPerCycle = (double)xferCount * (width / 8.0) / (double)cycles;
1315 std::cout << "[ READ] Hostmem bandwidth (" << width
1316 << "): " << formatBandwidth(bytesPerSec) << ", " << xferCount
1317 << " flits in " << duration.count() << " us, " << cycles
1318 << " cycles, " << bytesPerCycle << " bytes/cycle" << std::endl;
1319
1320 uint64_t gotChecksum = checksumPort->readInt();
1321 if (gotChecksum != expectedChecksum) {
1322 char eb[24], gb[24];
1323 std::snprintf(eb, sizeof(eb), "0x%016llx",
1324 (unsigned long long)expectedChecksum);
1325 std::snprintf(gb, sizeof(gb), "0x%016llx", (unsigned long long)gotChecksum);
1326 throw std::runtime_error(
1327 std::string(
1328 "hostmem read bandwidth data mismatch: checksum expected ") +
1329 eb + " got " + gb);
1330 }
1331}
1332
1334 uint32_t xferCount,
1335 const std::vector<uint32_t> &widths, bool read,
1336 bool write) {
1337 auto hostmemSvc = conn->getService<services::HostMem>();
1338 hostmemSvc->start();
1339 auto region = hostmemSvc->allocate(/*size(bytes)=*/1024 * 1024 * 1024,
1340 /*memOpts=*/{.writeable = true});
1341 for (uint32_t w : widths) {
1342 if (write)
1343 hostmemWriteBandwidthTest(conn, acc, *region, w, xferCount);
1344 if (read)
1345 hostmemReadBandwidthTest(conn, acc, *region, w, xferCount);
1346 }
1347}
1348
1350 uint32_t iterations, bool pipeline) {
1351 Logger &logger = conn->getLogger();
1352 auto loopbackChild = accel->getChildren().find(AppID("loopback"));
1353 if (loopbackChild == accel->getChildren().end())
1354 throw std::runtime_error("Loopback test: no 'loopback' child");
1355 auto &ports = loopbackChild->second->getPorts();
1356 auto addIter = ports.find(AppID("add"));
1357 if (addIter == ports.end())
1358 throw std::runtime_error("Loopback test: no 'add' port");
1359
1360 // Use FuncService::Func instead of raw channels.
1361 auto *funcPort = addIter->second.getAs<services::FuncService::Function>();
1362 if (!funcPort)
1363 throw std::runtime_error(
1364 "Loopback test: 'add' port not a FuncService::Function");
1365 funcPort->connect();
1366 if (iterations == 0) {
1367 logger.info("esitester", "Loopback add test: 0 iterations (skipped)");
1368 return;
1369 }
1370 std::mt19937_64 rng(0xC0FFEE);
1371 std::uniform_int_distribution<uint32_t> dist(0, (1u << 24) - 1);
1372
1373 if (!pipeline) {
1374 auto start = std::chrono::high_resolution_clock::now();
1375 for (uint32_t i = 0; i < iterations; ++i) {
1376 uint32_t argVal = dist(rng);
1377 uint32_t expected = (argVal + 11) & 0xFFFF;
1378 uint8_t argBytes[3] = {
1379 static_cast<uint8_t>(argVal & 0xFF),
1380 static_cast<uint8_t>((argVal >> 8) & 0xFF),
1381 static_cast<uint8_t>((argVal >> 16) & 0xFF),
1382 };
1383 MessageData argMsg(argBytes, 3);
1384 MessageData resMsg = funcPort->call(argMsg).get();
1385 uint16_t got = *resMsg.as<uint16_t>();
1386 std::cout << "[loopback] i=" << i << " arg=0x" << esi::toHex(argVal)
1387 << " got=0x" << esi::toHex(got) << " exp=0x"
1388 << esi::toHex(expected) << std::endl;
1389 if (got != expected)
1390 throw std::runtime_error("Loopback mismatch (non-pipelined)");
1391 }
1392 auto end = std::chrono::high_resolution_clock::now();
1393 auto us = std::chrono::duration_cast<std::chrono::microseconds>(end - start)
1394 .count();
1395 double callsPerSec = (double)iterations * 1e6 / (double)us;
1396 logger.info("esitester", "Loopback add test passed (non-pipelined, " +
1397 std::to_string(iterations) + " calls, " +
1398 std::to_string(us) + " us, " +
1399 std::to_string(callsPerSec) + " calls/s)");
1400 } else {
1401 // Pipelined mode: launch all calls first, then collect.
1402 std::vector<std::future<MessageData>> futures;
1403 futures.reserve(iterations);
1404 std::vector<uint32_t> expectedVals;
1405 expectedVals.reserve(iterations);
1406
1407 auto issueStart = std::chrono::high_resolution_clock::now();
1408 for (uint32_t i = 0; i < iterations; ++i) {
1409 uint32_t argVal = dist(rng);
1410 uint32_t expected = (argVal + 11) & 0xFFFF;
1411 uint8_t argBytes[3] = {
1412 static_cast<uint8_t>(argVal & 0xFF),
1413 static_cast<uint8_t>((argVal >> 8) & 0xFF),
1414 static_cast<uint8_t>((argVal >> 16) & 0xFF),
1415 };
1416 futures.emplace_back(funcPort->call(MessageData(argBytes, 3)));
1417 expectedVals.emplace_back(expected);
1418 }
1419 auto issueEnd = std::chrono::high_resolution_clock::now();
1420
1421 for (uint32_t i = 0; i < iterations; ++i) {
1422 MessageData resMsg = futures[i].get();
1423 uint16_t got = *resMsg.as<uint16_t>();
1424 uint16_t exp = (uint16_t)expectedVals[i];
1425 std::cout << "[loopback-pipelined] i=" << i << " got=0x"
1426 << esi::toHex(got) << " exp=0x" << esi::toHex(exp) << std::endl;
1427 if (got != exp)
1428 throw std::runtime_error("Loopback mismatch (pipelined) idx=" +
1429 std::to_string(i));
1430 }
1431 auto collectEnd = std::chrono::high_resolution_clock::now();
1432
1433 auto issueUs = std::chrono::duration_cast<std::chrono::microseconds>(
1434 issueEnd - issueStart)
1435 .count();
1436 auto totalUs = std::chrono::duration_cast<std::chrono::microseconds>(
1437 collectEnd - issueStart)
1438 .count();
1439
1440 double issueRate = (double)iterations * 1e6 / (double)issueUs;
1441 double completionRate = (double)iterations * 1e6 / (double)totalUs;
1442
1443 logger.info("esitester", "Loopback add test passed (pipelined). Issued " +
1444 std::to_string(iterations) + " in " +
1445 std::to_string(issueUs) + " us (" +
1446 std::to_string(issueRate) +
1447 " calls/s), total " + std::to_string(totalUs) +
1448 " us (" + std::to_string(completionRate) +
1449 " calls/s effective)");
1450 }
1451}
1452
1453// Exercise the design reset feature using existing telemetry. Run a hostmem
1454// write operation on the 'writemem' module, which increments its
1455// 'addrCmdResponses' telemetry counter. Confirm the telemetry advanced,
1456// request a design reset, then confirm the telemetry has been cleared back to
1457// zero (the counters live in the user design which the reset clears).
1458static void resetTest(AcceleratorConnection *conn, Accelerator *accel) {
1459 Logger &logger = conn->getLogger();
1460 constexpr uint32_t width = 64;
1461
1462 // Run an existing test that increments telemetry. The hostmem write test
1463 // bumps the writemem module's 'addrCmdResponses' counter.
1464 hostmemTest(conn, accel, {width}, /*write=*/true, /*read=*/false);
1465
1466 // Grab the writemem module's response telemetry counter to observe the
1467 // reset.
1468 auto writeMemChildIter = accel->getChildren().find(AppID("writemem", width));
1469 if (writeMemChildIter == accel->getChildren().end())
1470 throw std::runtime_error("Reset test: no 'writemem' child");
1471 auto &ports = writeMemChildIter->second->getPorts();
1472 auto respIter = ports.find(AppID("addrCmdResponses"));
1473 if (respIter == ports.end())
1474 throw std::runtime_error(
1475 "Reset test: no 'addrCmdResponses' telemetry port");
1476 auto *respMetric =
1477 respIter->second.getAs<services::TelemetryService::Metric>();
1478 if (!respMetric)
1479 throw std::runtime_error("Reset test: 'addrCmdResponses' not telemetry");
1480 respMetric->connect();
1481
1482 uint64_t before = respMetric->readInt();
1483 std::cout << "[reset] telemetry addrCmdResponses before reset = " << before
1484 << std::endl;
1485 if (before == 0)
1486 throw std::runtime_error(
1487 "Reset test: telemetry was not incremented by the hostmem write");
1488
1489 // Request a design reset.
1490 logger.info("esitester", "Requesting design reset");
1491 if (!conn->reset())
1492 throw std::runtime_error("Reset test: reset() reported failure");
1493 std::cout << "[reset] reset requested" << std::endl;
1494
1495 // The reset is asserted a fixed number of cycles after the request (to let
1496 // in-flight transactions drain), so poll the telemetry until it clears.
1497 uint64_t after = before;
1498 constexpr int maxPolls = 1000000;
1499 for (int polls = 0; polls < maxPolls; ++polls) {
1500 after = respMetric->readInt();
1501 if (after == 0)
1502 break;
1503 std::this_thread::sleep_for(std::chrono::microseconds(1));
1504 }
1505 std::cout << "[reset] telemetry addrCmdResponses after reset = " << after
1506 << std::endl;
1507 if (after != 0)
1508 throw std::runtime_error(
1509 "Reset test: telemetry was not cleared by the reset (got " +
1510 std::to_string(after) + ")");
1511
1512 std::cout << "Reset test passed" << std::endl;
1513}
1514
1516 Accelerator *acc, uint32_t width,
1517 uint32_t xferCount, bool read,
1518 bool write) {
1519 Logger &logger = conn->getLogger();
1520 if (!read && !write) {
1521 std::cout << "aggbandwidth: nothing to do (enable --read and/or --write)\n";
1522 return;
1523 }
1524 logger.info(
1525 "esitester",
1526 "Aggregate hostmem bandwidth start width=" + std::to_string(width) +
1527 " count=" + std::to_string(xferCount) +
1528 " read=" + (read ? "Y" : "N") + " write=" + (write ? "Y" : "N"));
1529
1530 auto hostmemSvc = conn->getService<services::HostMem>();
1531 hostmemSvc->start();
1532
1533 struct Unit {
1534 std::string prefix;
1535 bool isRead = false;
1536 bool isWrite = false;
1537 std::unique_ptr<esi::services::HostMem::HostMemRegion> region;
1538 services::TelemetryService::Metric *resp = nullptr;
1539 services::TelemetryService::Metric *cycles = nullptr;
1540 services::MMIO::MMIORegion *cmd = nullptr;
1541 bool launched = false;
1542 bool done = false;
1543 uint64_t bytes = 0;
1544 uint64_t duration_us = 0;
1545 uint64_t cycleCount = 0;
1546 std::chrono::high_resolution_clock::time_point start;
1547 };
1548 std::vector<Unit> units;
1549 const std::vector<std::string> readPrefixes = {"readmem", "readmem_0",
1550 "readmem_1", "readmem_2"};
1551 const std::vector<std::string> writePrefixes = {"writemem", "writemem_0",
1552 "writemem_1", "writemem_2"};
1553
1554 // Size each unit's region to the actual transfer (min 1 MiB) rather than a
1555 // fixed 1 GiB, so aggregating many units stays memory-bounded.
1556 size_t strideBytes = ((width + 31) / 32) * 4;
1557 size_t neededBytes = static_cast<size_t>(xferCount) * strideBytes;
1558 size_t regionBytes = neededBytes < (1u << 20) ? (1u << 20) : neededBytes;
1559
1560 auto addUnits = [&](const std::vector<std::string> &pref, bool doRead,
1561 bool doWrite) {
1562 for (auto &p : pref) {
1563 AppID id(p, width);
1564 auto childIt = acc->getChildren().find(id);
1565 if (childIt == acc->getChildren().end())
1566 continue; // silently skip missing variants
1567 auto &ports = childIt->second->getPorts();
1568 auto respIt = ports.find(AppID("addrCmdResponses"));
1569 // MMIO ('cmd') and the cycle metric are nested inside BurstCommand
1570 // submodules: <unit>/mmio[width]/cmd and <unit>/addrCmdResp/cycles.
1571 AppIDPath cmdPath, cycPath;
1572 BundlePort *cmdBundle =
1573 acc->resolvePort({id, AppID("mmio", width), AppID("cmd")}, cmdPath);
1574 BundlePort *cycBundle = acc->resolvePort(
1575 {id, AppID("addrCmdResp"), AppID("cycles")}, cycPath);
1576 if (respIt == ports.end() || !cmdBundle || !cycBundle)
1577 continue;
1578 auto *cmd = cmdBundle->getAs<services::MMIO::MMIORegion>();
1579 auto *resp = respIt->second.getAs<services::TelemetryService::Metric>();
1580 auto *cyc = cycBundle->getAs<services::TelemetryService::Metric>();
1581 if (!cmd || !resp || !cyc)
1582 continue;
1583 resp->connect();
1584 cyc->connect();
1585 Unit u;
1586 u.prefix = p;
1587 u.isRead = doRead;
1588 u.isWrite = doWrite;
1589 u.region = hostmemSvc->allocate(regionBytes, {.writeable = true});
1590 // Init pattern.
1591 uint64_t *ptr = static_cast<uint64_t *>(u.region->getPtr());
1592 size_t words = u.region->getSize() / 8;
1593 for (size_t i = 0; i < words; ++i)
1594 ptr[i] =
1595 (p[0] == 'w' ? (0xA5A500000000ull + i) : (0xCAFEBABE0000ull + i));
1596 u.region->flush();
1597 u.cmd = cmd;
1598 u.resp = resp;
1599 u.cycles = cyc;
1600 u.bytes = uint64_t(xferCount) * (width / 8);
1601 units.emplace_back(std::move(u));
1602 }
1603 };
1604 if (read)
1605 addUnits(readPrefixes, true, false);
1606 if (write)
1607 addUnits(writePrefixes, false, true);
1608 if (units.empty()) {
1609 std::cout << "aggbandwidth: no matching units present for width " << width
1610 << "\n";
1611 return;
1612 }
1613
1614 auto wallStart = std::chrono::high_resolution_clock::now();
1615 // Launch sequentially.
1616 for (auto &u : units) {
1617 uint64_t devPtr = reinterpret_cast<uint64_t>(u.region->getDevicePtr());
1618 u.cmd->write(0x08, devPtr);
1619 u.cmd->write(0x10, xferCount);
1620 u.cmd->write(0x18, 1);
1621 u.start = std::chrono::high_resolution_clock::now();
1622 u.launched = true;
1623 }
1624
1625 // Poll all until complete.
1626 const uint64_t timeoutLoops = 200000; // ~10s at 50us sleep
1627 uint64_t loops = 0;
1628 while (true) {
1629 bool allDone = true;
1630 for (auto &u : units) {
1631 if (u.done)
1632 continue;
1633 if (u.resp->readInt() == xferCount) {
1634 auto end = std::chrono::high_resolution_clock::now();
1635 u.duration_us =
1636 std::chrono::duration_cast<std::chrono::microseconds>(end - u.start)
1637 .count();
1638 u.cycleCount = u.cycles->readInt();
1639 u.done = true;
1640 } else {
1641 allDone = false;
1642 }
1643 }
1644 if (allDone)
1645 break;
1646 if (++loops >= timeoutLoops)
1647 throw std::runtime_error("aggbandwidth: timeout");
1648 std::this_thread::sleep_for(std::chrono::microseconds(50));
1649 }
1650 auto wallUs = std::chrono::duration_cast<std::chrono::microseconds>(
1651 std::chrono::high_resolution_clock::now() - wallStart)
1652 .count();
1653
1654 uint64_t totalBytes = 0;
1655 uint64_t totalReadBytes = 0;
1656 uint64_t totalWriteBytes = 0;
1657 for (auto &u : units) {
1658 totalBytes += u.bytes;
1659 if (u.isRead)
1660 totalReadBytes += u.bytes;
1661 if (u.isWrite)
1662 totalWriteBytes += u.bytes;
1663 double unitBps = (double)u.bytes * 1e6 / (double)u.duration_us;
1664 std::cout << "[agg-unit] " << u.prefix << "[" << width << "] "
1665 << (u.isRead ? "READ" : (u.isWrite ? "WRITE" : "UNK"))
1666 << " bytes=" << humanBytes(u.bytes) << " (" << u.bytes << " B)"
1667 << " time=" << humanTimeUS(u.duration_us) << " (" << u.duration_us
1668 << " us) cycles=" << u.cycleCount
1669 << " throughput=" << formatBandwidth(unitBps) << std::endl;
1670 }
1671 // Compute aggregate bandwidths as total size / total wall time (not sum of
1672 // unit throughputs).
1673 double aggReadBps =
1674 totalReadBytes ? (double)totalReadBytes * 1e6 / (double)wallUs : 0.0;
1675 double aggWriteBps =
1676 totalWriteBytes ? (double)totalWriteBytes * 1e6 / (double)wallUs : 0.0;
1677 double aggCombinedBps =
1678 totalBytes ? (double)totalBytes * 1e6 / (double)wallUs : 0.0;
1679
1680 std::cout << "[agg-total] units=" << units.size()
1681 << " read_bytes=" << humanBytes(totalReadBytes) << " ("
1682 << totalReadBytes << " B)"
1683 << " read_bw=" << formatBandwidth(aggReadBps)
1684 << " write_bytes=" << humanBytes(totalWriteBytes) << " ("
1685 << totalWriteBytes << " B)"
1686 << " write_bw=" << formatBandwidth(aggWriteBps)
1687 << " combined_bytes=" << humanBytes(totalBytes) << " ("
1688 << totalBytes << " B)"
1689 << " combined_bw=" << formatBandwidth(aggCombinedBps)
1690 << " wall_time=" << humanTimeUS(wallUs) << " (" << wallUs << " us)"
1691 << std::endl;
1692 logger.info("esitester", "Aggregate hostmem bandwidth test complete");
1693}
1694
1695/// Packed struct representing a parallel window argument for StreamingAdder.
1696/// Layout in SystemVerilog (so it must be reversed in C):
1697/// { add_amt: UInt(32), input: UInt(32), last: UInt(8) }
1698#pragma pack(push, 1)
1700 uint8_t last;
1701 uint32_t input;
1702 uint32_t addAmt;
1703};
1704#pragma pack(pop)
1705static_assert(sizeof(StreamingAddArg) == 9,
1706 "StreamingAddArg must be 9 bytes packed");
1707
1708/// Packed struct representing a parallel window result for StreamingAdder.
1709/// Layout in SystemVerilog (so it must be reversed in C):
1710/// { data: UInt(32), last: UInt(8) }
1711#pragma pack(push, 1)
1713 uint8_t last;
1714 uint32_t data;
1715};
1716#pragma pack(pop)
1717static_assert(sizeof(StreamingAddResult) == 5,
1718 "StreamingAddResult must be 5 bytes packed");
1719
1720/// Test the StreamingAdder module. This module takes a struct containing
1721/// an add_amt and a list of uint32s, adds add_amt to each element, and
1722/// returns the resulting list. The data is streamed using windowed types.
1724 uint32_t addAmt, uint32_t numItems) {
1725 Logger &logger = conn->getLogger();
1726 logger.info("esitester", "Starting streaming add test with add_amt=" +
1727 std::to_string(addAmt) +
1728 ", num_items=" + std::to_string(numItems));
1729
1730 // Generate random input data.
1731 std::mt19937 rng(0xDEADBEEF);
1732 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
1733 std::vector<uint32_t> inputData;
1734 inputData.reserve(numItems);
1735 for (uint32_t i = 0; i < numItems; ++i)
1736 inputData.push_back(dist(rng));
1737
1738 // Find the streaming_adder child.
1739 auto streamingAdderChild =
1740 accel->getChildren().find(AppID("streaming_adder"));
1741 if (streamingAdderChild == accel->getChildren().end())
1742 throw std::runtime_error(
1743 "Streaming add test: no 'streaming_adder' child found");
1744
1745 auto &ports = streamingAdderChild->second->getPorts();
1746 auto addIter = ports.find(AppID("streaming_add"));
1747 if (addIter == ports.end())
1748 throw std::runtime_error(
1749 "Streaming add test: no 'streaming_add' port found");
1750
1751 // Get the raw read/write channel ports for the windowed function.
1752 // The argument channel expects parallel windowed data where each message
1753 // contains: struct { add_amt: UInt(32), input: UInt(32), last: bool }
1754 WriteChannelPort &argPort = addIter->second.getRawWrite("arg");
1755 ReadChannelPort &resultPort = addIter->second.getRawRead("result");
1756
1757 argPort.connect(ChannelPort::ConnectOptions(std::nullopt, false));
1758 resultPort.connect(ChannelPort::ConnectOptions(std::nullopt, false));
1759
1760 // Send each list element with add_amt repeated in every message.
1761 for (size_t i = 0; i < inputData.size(); ++i) {
1762 StreamingAddArg arg;
1763 arg.addAmt = addAmt;
1764 arg.input = inputData[i];
1765 arg.last = (i == inputData.size() - 1) ? 1 : 0;
1766 argPort.write(
1767 MessageData(reinterpret_cast<const uint8_t *>(&arg), sizeof(arg)));
1768 logger.debug("esitester", "Sent {add_amt=" + std::to_string(arg.addAmt) +
1769 ", input=" + std::to_string(arg.input) +
1770 ", last=" + (arg.last ? "true" : "false") +
1771 "}");
1772 }
1773
1774 // Read the result list (also windowed).
1775 std::vector<uint32_t> results;
1776 bool lastSeen = false;
1777 while (!lastSeen) {
1778 MessageData resMsg;
1779 resultPort.read(resMsg);
1780 if (resMsg.getSize() < sizeof(StreamingAddResult))
1781 throw std::runtime_error(
1782 "Streaming add test: unexpected result message size");
1783
1784 const auto *res =
1785 reinterpret_cast<const StreamingAddResult *>(resMsg.getBytes());
1786 lastSeen = res->last != 0;
1787 results.push_back(res->data);
1788 logger.debug("esitester", "Received result=" + std::to_string(res->data) +
1789 " (last=" + (lastSeen ? "true" : "false") +
1790 ")");
1791 }
1792
1793 // Verify results.
1794 if (results.size() != inputData.size())
1795 throw std::runtime_error(
1796 "Streaming add test: result size mismatch. Expected " +
1797 std::to_string(inputData.size()) + ", got " +
1798 std::to_string(results.size()));
1799
1800 bool passed = true;
1801 std::cout << "Streaming add test results:" << std::endl;
1802 for (size_t i = 0; i < inputData.size(); ++i) {
1803 uint32_t expected = inputData[i] + addAmt;
1804 std::cout << " input[" << i << "]=" << inputData[i] << " + " << addAmt
1805 << " = " << results[i] << " (expected " << expected << ")";
1806 if (results[i] != expected) {
1807 std::cout << " MISMATCH!";
1808 passed = false;
1809 }
1810 std::cout << std::endl;
1811 }
1812
1813 argPort.disconnect();
1814 resultPort.disconnect();
1815
1816 if (!passed)
1817 throw std::runtime_error("Streaming add test failed: result mismatch");
1818
1819 logger.info("esitester", "Streaming add test passed");
1820 std::cout << "Streaming add test passed" << std::endl;
1821}
1822
1823/// Test the StreamingAdder module using message translation.
1824/// This version uses the list translation support where the message format is:
1825/// Argument: { add_amt (4 bytes), input_length (8 bytes), input_data[] }
1826/// Result: { data_length (8 bytes), data[] }
1827/// The translation layer automatically converts between this format and the
1828/// parallel windowed frames used by the hardware.
1829
1830/// Translated argument struct for StreamingAdder.
1831/// Memory layout (standard C struct ordering, fields in declaration order):
1832/// ESI type: struct { add_amt: UInt(32), input: List<UInt(32)> }
1833/// becomes host struct:
1834/// { input_length (size_t, 8 bytes on 64-bit), add_amt (uint32_t),
1835/// input_data[] }
1836/// Note: The translation layer handles the conversion between this C struct
1837/// layout and the hardware's SystemVerilog frame format.
1838/// Note: size_t is used for list lengths, so this format is platform-dependent.
1839#pragma pack(push, 1)
1842 uint32_t addAmt;
1843 // Trailing array data follows immediately after the struct in memory.
1844 // Use inputData() accessor to access it.
1845
1846 /// Get pointer to trailing input data array.
1847 uint32_t *inputData() { return reinterpret_cast<uint32_t *>(this + 1); }
1848 const uint32_t *inputData() const {
1849 return reinterpret_cast<const uint32_t *>(this + 1);
1850 }
1851 /// Get span view of input data (requires inputLength to be set first).
1852 std::span<uint32_t> inputDataSpan() { return {inputData(), inputLength}; }
1853 std::span<const uint32_t> inputDataSpan() const {
1854 return {inputData(), inputLength};
1855 }
1856
1857 static size_t allocSize(size_t numItems) {
1858 return sizeof(StreamingAddTranslatedArg) + numItems * sizeof(uint32_t);
1859 }
1860};
1861#pragma pack(pop)
1862
1863/// Translated result struct for StreamingAdder.
1864/// Memory layout:
1865/// struct { data: List<UInt(32)> }
1866/// becomes:
1867/// { data_length (size_t, 8 bytes on 64-bit), data[] }
1868#pragma pack(push, 1)
1871 // Trailing array data follows immediately after the struct in memory.
1872
1873 /// Get pointer to trailing result data array.
1874 uint32_t *data() { return reinterpret_cast<uint32_t *>(this + 1); }
1875 const uint32_t *data() const {
1876 return reinterpret_cast<const uint32_t *>(this + 1);
1877 }
1878 /// Get span view of result data (requires dataLength to be set first).
1879 std::span<uint32_t> dataSpan() { return {data(), dataLength}; }
1880 std::span<const uint32_t> dataSpan() const { return {data(), dataLength}; }
1881
1882 static size_t allocSize(size_t numItems) {
1883 return sizeof(StreamingAddTranslatedResult) + numItems * sizeof(uint32_t);
1884 }
1885};
1886#pragma pack(pop)
1887
1889 Accelerator *accel, uint32_t addAmt,
1890 uint32_t numItems) {
1891 Logger &logger = conn->getLogger();
1892 logger.info("esitester",
1893 "Starting streaming add test (translated) with add_amt=" +
1894 std::to_string(addAmt) +
1895 ", num_items=" + std::to_string(numItems));
1896
1897 // Generate random input data.
1898 std::mt19937 rng(0xDEADBEEF);
1899 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
1900 std::vector<uint32_t> inputData;
1901 inputData.reserve(numItems);
1902 for (uint32_t i = 0; i < numItems; ++i)
1903 inputData.push_back(dist(rng));
1904
1905 // Find the streaming_adder child.
1906 auto streamingAdderChild =
1907 accel->getChildren().find(AppID("streaming_adder"));
1908 if (streamingAdderChild == accel->getChildren().end())
1909 throw std::runtime_error(
1910 "Streaming add test: no 'streaming_adder' child found");
1911
1912 auto &ports = streamingAdderChild->second->getPorts();
1913 auto addIter = ports.find(AppID("streaming_add"));
1914 if (addIter == ports.end())
1915 throw std::runtime_error(
1916 "Streaming add test: no 'streaming_add' port found");
1917
1918 // Get the raw read/write channel ports with translation enabled (default).
1919 WriteChannelPort &argPort = addIter->second.getRawWrite("arg");
1920 ReadChannelPort &resultPort = addIter->second.getRawRead("result");
1921
1922 // Connect with translation enabled (the default).
1923 argPort.connect();
1924 resultPort.connect();
1925
1926 // Allocate the argument struct with proper alignment for the struct
1927 // members. We use aligned_alloc to ensure the buffer meets alignment
1928 // requirements.
1929 size_t argSize = StreamingAddTranslatedArg::allocSize(numItems);
1930 constexpr size_t alignment = alignof(StreamingAddTranslatedArg);
1931 // aligned_alloc requires size to be a multiple of alignment
1932 size_t allocSize = ((argSize + alignment - 1) / alignment) * alignment;
1933 void *argRaw = alignedAllocCompat(alignment, allocSize);
1934 if (!argRaw)
1935 throw std::bad_alloc();
1936 auto argDeleter = [](void *p) { alignedFreeCompat(p); };
1937 std::unique_ptr<void, decltype(argDeleter)> argBuffer(argRaw, argDeleter);
1938 auto *arg = static_cast<StreamingAddTranslatedArg *>(argRaw);
1939 arg->inputLength = numItems;
1940 arg->addAmt = addAmt;
1941 for (uint32_t i = 0; i < numItems; ++i)
1942 arg->inputData()[i] = inputData[i];
1943
1944 logger.debug("esitester",
1945 "Sending translated argument: " + std::to_string(argSize) +
1946 " bytes, list_length=" + std::to_string(arg->inputLength) +
1947 ", add_amt=" + std::to_string(arg->addAmt));
1948
1949 // Send the complete message - translation will split it into frames.
1950 argPort.write(MessageData(reinterpret_cast<const uint8_t *>(arg), argSize));
1951 // argBuffer automatically freed when it goes out of scope
1952
1953 // Read the translated result.
1954 MessageData resMsg;
1955 resultPort.read(resMsg);
1956
1957 logger.debug("esitester", "Received translated result: " +
1958 std::to_string(resMsg.getSize()) + " bytes");
1959
1960 if (resMsg.getSize() < sizeof(StreamingAddTranslatedResult))
1961 throw std::runtime_error(
1962 "Streaming add test (translated): result too small");
1963
1964 const auto *result =
1965 reinterpret_cast<const StreamingAddTranslatedResult *>(resMsg.getBytes());
1966
1967 if (resMsg.getSize() <
1968 StreamingAddTranslatedResult::allocSize(result->dataLength))
1969 throw std::runtime_error(
1970 "Streaming add test (translated): result data truncated");
1971
1972 // Verify results.
1973 if (result->dataLength != inputData.size())
1974 throw std::runtime_error(
1975 "Streaming add test (translated): result size mismatch. Expected " +
1976 std::to_string(inputData.size()) + ", got " +
1977 std::to_string(result->dataLength));
1978
1979 bool passed = true;
1980 std::cout << "Streaming add test results:" << std::endl;
1981 for (size_t i = 0; i < inputData.size(); ++i) {
1982 uint32_t expected = inputData[i] + addAmt;
1983 std::cout << " input[" << i << "]=" << inputData[i] << " + " << addAmt
1984 << " = " << result->data()[i] << " (expected " << expected << ")";
1985 if (result->data()[i] != expected) {
1986 std::cout << " MISMATCH!";
1987 passed = false;
1988 }
1989 std::cout << std::endl;
1990 }
1991
1992 argPort.disconnect();
1993 resultPort.disconnect();
1994
1995 if (!passed)
1996 throw std::runtime_error(
1997 "Streaming add test (translated) failed: result mismatch");
1998
1999 logger.info("esitester", "Streaming add test passed (translated)");
2000 std::cout << "Streaming add test passed" << std::endl;
2001}
2002
2003/// Test the CoordTranslator module using message translation.
2004/// This version uses the list translation support where the message format is:
2005/// Argument: { x_translation, y_translation, coords_length, coords[] }
2006/// Result: { coords_length, coords[] }
2007/// Each coord is a struct { x, y }.
2008
2009/// Coordinate struct for CoordTranslator.
2010/// SV ordering means y comes before x in memory.
2011#pragma pack(push, 1)
2012struct Coord {
2013 uint32_t y; // SV ordering: last declared field first in memory
2014 uint32_t x;
2015};
2016#pragma pack(pop)
2017static_assert(sizeof(Coord) == 8, "Coord must be 8 bytes packed");
2018
2019/// Translated argument struct for CoordTranslator.
2020/// Memory layout (standard C struct ordering):
2021/// ESI type: struct { x_translation: UInt(32), y_translation: UInt(32),
2022/// coords: List<struct{x, y}> }
2023/// becomes host struct:
2024/// { coords_length (size_t, 8 bytes on 64-bit), y_translation (uint32_t),
2025/// x_translation (uint32_t), coords[] }
2026/// Note: Fields are in reverse order due to SV struct ordering.
2027/// Note: size_t is used for list lengths, so this format is platform-dependent.
2028#pragma pack(push, 1)
2031 uint32_t yTranslation; // SV ordering: last declared field first in memory
2033 // Trailing array data follows immediately after the struct in memory.
2034
2035 /// Get pointer to trailing coords array.
2036 Coord *coords() { return reinterpret_cast<Coord *>(this + 1); }
2037 const Coord *coords() const {
2038 return reinterpret_cast<const Coord *>(this + 1);
2039 }
2040 /// Get span view of coords (requires coordsLength to be set first).
2041 std::span<Coord> coordsSpan() { return {coords(), coordsLength}; }
2042 std::span<const Coord> coordsSpan() const { return {coords(), coordsLength}; }
2043
2044 static size_t allocSize(size_t numCoords) {
2045 return sizeof(CoordTranslateArg) + numCoords * sizeof(Coord);
2046 }
2047};
2048#pragma pack(pop)
2049
2050/// Translated result struct for CoordTranslator.
2051/// Memory layout:
2052/// ESI type: List<struct{x, y}>
2053/// becomes host struct:
2054/// { coords_length (size_t, 8 bytes on 64-bit), coords[] }
2055#pragma pack(push, 1)
2058 // Trailing array data follows immediately after the struct in memory.
2059
2060 /// Get pointer to trailing coords array.
2061 Coord *coords() { return reinterpret_cast<Coord *>(this + 1); }
2062 const Coord *coords() const {
2063 return reinterpret_cast<const Coord *>(this + 1);
2064 }
2065 /// Get span view of coords (requires coordsLength to be set first).
2066 std::span<Coord> coordsSpan() { return {coords(), coordsLength}; }
2067 std::span<const Coord> coordsSpan() const { return {coords(), coordsLength}; }
2068
2069 static size_t allocSize(size_t numCoords) {
2070 return sizeof(CoordTranslateResult) + numCoords * sizeof(Coord);
2071 }
2072};
2073#pragma pack(pop)
2074
2076 uint32_t xTrans, uint32_t yTrans,
2077 uint32_t numCoords) {
2078 Logger &logger = conn->getLogger();
2079 logger.info("esitester", "Starting coord translate test with x_trans=" +
2080 std::to_string(xTrans) +
2081 ", y_trans=" + std::to_string(yTrans) +
2082 ", num_coords=" + std::to_string(numCoords));
2083
2084 // Generate random input coordinates.
2085 // Note: Coord struct has y before x due to SV ordering, but we generate
2086 // and display as (x, y) for human readability.
2087 std::mt19937 rng(0xDEADBEEF);
2088 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
2089 std::vector<Coord> inputCoords;
2090 inputCoords.reserve(numCoords);
2091 for (uint32_t i = 0; i < numCoords; ++i) {
2092 Coord c;
2093 c.x = dist(rng);
2094 c.y = dist(rng);
2095 inputCoords.push_back(c);
2096 }
2097
2098 // Find the coord_translator child.
2099 auto coordTranslatorChild =
2100 accel->getChildren().find(AppID("coord_translator"));
2101 if (coordTranslatorChild == accel->getChildren().end())
2102 throw std::runtime_error(
2103 "Coord translate test: no 'coord_translator' child found");
2104
2105 auto &ports = coordTranslatorChild->second->getPorts();
2106 auto translateIter = ports.find(AppID("translate_coords"));
2107 if (translateIter == ports.end())
2108 throw std::runtime_error(
2109 "Coord translate test: no 'translate_coords' port found");
2110
2111 // Use FuncService::Function which handles connection and translation.
2112 auto *funcPort =
2113 translateIter->second.getAs<services::FuncService::Function>();
2114 if (!funcPort)
2115 throw std::runtime_error(
2116 "Coord translate test: 'translate_coords' port not a "
2117 "FuncService::Function");
2118 funcPort->connect();
2119
2120 // Allocate the argument struct with proper alignment for the struct
2121 // members.
2122 size_t argSize = CoordTranslateArg::allocSize(numCoords);
2123 constexpr size_t alignment = alignof(CoordTranslateArg);
2124 // aligned_alloc requires size to be a multiple of alignment
2125 size_t allocSize = ((argSize + alignment - 1) / alignment) * alignment;
2126 void *argRaw = alignedAllocCompat(alignment, allocSize);
2127 if (!argRaw)
2128 throw std::bad_alloc();
2129 auto argDeleter = [](void *p) { alignedFreeCompat(p); };
2130 std::unique_ptr<void, decltype(argDeleter)> argBuffer(argRaw, argDeleter);
2131 auto *arg = static_cast<CoordTranslateArg *>(argRaw);
2132 arg->coordsLength = numCoords;
2133 arg->xTranslation = xTrans;
2134 arg->yTranslation = yTrans;
2135 for (uint32_t i = 0; i < numCoords; ++i)
2136 arg->coords()[i] = inputCoords[i];
2137
2138 logger.debug(
2139 "esitester",
2140 "Sending coord translate argument: " + std::to_string(argSize) +
2141 " bytes, coords_length=" + std::to_string(arg->coordsLength) +
2142 ", x_trans=" + std::to_string(arg->xTranslation) +
2143 ", y_trans=" + std::to_string(arg->yTranslation));
2144
2145 // Call the function - translation happens automatically.
2146 MessageData resMsg =
2147 funcPort
2148 ->call(MessageData(reinterpret_cast<const uint8_t *>(arg), argSize))
2149 .get();
2150 // argBuffer automatically freed when it goes out of scope
2151
2152 logger.debug("esitester", "Received coord translate result: " +
2153 std::to_string(resMsg.getSize()) + " bytes");
2154
2155 if (resMsg.getSize() < sizeof(CoordTranslateResult))
2156 throw std::runtime_error("Coord translate test: result too small");
2157
2158 const auto *result =
2159 reinterpret_cast<const CoordTranslateResult *>(resMsg.getBytes());
2160
2161 if (resMsg.getSize() < CoordTranslateResult::allocSize(result->coordsLength))
2162 throw std::runtime_error("Coord translate test: result data truncated");
2163
2164 // Verify results.
2165 if (result->coordsLength != inputCoords.size())
2166 throw std::runtime_error(
2167 "Coord translate test: result size mismatch. Expected " +
2168 std::to_string(inputCoords.size()) + ", got " +
2169 std::to_string(result->coordsLength));
2170
2171 bool passed = true;
2172 std::cout << "Coord translate test results:" << std::endl;
2173 for (size_t i = 0; i < inputCoords.size(); ++i) {
2174 uint32_t expectedX = inputCoords[i].x + xTrans;
2175 uint32_t expectedY = inputCoords[i].y + yTrans;
2176 std::cout << " coord[" << i << "]=(" << inputCoords[i].x << ","
2177 << inputCoords[i].y << ") + (" << xTrans << "," << yTrans
2178 << ") = (" << result->coords()[i].x << ","
2179 << result->coords()[i].y << ")";
2180 if (result->coords()[i].x != expectedX ||
2181 result->coords()[i].y != expectedY) {
2182 std::cout << " MISMATCH! (expected (" << expectedX << "," << expectedY
2183 << "))";
2184 passed = false;
2185 }
2186 std::cout << std::endl;
2187 }
2188
2189 if (!passed)
2190 throw std::runtime_error("Coord translate test failed: result mismatch");
2191
2192 logger.info("esitester", "Coord translate test passed");
2193 std::cout << "Coord translate test passed" << std::endl;
2194}
2195
2196//
2197// SerialCoordTranslator test
2198//
2199
2200#pragma pack(push, 1)
2202 uint16_t coordsCount;
2205};
2206static_assert(sizeof(SerialCoordHeader) == 10, "Size mismatch");
2208 SerialCoordData(uint32_t x, uint32_t y) : _pad_head(0), y(y), x(x) {}
2209 uint16_t _pad_head;
2210 uint32_t y;
2211 uint32_t x;
2212};
2213static_assert(sizeof(SerialCoordData) == sizeof(SerialCoordHeader),
2214 "Size mismatch");
2215#pragma pack(pop)
2216
2217// Note: this application is intended to test hardware. As such, we need
2218// to be able to send batches. So this is not the typical way one would define
2219// a message struct. It's closer to a streaming style.
2221private:
2223 std::vector<SerialCoordData> coords;
2225
2226public:
2228 header.coordsCount = 0;
2229 header.xTranslation = 0;
2230 header.yTranslation = 0;
2231 // The footer is a count==0 header that terminates the list per the ESI
2232 // bulk-transfer serial encoding. Static fields are constant within a
2233 // list so the footer's translation values are irrelevant; zero them.
2234 footer.coordsCount = 0;
2235 footer.xTranslation = 0;
2236 footer.yTranslation = 0;
2237 }
2238 void yTranslation(uint32_t yTrans) { header.yTranslation = yTrans; }
2239 uint32_t yTranslation() const { return header.yTranslation; }
2240 void xTranslation(uint32_t xTrans) { header.xTranslation = xTrans; }
2241 uint32_t xTranslation() const { return header.xTranslation; }
2242 void appendCoord(uint32_t x, uint32_t y) {
2243 coords.emplace_back(x, y);
2244 header.coordsCount = (uint16_t)coords.size();
2245 }
2246 const std::vector<SerialCoordData> &getCoords() const { return coords; }
2247
2248 size_t numSegments() const override { return 3; }
2249 Segment segment(size_t idx) const override {
2250 if (idx == 0)
2251 return {reinterpret_cast<const uint8_t *>(&header), sizeof(header)};
2252 else if (idx == 1)
2253 return {reinterpret_cast<const uint8_t *>(coords.data()),
2254 coords.size() * sizeof(SerialCoordData)};
2255 else if (idx == 2)
2256 return {reinterpret_cast<const uint8_t *>(&footer), sizeof(footer)};
2257 else
2258 throw std::out_of_range("SerialCoordInput: invalid segment index");
2259 }
2260};
2261
2262// Like SerialCoordInput but without the trailing count==0 terminator. Used
2263// when streaming multiple bursts that together comprise a single logical
2264// list; the caller is responsible for sending a separate terminator burst
2265// (a SerialCoordBurst with count==0 and no data).
2267private:
2269 std::vector<SerialCoordData> coords;
2270
2271public:
2277 void yTranslation(uint32_t yTrans) { header.yTranslation = yTrans; }
2278 void xTranslation(uint32_t xTrans) { header.xTranslation = xTrans; }
2279 void appendCoord(uint32_t x, uint32_t y) {
2280 coords.emplace_back(x, y);
2281 header.coordsCount = (uint16_t)coords.size();
2282 }
2283
2284 size_t numSegments() const override { return 2; }
2285 Segment segment(size_t idx) const override {
2286 if (idx == 0)
2287 return {reinterpret_cast<const uint8_t *>(&header), sizeof(header)};
2288 else if (idx == 1)
2289 return {reinterpret_cast<const uint8_t *>(coords.data()),
2290 coords.size() * sizeof(SerialCoordData)};
2291 else
2292 throw std::out_of_range("SerialCoordBurst: invalid segment index");
2293 }
2294};
2295
2296#pragma pack(push, 1)
2298 uint8_t _pad[6];
2299 uint16_t coordsCount;
2300};
2302 uint32_t y;
2303 uint32_t x;
2304};
2309#pragma pack(pop)
2310static_assert(sizeof(SerialCoordOutputFrame) == 8, "Size mismatch");
2311
2312/// Deserialized result batch from the serial coord translator. The
2313/// TypeDeserializer accumulates header+data frame sequences until the
2314/// zero-count footer header, then emits the complete coordinate list.
2316 std::vector<Coord> coords;
2317
2319 : public QueuedDecodeTypeDeserializer<SerialCoordOutputBatch> {
2320 public:
2324
2326 : Base(std::move(output)) {}
2327
2328 private:
2329 DecodedOutputs decode(std::unique_ptr<SegmentedMessageData> &msg) override {
2330 DecodedOutputs decoded;
2331
2332 MessageData scratch;
2333 const MessageData &flat =
2334 detail::getMessageDataRef<SerialCoordOutputBatch>(*msg, scratch);
2335 const uint8_t *bytes = flat.getBytes();
2336 size_t size = flat.getSize();
2337 constexpr size_t frameSize = sizeof(SerialCoordOutputFrame);
2338
2339 size_t offset = 0;
2340 while (offset < size) {
2341 size_t needed = frameSize - partialFrameBytes.size();
2342 size_t chunkSize = std::min(needed, size - offset);
2343 partialFrameBytes.insert(partialFrameBytes.end(), bytes + offset,
2344 bytes + offset + chunkSize);
2345 offset += chunkSize;
2346
2347 if (partialFrameBytes.size() != frameSize)
2348 break;
2349
2351 std::memcpy(&frame, partialFrameBytes.data(), frameSize);
2352 partialFrameBytes.clear();
2353
2354 if (remainingCoords == 0) {
2355 // Header frame.
2356 uint16_t batchCount = frame.header.coordsCount;
2357 if (batchCount == 0) {
2358 // Footer: end of list. Emit accumulated coordinates.
2359 auto batch = std::make_unique<SerialCoordOutputBatch>();
2360 batch->coords = std::move(accumulated);
2361 accumulated.clear();
2362 decoded.push_back(std::move(batch));
2363 msg.reset();
2364 return decoded;
2365 }
2366 remainingCoords = batchCount;
2367 continue;
2368 }
2369 // Data frame.
2370 accumulated.push_back({frame.data.y, frame.data.x});
2372 }
2373
2374 msg.reset();
2375 return decoded;
2376 }
2377
2378 std::vector<Coord> accumulated;
2379 std::vector<uint8_t> partialFrameBytes;
2381 };
2382};
2383
2385 Accelerator *accel, uint32_t xTrans,
2386 uint32_t yTrans, uint32_t numCoords,
2387 size_t batchSizeLimit) {
2388 Logger &logger = conn->getLogger();
2389 logger.info("esitester", "Starting Serial coord translate test");
2390
2391 // Generate random coordinates.
2392 std::mt19937 rng(0xDEADBEEF);
2393 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
2394 std::vector<Coord> inputCoords;
2395 inputCoords.reserve(numCoords);
2396 for (uint32_t i = 0; i < numCoords; ++i)
2397 inputCoords.push_back({dist(rng), dist(rng)});
2398
2399 auto child = accel->getChildren().find(AppID("coord_translator_serial"));
2400 if (child == accel->getChildren().end())
2401 throw std::runtime_error("Serial coord translate test: no "
2402 "'coord_translator_serial' child found");
2403
2404 auto &ports = child->second->getPorts();
2405 auto portIter = ports.find(AppID("translate_coords_serial"));
2406 if (portIter == ports.end())
2407 throw std::runtime_error("Serial coord translate test: no "
2408 "'translate_coords_serial' port found");
2409
2410 TypedWritePort<SerialCoordBurst, /*SkipTypeCheck=*/true> argPort(
2411 portIter->second.getRawWrite("arg"));
2412 // Use the raw read port so we can verify the multi-burst output framing
2413 // explicitly rather than relying on the typed deserializer to accumulate
2414 // frames until the terminator.
2415 ReadChannelPort &resultRaw = portIter->second.getRawRead("result");
2416
2417 argPort.connect(ChannelPort::ConnectOptions(std::nullopt, false));
2418 // Use an unlimited read queue so the device output isn't stalled by a full
2419 // queue while we're still writing. With raw reads (translateMessage=false),
2420 // each output frame becomes its own queued message, so the default 32-msg
2421 // limit can be hit easily on a multi-burst run.
2422 resultRaw.connect(ChannelPort::ConnectOptions(/*bufferSize=*/0,
2423 /*translateMessage=*/false));
2424
2425 size_t sent = 0;
2426 while (sent < numCoords) {
2427 size_t batchSize = std::min(batchSizeLimit, numCoords - sent);
2428
2429 // Send Header. Only the first header needs the translation values, test
2430 // the subsequent ones with zero translation to verify that the hardware
2431 // correctly applies the first header's translation to the whole list.
2432 auto batch = std::make_unique<SerialCoordBurst>();
2433 batch->xTranslation(sent == 0 ? xTrans : 0);
2434 batch->yTranslation(sent == 0 ? yTrans : 0);
2435 // Send Data
2436 for (size_t i = 0; i < batchSize; ++i) {
2437 batch->appendCoord(inputCoords[sent + i].x, inputCoords[sent + i].y);
2438 }
2439 argPort.write(batch);
2440 sent += batchSize;
2441 }
2442 // Send final header with count=0 to signal end of input.
2443 auto footerBurst = std::make_unique<SerialCoordBurst>();
2444 argPort.write(footerBurst);
2445
2446 // Read raw output frames, walking the bulk-transfer wire format: zero or
2447 // more (HDR(N) + N data frames) sequences followed by a single HDR(0)
2448 // terminator. Each `read()` returns whatever the transport layer has
2449 // available, which is not guaranteed to align with frame boundaries
2450 // (e.g., DMA channel engines may coalesce or split across frames). So
2451 // we accumulate bytes into a buffer and only consume whole frames.
2452 constexpr size_t frameSize = sizeof(SerialCoordOutputFrame);
2453 std::vector<uint8_t> rxBuf;
2454 auto readFrame = [&](SerialCoordOutputFrame &out) {
2455 while (rxBuf.size() < frameSize) {
2456 MessageData data;
2457 resultRaw.read(data);
2458 rxBuf.insert(rxBuf.end(), data.getBytes(),
2459 data.getBytes() + data.getSize());
2460 }
2461 std::memcpy(&out, rxBuf.data(), frameSize);
2462 rxBuf.erase(rxBuf.begin(), rxBuf.begin() + frameSize);
2463 };
2464
2465 std::vector<Coord> results;
2466 results.reserve(numCoords);
2467 while (true) {
2469 readFrame(hdr);
2470 uint16_t batchCount = hdr.header.coordsCount;
2471 if (batchCount == 0)
2472 break;
2473 for (uint16_t i = 0; i < batchCount; ++i) {
2474 SerialCoordOutputFrame frame{};
2475 readFrame(frame);
2476 results.push_back({frame.data.y, frame.data.x});
2477 }
2478 }
2479
2480 // Verify
2481 bool passed = true;
2482 std::cout << "Serial coord translate test results:" << std::endl;
2483 if (results.size() != inputCoords.size()) {
2484 std::cout << "Result size mismatch. Expected " << inputCoords.size()
2485 << ", got " << results.size() << std::endl;
2486 passed = false;
2487 }
2488 for (size_t i = 0; i < std::min(inputCoords.size(), results.size()); ++i) {
2489 uint32_t expX = inputCoords[i].x + xTrans;
2490 uint32_t expY = inputCoords[i].y + yTrans;
2491 std::cout << " coord[" << i << "]=(" << inputCoords[i].x << ","
2492 << inputCoords[i].y << ") + (" << xTrans << "," << yTrans
2493 << ") = (" << results[i].x << "," << results[i].y
2494 << ") (expected (" << expX << "," << expY << "))";
2495 if (results[i].x != expX || results[i].y != expY) {
2496 std::cout << " MISMATCH!";
2497 passed = false;
2498 }
2499 std::cout << std::endl;
2500 }
2501
2502 argPort.disconnect();
2503 resultRaw.disconnect();
2504
2505 if (!passed)
2506 throw std::runtime_error("Serial coord translate test failed");
2507
2508 logger.info("esitester", "Serial coord translate test passed");
2509 std::cout << "Serial coord translate test passed" << std::endl;
2510}
2511
2512//
2513// AutoSerialCoordTranslator test
2514//
2515// The hardware module pipes the input through ListWindowToParallel ->
2516// per-coordinate translation -> ListWindowToSerial. The conversion modules
2517// emit one or more bulk transfers per call (each `header(count>0)` followed
2518// by `count` data frames) terminated by a `header(count==0)` footer per the
2519// ESI WindowField serial-encoding spec. This test:
2520// * Sends exactly one input batch: header(numCoords) + numCoords data
2521// frames + header(0) footer.
2522// * Reads back: a sequence of one-or-more `header(count>0) + count data`
2523// bursts terminated by `header(0)`. Use raw frame reads since the
2524// canonical `SerialCoordOutputBatch` deserializer hasn't been wired in
2525// for the converter pair.
2526//
2528 Accelerator *accel, uint32_t xTrans,
2529 uint32_t yTrans, uint32_t numCoords) {
2530 Logger &logger = conn->getLogger();
2531 logger.info("esitester", "Starting Auto serial coord translate test");
2532
2533 // Generate random coordinates.
2534 std::mt19937 rng(0xDEADBEEF);
2535 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
2536 std::vector<Coord> inputCoords;
2537 inputCoords.reserve(numCoords);
2538 for (uint32_t i = 0; i < numCoords; ++i)
2539 inputCoords.push_back({dist(rng), dist(rng)});
2540
2541 auto child = accel->getChildren().find(AppID("coord_translator_auto_serial"));
2542 if (child == accel->getChildren().end())
2543 throw std::runtime_error("Auto serial coord translate test: no "
2544 "'coord_translator_auto_serial' child found");
2545
2546 auto &ports = child->second->getPorts();
2547 auto portIter = ports.find(AppID("translate_coords_auto_serial"));
2548 if (portIter == ports.end())
2549 throw std::runtime_error("Auto serial coord translate test: no "
2550 "'translate_coords_auto_serial' port found");
2551
2552 // Reuse SerialCoordInput: the input wire format is identical (header with
2553 // x/y_translation+count, followed by data frames each carrying one coord).
2554 TypedWritePort<SerialCoordInput, /*SkipTypeCheck=*/true> argPort(
2555 portIter->second.getRawWrite("arg"));
2556 argPort.connect(ChannelPort::ConnectOptions(std::nullopt, false));
2557
2558 // Use the raw read port for results: read one header frame then numCoords
2559 // data frames as raw `SerialCoordOutputFrame`-shaped messages. Disable
2560 // window-message translation so we get one frame per `read()` instead of
2561 // assembled higher-level messages.
2562 ReadChannelPort &resultRaw = portIter->second.getRawRead("result");
2563 // Use an unlimited read queue so the device output isn't stalled by a full
2564 // queue while we're still writing. With raw reads (translateMessage=false),
2565 // each output frame becomes its own queued message, so the default 32-msg
2566 // limit can be hit easily on a multi-frame run.
2567 resultRaw.connect(ChannelPort::ConnectOptions(/*bufferSize=*/0,
2568 /*translateMessage=*/false));
2569
2570 // Send a single header+data burst.
2571 auto batch = std::make_unique<SerialCoordInput>();
2572 batch->xTranslation(xTrans);
2573 batch->yTranslation(yTrans);
2574 for (uint32_t i = 0; i < numCoords; ++i)
2575 batch->appendCoord(inputCoords[i].x, inputCoords[i].y);
2576 argPort.write(batch);
2577
2578 // Helper: read one raw frame, accumulating bytes across `read()` calls
2579 // since transports such as DMA channel engines do not guarantee that
2580 // each `read()` returns exactly one frame.
2581 constexpr size_t frameSize = sizeof(SerialCoordOutputFrame);
2582 std::vector<uint8_t> rxBuf;
2583 auto readFrame = [&](SerialCoordOutputFrame &out) {
2584 while (rxBuf.size() < frameSize) {
2585 MessageData data;
2586 resultRaw.read(data);
2587 rxBuf.insert(rxBuf.end(), data.getBytes(),
2588 data.getBytes() + data.getSize());
2589 }
2590 std::memcpy(&out, rxBuf.data(), frameSize);
2591 rxBuf.erase(rxBuf.begin(), rxBuf.begin() + frameSize);
2592 };
2593
2594 // Read a sequence of one-or-more `header(count>0) + count data` bursts
2595 // followed by a `header(count==0)` terminator footer. Total data items
2596 // received across all bursts must equal numCoords.
2597 std::vector<Coord> results;
2598 results.reserve(numCoords);
2599 while (true) {
2601 readFrame(hdr);
2602 uint16_t burstCount = hdr.header.coordsCount;
2603 if (burstCount == 0)
2604 break;
2605 if (results.size() + burstCount > numCoords)
2606 throw std::runtime_error("Auto serial coord translate test: bursts "
2607 "overflow expected total " +
2608 std::to_string(numCoords));
2609 for (uint32_t i = 0; i < burstCount; ++i) {
2610 SerialCoordOutputFrame frame{};
2611 readFrame(frame);
2612 results.push_back({frame.data.y, frame.data.x});
2613 }
2614 }
2615 if (results.size() != numCoords)
2616 throw std::runtime_error("Auto serial coord translate test: got " +
2617 std::to_string(results.size()) +
2618 " coords across all bursts " + "(expected " +
2619 std::to_string(numCoords) + ")");
2620
2621 argPort.disconnect();
2622 resultRaw.disconnect();
2623
2624 bool passed = true;
2625 std::cout << "Auto serial coord translate test results:" << std::endl;
2626 for (size_t i = 0; i < inputCoords.size(); ++i) {
2627 uint32_t expX = inputCoords[i].x + xTrans;
2628 uint32_t expY = inputCoords[i].y + yTrans;
2629 std::cout << " coord[" << i << "]=(" << inputCoords[i].x << ","
2630 << inputCoords[i].y << ") + (" << xTrans << "," << yTrans
2631 << ") = (" << results[i].x << "," << results[i].y
2632 << ") (expected (" << expX << "," << expY << "))";
2633 if (results[i].x != expX || results[i].y != expY) {
2634 std::cout << " MISMATCH!";
2635 passed = false;
2636 }
2637 std::cout << std::endl;
2638 }
2639
2640 if (!passed)
2641 throw std::runtime_error("Auto serial coord translate test failed");
2642
2643 logger.info("esitester", "Auto serial coord translate test passed");
2644 std::cout << "Auto serial coord translate test passed" << std::endl;
2645}
2646
2648 uint32_t iterations) {
2649 Logger &logger = conn->getLogger();
2650
2651 auto channelChild = accel->getChildren().find(AppID("channel_test"));
2652 if (channelChild == accel->getChildren().end())
2653 throw std::runtime_error("Channel test: no 'channel_test' child");
2654 auto &ports = channelChild->second->getPorts();
2655
2656 // --- Get the MMIO port to trigger the producer ---
2657 auto cmdIter = ports.find(AppID("cmd"));
2658 if (cmdIter == ports.end())
2659 throw std::runtime_error("Channel test: no 'cmd' port");
2660 auto *cmdMMIO = cmdIter->second.getAs<services::MMIO::MMIORegion>();
2661 if (!cmdMMIO)
2662 throw std::runtime_error("Channel test: 'cmd' is not MMIO");
2663
2664 // --- Get the producer to_host port ---
2665 auto producerIter = ports.find(AppID("producer"));
2666 if (producerIter == ports.end())
2667 throw std::runtime_error("Channel test: no 'producer' port");
2668 auto *producerPort =
2669 producerIter->second.getAs<services::ChannelService::ToHost>();
2670 if (!producerPort)
2671 throw std::runtime_error(
2672 "Channel test: 'producer' is not a ChannelService::ToHost");
2673 producerPort->connect();
2674
2675 // --- Test to_host: MMIO-triggered incrementing values ---
2676 // Write the number of values to send at offset 0x0.
2677 cmdMMIO->write(0x0, iterations);
2678
2679 for (uint32_t i = 0; i < iterations; ++i) {
2680 MessageData recvData = producerPort->read().get();
2681 uint32_t got = *recvData.as<uint32_t>();
2682 std::cout << "[channel] producer i=" << i << " got=" << got << std::endl;
2683 if (got != i)
2684 throw std::runtime_error("Channel producer: expected " +
2685 std::to_string(i) + ", got " +
2686 std::to_string(got));
2687 }
2688 logger.info("esitester", "Channel test: producer passed (" +
2689 std::to_string(iterations) +
2690 " incrementing values)");
2691
2692 // --- Test from_host -> to_host loopback ---
2693 auto loopbackInIter = ports.find(AppID("loopback_in"));
2694 if (loopbackInIter == ports.end())
2695 throw std::runtime_error("Channel test: no 'loopback_in' port");
2696 auto *fromHostPort =
2697 loopbackInIter->second.getAs<services::ChannelService::FromHost>();
2698 if (!fromHostPort)
2699 throw std::runtime_error(
2700 "Channel test: 'loopback_in' is not a ChannelService::FromHost");
2701 fromHostPort->connect();
2702
2703 auto loopbackOutIter = ports.find(AppID("loopback_out"));
2704 if (loopbackOutIter == ports.end())
2705 throw std::runtime_error("Channel test: no 'loopback_out' port");
2706 auto *loopbackOutPort =
2707 loopbackOutIter->second.getAs<services::ChannelService::ToHost>();
2708 if (!loopbackOutPort)
2709 throw std::runtime_error(
2710 "Channel test: 'loopback_out' is not a ChannelService::ToHost");
2711 loopbackOutPort->connect();
2712
2713 std::mt19937_64 rng(0xDEADBEEF);
2714 std::uniform_int_distribution<uint32_t> dist(0, UINT32_MAX);
2715
2716 for (uint32_t i = 0; i < iterations; ++i) {
2717 uint32_t sendVal = dist(rng);
2718 fromHostPort->write(MessageData::from(sendVal));
2719 MessageData recvData = loopbackOutPort->read().get();
2720 uint32_t recvVal = *recvData.as<uint32_t>();
2721 std::cout << "[channel] loopback i=" << i << " sent=0x"
2722 << esi::toHex(sendVal) << " recv=0x" << esi::toHex(recvVal)
2723 << std::endl;
2724 if (recvVal != sendVal)
2725 throw std::runtime_error("Channel loopback mismatch at i=" +
2726 std::to_string(i));
2727 }
2728
2729 logger.info("esitester", "Channel test: loopback passed (" +
2730 std::to_string(iterations) + " iterations)");
2731 std::cout << "Channel test passed" << std::endl;
2732}
static void print(TypedAttr val, llvm::raw_ostream &os)
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:84
Services provide connections to 'bundles' – collections of named, unidirectional communication channe...
Definition Ports.h:611
T * getAs() const
Cast this Bundle port to a subclass which is actually useful.
Definition Ports.h:639
ReadChannelPort & getRawRead(const std::string &name) const
Definition Ports.cpp:52
WriteChannelPort & getRawWrite(const std::string &name) const
Get access to the raw byte streams of a channel.
Definition Ports.cpp:42
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:155
const uint8_t * getBytes() const
Definition Common.h:166
const T * as() const
Cast to a type.
Definition Common.h:190
size_t getSize() const
Get the size of the data in bytes.
Definition Common.h:180
static MessageData from(T &t)
Cast from a type to its raw bytes.
Definition Common.h:200
Helper base class for stateful deserializers which may emit zero, one, or many typed outputs for each...
Definition TypedPorts.h:246
detail::TypedReadOwnedCallback< SerialCoordOutputBatch > OutputCallback
Definition TypedPorts.h:248
std::vector< std::unique_ptr< SerialCoordOutputBatch > > DecodedOutputs
Definition TypedPorts.h:249
A ChannelPort which reads data from the accelerator.
Definition Ports.h:453
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:517
Abstract multi-segment message.
Definition Common.h:133
void connect(const ChannelPort::ConnectOptions &opts={std::nullopt, false})
Definition TypedPorts.h:626
void write(const T &data)
Definition TypedPorts.h:636
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:357
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:411
A port which writes data to the accelerator (from_host).
Definition Services.h:321
A port which reads data from the accelerator (to_host).
Definition Services.h:297
A function call which gets attached to a service port.
Definition Services.h:359
virtual void start()
In cases where necessary, enable host memory services.
Definition Services.h:267
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:476
void connect()
Connect to a particular telemetry port. Offset should be non-nullopt.
Definition Services.cpp:459
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)
Definition debug.py:1
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:37
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.
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.
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:118
size_t size
Definition Common.h:120
RAII memory region for host memory.
Definition Services.h:243
virtual void * getDevicePtr() const
Sometimes the pointer the device sees is different from the pointer the host sees.
Definition Services.h:249
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:257
virtual std::size_t getSize() const =0
SerialCoordOutputData data
SerialCoordOutputHeader header