rigless 0.3.0

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model and unit of virtual time in Rust, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The chips a board puts on a part's buses, and the seams they attach through.
//!
//! A peripheral of an MCU belongs to `soc`, and a chip on a board belongs here. The two meet at a
//! trait per bus kind. The controller calls it and knows nothing of what answers. The chip
//! implements it and knows nothing of registers. That's the whole arrangement, and it's why a host
//! test can drive a card with no MCU in sight, and a firmware can drive it with no test in sight.
//!
//! A chip owns its own document, and no model here names an SoC. The same card belongs on any part
//! with a card interface. That's why it's a chip and not a peripheral.
//!
//! If your bench wants something known on a bus, and not the chip that will eventually be there,
//! use a stand-in the controller's own module keeps. A stand-in answers something recognizable, so a
//! controller that never reached it gets caught. A chip answers what the part it models would
//! answer. They're different things, and they live apart.

pub mod ak8963;
pub mod dps310;
pub mod fram;
pub mod i2c;
pub mod icm20608;
pub mod icm42605;
pub mod icm42688p;
mod icm426xx;
mod invensense;
pub mod lis3mdl;
pub mod memory_card;
pub mod mpu9250;
pub mod ms5611;
pub mod nmea;
pub mod parallel;
pub mod qmc5883l;
pub mod sd;
pub mod spi;
pub mod uart;

use crate::device::DeviceContext;
use crate::snapshot::{Error, Reader, Store, Writer};
use crate::time::{EventId, VirtualNanos};

/// `numerator / denominator` to the nearest integer, halves away from zero, for either sign of either.
pub(crate) fn round(numerator: i64, denominator: i64) -> i64 {
    let (numerator, denominator) = if denominator < 0 {
        (-numerator, -denominator)
    } else {
        (numerator, denominator)
    };
    let half = denominator / 2;
    if numerator >= 0 {
        (numerator + half) / denominator
    } else {
        (numerator - half) / denominator
    }
}

/// `counts` held to a sixteen-bit two's complement register, which saturates rather than wraps.
pub(crate) fn saturate(counts: i64) -> i16 {
    i16::try_from(counts).unwrap_or(if counts < 0 { i16::MIN } else { i16::MAX })
}

/// What a chip on a controller's wires reaches of the run when the controller calls it: the instant
/// of the call, and the controller's event queue, where the chip schedules an event the controller
/// hands back at the seam's `on_event`. It's the controller's [`DeviceContext`], narrowed, so a chip
/// can't drive the controller's interrupt lines, its requests or a reset behind its back. A chip
/// in a [`Window`](crate::soc::Window) is the one that gets the whole context, through
/// [`parallel::Peer`], since the window has lines of its own.
#[derive(Debug)]
pub struct Timeline<'a, 'b> {
    context: &'a mut DeviceContext<'b>,
}

impl<'a, 'b> Timeline<'a, 'b> {
    /// `context`, the controller's own, narrowed for a chip it calls.
    pub fn new(context: &'a mut DeviceContext<'b>) -> Self {
        Self { context }
    }

    /// The instant of this call on the virtual clock.
    #[must_use]
    pub fn now(&self) -> VirtualNanos {
        self.context.now()
    }

    /// Schedules an event at `at`, which the controller hands back at the seam's `on_event`. See
    /// [`DeviceContext::schedule`].
    pub fn schedule(&mut self, at: VirtualNanos) -> EventId {
        self.context.schedule(at)
    }
}

/// A chip's place on a bus: the chip, whether it answers, how many transactions it's had, and the
/// one-shot faults waiting for it.
///
/// A controller holds each chip it carries in one and reaches it through [`Place::answering`], which
/// is `None` while the place is silent. So a silent chip takes the path the controller already takes
/// with nothing there, whatever the bus: a select reads what it reads with nothing selected, an
/// address isn't acknowledged, a serial line rests at mark, a card never answers a command. What a
/// seam does with a silent chip beyond that is the seam's, like a serial line losing what the far end
/// says. The board that hung the chip reaches it through [`Place::chip_mut`] whatever the flag,
/// because what the chip senses goes on while it's silent. That's a cut connection, not a chip that
/// lost power: it keeps its state and answers as it did once the flag clears. The chip never learns
/// it was silenced.
///
/// The controller marks where each transaction with the chip begins and ends, with [`Place::begin`]
/// and [`Place::release`], and passes every word the chip answers through [`Place::answered`]. What
/// a transaction is belongs to the bus: a select to its deselect, a card's command, or a word on a
/// serial line, where nothing else begins or ends one. That's what [`Placed`] counts and where its
/// one-shot faults land.
#[derive(Debug)]
pub struct Place<T: ?Sized> {
    chip: Box<T>,
    silent: bool,
    /// A silence asked for while the chip was in a transaction, which takes effect when it ends.
    waiting: Option<bool>,
    /// Whether a transaction with the chip is under way.
    open: bool,
    /// How many transactions with the chip have ended.
    ended: u64,
    /// The bits the next word the chip answers crosses with flipped.
    flip: u64,
    /// How many of the transactions still to begin the chip loses.
    lose: u32,
    /// Whether it's losing the one under way.
    losing: bool,
}

