Loop Timing Witness
Native controller and runtime interfaces; hardware qualification pending
Loading...
Searching...
No Matches
run_control.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 — native IRQ sample command and drain lifecycle
8
12
13#ifndef WITNESS_RUN_CONTROL_H
14#define WITNESS_RUN_CONTROL_H
15#include "run_output.h"
16#include "refusal.h"
17#include <cstring>
18#include <functional>
19
20namespace witness {
22struct RunHooks {
23 std::function<void()> start, check, finish;
24};
25
27struct RunResult {
28 std::uint64_t samples = 0, records = 0;
29 std::uint32_t misses = 0, overflow = 0;
30 bool safe = false;
31};
32
34inline std::int32_t signed_word(std::uint32_t value) {
35 std::int32_t result;
36 std::memcpy(&result, &value, sizeof(result));
37 return result;
38}
39
41template <class Device> std::uint32_t read_register(Device &device, std::uint8_t address) {
42 const auto result = device.read(address);
43 if (result.response != 0)
44 throw witness::RuntimeRefused("register read refused at " + std::to_string(address));
45 return result.data;
46}
47
49template <class Device>
50void write_register(Device &device, std::uint8_t address, std::uint32_t value) {
51 if (device.write(address, value, 15).response != 0)
52 throw witness::RuntimeRefused("register write refused at " + std::to_string(address));
53}
54
56template <class Device> void configure_run(Device &device, const RunConfiguration &configuration) {
57 validate_configuration(configuration);
58 if (!(read_register(device, 0x98) & 8))
59 throw witness::RuntimeRefused("cannot reset an active run");
60 if ((read_register(device, 0x90) & 7) != 4)
61 throw witness::RuntimeRefused("unread previous run records prevent reset");
62 write_register(device, 0x98, 0);
63 write_register(device, 0x98, 1);
64 const auto start = device.time();
65 while ((read_register(device, 0x98) & 7) != 7)
66 if (device.time() - start > 1000000000)
67 throw witness::RuntimeRefused("run bank release timed out");
68 if (read_register(device, 0x7c) != 1 || read_register(device, 0x78) != 24 ||
69 read_register(device, 0x40) != configuration.period_ticks)
70 throw witness::RuntimeRefused("compiled register ABI, Q format or period mismatch");
71 write_register(device, 0x3c, configuration.cycles - 1);
72 write_register(device, 0x44, configuration.reference_mode);
73 write_register(device, 0x48, static_cast<std::uint32_t>(configuration.amplitude));
74 write_register(device, 0x4c, static_cast<std::uint32_t>(configuration.offset));
75 write_register(device, 0x50, static_cast<std::uint32_t>(configuration.ramp));
76 write_register(device, 0x54, configuration.phase);
77 if (configuration.fault_enabled) {
78 write_register(device, 0x58, configuration.fault_kind);
79 write_register(device, 0x5c, configuration.fault_cycle);
80 write_register(device, 0x60, configuration.fault_periods);
81 write_register(device, 0x64, 1);
82 }
83}
84
86template <class Device> void drain_available(Device &device, RunOutput &output, RunResult &result) {
87 for (unsigned batch = 0; batch < 8 && (read_register(device, 0x90) & 1); ++batch) {
88 std::array<std::uint32_t, 4> words{};
89 for (unsigned index = 0; index < 4; ++index)
90 words[index] = read_register(device, static_cast<std::uint8_t>(0x80 + index * 4));
91 output.event(words);
92 write_register(device, 0x94, 1);
93 ++result.records;
94 }
95}
96
98template <class Device>
99void control_sample(Device &device, const RunConfiguration &configuration, witness_pid_state &state,
100 RunOutput &output, RunResult &result, std::uint64_t generation,
101 std::uint32_t &previous_cycle, bool &have_previous) {
102 const auto position = signed_word(read_register(device, 0));
103 const auto cycle = read_register(device, 0x10);
104 const auto velocity = signed_word(read_register(device, 0x14));
105 const auto reference = signed_word(read_register(device, 0x18));
106 const std::uint64_t low = read_register(device, 0x1c);
107 const auto ticks = low | (static_cast<std::uint64_t>(read_register(device, 0x20)) << 32);
108 if (cycle >= configuration.cycles || (have_previous && cycle <= previous_cycle))
109 throw witness::RuntimeRefused("duplicate or out-of-order sample cycle");
110 previous_cycle = cycle;
111 have_previous = true;
112 witness_command command{};
113 const bool computed = configuration.lqr
114 ? witness_lqr_step(&configuration.coefficients, cycle, reference,
115 position, velocity, &command)
116 : witness_pid_step(&configuration.coefficients, &state, cycle,
117 reference, position, &command);
118 if (!computed)
119 throw witness::RuntimeRefused("native controller refused validated coefficients");
120 std::uint64_t work = 0;
121 if (read_register(device, 0x68) & 4) {
122 volatile std::uint64_t accumulator = 0;
123 for (std::uint32_t index = 0; index < configuration.overload_iterations; ++index)
124 accumulator = accumulator + static_cast<std::uint64_t>(index) * index;
125 work = accumulator;
126 device.advance(configuration.modeled_overload_ns);
127 }
128 bool submitted = false;
129 if (!(read_register(device, 4) & 12)) {
130 write_register(device, 0x28, cycle);
131 write_register(device, 0x2c, static_cast<std::uint32_t>(command.command));
132 const auto commit = device.write(0x30, 1, 15);
133 if (commit.response != 0 && !(read_register(device, 4) & 12))
134 throw witness::RuntimeRefused("command commit refused without a safe or finished run");
135 submitted = commit.response == 0;
136 }
137 output.sample(reference, position, velocity, command, submitted, ticks, generation, work);
138 ++result.samples;
139}
140
142template <class Device>
143RunResult execute_run(Device &device, const RunConfiguration &configuration, RunOutput &output,
144 const RunHooks *hooks = nullptr) {
145 configure_run(device, configuration);
146 witness_pid_state state{};
147 witness_pid_reset(&state);
148 RunResult result;
149 std::uint32_t previous_cycle = 0;
150 bool have_previous = false, finished = false;
151 const auto run_limit =
152 static_cast<std::uint64_t>(configuration.period_ticks) * 10 * configuration.cycles +
153 1000000000;
154 if (hooks)
155 hooks->start();
156 write_register(device, 0x38, 1);
157 const auto run_start = device.time();
158 while (!finished) {
159 if (hooks)
160 hooks->check();
161 if (device.wait_interrupt(1000000)) {
162 const std::uint64_t low = read_register(device, 0x9c);
163 const auto generation =
164 low | (static_cast<std::uint64_t>(read_register(device, 0xa0)) << 32);
165 const auto status = read_register(device, 4);
166 finished = (status & 4) != 0;
167 if (!finished && !(status & 8))
168 control_sample(device, configuration, state, output, result, generation,
169 previous_cycle, have_previous);
170 write_register(device, 0xa4, 1);
171 }
172 if (device.time() - run_start > run_limit)
173 throw witness::RuntimeRefused("configured run completion timed out");
174 drain_available(device, output, result);
175 }
176 const auto drain_start = device.time();
177 while (!(read_register(device, 0x90) & 8)) {
178 if (hooks)
179 hooks->check();
180 drain_available(device, output, result);
181 if (device.time() - drain_start > 1000000000)
182 throw witness::RuntimeRefused("final record drain timed out");
183 }
184 result.misses = read_register(device, 0x34);
185 result.overflow = read_register(device, 0x30);
186 result.safe = (read_register(device, 4) & 8) != 0;
187 if (hooks)
188 hooks->finish();
189 output.finish();
190 return result;
191}
192} // namespace witness
217
218#endif
void event(const std::array< std::uint32_t, 4 > &words)
Definition run_output.h:45
void sample(std::int32_t reference, std::int32_t position, std::int32_t velocity, const witness_command &command, bool submitted, std::uint64_t ticks, std::uint64_t generation, std::uint64_t work)
Definition run_output.h:55
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 configure_run(Device &device, const RunConfiguration &configuration)
Definition run_control.h:56
void control_sample(Device &device, const RunConfiguration &configuration, witness_pid_state &state, RunOutput &output, RunResult &result, std::uint64_t generation, std::uint32_t &previous_cycle, bool &have_previous)
Definition run_control.h:99
void write_register(Device &device, std::uint8_t address, std::uint32_t value)
Definition run_control.h:50
RunResult execute_run(Device &device, const RunConfiguration &configuration, RunOutput &output, const RunHooks *hooks=nullptr)
witness_coefficients coefficients
std::function< void()> check
Definition run_control.h:23
std::function< void()> finish
Definition run_control.h:23
std::function< void()> start
Definition run_control.h:23
std::uint32_t overflow
Definition run_control.h:29
std::uint64_t samples
Definition run_control.h:28
std::uint64_t records
Definition run_control.h:28
std::uint32_t misses
Definition run_control.h:29
bool witness_lqr_step(const witness_coefficients *coefficients, uint32_t cycle, int32_t reference, int32_t position, int32_t velocity, witness_command *command)
void witness_pid_reset(witness_pid_state *state)
bool witness_pid_step(const witness_coefficients *coefficients, witness_pid_state *state, uint32_t cycle, int32_t reference, int32_t position, witness_command *command)