CIRCT 24.0.0git
Loading...
Searching...
No Matches
loopback.cpp
Go to the documentation of this file.
1#include "loopback/LoopbackIP.h"
2
3#include "probe_runner.h"
4
5#include "esi/Accelerator.h"
6#include "esi/Manifest.h"
7#include "esi/Services.h"
8#include "esi/TypedPorts.h"
9
10#include <cstdint>
11#include <cstring>
12#include <iostream>
13#include <random>
14#include <stdexcept>
15
16using namespace esi;
17
18static int runLoopbackI8(Accelerator *accel) {
19 AppIDPath lastLookup;
20 BundlePort *inPort = accel->resolvePort(
21 {AppID("loopback_inst", 0), AppID("loopback_tohw")}, lastLookup);
22 if (!inPort)
23 throw std::runtime_error("No loopback_tohw port found");
24 BundlePort *outPort = accel->resolvePort(
25 {AppID("loopback_inst", 0), AppID("loopback_fromhw")}, lastLookup);
26 if (!outPort)
27 throw std::runtime_error("No loopback_fromhw port found");
28
29 WriteChannelPort &toHw = inPort->getRawWrite("recv");
30 ReadChannelPort &fromHw = outPort->getRawRead("send");
31 toHw.connect();
32 fromHw.connect();
33
34 uint8_t sendVal = 0x5a;
35 toHw.write(MessageData(&sendVal, sizeof(sendVal)));
36
37 MessageData recvMsg;
38 fromHw.read(recvMsg);
39 if (recvMsg.getSize() != sizeof(uint8_t))
40 throw std::runtime_error("Unexpected loopback recv size");
41 uint8_t got = *recvMsg.as<uint8_t>();
42 if (got != sendVal)
43 throw std::runtime_error("Loopback byte mismatch");
44
45 std::cout << "loopback_i8 ok: 0x" << std::hex << (int)got << std::dec << "\n";
46 return 0;
47}
48
49static int runStructFunc(Accelerator *accel) {
50 AppIDPath lastLookup;
51 BundlePort *port = accel->resolvePort({AppID("structFunc")}, lastLookup);
52 if (!port)
53 throw std::runtime_error("No structFunc port found");
54
55 auto *func = port->getAs<services::FuncService::Function>();
56 if (!func)
57 throw std::runtime_error("structFunc not a FuncService::Function");
58 func->connect();
59
60 esi_system::ArgStruct arg{};
61 arg.a(0x1234);
62 arg.b(static_cast<int8_t>(-7));
63
64 MessageData resMsg = func->call(MessageData::from(arg)).get();
65 const auto *res = resMsg.as<esi_system::ResultStruct>();
66
67 int8_t expectedX = static_cast<int8_t>(arg.b() + 1);
68 if (res->x() != expectedX || res->y() != arg.b())
69 throw std::runtime_error("Struct func result mismatch");
70
71 std::cout << "struct_func ok: b=" << (int)arg.b() << " x=" << (int)res->x()
72 << " y=" << (int)res->y() << "\n";
73 return 0;
74}
75
76static int runOddStructFunc(Accelerator *accel) {
77 AppIDPath lastLookup;
78 BundlePort *port = accel->resolvePort({AppID("oddStructFunc")}, lastLookup);
79 if (!port)
80 throw std::runtime_error("No oddStructFunc port found");
81
82 auto *func = port->getAs<services::FuncService::Function>();
83 if (!func)
84 throw std::runtime_error("oddStructFunc not a FuncService::Function");
85 func->connect();
86
87 esi_system::OddStruct arg{
88 /*a=*/0xabc,
89 /*b=*/static_cast<int8_t>(-17),
90 /*inner=*/{/*p=*/5, /*q=*/static_cast<int8_t>(-7), /*r=*/{3, 4}},
91 };
92
93 MessageData resMsg = func->call(MessageData::from(arg)).get();
94 const auto *res = resMsg.as<esi_system::OddStruct>();
95
96 uint16_t expectA = static_cast<uint16_t>(arg.a() + 1);
97 int8_t expectB = static_cast<int8_t>(arg.b() - 3);
98 uint8_t expectP = static_cast<uint8_t>(arg.inner().p() + 5);
99 int8_t expectQ = static_cast<int8_t>(arg.inner().q() + 2);
100 uint8_t expectR0 = static_cast<uint8_t>(arg.inner().r()[0] + 1);
101 uint8_t expectR1 = static_cast<uint8_t>(arg.inner().r()[1] + 2);
102 if (res->a() != expectA || res->b() != expectB ||
103 res->inner().p() != expectP || res->inner().q() != expectQ ||
104 res->inner().r()[0] != expectR0 || res->inner().r()[1] != expectR1)
105 throw std::runtime_error("Odd struct func result mismatch");
106
107 std::cout << "odd_struct_func ok: a=" << res->a() << " b=" << (int)res->b()
108 << " p=" << (int)res->inner().p() << " q=" << (int)res->inner().q()
109 << " r0=" << (int)res->inner().r()[0]
110 << " r1=" << (int)res->inner().r()[1] << "\n";
111 return 0;
112}
113
114static int runArrayFunc(Accelerator *accel) {
115 AppIDPath lastLookup;
116 BundlePort *port = accel->resolvePort({AppID("arrayFunc")}, lastLookup);
117 if (!port)
118 throw std::runtime_error("No arrayFunc port found");
119
120 auto *func = port->getAs<services::FuncService::Function>();
121 if (!func)
122 throw std::runtime_error("arrayFunc not a FuncService::Function");
123 func->connect();
124
125 int8_t argArray[1] = {static_cast<int8_t>(-3)};
126 MessageData resMsg =
127 func->call(MessageData(reinterpret_cast<const uint8_t *>(argArray),
128 sizeof(argArray)))
129 .get();
130
131 const auto *res = resMsg.as<esi_system::ResultArray>();
132 int8_t a = (*res)[0];
133 int8_t b = (*res)[1];
134 int8_t expect0 = argArray[0];
135 int8_t expect1 = static_cast<int8_t>(argArray[0] + 1);
136
137 bool ok = (a == expect0 && b == expect1) || (a == expect1 && b == expect0);
138 if (!ok)
139 throw std::runtime_error("Array func result mismatch");
140
141 int8_t low = a;
142 int8_t high = b;
143 if (low > high) {
144 int8_t tmp = low;
145 low = high;
146 high = tmp;
147 }
148 std::cout << "array_func ok: " << (int)low << " " << (int)high << "\n";
149 return 0;
150}
151
152//
153// SerialCoordTranslator test
154//
155
156struct Coord {
157 uint32_t y; // SV ordering: last declared field first in memory
158 uint32_t x;
159};
160#pragma pack(push, 1)
161struct SerialCoordHeader {
162 uint16_t coordsCount;
163 uint32_t yTranslation;
164 uint32_t xTranslation;
165};
166static_assert(sizeof(SerialCoordHeader) == 10, "Size mismatch");
167struct SerialCoordData {
168 SerialCoordData(uint32_t x, uint32_t y) : _pad_head(0), y(y), x(x) {}
169 uint16_t _pad_head;
170 uint32_t y;
171 uint32_t x;
172};
173static_assert(sizeof(SerialCoordData) == sizeof(SerialCoordHeader),
174 "Size mismatch");
175#pragma pack(pop)
176
177// Note: this application is intended to test hardware. As such, we need
178// to be able to send batches. So this is not the typical way one would define a
179// message struct. It's closer to a streaming style.
180//
181// If constructed with a HostMemAllocator, the coordinates are stored in a
182// HostMem region (owned by the message) which backends can DMA directly from
183// and take() once sent.
185private:
187 std::vector<SerialCoordData> coords;
189 // Set iff the coordinates live in a HostMem region rather than `coords`.
190 bool coordsInRegion = false;
191 // Null after the region has been taken.
192 std::unique_ptr<services::HostMemRegion> coordsRegion;
194
195public:
196 SerialCoordInput(uint32_t xTrans, uint32_t yTrans,
197 std::vector<SerialCoordData> &coords,
198 services::HostMemAllocator *allocator = nullptr)
199 : coordsBytes(coords.size() * sizeof(SerialCoordData)) {
200 header.coordsCount = (uint16_t)coords.size();
201 header.xTranslation = xTrans;
202 header.yTranslation = yTrans;
203 footer.coordsCount = 0;
204 if (allocator && coordsBytes > 0)
205 coordsRegion = allocator->allocate(coordsBytes, {});
206 if (coordsRegion) {
207 std::memcpy(coordsRegion->getPtr(), coords.data(), coordsBytes);
208 coordsRegion->flush();
209 coordsInRegion = true;
210 } else {
211 // No allocator, or the allocation failed: keep the data in the vector.
212 this->coords = coords;
213 }
214 }
215
216 size_t numSegments() const override { return 3; }
217 Segment segment(size_t idx) const override {
218 if (idx == 0)
219 return {reinterpret_cast<const uint8_t *>(&header), sizeof(header)};
220 else if (idx == 1) {
221 if (!coordsInRegion)
222 return {reinterpret_cast<const uint8_t *>(coords.data()), coordsBytes};
223 if (!coordsRegion)
224 throw std::runtime_error(
225 "SerialCoordInput: coordinates segment has been taken");
226 return {static_cast<const uint8_t *>(coordsRegion->getPtr()), coordsBytes,
227 coordsRegion.get()};
228 } else if (idx == 2)
229 return {reinterpret_cast<const uint8_t *>(&footer), sizeof(footer)};
230 else
231 throw std::out_of_range("SerialCoordInput: invalid segment index");
232 }
233
234 std::unique_ptr<services::HostMemRegion> take(size_t segIdx) override {
235 if (segIdx >= numSegments())
236 throw std::out_of_range("SerialCoordInput: invalid segment index");
237 if (segIdx != 1 || !coordsInRegion)
238 return nullptr;
239 if (!coordsRegion)
240 throw std::runtime_error(
241 "SerialCoordInput: coordinates segment has been taken");
242 return std::move(coordsRegion);
243 }
244};
245
246#pragma pack(push, 1)
248 uint8_t _pad[6];
249 uint16_t coordsCount;
250};
252 uint32_t y;
253 uint32_t x;
254};
256 SerialCoordOutputHeader header;
257 SerialCoordOutputData data;
258};
259#pragma pack(pop)
260static_assert(sizeof(SerialCoordOutputFrame) == 8, "Size mismatch");
261
264 const char *probeName) {
265 size_t numCoords = 100;
266 uint32_t xTrans = 10, yTrans = 20;
267
268 // Generate random coordinates.
269 std::mt19937 rng(0xDEADBEEF);
270 std::uniform_int_distribution<uint32_t> dist(0, 1000000);
271 std::vector<Coord> inputCoords;
272 inputCoords.reserve(numCoords);
273 for (uint32_t i = 0; i < numCoords; ++i)
274 inputCoords.push_back({dist(rng), dist(rng)});
275
276 auto child = accel->getChildren().find(AppID("coord_translator_serial"));
277 if (child == accel->getChildren().end())
278 throw std::runtime_error("Serial coord translate test: no "
279 "'coord_translator_serial' child found");
280
281 auto &ports = child->second->getPorts();
282 auto portIter = ports.find(AppID("translate_coords_serial"));
283 if (portIter == ports.end())
284 throw std::runtime_error(
285 "Serial coord translate test: no 'translate_coords_serial' port found");
286
287 auto *func = portIter->second.getAs<services::FuncService::Function>();
288 if (!func)
289 throw std::runtime_error(
290 "Serial coord translate test: port is not a FuncService::Function");
291
292 // Drive the raw arg port through TypedWritePort so the segmented batch is
293 // packed correctly; drain the raw result port directly. We bypass
294 // TypedFunction here because `SerialCoordOutputFrame` is a hand-written
295 // union (not a real ESI type) and we don't want the typed result decoder
296 // to backpressure on the N+2 reply frames.
297 TypedWritePort<SerialCoordInput, /*SkipTypeCheck=*/true> argPort(
298 func->getRawWrite("arg"));
299 ChannelPort::ConnectOptions argOpts(/*bufferSize=*/std::nullopt,
300 /*translateMessage=*/false);
301 argPort.connect(argOpts);
302 ReadChannelPort &rawResult = func->getRawRead("result");
303 // The reply contains numCoords + 2 raw frames (one header + N data + one
304 // footer). Use an unbounded buffer (bufferSize=0) so the polling queue
305 // doesn't backpressure the backend mid-stream.
306 ChannelPort::ConnectOptions resultOpts(/*bufferSize=*/0,
307 /*translateMessage=*/false);
308 rawResult.connect(resultOpts);
309
310 std::vector<SerialCoordData> coords;
311 for (auto &c : inputCoords)
312 coords.emplace_back(c.x, c.y);
313 auto batch =
314 std::make_unique<SerialCoordInput>(xTrans, yTrans, coords, allocator);
315 if (allocator) {
316 // The coordinates segment should reference the HostMem region holding it.
317 Segment coordsSeg = batch->segment(1);
318 if (!coordsSeg.region || !coordsSeg.getDeviceAddress())
319 throw std::runtime_error(
320 "Serial coord translate test: coordinates not in a HostMem region");
321 if (batch->take(0) != nullptr)
322 throw std::runtime_error(
323 "Serial coord translate test: header segment has a region");
324 }
325 // Transfers ownership of the batch (and any HostMem region it owns) to the
326 // port; `batch` must not be used afterwards.
327 argPort.write(batch);
328
329 // The bulk-list reply is one header frame + numCoords data frames + one
330 // footer frame. TODO: list results aren't decoded here; we just drain the
331 // frames so the backend doesn't backpressure / hang.
332 MessageData drained;
333 for (size_t i = 0; i < numCoords + 2; ++i)
334 rawResult.read(drained);
335 std::cout << probeName << " ok\n";
336 return 0;
337}
338
339// Check HostMemAllocator failure and SerialCoordInput::take() semantics on a
340// message which is never sent.
342 if (allocator.allocate(0, {}) != nullptr)
343 throw std::runtime_error("HostMem take test: allocate(0) should fail");
344
345 std::vector<SerialCoordData> coords = {{1, 2}, {3, 4}, {5, 6}};
346 SerialCoordInput msg(10, 20, coords, &allocator);
347 Segment coordsSeg = msg.segment(1);
348 if (!coordsSeg.region)
349 throw std::runtime_error(
350 "HostMem take test: coordinates not in a HostMem region");
351
352 // Take the region from the message (as a DMA engine would once the segment
353 // has been transmitted, to re-use it or return it to a pool).
354 std::unique_ptr<services::HostMemRegion> region = msg.take(1);
355 if (region.get() != coordsSeg.region)
356 throw std::runtime_error("HostMem take test: take() returned the wrong "
357 "region");
358 if (std::memcmp(region->getPtr(), coords.data(),
359 coords.size() * sizeof(SerialCoordData)) != 0)
360 throw std::runtime_error("HostMem take test: region contents corrupted");
361
362 // The taken segment is no longer accessible.
363 auto throws = [](auto fn) {
364 try {
365 fn();
366 } catch (const std::runtime_error &) {
367 return true;
368 }
369 return false;
370 };
371 if (!throws([&] { msg.segment(1); }) || !throws([&] { msg.take(1); }))
372 throw std::runtime_error(
373 "HostMem take test: taken segment is still accessible");
374 if (msg.segment(0).size != sizeof(SerialCoordHeader))
375 throw std::runtime_error("HostMem take test: header segment inaccessible");
376}
377
379 return serialCoordTranslate(accel, nullptr, "serial_coord_translate");
380}
381
382// Same as above, but the coordinates are allocated in HostMem.
384 auto *hostmem = esi_test::probeConnection->getService<services::HostMem>();
385 checkHostMemTake(*hostmem);
386 return serialCoordTranslate(accel, hostmem, "serial_coord_translate_hostmem");
387}
388
390 std::cout << "depth: 0x" << std::hex << esi_system::LoopbackIP::depth
391 << std::dec << "\n";
392 std::cout << "depth_constant ok\n";
393 return 0;
394}
395
397 "loopback-cpp", "Loopback cosim test using generated ESI headers.",
398 {"depth_constant", &runDepthConstant}, {"loopback_i8", &runLoopbackI8},
399 {"struct_func", &runStructFunc}, {"odd_struct_func", &runOddStructFunc},
400 {"array_func", &runArrayFunc},
401 {"serial_coord_translate", &serialCoordTranslateTest},
402 {"serial_coord_translate_hostmem", &serialCoordTranslateHostMemTest}, );
if(parseOptionalKeywordOrOptionalString(parser, result.rawName, found)) return failure()
Top level accelerator class.
Definition Accelerator.h:85
BundlePort * resolvePort(const AppIDPath &path, AppIDPath &lastLookup) const
Attempt to resolve a path to a port.
Definition Design.cpp:72
const std::map< AppID, Instance * > & getChildren() const
Access the module's children by ID.
Definition Design.h:71
A concrete flat message backed by a single vector of bytes.
Definition Common.h:197
const T * as() const
Cast to a type.
Definition Common.h:232
size_t getSize() const
Get the size of the data in bytes.
Definition Common.h:222
static MessageData from(T &t)
Cast from a type to its raw bytes.
Definition Common.h:242
A ChannelPort which reads data from the accelerator.
Definition Ports.h:479
virtual void connect(ReadCallback callback, const ConnectOptions &options={})
Definition Ports.cpp:140
virtual void read(MessageData &outData)
Specify a buffer to read into.
Definition Ports.h:543
Abstract multi-segment message.
Definition Common.h:151
void connect(const ChannelPort::ConnectOptions &opts={std::nullopt, false})
Definition TypedPorts.h:619
void write(const T &data)
Definition TypedPorts.h:629
A ChannelPort which sends data to the accelerator.
Definition Ports.h:308
void write(const MessageData &data)
A very basic blocking write API.
Definition Ports.h:327
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:385
Interface for anything which can allocate HostMemRegions.
Definition Services.h:263
virtual std::unique_ptr< HostMemRegion > allocate(std::size_t size, Options opts) const =0
Allocate a region of host memory in accelerator accessible address space.
static void serialCoordTranslateTest(AcceleratorConnection *, Accelerator *, uint32_t xTrans, uint32_t yTrans, uint32_t numCoords, size_t batchSizeLimit)
static int serialCoordTranslateTest(Accelerator *accel)
Definition loopback.cpp:378
static void checkHostMemTake(services::HostMemAllocator &allocator)
Definition loopback.cpp:341
static int runDepthConstant(Accelerator *)
Definition loopback.cpp:389
static int serialCoordTranslate(Accelerator *accel, services::HostMemAllocator *allocator, const char *probeName)
Definition loopback.cpp:262
static int runArrayFunc(Accelerator *accel)
Definition loopback.cpp:114
static int serialCoordTranslateHostMemTest(Accelerator *accel)
Definition loopback.cpp:383
static int runOddStructFunc(Accelerator *accel)
Definition loopback.cpp:76
static int runStructFunc(Accelerator *accel)
Definition loopback.cpp:49
static int runLoopbackI8(Accelerator *accel)
Definition loopback.cpp:18
esi::AcceleratorConnection * probeConnection
The connection the probes are running against, for probes which need connection-level services (e....
Definition esi.py:1
#define ESI_PROBE_REGISTRY(name, description,...)
Convenience macro: defines main() with a probe registry.
Test the CoordTranslator module using message translation.
uint32_t x
uint32_t y
uint16_t _pad_head
Definition loopback.cpp:169
SerialCoordData(uint32_t x, uint32_t y)
Definition loopback.cpp:168
size_t numSegments() const override
Number of segments in the message.
Definition loopback.cpp:216
SerialCoordInput(uint32_t xTrans, uint32_t yTrans, std::vector< SerialCoordData > &coords, services::HostMemAllocator *allocator=nullptr)
Definition loopback.cpp:196
SerialCoordHeader header
std::unique_ptr< services::HostMemRegion > take(size_t segIdx) override
Optionally called by a backend once it has transmitted segment segIdx, to obtain ownership of the Hos...
Definition loopback.cpp:234
SerialCoordHeader footer
Segment segment(size_t idx) const override
Get a segment by index. Throws if the segment has been take()n.
Definition loopback.cpp:217
std::unique_ptr< services::HostMemRegion > coordsRegion
Definition loopback.cpp:192
std::vector< SerialCoordData > coords
A contiguous, non-owning view of bytes within a SegmentedMessageData.
Definition Common.h:122
std::optional< uint64_t > getDeviceAddress() const
Device address of this segment's bytes, if region is set and contains them; std::nullopt otherwise.
Definition Common.cpp:80
services::HostMemRegion * region
The HostMem region (from services::HostMem::allocate()) which holds [data, data + size),...
Definition Common.h:129
size_t size
Definition Common.h:124
SerialCoordOutputData data
SerialCoordOutputHeader header