Loop Timing Witness
Native controller and runtime interfaces; hardware qualification pending
Loading...
Searching...
No Matches
amp_logger.h
Go to the documentation of this file.
1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Commercial license available
3// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4// © Code 2020–2026 Miroslav Šotek. All rights reserved.
5// ORCID: 0009-0009-3560-0851
6// Contact: www.anulum.li | protoscience@anulum.li
7// Loop Timing Witness — separate AMP telemetry and fabric event logger
8
12
13#ifndef WITNESS_AMP_LOGGER_H
14#define WITNESS_AMP_LOGGER_H
15#include "run_control.h"
16#include "refusal.h"
17extern "C" {
19}
20#include <cstddef>
21
22namespace witness {
23static_assert(sizeof(witness_amp_sample) == 64, "shared sample ABI mismatch");
24static_assert(sizeof(witness_amp_mailbox) == 16496, "shared mailbox ABI mismatch");
25static_assert(sizeof(witness_amp_run_contract) == 60, "shared run ABI mismatch");
26
28template <class Device> class AmpLogger {
29 Device &device;
30 const RunConfiguration &configuration;
31 RunOutput &output;
32 const RunHooks *hooks;
33 std::uint64_t shared;
34 RunResult result;
35 bool ready = false, started = false, completed = false, have_previous = false;
36 std::uint32_t consumer = 0, previous_cycle = 0;
37 std::uint64_t previous_ticks = 0, previous_generation = 0;
38
40 std::uint32_t memory(std::size_t offset) { return device.read_memory32(shared + offset); }
41
43 std::uint64_t wide(std::size_t offset) {
44 const auto low = memory(offset);
45 return low | (static_cast<std::uint64_t>(memory(offset + 4)) << 32);
46 }
47
49 void verify_run() {
50 const auto &c = configuration.coefficients;
51 const std::array<std::uint32_t, 15> expected{configuration.cycles,
52 configuration.period_ticks,
53 configuration.lqr ? 1U : 0U,
54 configuration.overload_iterations,
55 static_cast<std::uint32_t>(c.kp),
56 static_cast<std::uint32_t>(c.ki_period),
57 static_cast<std::uint32_t>(c.derivative_decay),
58 static_cast<std::uint32_t>(c.derivative_gain),
59 static_cast<std::uint32_t>(c.position_gain),
60 static_cast<std::uint32_t>(c.velocity_gain),
61 static_cast<std::uint32_t>(c.reference_gain),
62 static_cast<std::uint32_t>(c.output_min),
63 static_cast<std::uint32_t>(c.output_max),
64 static_cast<std::uint32_t>(c.integral_min),
65 static_cast<std::uint32_t>(c.integral_max)};
66 for (std::size_t index = 0; index < expected.size(); ++index)
67 if (memory(offsetof(witness_amp_mailbox, run) + index * 4) != expected[index])
69 "AMP running firmware contract differs from logger configuration");
70 if (memory(offsetof(witness_amp_mailbox, run_reserved)))
71 throw witness::RuntimeRefused("AMP run contract reserved field changed");
72 }
73
75 void consume(std::uint32_t producer) {
76 if (producer - consumer > WITNESS_AMP_CAPACITY)
77 throw witness::RuntimeRefused("AMP producer advanced beyond telemetry capacity");
78 for (unsigned batch = 0; batch < 8 && consumer != producer; ++batch) {
79 const auto slot = offsetof(witness_amp_mailbox, records) +
80 (consumer & (WITNESS_AMP_CAPACITY - 1)) * sizeof(witness_amp_sample);
81 const auto ticks = wide(slot);
82 const auto generation = wide(slot + 8);
83 const auto work = wide(slot + 16);
84 const auto cycle = memory(slot + 24);
85 const auto reference = signed_word(memory(slot + 28));
86 const auto position = signed_word(memory(slot + 32));
87 const auto velocity = signed_word(memory(slot + 36));
88 const auto command = signed_word(memory(slot + 40));
89 const auto integral = signed_word(memory(slot + 44));
90 const auto derivative = signed_word(memory(slot + 48));
91 const auto clipped = memory(slot + 52);
92 const auto held = memory(slot + 56);
93 const auto submitted = memory(slot + 60);
94 if (cycle >= configuration.cycles || !generation || clipped > 1 || held > 1 ||
95 submitted > 1 ||
96 (have_previous && (cycle <= previous_cycle || ticks <= previous_ticks ||
97 generation <= previous_generation)))
99 "AMP telemetry contradicts the observed sample sequence");
100 const witness_command observed{cycle, command, integral,
101 derivative, clipped != 0, held != 0};
102 output.sample(reference, position, velocity, observed, submitted != 0, ticks,
103 generation, work);
104 previous_cycle = cycle;
105 previous_ticks = ticks;
106 previous_generation = generation;
107 have_previous = true;
108 ++result.samples;
109 ++consumer;
110 device.write_memory32(shared + offsetof(witness_amp_mailbox, consumer), consumer);
111 }
112 }
113
114 public:
116 AmpLogger(Device &backend, const RunConfiguration &run, RunOutput &files,
117 std::uint64_t mailbox_address, const RunHooks *acquisition = nullptr)
118 : device(backend), configuration(run), output(files), hooks(acquisition),
119 shared(mailbox_address) {
120 validate_configuration(configuration);
121 if (!shared || shared % 8 || shared > UINT64_MAX - sizeof(witness_amp_mailbox))
122 throw witness::ArgumentRefused("AMP mailbox address outside bounds");
123 }
124
126 bool poll() {
127 if (completed)
128 throw witness::RuntimeRefused("AMP logger is already complete");
129 if (hooks)
130 hooks->check();
131 const auto status = memory(offsetof(witness_amp_mailbox, status));
132 if (status > WITNESS_AMP_REFUSED)
133 throw witness::RuntimeRefused("unknown AMP firmware status");
134 if (status == WITNESS_AMP_REFUSED ||
135 memory(offsetof(witness_amp_mailbox, telemetry_overflow)))
137 "AMP firmware refused or telemetry overflowed: cause=" +
138 std::to_string(wide(offsetof(witness_amp_mailbox, trap_cause))) +
139 " value=" + std::to_string(wide(offsetof(witness_amp_mailbox, trap_value))));
140 const auto abi = memory(offsetof(witness_amp_mailbox, abi));
141 if (!abi) {
142 if (ready)
143 throw witness::RuntimeRefused("AMP ABI disappeared during the run");
144 return false;
145 }
146 if (abi != WITNESS_AMP_ABI)
147 throw witness::RuntimeRefused("AMP telemetry ABI mismatch");
148 if (!ready) {
149 if (status != WITNESS_AMP_INITIAL || memory(offsetof(witness_amp_mailbox, producer)) ||
150 memory(offsetof(witness_amp_mailbox, consumer)) ||
151 wide(offsetof(witness_amp_mailbox, trap_cause)) ||
152 wide(offsetof(witness_amp_mailbox, trap_value)) ||
153 memory(offsetof(witness_amp_mailbox, samples)) ||
154 memory(offsetof(witness_amp_mailbox, logger_status)) ||
155 memory(offsetof(witness_amp_mailbox, reserved)))
156 throw witness::RuntimeRefused("AMP firmware did not initialize a fresh mailbox");
157 verify_run();
158 device.write_memory32(shared + offsetof(witness_amp_mailbox, logger_status),
160 ready = true;
161 return false;
162 }
163 if ((started && status == WITNESS_AMP_INITIAL) ||
164 memory(offsetof(witness_amp_mailbox, logger_status)) != WITNESS_AMP_LOGGER_READY ||
165 memory(offsetof(witness_amp_mailbox, reserved)) ||
166 memory(offsetof(witness_amp_mailbox, run_reserved)))
167 throw witness::RuntimeRefused("AMP shared ownership or reserved fields changed");
168 if (wide(offsetof(witness_amp_mailbox, trap_cause)) ||
169 wide(offsetof(witness_amp_mailbox, trap_value)))
170 throw witness::RuntimeRefused("AMP trap state contradicts active firmware status");
171 if (!started) {
172 if (status == WITNESS_AMP_INITIAL)
173 return false;
174 if (status != WITNESS_AMP_ARMED || memory(offsetof(witness_amp_mailbox, consumer)) ||
175 memory(offsetof(witness_amp_mailbox, producer)))
176 throw witness::RuntimeRefused("AMP firmware did not arm a fresh run");
177 if (hooks)
178 hooks->start();
179 write_register(device, 0x38, 1);
180 started = true;
181 }
182 if (memory(offsetof(witness_amp_mailbox, consumer)) != consumer)
183 throw witness::RuntimeRefused("AMP telemetry consumer ownership changed");
184 const auto producer = memory(offsetof(witness_amp_mailbox, producer));
185 consume(producer);
186 drain_available(device, output, result);
187 if (status != WITNESS_AMP_FINISHED)
188 return false;
189 if (!(read_register(device, 4) & 4))
190 throw witness::RuntimeRefused("AMP completion precedes the actual fabric run finish");
191 if (consumer != memory(offsetof(witness_amp_mailbox, producer)) ||
192 !(read_register(device, 0x90) & 8))
193 return false;
194 if (memory(offsetof(witness_amp_mailbox, samples)) != result.samples)
195 throw witness::RuntimeRefused("AMP sample count differs from consumed telemetry");
196 result.misses = read_register(device, 0x34);
197 result.overflow = read_register(device, 0x30);
198 result.safe = (read_register(device, 4) & 8) != 0;
199 if (hooks)
200 hooks->finish();
201 output.finish();
202 device.write_memory32(shared + offsetof(witness_amp_mailbox, logger_status),
204 completed = true;
205 return true;
206 }
207
210 if (!completed)
211 throw witness::RuntimeRefused("AMP run is not complete");
212 return result;
213 }
214};
215} // namespace witness
216#endif
#define WITNESS_AMP_ABI
#define WITNESS_AMP_CAPACITY
@ WITNESS_AMP_ARMED
@ WITNESS_AMP_FINISHED
@ WITNESS_AMP_INITIAL
@ WITNESS_AMP_REFUSED
@ WITNESS_AMP_LOGGER_COMPLETE
@ WITNESS_AMP_LOGGER_READY
AmpLogger(Device &backend, const RunConfiguration &run, RunOutput &files, std::uint64_t mailbox_address, const RunHooks *acquisition=nullptr)
Definition amp_logger.h:116
RunResult completion() const
Definition amp_logger.h:209
void validate_configuration(const RunConfiguration &configuration)
std::uint32_t read_register(Device &device, std::uint8_t address)
Definition run_control.h:41
void drain_available(Device &device, RunOutput &output, RunResult &result)
Definition run_control.h:86
std::int32_t signed_word(std::uint32_t value)
Definition run_control.h:34
void write_register(Device &device, std::uint8_t address, std::uint32_t value)
Definition run_control.h:50