rigless 0.1.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
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! What joins a [`Bus`] to the models that give its peripherals behaviour.
//!
//! A [`Bus`] knows where an address lands and what that register holds. It owns no models. An MCU
//! owns them as fields of its own type, like `mcu.clocks` and `mcu.serial`, and implements [`Mcu`]
//! to say which field answers for which slot. That one `match` is the whole of the mapping, and it
//! is why nothing after construction reaches a model by name or downcasts to its type.
//!
//! Everything the runtime needs of an MCU is here as a provided method: [`Mcu::read`],
//! [`Mcu::write`] and [`Mcu::advance_to`] are written once against [`Mcu::split`], so an SoC module
//! writes its fields and its dispatch and nothing else.

use crate::device::{
    ClockChange, ErasedDevice, InterruptChange, PendingAccess, RegisterFile, RequestChange,
    ResetCause, Told,
};
use crate::emulator::{AccessKind, Error as EmulatorError, Memory};
use crate::soc::{Bus, Cpu, Error, Fault, Region, SAME_INSTANT_LIMIT, SETTLE_LIMIT, Width};
use crate::time::VirtualNanos;

/// A requested access the bus refused.
///
/// A model is answered only when its access lands, so a refusal is otherwise silent: the controller waits
/// for a word that never arrives. A run collects these the way it collects the addresses a firmware reached
/// and nothing answered.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Refused {
    /// The instance that asked.
    pub by: &'static str,
    /// The address it asked for.
    pub address: u64,
    /// Why the bus refused.
    pub fault: Fault,
}

/// One whole microcontroller: a bus, and a model for each of its slots that has one.
///
/// The three required methods are the only thing an SoC module writes by hand. [`Mcu::split`] borrows
/// the bus and one model apart, which is what lets a model be called with its own register file, and
/// [`Mcu::check`] holds the dispatch to the slots it claims when the MCU is built.
pub trait Mcu {
    /// The bus, for reading the map and what the registers hold.
    fn bus(&self) -> &Bus;

    /// A system reset: the part as its firmware finds it at the reset vector, every register at its reset
    /// value and nothing in flight, with whatever a board put on its pins still there. `cause` is why,
    /// for the part's own reset flags. A bus with no such flags ignores it. When a second cause joins a
    /// restart, like the core's own request at the stop a model asked at, it's called again with that
    /// cause, so the flags gain both.
    ///
    /// The default resets the bus, every register and nothing scheduled, driven or waiting for an
    /// answer, and tells every model through [`Device::reset`](crate::device::Device::reset), so each
    /// puts back what a reset clears in it. A part can instead build itself again around what's on
    /// its pins, as the parts this crate ships do.
    fn reset(&mut self, _cause: ResetCause) {
        self.bus_mut().reset();
        for slot in 0..self.bus().slots().len() {
            if let (_, Some(model)) = self.split(slot) {
                model.reset();
            }
        }
    }