/// A place keeps whether it answers, what it's counted and what's waiting for it, and the chip keeps
/// itself.
impl<T: Store + ?Sized> Store for Place<T> {
    fn store(&self, to: &mut Writer) -> Result<(), Error> {
        let Self {
            chip,
            silent,
            waiting,
            open,
            ended,
            flip,
            lose,
            losing,
        } = self;
        to.put(silent)?;
        to.put(waiting)?;
        to.put(open)?;
        to.put(ended)?;
        to.put(flip)?;
        to.put(lose)?;
        to.put(losing)?;
        chip.store(to)
    }

    fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
        let Self {
            chip,
            silent,
            waiting,
            open,
            ended,
            flip,
            lose,
            losing,
        } = self;
        *silent = from.take()?;
        *waiting = from.take()?;
        *open = from.take()?;
        *ended = from.take()?;
        *flip = from.take()?;
        *lose = from.take()?;
        *losing = from.take()?;
        chip.load(from)
    }
}

impl<T: ?Sized> Place<T> {
    /// A place with `chip` in it, answering.
    #[must_use]
    pub fn new(chip: Box<T>) -> Self {
        Self {
            chip,
            silent: false,
            waiting: None,
            open: false,
            ended: 0,
            flip: 0,
            lose: 0,
            losing: false,
        }
    }

    /// The chip, for the controller to call, or `None` while the place is silent or losing the
    /// transaction under way.
    pub fn answering(&mut self) -> Option<&mut T> {
        (!self.silent && !self.losing).then_some(&mut *self.chip)
    }

    /// The chip, whether or not it answers.
    #[must_use]
    pub fn chip(&self) -> &T {
        &self.chip
    }

    /// The chip, whether or not it answers, for the board that hung it.
    pub fn chip_mut(&mut self) -> &mut T {
        &mut self.chip
    }

    /// Stops the chip answering, or lets it answer again, from now on.
    pub fn silence(&mut self, silent: bool) {
        self.silent = silent;
        self.waiting = None;
    }

    /// The same, once the transaction the chip is in ends at [`Place::release`], so a transaction is
    /// never cut in half and a chip that comes back starts at the next one.
    pub fn silence_at_release(&mut self, silent: bool) {
        self.waiting = Some(silent);
    }

    /// A transaction with the chip begins, whether or not it answers. Call it before reaching the
    /// chip for it, so a drop waiting for it takes it whole.
    pub fn begin(&mut self) {
        self.open = true;
        if self.lose > 0 {
            self.lose -= 1;
            self.losing = true;
        }
    }

    /// The transaction the chip was in has ended, so it's counted and a silence waiting for it takes
    /// effect. A release with none under way counts nothing.
    pub fn release(&mut self) {
        if std::mem::take(&mut self.open) {
            self.ended += 1;
        }
        self.losing = false;
        if let Some(silent) = self.waiting.take() {
            self.silent = silent;
        }
    }

    /// A word the chip answered, as it crosses to the controller: with the bits a flip waits on
    /// flipped, once. Bits past the word's width flip nothing.
    pub fn answered(&mut self, word: u32) -> u32 {
        let flip = std::mem::take(&mut self.flip) & u64::from(u32::MAX);
        word ^ u32::try_from(flip).expect("masked to the word's width")
    }

    /// A word the chip sent on a line where each word is a transaction of its own: counted, flipped
    /// by a flip waiting for it, and `None` where a drop takes it, so it's lost.
    pub fn sent(&mut self, word: u32) -> Option<u32> {
        self.begin();
        let word = (!self.losing).then(|| self.answered(word));
        self.release();
        word
    }
}

/// What a board asks a controller for its chip back by: the type it hung there. One method per seam,
/// since a place holds its chip as that seam's trait object. A window's chip needs none, since
/// [`Wired`](crate::mcu::Wired) holds it as its own type.
macro_rules! chip_as {
    ($($seam:path),* $(,)?) => {$(
        impl Place<dyn $seam> {
            /// The chip as the type it was hung as, whether or not it answers, or `None` for
            /// another chip.
            pub fn chip_as<C: $seam>(&mut self) -> Option<&mut C> {
                let chip: &mut dyn std::any::Any = &mut *self.chip;
                chip.downcast_mut()
            }
        }
    )*};
}

