CIRCT 23.0.0git
Loading...
Searching...
No Matches
test_codegen.cpp
Go to the documentation of this file.
1// Driver for the codegen / port-kind coverage integration test. Each probe
2// below targets exactly one combination of port kind (function / callback /
3// channel / MMIO / metric) and codegen path (typed scalar / typed struct /
4// void specialization / indexed group). Each probe is small and self-checking
5// so that a runtime regression in any one path lights up exactly one probe.
6//
7// The binary supports a ``--probe NAME`` flag that runs only one probe; the
8// pytest harness uses this to surface each probe as a separate pytest test.
9// With no ``--probe`` flag, every probe runs in sequence.
10
11#include "test_codegen/CallServiceCallback.h"
12#include "test_codegen/CallbackWindowedList.h"
13#include "test_codegen/ChannelMultiBurstListRead.h"
14#include "test_codegen/ChannelMultiBurstListWrite.h"
15#include "test_codegen/ChannelNarrowCountListWrite.h"
16#include "test_codegen/ChannelWindowedListRead.h"
17#include "test_codegen/ChannelWindowedListWrite.h"
18#include "test_codegen/CustomServiceDeclChannel.h"
19#include "test_codegen/IndexedFuncGroup.h"
20#include "test_codegen/MmioReadWrite.h"
21#include "test_codegen/TelemetryMetric.h"
22#include "test_codegen/TypedFuncArrayResult.h"
23#include "test_codegen/TypedFuncMultiArg.h"
24#include "test_codegen/TypedFuncNestedStruct.h"
25#include "test_codegen/TypedFuncStruct.h"
26#include "test_codegen/TypedFuncSubByteSigned.h"
27#include "test_codegen/TypedFuncVoidArg.h"
28#include "test_codegen/TypedFuncVoidResult.h"
29#include "test_codegen/TypedFuncWindowedList.h"
30#include "test_codegen/TypedReadChannelStruct.h"
31#include "test_codegen/TypedWriteChannelByte.h"
32
33#include "probe_runner.h"
34
35#include "esi/Accelerator.h"
36#include "esi/Manifest.h"
37#include "esi/Services.h"
38#include "esi/TypedPorts.h"
39
40#include <atomic>
41#include <chrono>
42#include <cstdint>
43#include <iostream>
44#include <map>
45#include <mutex>
46#include <stdexcept>
47#include <string>
48#include <thread>
49#include <vector>
50
51using namespace esi;
52
53// Resolve a child instance by AppID name from the top-level accelerator.
54static esi::HWModule *findInst(Accelerator *accel, const char *appidName) {
55 auto it = accel->getChildren().find(AppID(appidName));
56 if (it == accel->getChildren().end())
57 throw std::runtime_error(std::string("test_codegen instance '") +
58 appidName + "' not found");
59 return it->second;
60}
61
62//===----------------------------------------------------------------------===//
63// Function: typed multi-arg call via emplace ctor.
64//===----------------------------------------------------------------------===//
66 esi_system::TypedFuncMultiArg mod(
67 findInst(accel, "typed_func_multi_arg_inst"));
68 auto c = mod.connect();
69
70 // The emplace-style call() forwards its arguments into the generated
71 // arg struct's constructor, so we never have to spell that struct out.
72 uint32_t got = c->call(7u, 6u).get();
73 if (got != 42u)
74 throw std::runtime_error("typed_func_multi_arg: expected 42, got " +
75 std::to_string(got));
76 std::cout << "typed_func_multi_arg ok\n";
77 return 0;
78}
79
80//===----------------------------------------------------------------------===//
81// Function: void argument (typed-result specialization).
82//===----------------------------------------------------------------------===//
83static int runTypedFuncVoidArg(Accelerator *accel) {
84 esi_system::TypedFuncVoidArg mod(findInst(accel, "typed_func_void_arg_inst"));
85 auto c = mod.connect();
86
87 uint32_t got = c->call().get();
88 if (got != 0xCAFEF00Du)
89 throw std::runtime_error(
90 "typed_func_void_arg: expected 0xCAFEF00D, got 0x" + toHex(got));
91 std::cout << "typed_func_void_arg ok\n";
92 return 0;
93}
94
95//===----------------------------------------------------------------------===//
96// Function: void return (typed-arg specialization).
97//===----------------------------------------------------------------------===//
99 esi_system::TypedFuncVoidResult mod(
100 findInst(accel, "typed_func_void_result_inst"));
101 auto c = mod.connect();
102
103 // Just asserts that the future resolves without throwing. A void result is
104 // the wire-level zero byte; any failure to consume that byte would surface
105 // here as a hung future or a deserializer exception.
106 c->call(esi_system::AckArgs(0x5A, 0x1234)).get();
107 std::cout << "typed_func_void_result ok\n";
108 return 0;
109}
110
111//===----------------------------------------------------------------------===//
112// Callback: HW-initiated call into the host (triggered via an MMIO write).
113//===----------------------------------------------------------------------===//
115 esi_system::CallServiceCallback mod(
116 findInst(accel, "call_service_callback_inst"));
117 auto c = mod.connect();
118
119 // Install the user callback. The handler stores what it saw and signals a
120 // flag; the driver thread polls the flag (with a timeout) so this works
121 // both for inline-from-callback-thread dispatch and for service-thread
122 // dispatch.
123 std::atomic<bool> got_call(false);
124 esi_system::NotifyArgs seen{};
125 c->callback.connect([&](const esi_system::NotifyArgs &a) {
126 seen = a;
127 got_call.store(true, std::memory_order_release);
128 });
129
130 // Trigger the callback by writing the payload to the MMIO command region
131 // at offset 0x10. The HW module forwards the bottom 32 bits of the write
132 // data into the callback as ``payload`` and uses a fixed ``tag = 0xA5``.
133 constexpr uint32_t kPayload = 0xDEADBEEFu;
134 c->trigger.write(0x10, static_cast<uint64_t>(kPayload));
135
136 // Wait up to ~5s for the callback to fire.
137 using clock = std::chrono::steady_clock;
138 auto deadline = clock::now() + std::chrono::seconds(5);
139 while (!got_call.load(std::memory_order_acquire) && clock::now() < deadline)
140 std::this_thread::sleep_for(std::chrono::milliseconds(10));
141 if (!got_call.load(std::memory_order_acquire))
142 throw std::runtime_error(
143 "call_service_callback: callback did not fire within timeout");
144
145 if (seen.tag() != 0xA5)
146 throw std::runtime_error(
147 "call_service_callback: wrong tag, expected 0xA5 got 0x" +
148 toHex(static_cast<uint64_t>(seen.tag())));
149 if (seen.payload() != kPayload)
150 throw std::runtime_error(
151 "call_service_callback: wrong payload, expected 0x" + toHex(kPayload) +
152 " got 0x" + toHex(seen.payload()));
153 std::cout << "call_service_callback ok\n";
154 return 0;
155}
156
157//===----------------------------------------------------------------------===//
158// To-host channel: TypedReadPort<EventStruct> polling.
159//===----------------------------------------------------------------------===//
161 esi_system::TypedReadChannelStruct mod(
162 findInst(accel, "typed_read_channel_struct_inst"));
163 auto c = mod.connect();
164
165 // The constant on the HW side bounds how many events get pushed.
166 constexpr size_t kNum = esi_system::TypedReadChannelStruct::num_events;
167 for (size_t i = 1; i <= kNum; ++i) {
168 auto ev = c->data.read();
169 if (!ev)
170 throw std::runtime_error(
171 "typed_read_channel_struct: null read result at i=" +
172 std::to_string(i));
173 if (ev->ts() != i)
174 throw std::runtime_error(
175 "typed_read_channel_struct: wrong ts at i=" + std::to_string(i) +
176 ", got " + std::to_string(ev->ts()));
177 int32_t expected = -static_cast<int32_t>(i);
178 if (ev->val() != expected)
179 throw std::runtime_error(
180 "typed_read_channel_struct: wrong val at i=" + std::to_string(i) +
181 ", got " + std::to_string(ev->val()));
182 }
183 std::cout << "typed_read_channel_struct ok (" << kNum << " events)\n";
184 return 0;
185}
186
187//===----------------------------------------------------------------------===//
188// From-host channel: TypedWritePort<uint8_t> + MMIO read-back accumulator.
189//===----------------------------------------------------------------------===//
191 esi_system::TypedWriteChannelByte mod(
192 findInst(accel, "typed_write_channel_byte_inst"));
193 auto c = mod.connect();
194
195 // Send a sequence and accumulate the expected XOR. The HW receiver is
196 // always-ready and XORs every byte into a register whose value is exposed
197 // via the ``accumulator`` MMIO read port.
198 static constexpr uint8_t kBytes[] = {0x11, 0x22, 0x44, 0x88, 0x10, 0x55};
199 uint8_t expected = 0;
200 for (uint8_t b : kBytes) {
201 c->data.write(b);
202 expected ^= b;
203 }
204
205 // Poll the accumulator MMIO until it matches (or we time out). A small
206 // poll loop covers the case where the last byte hasn't drained yet.
207 using clock = std::chrono::steady_clock;
208 auto deadline = clock::now() + std::chrono::seconds(2);
209 uint8_t got = 0;
210 while (clock::now() < deadline) {
211 uint64_t resp = c->accumulator.read(0);
212 got = static_cast<uint8_t>(resp & 0xff);
213 if (got == expected)
214 break;
215 std::this_thread::sleep_for(std::chrono::milliseconds(5));
216 }
217 if (got != expected)
218 throw std::runtime_error(
219 "typed_write_channel_byte: accumulator mismatch (expected 0x" +
220 toHex(static_cast<uint64_t>(expected)) + ", got 0x" +
221 toHex(static_cast<uint64_t>(got)) + ")");
222 std::cout << "typed_write_channel_byte ok (acc=0x"
223 << toHex(static_cast<uint64_t>(expected)) << ")\n";
224 return 0;
225}
226
227//===----------------------------------------------------------------------===//
228// MMIO region: read-write loopback at offset 0x10.
229//===----------------------------------------------------------------------===//
230static int runMmioReadWrite(Accelerator *accel) {
231 esi_system::MmioReadWrite mod(findInst(accel, "mmio_read_write_inst"));
232 auto c = mod.connect();
233
234 // Write a 64-bit token, then read it back. The HW's storage register is
235 // shared across all offsets, so ``offset`` here is just for completeness.
236 constexpr uint64_t kToken = 0xA5A51234'56789ABCULL;
237 c->region.write(0x10, kToken);
238 uint64_t got = c->region.read(0x10);
239 if (got != kToken)
240 throw std::runtime_error("mmio_read_write: round-trip mismatch (wrote 0x" +
241 toHex(kToken) + ", read 0x" + toHex(got) + ")");
242 std::cout << "mmio_read_write ok (round-trip 0x" << toHex(kToken) << ")\n";
243 return 0;
244}
245
246//===----------------------------------------------------------------------===//
247// Telemetry: free-running cycle counter is monotonic between reads.
248//===----------------------------------------------------------------------===//
249static int runTelemetryMetric(Accelerator *accel) {
250 esi_system::TelemetryMetric mod(findInst(accel, "telemetry_metric_inst"));
251 auto c = mod.connect();
252
253 uint64_t first = c->cycleCount.readInt();
254 // Sleep enough wall-time for the simulator to advance many cycles even
255 // under heavy load.
256 std::this_thread::sleep_for(std::chrono::milliseconds(50));
257 uint64_t second = c->cycleCount.readInt();
258
259 if (second <= first)
260 throw std::runtime_error(
261 "telemetry_metric: counter did not advance (first=" +
262 std::to_string(first) + ", second=" + std::to_string(second) + ")");
263 std::cout << "telemetry_metric ok (advanced by " << (second - first) << ")\n";
264 return 0;
265}
266
267//===----------------------------------------------------------------------===//
268// Indexed function group: exercise every entry of IndexedPorts<TypedFunction>.
269//===----------------------------------------------------------------------===//
271 // Single IndexedFuncGroup module exposes N typed-function ports under the
272 // same appid name ``call`` with indices 0..N-1; codegen groups them into a
273 // single ``IndexedPorts<TypedFunction<...>>`` member that the driver
274 // iterates with ``c->call[idx]``.
275 esi_system::IndexedFuncGroup mod(findInst(accel, "indexed_func_group_inst"));
276 auto c = mod.connect();
277 constexpr size_t kN = esi_system::IndexedFuncGroup::num_entries;
278 for (uint32_t idx = 0; idx < kN; ++idx) {
279 constexpr uint16_t kArg = 100;
280 uint16_t got = c->call[idx](kArg).get();
281 uint16_t expected = static_cast<uint16_t>(kArg + (idx + 1));
282 if (got != expected)
283 throw std::runtime_error("indexed_func_group[" + std::to_string(idx) +
284 "]: expected " + std::to_string(expected) +
285 ", got " + std::to_string(got));
286 }
287 std::cout << "indexed_func_group ok (" << kN << " entries)\n";
288 return 0;
289}
290
291//===----------------------------------------------------------------------===//
292// Custom-`@esi.ServiceDecl` raw-channel byte loopback. Exercises bundle ports
293// backed by a custom service decl rather than the standard `ChannelService`,
294// across two indexed instances. The HW also exposes void (Bits(0)) bundles
295// for elaboration coverage; the C++ driver does not exercise them because
296// the runtime's blocking ``ReadChannelPort::read`` does not surface a
297// completion for zero-byte messages.
298//===----------------------------------------------------------------------===//
299static int runCustomServiceDeclChannel(Accelerator *accel, uint32_t idx) {
300 auto it =
301 accel->getChildren().find(AppID("custom_service_decl_channel", idx));
302 if (it == accel->getChildren().end())
303 throw std::runtime_error("custom_service_decl_channel[" +
304 std::to_string(idx) + "]: instance not found");
305 esi_system::CustomServiceDeclChannel mod(it->second);
306 auto c = mod.connect();
307
308 // Byte channel: send a unique byte per instance and verify the echo so a
309 // crossed-wires bug between the two CustomServiceDeclChannel instances
310 // would be caught (same-AppID-name multi-instance regression).
311 TypedWritePort<uint8_t> toHw(c->byte_in.getRawWrite("recv"));
312 TypedReadPort<uint8_t> fromHw(c->byte_out.getRawRead("send"));
313 toHw.connect();
314 fromHw.connect();
315
316 uint8_t sendVal = static_cast<uint8_t>(0x40 + idx);
317 toHw.write(sendVal);
318 std::unique_ptr<uint8_t> got = fromHw.read();
319 if (!got || *got != sendVal)
320 throw std::runtime_error(
321 "custom_service_decl_channel[" + std::to_string(idx) +
322 "]: byte loopback mismatch (sent 0x" +
323 toHex(static_cast<uint64_t>(sendVal)) + ", got 0x" +
324 toHex(static_cast<uint64_t>(got ? *got : 0u)) + ")");
325
326 std::cout << "custom_service_decl_channel_" << idx << " ok (byte 0x"
327 << toHex(static_cast<uint64_t>(sendVal)) << ")\n";
328 return 0;
329}
330
333 return 0;
334}
337 return 0;
338}
339
340//===----------------------------------------------------------------------===//
341// Typed function: small struct -> small struct.
342//===----------------------------------------------------------------------===//
343static int runTypedFuncStruct(Accelerator *accel) {
344 esi_system::TypedFuncStruct mod(findInst(accel, "typed_func_struct_inst"));
345 auto c = mod.connect();
346
347 esi_system::StructArgs arg(0x1234, static_cast<int8_t>(-7));
348 esi_system::StructResult res = c->call(arg).get();
349 int8_t expectedX = static_cast<int8_t>(arg.b() + 1);
350 if (res.x() != expectedX || res.y() != arg.b())
351 throw std::runtime_error(
352 "typed_func_struct: wrong result (b=" + std::to_string(arg.b()) +
353 " x=" + std::to_string(res.x()) + " y=" + std::to_string(res.y()) +
354 ")");
355 std::cout << "typed_func_struct ok (b=" << (int)arg.b()
356 << " -> x=" << (int)res.x() << " y=" << (int)res.y() << ")\n";
357 return 0;
358}
359
360//===----------------------------------------------------------------------===//
361// Typed function: nested odd-bit-width struct round-trip.
362//===----------------------------------------------------------------------===//
364 esi_system::TypedFuncNestedStruct mod(
365 findInst(accel, "typed_func_nested_struct_inst"));
366 auto c = mod.connect();
367
368 esi_system::OddStruct arg;
369 arg.a(0xabc);
370 arg.b(static_cast<int8_t>(-17));
371 auto inner = arg.inner();
372 inner.p(5);
373 inner.q(static_cast<int8_t>(-7));
374 inner.r({3, 4});
375 arg.inner(inner);
376
377 esi_system::OddStruct res = c->call(arg).get();
378 uint16_t expA = static_cast<uint16_t>(arg.a() + 1);
379 int8_t expB = static_cast<int8_t>(arg.b() - 3);
380 uint8_t expP = static_cast<uint8_t>(arg.inner().p() + 5);
381 int8_t expQ = static_cast<int8_t>(arg.inner().q() + 2);
382 uint8_t expR0 = static_cast<uint8_t>(arg.inner().r()[0] + 1);
383 uint8_t expR1 = static_cast<uint8_t>(arg.inner().r()[1] + 2);
384 if (res.a() != expA || res.b() != expB || res.inner().p() != expP ||
385 res.inner().q() != expQ || res.inner().r()[0] != expR0 ||
386 res.inner().r()[1] != expR1)
387 throw std::runtime_error("typed_func_nested_struct: result mismatch");
388 std::cout << "typed_func_nested_struct ok (a=" << res.a()
389 << " b=" << (int)res.b() << " p=" << (int)res.inner().p()
390 << " q=" << (int)res.inner().q() << " r=["
391 << (int)res.inner().r()[0] << "," << (int)res.inner().r()[1]
392 << "])\n";
393 return 0;
394}
395
396//===----------------------------------------------------------------------===//
397// Typed function: ``si4 -> si4`` identity. Probes sign extension at a
398// sub-byte width through the typed facade.
399//===----------------------------------------------------------------------===//
401 esi_system::TypedFuncSubByteSigned mod(
402 findInst(accel, "typed_func_subbyte_signed_inst"));
403 auto c = mod.connect();
404
405 for (int8_t arg : {static_cast<int8_t>(5), static_cast<int8_t>(-3),
406 static_cast<int8_t>(-8), static_cast<int8_t>(7)}) {
407 int8_t got = c->call(arg).get();
408 if (got != arg)
409 throw std::runtime_error(
410 "typed_func_subbyte_signed: arg=" + std::to_string(arg) +
411 " got=" + std::to_string(got));
412 }
413 std::cout << "typed_func_subbyte_signed ok (4 values)\n";
414 return 0;
415}
416
417//===----------------------------------------------------------------------===//
418// Typed function with an array result.
419//===----------------------------------------------------------------------===//
421 esi_system::TypedFuncArrayResult mod(
422 findInst(accel, "typed_func_array_result_inst"));
423 auto c = mod.connect();
424
425 esi_system::TypedFuncArrayResult::callArgs arg{static_cast<int8_t>(-3)};
426 esi_system::ArrayResult res = c->call(arg).get();
427 int8_t a = res[0];
428 int8_t b = res[1];
429 int8_t expect0 = arg[0];
430 int8_t expect1 = static_cast<int8_t>(arg[0] + 1);
431 bool ok = (a == expect0 && b == expect1) || (a == expect1 && b == expect0);
432 if (!ok)
433 throw std::runtime_error("typed_func_array_result: result mismatch");
434 std::cout << "typed_func_array_result ok ([" << (int)a << "," << (int)b
435 << "])\n";
436 return 0;
437}
438
439//===----------------------------------------------------------------------===//
440// Typed function over a windowed list payload. Doubles each element of the
441// input list and reads the result back as another serial-burst window.
442// Exercises the auto serial<->parallel windowed-list converters and the
443// `SerialListTypeDeserializer` end-to-end.
444//===----------------------------------------------------------------------===//
446 esi_system::TypedFuncWindowedList mod(
447 findInst(accel, "typed_func_windowed_list_inst"));
448 auto c = mod.connect();
449
450 using ArgT = esi_system::TypedFuncWindowedList::callArgs;
451 using ResT = esi_system::TypedFuncWindowedList::callResult;
452 std::vector<esi_system::TransformListItem> input;
453 for (uint32_t v : {3u, 5u, 7u, 9u, 11u})
454 input.emplace_back(v);
455
456 ArgT arg(input);
457 ResT result = c->call(arg).get();
458
459 if (result.data_count() != input.size())
460 throw std::runtime_error(
461 "typed_func_windowed_list: wrong result size (got " +
462 std::to_string(result.data_count()) + ")");
463 size_t i = 0;
464 for (const esi_system::TransformListItem &item : result.data()) {
465 uint32_t expected = input[i].v() + input[i].v();
466 if (item.v() != expected)
467 throw std::runtime_error("typed_func_windowed_list: element " +
468 std::to_string(i) + " expected " +
469 std::to_string(expected) + ", got " +
470 std::to_string(item.v()));
471 ++i;
472 }
473 std::cout << "typed_func_windowed_list ok (" << input.size()
474 << " items doubled)\n";
475 return 0;
476}
477
478//===----------------------------------------------------------------------===//
479// To-host channel of windowed list-with-header. Exercises the typed read path
480// for serial-burst bulk transfers.
481//===----------------------------------------------------------------------===//
483 esi_system::ChannelWindowedListRead mod(
484 findInst(accel, "channel_windowed_list_read_inst"));
485 auto c = mod.connect();
486
487 using WinT = esi_system::ChannelWindowedListRead::dataData;
488
489 // Arm one burst via the MMIO trigger. The HW only emits when triggered, so
490 // free-running emission can't fill the host's polling buffer.
491 c->trigger.write(0x10, 0u);
492
493 std::unique_ptr<WinT> got = c->data.read();
494 if (!got)
495 throw std::runtime_error("channel_windowed_list_read: null read result");
496 // The HW emits one burst with ``[10, 20, 30, 40]`` and ``tag = 0xCAFE``.
497 static constexpr uint16_t kTag = 0xCAFE;
498 static const uint32_t kExpected[] = {10u, 20u, 30u, 40u};
499 if (got->tag() != kTag)
500 throw std::runtime_error(
501 "channel_windowed_list_read: wrong tag, expected 0x" +
502 toHex(static_cast<uint64_t>(kTag)) + " got 0x" +
503 toHex(static_cast<uint64_t>(got->tag())));
504 if (got->items_count() != 4)
505 throw std::runtime_error(
506 "channel_windowed_list_read: wrong item count, expected 4 got " +
507 std::to_string(got->items_count()));
508 size_t i = 0;
509 for (uint32_t v : got->items()) {
510 if (v != kExpected[i])
511 throw std::runtime_error("channel_windowed_list_read: element " +
512 std::to_string(i) + " expected " +
513 std::to_string(kExpected[i]) + " got " +
514 std::to_string(v));
515 ++i;
516 }
517 std::cout << "channel_windowed_list_read ok (tag=0x"
518 << toHex(static_cast<uint64_t>(kTag)) << ", items=[10,20,30,40])\n";
519 return 0;
520}
521
522//===----------------------------------------------------------------------===//
523// To-host channel whose list is longer than the HW serial encoder's data FIFO,
524// so `ListWindowToSerial` emits it as several header/data bursts terminated by
525// a single count==0 footer. Exercises the host-side
526// `SerialListTypeDeserializer` multi-burst *reassembly* path end-to-end.
527//===----------------------------------------------------------------------===//
529 esi_system::ChannelMultiBurstListRead mod(
530 findInst(accel, "channel_multiburst_list_read_inst"));
531 auto c = mod.connect();
532
533 using WinT = esi_system::ChannelMultiBurstListRead::dataData;
534
535 // Arm one transfer via the MMIO trigger.
536 c->trigger.write(0x10, 0u);
537
538 std::unique_ptr<WinT> got = c->data.read();
539 if (!got)
540 throw std::runtime_error("channel_multiburst_list_read: null read result");
541 // The HW streams ten items ``[0x1000 .. 0x1009]`` with ``tag = 0xF00D``; the
542 // depth-4 encoder FIFO splits them into three bursts (4 + 4 + 2) that the
543 // host deserializer must reassemble into a single ten-element list.
544 static constexpr uint16_t kTag = 0xF00D;
545 static constexpr size_t kNumItems = 10;
546 if (got->tag() != kTag)
547 throw std::runtime_error(
548 "channel_multiburst_list_read: wrong tag, expected 0x" +
549 toHex(static_cast<uint64_t>(kTag)) + " got 0x" +
550 toHex(static_cast<uint64_t>(got->tag())));
551 if (got->items_count() != kNumItems)
552 throw std::runtime_error(
553 "channel_multiburst_list_read: wrong item count, expected " +
554 std::to_string(kNumItems) + " got " +
555 std::to_string(got->items_count()));
556 size_t i = 0;
557 for (uint32_t v : got->items()) {
558 uint32_t expected = 0x1000u + static_cast<uint32_t>(i);
559 if (v != expected)
560 throw std::runtime_error("channel_multiburst_list_read: element " +
561 std::to_string(i) + " expected 0x" +
562 toHex(expected) + " got 0x" + toHex(v));
563 ++i;
564 }
565 std::cout << "channel_multiburst_list_read ok (10 items reassembled from 3 "
566 "bursts)\n";
567 return 0;
568}
569
570//===----------------------------------------------------------------------===//
571// From-host channel of windowed list-with-header. Exercises the typed write
572// path: the host constructs a complete burst from a header tag plus a list of
573// items, and the HW AND-reduces each beat against the expected pattern. The
574// driver verifies success via the ``match`` MMIO read region.
575//===----------------------------------------------------------------------===//
577 esi_system::ChannelWindowedListWrite mod(
578 findInst(accel, "channel_windowed_list_write_inst"));
579 auto c = mod.connect();
580
581 using WinT = esi_system::ChannelWindowedListWrite::dataData;
582
583 static constexpr uint16_t kTag = 0xCAFE;
584 std::vector<uint32_t> items{10u, 20u, 30u, 40u};
585 c->data.write(WinT(kTag, items));
586
587 // Poll the match flag MMIO until the burst has been processed (or time
588 // out). The HW updates the latch on the burst-end beat.
589 using clock = std::chrono::steady_clock;
590 auto deadline = clock::now() + std::chrono::seconds(5);
591 uint64_t match = 0;
592 while (clock::now() < deadline) {
593 match = c->match.read(0);
594 if (match & 1)
595 break;
596 std::this_thread::sleep_for(std::chrono::milliseconds(5));
597 }
598 if (!(match & 1))
599 throw std::runtime_error(
600 "channel_windowed_list_write: HW did not report a match within "
601 "timeout (got 0x" +
602 toHex(match) + ")");
603 std::cout << "channel_windowed_list_write ok (tag=0x"
604 << toHex(static_cast<uint64_t>(kTag)) << ", items=[10,20,30,40])\n";
605 return 0;
606}
607
608//===----------------------------------------------------------------------===//
609// From-host channel of a windowed list whose count field is too narrow to
610// encode the whole list in one burst. The host serializer must split the
611// 256-item list into multiple bursts (the window's 8-bit count caps each
612// burst at 255 items); the HW reassembles them and validates every item.
613// Exercises the write-side multi-burst chunking end-to-end.
614//===----------------------------------------------------------------------===//
616 esi_system::ChannelMultiBurstListWrite mod(
617 findInst(accel, "channel_multiburst_list_write_inst"));
618 auto c = mod.connect();
619
620 using WinT = esi_system::ChannelMultiBurstListWrite::dataData;
621
622 // 256 items, value == index. The window's 8-bit count caps each burst at
623 // 255, so the host serializer splits this into two bursts (255 + 1).
624 std::vector<uint8_t> items;
625 items.reserve(256);
626 for (int v = 0; v < 256; ++v)
627 items.push_back(static_cast<uint8_t>(v));
628 c->data.write(WinT(items));
629
630 // Poll the match flag MMIO until the (reassembled) burst has been processed
631 // or we time out. The HW updates the latch on the final item's beat.
632 using clock = std::chrono::steady_clock;
633 auto deadline = clock::now() + std::chrono::seconds(5);
634 uint64_t match = 0;
635 while (clock::now() < deadline) {
636 match = c->match.read(0);
637 if (match & 1)
638 break;
639 std::this_thread::sleep_for(std::chrono::milliseconds(5));
640 }
641 if (!(match & 1))
642 throw std::runtime_error(
643 "channel_multiburst_list_write: HW did not report a match within "
644 "timeout (got 0x" +
645 toHex(match) + ")");
646 std::cout << "channel_multiburst_list_write ok (256 items split across 2 "
647 "bursts)\n";
648 return 0;
649}
650
651//===----------------------------------------------------------------------===//
652// From-host channel of a windowed list with a *narrow* (2-bit) bulk count and
653// a static header field. The 2-bit count caps each burst at 3 items, so the
654// host serializer splits the 7-item list into three bursts (3 + 3 + 1). The
655// header content (ui16 tag + 2-bit count = 18 bits) does not fill the 32-bit
656// data frame, so this exercises the MSB-aligned, sub-byte frame layout
657// end-to-end: the generated facade must place the count at bits [15:14] and
658// the tag at bits [31:16] to match CIRCT's frame lowering, or the HW reads a
659// garbage count and never reports a match.
660//===----------------------------------------------------------------------===//
662 esi_system::ChannelNarrowCountListWrite mod(
663 findInst(accel, "channel_narrow_count_list_write_inst"));
664 auto c = mod.connect();
665
666 using WinT = esi_system::ChannelNarrowCountListWrite::dataData;
667
668 static constexpr uint16_t kTag = 0xBEEF;
669 std::vector<uint32_t> items{0x1000u, 0x1001u, 0x1002u, 0x1003u,
670 0x1004u, 0x1005u, 0x1006u};
671 c->data.write(WinT(kTag, items));
672
673 // Poll the match flag MMIO until the (reassembled) list has been processed
674 // or we time out. The HW updates the latch on the final item's beat.
675 using clock = std::chrono::steady_clock;
676 auto deadline = clock::now() + std::chrono::seconds(5);
677 uint64_t match = 0;
678 while (clock::now() < deadline) {
679 match = c->match.read(0);
680 if (match & 1)
681 break;
682 std::this_thread::sleep_for(std::chrono::milliseconds(5));
683 }
684 if (!(match & 1))
685 throw std::runtime_error(
686 "channel_narrow_count_list_write: HW did not report a match within "
687 "timeout (got 0x" +
688 toHex(match) + ")");
689 std::cout << "channel_narrow_count_list_write ok (2-bit count, tag=0x"
690 << toHex(static_cast<uint64_t>(kTag))
691 << ", 7 items split across 3 bursts)\n";
692 return 0;
693}
694
695//===----------------------------------------------------------------------===//
696// Callback with windowed list argument: HW sends a serial-burst windowed
697// list (tag + items) into a host callback. Verifies that the
698// `SerialListTypeDeserializer` works end-to-end through the
699// `TypedCallback<WindowT, void>` path.
700//===----------------------------------------------------------------------===//
702 esi_system::CallbackWindowedList mod(
703 findInst(accel, "callback_windowed_list_inst"));
704 auto c = mod.connect();
705
706 using WinT = esi_system::CallbackWindowedList::callbackArgs;
707
708 std::atomic<bool> got_call(false);
709 std::string error_msg;
710 std::mutex error_mtx;
711 c->callback.connect([&](const WinT &arg) {
712 try {
713 static constexpr uint16_t kTag = 0xCAFE;
714 static const uint32_t kExpected[] = {10u, 20u, 30u, 40u};
715 if (arg.tag() != kTag)
716 throw std::runtime_error(
717 "callback_windowed_list: wrong tag, expected 0x" +
718 toHex(static_cast<uint64_t>(kTag)) + " got 0x" +
719 toHex(static_cast<uint64_t>(arg.tag())));
720 if (arg.items_count() != 4)
721 throw std::runtime_error(
722 "callback_windowed_list: wrong item count, expected 4 got " +
723 std::to_string(arg.items_count()));
724 size_t i = 0;
725 for (uint32_t v : arg.items()) {
726 if (v != kExpected[i])
727 throw std::runtime_error("callback_windowed_list: element " +
728 std::to_string(i) + " expected " +
729 std::to_string(kExpected[i]) + " got " +
730 std::to_string(v));
731 ++i;
732 }
733
734 std::cout << "callback_windowed_list ok (tag=0x"
735 << toHex(static_cast<uint64_t>(kTag))
736 << ", items=[10,20,30,40])\n";
737 } catch (const std::exception &e) {
738 std::lock_guard<std::mutex> lk(error_mtx);
739 error_msg = e.what();
740 }
741 got_call.store(true, std::memory_order_release);
742 });
743
744 // Arm the burst via MMIO trigger.
745 c->trigger.write(0x10, 0u);
746
747 using clock = std::chrono::steady_clock;
748 auto deadline = clock::now() + std::chrono::seconds(5);
749 while (!got_call.load(std::memory_order_acquire) && clock::now() < deadline)
750 std::this_thread::sleep_for(std::chrono::milliseconds(10));
751 if (!got_call.load(std::memory_order_acquire))
752 throw std::runtime_error(
753 "callback_windowed_list: callback did not fire within timeout");
754 std::lock_guard<std::mutex> lk(error_mtx);
755 if (!error_msg.empty())
756 throw std::runtime_error(error_msg);
757 return 0;
758}
759
761 "test-codegen",
762 "Per-port-kind coverage tests for ESI runtime + facade codegen. "
763 "Run a single probe with --probe NAME or run all probes (in registry "
764 "order) by omitting the flag.",
765 {"typed_func_multi_arg", &runTypedFuncMultiArg},
766 {"typed_func_void_arg", &runTypedFuncVoidArg},
767 {"typed_func_void_result", &runTypedFuncVoidResult},
768 {"call_service_callback", &runCallServiceCallback},
769 {"typed_read_channel_struct", &runTypedReadChannelStruct},
770 {"typed_write_channel_byte", &runTypedWriteChannelByte},
771 {"mmio_read_write", &runMmioReadWrite},
772 {"telemetry_metric", &runTelemetryMetric},
773 {"indexed_func_group", &runIndexedFuncGroup},
774 {"custom_service_decl_channel_0", &runCustomServiceDeclChannel0},
775 {"custom_service_decl_channel_1", &runCustomServiceDeclChannel1},
776 {"typed_func_struct", &runTypedFuncStruct},
777 {"typed_func_nested_struct", &runTypedFuncNestedStruct},
778 {"typed_func_subbyte_signed", &runTypedFuncSubByteSigned},
779 {"typed_func_array_result", &runTypedFuncArrayResult},
780 {"typed_func_windowed_list", &runTypedFuncWindowedList},
781 {"channel_windowed_list_read", &runChannelWindowedListRead},
782 {"channel_multiburst_list_read", &runChannelMultiBurstListRead},
783 {"channel_windowed_list_write", &runChannelWindowedListWrite},
784 {"channel_multiburst_list_write", &runChannelMultiBurstListWrite},
785 {"channel_narrow_count_list_write", &runChannelNarrowCountListWrite},
786 {"callback_windowed_list", &runCallbackWindowedList}, );
Top level accelerator class.
Definition Accelerator.h:77
Represents either the top level or an instance of a hardware module.
Definition Design.h:47
const std::map< AppID, Instance * > & getChildren() const
Access the module's children by ID.
Definition Design.h:71
Strongly typed wrapper around a raw read channel.
Definition TypedPorts.h:718
std::unique_ptr< T > read()
Blocking typed read in polling mode.
Definition TypedPorts.h:801
void connect(const ChannelPort::ConnectOptions &opts={std::nullopt, false})
Connect in polling mode.
Definition TypedPorts.h:737
void connect(const ChannelPort::ConnectOptions &opts={std::nullopt, false})
Definition TypedPorts.h:626
void write(const T &data)
Definition TypedPorts.h:636
Definition esi.py:1
std::string toHex(void *val)
Definition Common.cpp:37
#define ESI_PROBE_REGISTRY(name, description,...)
Convenience macro: defines main() with a probe registry.
static int runCustomServiceDeclChannel0(Accelerator *accel)
static int runTypedFuncWindowedList(Accelerator *accel)
static int runChannelWindowedListRead(Accelerator *accel)
static int runMmioReadWrite(Accelerator *accel)
static int runTypedReadChannelStruct(Accelerator *accel)
static int runTypedFuncVoidResult(Accelerator *accel)
static int runCustomServiceDeclChannel(Accelerator *accel, uint32_t idx)
static int runTypedFuncSubByteSigned(Accelerator *accel)
static esi::HWModule * findInst(Accelerator *accel, const char *appidName)
static int runCallServiceCallback(Accelerator *accel)
static int runTypedFuncArrayResult(Accelerator *accel)
static int runChannelMultiBurstListWrite(Accelerator *accel)
static int runCallbackWindowedList(Accelerator *accel)
static int runTypedFuncMultiArg(Accelerator *accel)
static int runTelemetryMetric(Accelerator *accel)
static int runIndexedFuncGroup(Accelerator *accel)
static int runTypedFuncVoidArg(Accelerator *accel)
static int runTypedWriteChannelByte(Accelerator *accel)
static int runChannelMultiBurstListRead(Accelerator *accel)
static int runChannelNarrowCountListWrite(Accelerator *accel)
static int runCustomServiceDeclChannel1(Accelerator *accel)
static int runTypedFuncNestedStruct(Accelerator *accel)
static int runTypedFuncStruct(Accelerator *accel)
static int runChannelWindowedListWrite(Accelerator *accel)