    /// The bus, and the model that answers for `slot` if this MCU has one, borrowed apart.
    ///
    /// The implementation is one `match` from the slot to a field:
    ///
    /// ```ignore
    /// fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
    ///     let Self { bus, clocks, serial, wiring, .. } = self;
    ///     let model: Option<&mut dyn ErasedDevice> = match wiring[slot] {
    ///         Field::Clocks => Some(clocks),
    ///         Field::Serial => Some(serial),
    ///         Field::None => None,
    ///     };
    ///     (bus, model)
    /// }
    /// ```
    fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>);

    /// That every model is wired to a slot carrying the layout it was written for.
    ///
    /// The one mistake a hand-written dispatch can make is pointing a slot at the wrong field, and it
    /// is a mistake the type system cannot catch, because every model erases to the same trait object.
    /// This asks `split` itself what it hands over and compares that model's own layout to the slot's,
    /// so the answer comes from the dispatch under test rather than from a second table beside it.
    fn check(&mut self) -> Result<(), Error> {
        for index in 0..self.bus().slots().len() {
            let (bus, model) = self.split(index);
            let Some(model) = model else {
                continue;
            };
            let carried = bus.slots()[index].peripheral();
            if !std::ptr::eq(model.layout(), carried.registers) {
                return Err(Error::LayoutMismatch {
                    instance: carried.name,
                });
            }
        }
        Ok(())
    }

    /// The processor core this MCU carries.
    fn cpu(&self) -> Cpu {
        self.bus().cpu()
    }

    /// The memory regions, which is what a target's emulator is given.
    fn memory(&self) -> &'static [Region] {
        self.bus().memory()
    }

    /// The instant of the earliest pending event, which is when the emulator next has to stop.
    fn next_event(&self) -> Option<VirtualNanos> {
        self.bus().next_event()
    }

    /// A read from the firmware of `width` bits at `address`, which may be part of a wider register.
    ///
    /// The model answering for that slot sees it first. What it doesn't answer, the register file
    /// does.
    fn read(&mut self, at: VirtualNanos, address: u64, width: Width) -> Result<u64, Fault> {
        let located = self.bus().locate(address, width, AccessKind::Read)?;
        let (bus, model) = self.split(located.slot);
        let (slot, mut context) = bus.call(at, located.slot);
        let answered =
            model.and_then(|model| model.read(located.row, slot.registers_mut(), &mut context));
        let value = answered.unwrap_or_else(|| slot.registers_mut().bus_read(located.row));
        Ok((value >> located.lane.shift) & width.mask())
    }

    /// What [`Mcu::read`] would answer at `at`, with nothing changed and no model told, whatever
    /// the instance's gate says. That's a debugger's read. The model answers through
    /// [`Device::peek`](crate::device::Device::peek), and what it doesn't answer, the register file
    /// does. Advance the part to `at` first, as the runtime does before a debugger looks, since a
    /// peek doesn't catch up on what's due. It takes `&mut` only because [`Mcu::split`] does.
    fn peek(&mut self, at: VirtualNanos, address: u64, width: Width) -> Result<u64, Fault> {
        let located = self
            .bus()
            .locate_ungated(address, width, AccessKind::Read)?;
        let (bus, model) = self.split(located.slot);
        let registers = bus.slots()[located.slot].registers();
        let value = model
            .and_then(|model| model.peek(located.row, registers, at))
            .unwrap_or_else(|| registers.bus_peek(located.row));
        Ok((value >> located.lane.shift) & width.mask())
    }

    /// A write over the bus at `at`: the register file applies the rules, then the model sees what landed.
    fn write(
        &mut self,
        at: VirtualNanos,
        address: u64,
        width: Width,
        value: u64,
    ) -> Result<(), Fault> {
        let located = self.bus().locate(address, width, AccessKind::Write)?;
        let (bus, model) = self.split(located.slot);
        let (slot, mut context) = bus.call(at, located.slot);
        let register = &slot.peripheral().registers[located.row];
        // A write-only register stores nothing, so the lanes a narrow write leaves alone carry no action.
        let neutral = if register.access.is_readable() {
            let registers = slot.registers_mut();
            registers
                .write_rule(located.row)
                .neutral(registers.get_row(located.row))
        } else {
            0
        };
        let untouched = neutral & !(width.mask() << located.lane.shift);
        let written = untouched | ((value & width.mask()) << located.lane.shift);
        slot.registers_mut().bus_write(located.row, written);
        if let Some(model) = model {
            model.write(located.row, written, slot.registers_mut(), &mut context);
        }
        Ok(())
    }

    /// Fires every event due by `now`, earliest first, each to the model that scheduled it. An
    /// event scheduled meanwhile at `now` fires in the same pass.
    ///
    /// What the events asked for beyond their own registers is carried out by the next
    /// [`Mcu::settle`], each at the instant of the event that asked. A caller stepping across
    /// several instants at once therefore settles them all after the last of them. A session steps
    /// to its next event, so for it one call is one instant.
    fn advance_to(&mut self, now: VirtualNanos) {
        // Each event runs at its own instant, not at the one being advanced to: a model that schedules a
        // second event from the first would otherwise anchor it to however far the caller happened to step.
        //
        // A model that asks for an instant already gone gets this one instead, and would then be called
        // again in this same pass, forever. That is a model bug rather than a state a run should reach, so
        // it is caught here where it can be named rather than left to hang.
        let mut fired_here = 0_u32;
        let mut last = None;
        while let Some((event, owner, at)) = self.bus_mut().pop_due(now) {
            if last == Some(at) {
                fired_here += 1;
                assert!(
                    fired_here <= SAME_INSTANT_LIMIT,
                    "{} scheduled {fired_here} events at {at:?} without advancing the clock",
                    self.bus().slots()[owner].peripheral().name
                );
            } else {
                last = Some(at);
                fired_here = 0;
            }
            let (bus, model) = self.split(owner);
            let (slot, mut context) = bus.call(at, owner);
            if let Some(model) = model {
                model.on_event(event, slot.registers_mut(), &mut context);
            }
        }
    }

    /// Gives every model a look outside at `now`, which the session does at every stop of the
    /// target. See [`Device::poll`](crate::device::Device::poll).
    fn poll(&mut self, now: VirtualNanos) {
        for slot in 0..self.bus().slots().len() {
            let (bus, model) = self.split(slot);
            let Some(model) = model else {
                continue;
            };
            let (carried, mut context) = bus.call(now, slot);
            model.poll(carried.registers_mut(), &mut context);
        }
    }

    /// Carries out what the models asked for during an access or an event, until nothing is left
    /// or a model asks for a reset, and what the reset would discard is left undone.
    ///
    /// Four things reach this, and in this order. A clock one instance drives, which the instances
    /// the description runs on that output are told about. A request one instance drives, which
    /// every sink the description named is told about. An answer to an access a model asked for,
    /// which goes back to the model that asked. And an access itself, which goes to a peripheral
    /// through [`Mcu::read`] and [`Mcu::write`] or to `memory` when it lies wholly inside one
    /// region. Each may produce more of any of them, so it repeats. A DMA controller moving a block
    /// between two memories does the whole block here.
    ///
    /// The order is what makes a request line mean what it means. An access a model makes may
    /// change a level, and a model handed its answer first would read the level as it stood before
    /// its own access. The hardware releases a request when whatever it asked reaches it, and that
    /// reader sees the release.
    ///
    /// Each effect happens at the instant of the access or the event that set it off, whatever instant
    /// the run has reached by the time it is served: a settle is the part reaching its next steady state
    /// and not time passing, so an event fired at one instant cannot have its consequences stamped with
    /// another's. An access the bus refuses is reported rather than answered, so a model waiting on one
    /// is told so and decides for itself what it means.
    ///
    /// # Panics
    ///
    /// Panics when the models have not run out of work after a bounded number of steps, which is a model
    /// asking for an access on every answer and never finishing. The bound is twice the largest transfer
    /// a part can legitimately ask for in one settle.
    fn settle(&mut self, memory: &mut dyn Memory) -> Result<Vec<Refused>, EmulatorError> {
        let mut refused = Vec::new();
        for _ in 0..SETTLE_LIMIT {
            if self.bus().reset_requested() {
                return Ok(refused);
            }
            if let Some((at, change)) = self.bus_mut().next_clock_change() {
                let readers: Vec<usize> = self.bus().readers_of(change).collect();
                for reader in readers {
                    let (bus, model) = self.split(reader);
                    let (slot, mut context) = bus.call(at, reader);
                    if let Some(model) = model {
                        model.on_clock(change.hz, slot.registers_mut(), &mut context);
                    }
                }
                continue;
            }
            if let Some((at, change)) = self.bus_mut().next_request_change() {
                for index in 0..self.bus().sinks().len() {
                    let sink = self.bus().sinks()[index];
                    let (bus, model) = self.split(sink);
                    let (slot, mut context) = bus.call(at, sink);
                    if let Some(model) = model {
                        model.on_request(change, slot.registers_mut(), &mut context);
                    }
                }
                continue;
            }
            if let Some(answer) = self.bus_mut().next_answer() {
                let (bus, model) = self.split(answer.owner);
                let (slot, mut context) = bus.call(answer.at, answer.owner);
                if let Some(model) = model {
                    let registers = slot.registers_mut();
                    if let Some(value) = answer.found {
                        model.on_access(answer.token, value, registers, &mut context);
                    } else {
                        model.on_refused(answer.token, registers, &mut context);
                    }
                }
                continue;
            }
            let Some(access) = self.bus_mut().next_pending() else {
                return Ok(refused);
            };
            match perform(self, access, memory)? {
                Ok(value) => self.bus_mut().answer(&access, Some(value)),
                Err(fault) => {
                    refused.push(Refused {
                        by: self.bus().slots()[access.owner].peripheral().name,
                        address: access.address,
                        fault,
                    });
                    self.bus_mut().answer(&access, None);
                }
            }
        }
        panic!("the models took {SETTLE_LIMIT} steps without settling");
    }

    /// Takes the interrupt levels the models set since the last drain, in the order they set them.
    fn drain_interrupts(&mut self) -> Vec<InterruptChange> {
        self.bus_mut().drain_interrupts().collect()
    }

    /// Takes the request levels the models set since the last drain, in the order they set them.
    ///
    /// [`Mcu::settle`] is what normally takes them, and gives each to every sink the description named. A
    /// test putting one model on a bus of its own has no sink, so this is how it reads what the model
    /// drove.
    fn drain_requests(&mut self) -> Vec<RequestChange> {
        self.bus_mut().drain_requests().collect()
    }

    /// Takes the clock rates the models drove since the last drain, in the order they drove them.
    /// It's what [`Mcu::drain_requests`] is for a request, for a test reading what a clock tree
    /// drove.
    fn drain_clocks(&mut self) -> Vec<ClockChange> {
        self.bus_mut().drain_clocks().collect()
    }

    /// Takes the reset a model asked for during the last access or stop, if one did.
    fn take_reset_request(&mut self) -> Option<ResetCause> {
        self.bus_mut().take_reset_request()
    }

    /// Takes what the models told since the last drain, in the order they told it.
    fn drain_told(&mut self) -> Vec<Told> {
        self.bus_mut().drain_told().collect()
    }

    /// The bus, mutably. `split` gives it alongside a model, and this gives it alone.
    fn bus_mut(&mut self) -> &mut Bus;

    /// What one instance holds, for a test reading a register back.
    fn registers(&self, instance: &str) -> Option<&RegisterFile> {
        self.bus().registers(instance)
    }
}