chip_as!(spi::Peer, i2c::Peer, uart::Peer, sd::Card);

/// A place reached without its chip's type: what a trigger counts on it and the one-shot faults it
/// fires there. A board hands one out for each chip it names.
pub trait Placed {
    /// How many transactions with the chip have ended.
    fn ended(&self) -> u64;

    /// Flips `mask`'s bits in the next word the chip answers. Two flips before it compose, so a bit
    /// flipped twice crosses as it was.
    fn flip(&mut self, mask: u64);

    /// Loses the next transaction to begin with the chip, as if it were silent for that one alone.
    /// The one under way, if any, carries on as it was, and a second drop loses the one after.
    fn drop_next(&mut self);
}

impl<T: ?Sized> Placed for Place<T> {
    fn ended(&self) -> u64 {
        self.ended
    }

    fn flip(&mut self, mask: u64) {
        self.flip ^= mask;
    }

    fn drop_next(&mut self) {
        self.lose += 1;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn a_silent_place_keeps_its_chip_from_the_controller_and_not_from_the_board() {
        let mut place = Place::new(Box::new(7_u8));
        assert_eq!(place.answering().copied(), Some(7));
        place.silence(true);
        assert_eq!(place.answering(), None);
        *place.chip_mut() = 8;
        assert_eq!(*place.chip(), 8, "the board still reaches it");
        place.silence(false);
        assert_eq!(place.answering().copied(), Some(8), "and it answers again");
    }

    #[test]
    fn a_place_counts_the_transactions_that_end_and_a_release_with_none_open_counts_nothing() {
        let mut place = Place::new(Box::new(7_u8));
        place.release();
        assert_eq!(place.ended(), 0);
        place.begin();
        assert_eq!(place.ended(), 0, "not until it ends");
        place.release();
        place.release();
        assert_eq!(place.ended(), 1);
    }

    #[test]
    fn a_flip_takes_the_next_word_alone() {
        let mut place = Place::new(Box::new(7_u8));
        assert_eq!(place.answered(0x1234), 0x1234);
        place.flip(0x0101);
        assert_eq!(place.answered(0x1234), 0x1335);
        assert_eq!(place.answered(0x1234), 0x1234, "once");
    }

    #[test]
    fn a_drop_takes_the_next_transaction_whole_and_lets_the_one_under_way_finish() {
        let mut place = Place::new(Box::new(7_u8));
        place.begin();
        place.drop_next();
        assert_eq!(place.answering().copied(), Some(7), "the one under way");
        place.release();
        place.begin();
        assert_eq!(place.answering(), None, "the next is lost");
        place.release();
        place.begin();
        assert_eq!(place.answering().copied(), Some(7), "and only that one");
        place.release();
        assert_eq!(place.ended(), 3, "a lost transaction still happened");
    }

    #[test]
    fn a_drop_during_a_lost_transaction_loses_the_next_too_and_two_flips_compose() {
        let mut place = Place::new(Box::new(7_u8));
        place.drop_next();
        place.begin();
        place.drop_next();
        assert_eq!(place.answering(), None, "the one under way stays lost");
        place.release();
        place.begin();
        assert_eq!(place.answering(), None, "and so is the next");
        place.release();
        place.begin();
        assert_eq!(place.answering().copied(), Some(7));
        place.flip(0x3);
        place.flip(0x1);
        assert_eq!(place.answered(0), 0x2, "a bit flipped twice is as it was");
    }

    #[test]
    fn a_word_on_a_serial_line_is_a_transaction_that_a_drop_loses_and_a_flip_changes() {
        let mut place = Place::new(Box::new(7_u8));
        assert_eq!(place.sent(0x41), Some(0x41));
        place.drop_next();
        place.flip(0x01);
        assert_eq!(place.sent(0x41), None, "lost, and the flip waits");
        assert_eq!(place.sent(0x41), Some(0x40));
        assert_eq!(place.ended(), 3);
    }

    #[test]
    fn a_silence_asked_for_mid_transaction_waits_for_its_release() {
        let mut place = Place::new(Box::new(7_u8));
        place.silence_at_release(true);
        assert_eq!(
            place.answering().copied(),
            Some(7),
            "the transaction finishes"
        );
        place.release();
        assert_eq!(place.answering(), None);
        place.silence_at_release(false);
        place.silence(true);
        place.release();
        assert_eq!(
            place.answering(),
            None,
            "a silence from now on replaces one waiting"
        );
    }
}