MLIR-AIE 1.4.5.dev
parameter_scratchpad.h
Go to the documentation of this file.
1//===- parameter_scratchpad.h - Host-side parameter runtime ------*- C++-*-===//
2//
3// Copyright (C) 2026 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7//
8// ParameterScratchpad: Host-side runtime class for writing named parameters
9// to AIE cores via the scratchpad mechanism.
10//
11// Usage:
12// auto params = test_utils::ParameterScratchpad(run, "params.txt");
13// params.write("foo", 42u);
14// params.write("bar", std::bfloat16_t(3.14f));
15// params.sync();
16//
17// A module of several commands, runtime sequences no `aiex.run` calls, gives
18// each its own scratchpad, in which parameters of different commands may
19// share a slot: construct each command's with its `device:sequence` name, and
20// write it with that command's parameters (`names()`) only. With no name, the
21// module must have one command.
22//
23//===----------------------------------------------------------------------===//
24
25#ifndef AIE_RUNTIME_TEST_LIB_PARAMETER_SCRATCHPAD_H
26#define AIE_RUNTIME_TEST_LIB_PARAMETER_SCRATCHPAD_H
27
28#include <cstdint>
29#include <cstring>
30#include <fstream>
31#include <stdexcept>
32#include <string>
33#include <unordered_map>
34#include <unordered_set>
35#include <utility>
36#include <vector>
37
38#if !defined(TEST_UTILS_USE_XRT)
39#if defined(__has_include) && __has_include(<xrt/xrt_bo.h>) && \
40 __has_include(<xrt/xrt_kernel.h>)
41#define TEST_UTILS_USE_XRT 1
42#else
43#define TEST_UTILS_USE_XRT 0
44#endif
45#endif
46
47#if TEST_UTILS_USE_XRT
48#include <xrt/xrt_bo.h>
49#include <xrt/xrt_kernel.h>
50#endif
51
52namespace test_utils {
53
55public:
56#if TEST_UTILS_USE_XRT
57 /// Construct from an XRT run handle (C++ usage), for the parameters of
58 /// `command` (`device:sequence`), or of the module's one command when it
59 /// is empty.
60 ParameterScratchpad(xrt::run &run, const std::string &paramsPath,
61 const std::string &command = "") {
62 parseParams(paramsPath);
63 selectCommand(command, paramsPath);
64 scratchpadBo = run.get_ctrl_scratchpad_bo();
65 boMap = scratchpadBo.map<uint32_t *>();
66 if (scratchpadBo.size() < scratchpadSizeBytes)
67 throw std::runtime_error("ParameterScratchpad: BO size (" +
68 std::to_string(scratchpadBo.size()) +
69 ") < required scratchpad size (" +
70 std::to_string(scratchpadSizeBytes) + ")");
71 clear();
72 }
73#endif
74
75 /// Construct from a raw buffer and its size in bytes (Python bindings /
76 /// testing). The size is required: clear() and every write index off
77 /// scratchpadSizeBytes, which parseParams derives from the params file, not
78 /// from the buffer.
79 ParameterScratchpad(uint32_t *buffer, size_t bufferSizeBytes,
80 const std::string &paramsPath,
81 const std::string &command = "")
82 : boMap(buffer) {
83 parseParams(paramsPath);
84 selectCommand(command, paramsPath);
85 if (bufferSizeBytes < scratchpadSizeBytes)
86 throw std::runtime_error("ParameterScratchpad: buffer size (" +
87 std::to_string(bufferSizeBytes) +
88 ") < required scratchpad size (" +
89 std::to_string(scratchpadSizeBytes) + ")");
90 clear();
91 }
92
93 /// Write raw bytes (up to 4) by name, interpreted as a little-endian
94 /// uint32. For core-kind parameters, the bits are left-shifted by 2
95 /// (firmware requirement); for repeat-kind ones, by 16 (into the task
96 /// queue's repeat field).
97 /// For addr-kind parameters, the value is written raw (no shift).
98 void writeBytes(const std::string &name, const void *data, size_t len) {
99 uint32_t bits = 0;
100 std::memcpy(&bits, data, std::min(len, sizeof(bits)));
101 writeBits(name, bits);
102 }
103
104 /// Write a raw 32-bit value by name. For core-kind parameters, the bits
105 /// are left-shifted by 2 (firmware requirement); for repeat-kind ones, by
106 /// 16 (into the task queue's repeat field). For addr-kind parameters,
107 /// the value is written directly (no shift). A DMA offset or length
108 /// parameter's value, read as an int32, must lie in the range the compiler
109 /// gave it, which keeps its transfers within their buffers; a repeat
110 /// parameter's, at most its task's repeat count.
111 void writeBits(const std::string &name, uint32_t bits) {
112 if (unslotted.count(name))
113 throw std::runtime_error("ParameterScratchpad: no command reads "
114 "parameter '" +
115 name + "', so it has no slot");
116 auto it = paramMap.find(name);
117 if (it == paramMap.end()) {
118 throw std::runtime_error("ParameterScratchpad: unknown parameter '" +
119 name + "'");
120 }
121 if (!members.count(name))
122 throw std::runtime_error("ParameterScratchpad: command '" + command +
123 "' does not read parameter '" + name +
124 "', whose slot another of its parameters may "
125 "hold");
126 if (auto range = ranges.find(name); range != ranges.end()) {
127 int32_t value = static_cast<int32_t>(bits);
128 if (value < range->second.first || value > range->second.second)
129 throw std::invalid_argument(
130 "ParameterScratchpad: value " + std::to_string(value) + " of '" +
131 name + "' is outside [" + std::to_string(range->second.first) +
132 ", " + std::to_string(range->second.second) + "], " +
133 (repeatParams.count(name)
134 ? "its task's largest repeat count"
135 : "which keeps its DMA transfers within their buffers"));
136 values[name] = value;
137 }
138 uint8_t idx = it->second;
139 uint32_t encoded = bits;
140 if (coreParams.count(name)) {
141 // core parameters require shift-2 to survive masking of lowest bits by
142 // firmware op
143 encoded = bits << 2;
144 } else if (repeatParams.count(name)) {
145 encoded = bits << 16;
146 }
147 boMap[idx] = encoded;
148 }
149
150 /// Write a typed parameter value. For core-kind parameters, the raw bits
151 /// are left-shifted by 2 as required by the firmware's UPDATE_REG Incr mode,
152 /// and the core right-shifts (unsigned) by 2 after reading. The round trip
153 /// therefore zeroes the top 2 bits of the value (the bottom 2 bits are
154 /// preserved). Types that fit in 30 bits (e.g. uint16_t, int16_t,
155 /// std::bfloat16_t, and uint32_t values < 2^30) round-trip losslessly.
156 /// `float` is not supported as a core-kind parameter (the verifier in
157 /// `--aie-lower-scratchpad-parameters` rejects it).
158 template <typename T>
159 void write(const std::string &name, T value) {
160 static_assert(sizeof(T) <= 4, "Parameter values must be at most 32 bits");
161 uint32_t bits = 0;
162 std::memcpy(&bits, &value, sizeof(T));
163 writeBits(name, bits);
164 }
165
166 /// Check the written values of each offset and length parameter pair that
167 /// one DMA transfer uses. Throws std::invalid_argument if they take the
168 /// transfer past the end of its buffer.
169 void validate() const {
170 for (const JointBound &bound : jointBounds) {
171 int64_t offset = values.at(bound.offset);
172 int64_t length = values.at(bound.length);
173 int64_t end = offset + bound.lengthStep * length;
174 if (end > bound.max)
175 throw std::invalid_argument(
176 "ParameterScratchpad: '" + bound.offset +
177 "' = " + std::to_string(offset) + " plus " +
178 std::to_string(bound.lengthStep) + " * '" + bound.length +
179 "' = " + std::to_string(length) + " is " + std::to_string(end) +
180 ", above " + std::to_string(bound.max) +
181 ", so a DMA transfer using both runs past its buffer");
182 }
183 }
184
185#if TEST_UTILS_USE_XRT
186 /// Validate, then sync the scratchpad buffer to device. Call after all writes
187 /// for this run.
188 void sync() {
189 validate();
190 scratchpadBo.sync(XCL_BO_SYNC_BO_TO_DEVICE);
191 }
192#endif
193
194 /// The parameters this scratchpad holds: its command's.
195 std::vector<std::string> names() const {
196 return std::vector<std::string>(members.begin(), members.end());
197 }
198
199 /// Read back a parameter's current encoded value (for debugging).
200 uint32_t read(const std::string &name) const {
201 if (unslotted.count(name))
202 throw std::runtime_error("ParameterScratchpad: no command reads "
203 "parameter '" +
204 name + "', so it has no slot");
205 auto it = paramMap.find(name);
206 if (it == paramMap.end()) {
207 throw std::runtime_error("ParameterScratchpad: unknown parameter '" +
208 name + "'");
209 }
210 return boMap[it->second];
211 }
212
213private:
214#if TEST_UTILS_USE_XRT
215 xrt::bo scratchpadBo;
216#endif
217 uint32_t *boMap = nullptr;
218 size_t scratchpadSizeBytes = 0;
219 std::unordered_map<std::string, uint8_t> paramMap;
220 std::unordered_set<std::string> unslotted; // params no command reads
221 std::unordered_set<std::string> coreParams; // params with kind="core"
222 std::unordered_set<std::string> repeatParams; // params with kind="repeat"
223 std::unordered_map<std::string, std::pair<int32_t, int32_t>> ranges;
224 std::unordered_map<std::string, int32_t> values; // of the ranged params
225 struct JointBound {
226 std::string length, offset;
227 int64_t lengthStep, max;
228 };
229 std::vector<JointBound> jointBounds;
230 // Each command's parameters, by `device:sequence`.
231 std::unordered_map<std::string, std::unordered_set<std::string>> commands;
232 std::vector<std::string> commandNames; // in params.txt order
233 std::string command;
234 std::unordered_set<std::string> members;
235
236 void selectCommand(const std::string &name, const std::string &path) {
237 command = name;
238 if (name.empty()) {
239 if (commandNames.empty())
240 throw std::runtime_error("ParameterScratchpad: '" + path +
241 "' lists no command to hold parameters for");
242 if (commandNames.size() > 1) {
243 std::string available;
244 for (const std::string &entry : commandNames)
245 available += (available.empty() ? "" : ", ") + entry;
246 throw std::runtime_error(
247 "ParameterScratchpad: '" + path + "' lists " +
248 std::to_string(commandNames.size()) +
249 " commands, whose parameters may share slots: name the one this "
250 "scratchpad is for, of " +
251 available);
252 }
253 command = commandNames.front();
254 }
255 auto it = commands.find(command);
256 if (it == commands.end())
257 throw std::runtime_error("ParameterScratchpad: '" + path +
258 "' has no command '" + name + "'");
259 members = it->second;
260 }
261
262 void clear() {
263 for (size_t i = 0; i < scratchpadSizeBytes / 4; i++) {
264 boMap[i] = 0;
265 }
266 }
267
268 void parseParams(const std::string &path) {
269 std::ifstream file(path);
270 if (!file.is_open()) {
271 throw std::runtime_error("ParameterScratchpad: cannot open '" + path +
272 "'");
273 }
274
275 // Format (emitScratchpadParamsFile's):
276 // <num_parameters> <num_slots>
277 // <name> <state_table_idx> <type> <kind> <min> <max>
278 // ...
279 // <num_joint_bounds>
280 // <length> <offset> <length_step> <max>
281 // ...
282 // <num_commands>
283 // <device>:<sequence> <num_read> <name> ...
284 // ...
285 // where <state_table_idx> is "-" for a parameter no command reads, kind
286 // is "core", "addr" or "repeat", and <min> <max> is "- -" for a parameter
287 // with no range. A joint bound requires
288 // offset + length_step * length <= max.
289 unsigned numParams = 0, numSlots = 0;
290 file >> numParams >> numSlots;
291 if (!file)
292 throw std::runtime_error(
293 "ParameterScratchpad: missing parameter and slot counts in '" + path +
294 "'");
295 scratchpadSizeBytes = numSlots * 4;
296
297 for (unsigned i = 0; i < numParams; i++) {
298 std::string name, slot, type, kind, min, max;
299 file >> name >> slot >> type >> kind >> min >> max;
300 if (!file)
301 throw std::runtime_error("ParameterScratchpad: malformed entry " +
302 std::to_string(i) + " in '" + path + "'");
303 if (paramMap.count(name) || unslotted.count(name))
304 throw std::runtime_error(
305 "ParameterScratchpad: duplicate parameter name '" + name + "'");
306 if (slot == "-") {
307 unslotted.insert(name);
308 } else {
309 unsigned long idx = 0;
310 try {
311 idx = std::stoul(slot);
312 } catch (const std::logic_error &) {
313 throw std::runtime_error("ParameterScratchpad: malformed slot '" +
314 slot + "' for parameter '" + name +
315 "' in '" + path + "'");
316 }
317 if (idx >= numSlots)
318 throw std::runtime_error("ParameterScratchpad: state_table_idx " +
319 std::to_string(idx) + " for '" + name +
320 "' is past the " + std::to_string(numSlots) +
321 " slots of '" + path + "'");
322 paramMap[name] = static_cast<uint8_t>(idx);
323 }
324 if (kind != "core" && kind != "addr" && kind != "repeat") {
325 throw std::runtime_error("ParameterScratchpad: invalid kind '" + kind +
326 "' for parameter '" + name + "'");
327 } else if (kind == "core") {
328 coreParams.insert(name);
329 } else if (kind == "repeat") {
330 repeatParams.insert(name);
331 }
332 if (min != "-") {
333 try {
334 ranges[name] = {std::stoi(min), std::stoi(max)};
335 } catch (const std::logic_error &) {
336 throw std::runtime_error("ParameterScratchpad: malformed range '" +
337 min + " " + max + "' for parameter '" +
338 name + "' in '" + path + "'");
339 }
340 values[name] = 0;
341 }
342 }
343
344 unsigned numJointBounds = 0;
345 file >> numJointBounds;
346 if (!file)
347 throw std::runtime_error(
348 "ParameterScratchpad: missing joint bound count in '" + path + "'");
349 for (unsigned i = 0; i < numJointBounds; i++) {
350 JointBound bound;
351 file >> bound.length >> bound.offset >> bound.lengthStep >> bound.max;
352 if (!file)
353 throw std::runtime_error("ParameterScratchpad: malformed joint bound " +
354 std::to_string(i) + " in '" + path + "'");
355 for (const std::string &name : {bound.length, bound.offset})
356 if (!ranges.count(name))
357 throw std::runtime_error("ParameterScratchpad: joint bound " +
358 std::to_string(i) + " names '" + name +
359 "', which has no range, in '" + path + "'");
360 jointBounds.push_back(bound);
361 }
362
363 unsigned numCommands = 0;
364 file >> numCommands;
365 if (!file)
366 throw std::runtime_error(
367 "ParameterScratchpad: missing command count in '" + path + "'");
368 for (unsigned i = 0; i < numCommands; i++) {
369 std::string name;
370 unsigned numRead = 0;
371 file >> name >> numRead;
372 commandNames.push_back(name);
373 std::unordered_set<std::string> &reads = commands[name];
374 for (unsigned j = 0; j < numRead; j++) {
375 std::string param;
376 file >> param;
377 if (file && !paramMap.count(param))
378 throw std::runtime_error("ParameterScratchpad: command '" + name +
379 "' names unknown parameter '" + param +
380 "' in '" + path + "'");
381 reads.insert(param);
382 }
383 if (!file)
384 throw std::runtime_error("ParameterScratchpad: malformed command " +
385 std::to_string(i) + " in '" + path + "'");
386 }
387 }
388};
389
390} // namespace test_utils
391
392#endif // AIE_RUNTIME_TEST_LIB_PARAMETER_SCRATCHPAD_H
void writeBytes(const std::string &name, const void *data, size_t len)
Write raw bytes (up to 4) by name, interpreted as a little-endian uint32.
void writeBits(const std::string &name, uint32_t bits)
Write a raw 32-bit value by name.
void validate() const
Check the written values of each offset and length parameter pair that one DMA transfer uses.
uint32_t read(const std::string &name) const
Read back a parameter's current encoded value (for debugging).
void write(const std::string &name, T value)
Write a typed parameter value.
std::vector< std::string > names() const
The parameters this scratchpad holds: its command's.
ParameterScratchpad(uint32_t *buffer, size_t bufferSizeBytes, const std::string &paramsPath, const std::string &command="")
Construct from a raw buffer and its size in bytes (Python bindings / testing).