/// One access a model asked for: memory where the whole access lies inside one region, and the
/// peripheral bus everywhere else. A write answers zero, which is what its model is told.
fn perform<M: Mcu + ?Sized>(
    mcu: &mut M,
    access: PendingAccess,
    memory: &mut dyn Memory,
) -> Result<Result<u64, Fault>, EmulatorError> {
    let PendingAccess {
        at,
        kind,
        address,
        width,
        value,
        ..
    } = access;
    if mcu.bus().region_holding(address, width).is_some() {
        return match kind {
            AccessKind::Read => memory.read(address, width).map(Ok),
            AccessKind::Write => memory.write(address, width, value).map(|()| Ok(0)),
        };
    }
    Ok(match kind {
        AccessKind::Read => mcu.read(at, address, width),
        AccessKind::Write => mcu.write(at, address, width, value).map(|()| 0),
    })
}

/// One model on a bus of its own: what a test wants when it puts a model under the microscope.
///
/// It's an [`Mcu`] with exactly one field, so everything the runtime does to a whole part works on
/// it unchanged: serve a read, fire an event, drain an interrupt. The model is `single.model`, a
/// field, with `single.registers()` beside it.
#[derive(Debug)]
pub struct Single<D> {
    /// The model, reachable as a field like any other.
    pub model: D,
    bus: Bus,
    slot: usize,
}

impl<D: crate::device::Device + 'static> Single<D> {
    /// Puts `model` on `instance` of the bus `builder` describes, with every gate the description
    /// declared dropped, so that the one model answers alone. A part of your own builds its bus with
    /// [`crate::soc::Builder::build`] and keeps its gates.
    ///
    /// Fails when the bus has no such instance, or when that instance's layout is not the one the
    /// model was written for.
    pub fn new(
        builder: crate::soc::Builder,
        instance: &'static str,
        model: D,
    ) -> Result<Self, Error> {
        let bus = builder.ungated().build()?;
        let slot = bus
            .slot_of(instance)
            .ok_or(Error::UnknownInstance { instance })?;
        let mut single = Self { model, bus, slot };
        single.check()?;
        Ok(single)
    }

    /// What the instance holds, which is where a model keeps half its state.
    pub fn registers(&mut self) -> &mut RegisterFile {
        self.bus.slot_mut(self.slot).registers_mut()
    }

    /// The model and its registers together, which is what a boundary method takes.
    pub fn pair(&mut self) -> (&mut D, &mut RegisterFile) {
        let Self { model, bus, slot } = self;
        (model, bus.slot_mut(*slot).registers_mut())
    }

    /// Drives an input the bus does not carry at `at`: the model gets its registers and a context as
    /// on a bus access, so it may schedule and raise.
    pub fn drive<T>(
        &mut self,
        at: VirtualNanos,
        input: impl FnOnce(&mut D, &mut RegisterFile, &mut crate::device::DeviceContext<'_>) -> T,
    ) -> T {
        let Self { model, bus, slot } = self;
        let (registers, mut context) = bus.drive(at, *slot);
        input(model, registers, &mut context)
    }
}

impl<D: crate::device::Device + 'static> Mcu for Single<D> {
    fn bus(&self) -> &Bus {
        &self.bus
    }

    fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
        let Self {
            model,
            bus,
            slot: mine,
        } = self;
        let answering: Option<&mut dyn ErasedDevice> = (slot == *mine).then_some(model);
        (bus, answering)
    }

    fn bus_mut(&mut self) -> &mut Bus {
        &mut self.bus
    }
}

/// A bus with no models at all: every instance answers from its register file alone.
///
/// It is what "register-only" means, made a type. A part is this plus its models, and a test that
/// wants to see what the tables alone do wants this.
#[derive(Debug)]
pub struct Bare(Bus);

impl Bare {
    /// The bus `builder` describes, with nothing on it.
    pub fn new(builder: crate::soc::Builder) -> Result<Self, Error> {
        Ok(Self(builder.ungated().build()?))
    }
}

impl Mcu for Bare {
    fn bus(&self) -> &Bus {
        &self.0
    }

    fn split(&mut self, _slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
        (&mut self.0, None)
    }

    fn bus_mut(&mut self) -> &mut Bus {
        &mut self.0
    }
}