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
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
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! One firmware on one target of one machine: the bus, the events, and what the run records.
//!
//! `Session`, in `rigless::session`, is the models' side of a run, called by whatever executes the
//! instructions. The emulator calls [`Session::access`] for every peripheral access as the core
//! makes it, at the access's own instant: a read answers with the model's or the register file's
//! value and a write applies to it, the models that asked for an access of their own or drove a
//! request get it at that same instant, and every line a model set is driven before the next
//! instruction executes. The answer carries the next instant the models need control at, the
//! earlier of the next event and where the run is asked to reach, and the emulator stops there and
//! calls [`Session::stop`]: the events due fire in order, every model gets a look outside, the part
//! settles again, and the answer is the next instant once more. A run is asked to reach an instant
//! with [`Session::run_until`] before it starts, and with [`Session::resume`] at each stop after. A
//! stop at or past it is [`Session::reached`], and whoever drives the run decides what comes next. A
//! driver can also pause the run at an instant of its own with [`Session::pause_at`], which the models
//! never hear of.
//!
//! An access the bus refuses because no register covers the address, or the access runs past the
//! register that does, answers zero and is remembered once with the program counter of its first
//! occurrence, which [`Session::unmapped`] lists by ascending address. One it refuses although it
//! carries the register, because a gate the part declares is shut, is remembered with its fault and
//! listed by [`Session::refused`], as is every access a model asked for that the bus wouldn't make.
//! An access to a register no model implements, served from the register file, is remembered for
//! [`Session::unimplemented`], and an interrupt the core has no line for as instance and
//! number for [`Session::unrouted`]. A reset the firmware requests is a stop of its own: at its
//! instant the part restarts as [`crate::mcu::Mcu::reset`] leaves it, the emulator lowers every line
//! the part drove, the instant is remembered for [`Session::resets`], and the run goes on from the
//! restarted core, which finds flash and memory as the firmware left them. A model asking through
//! [`crate::device::DeviceContext::request_reset`] restarts the part the same way, inside the access
//! or the stop that carried the ask, with no instruction of the core's own in between. A core halted
//! when an instant comes has had its clock warped to it, which is the instant reached as well. A
//! model that finds its own instance in a state the manual forbids, checked against another instance
//! after the access already landed, has nothing to refuse and tells the run instead, listed by
//! [`Session::told`].

use std::collections::{BTreeMap, BTreeSet};
use std::fmt;

use crate::device::{Level, LineChange, ResetCause, Told};
use crate::emulator::{Access, AccessKind, Error, Host, InterruptLine, Stop, StopReason, Watch};
use crate::machine::Board;
use crate::mcu::{Mcu, Operate, Refused};
use crate::snapshot::{self, Reader, Store, Writer};
use crate::soc::{Bus, Extent, Fault, Width};
use crate::time::VirtualNanos;

/// A firmware on one target of a machine, from the first access to the last stop.
///
/// The session owns the board and steps one of its MCUs. The board keeps every model reachable by
/// field once the run has ended, through [`Session::board`].
#[derive(Debug)]
pub struct Session<D: Board + ?Sized = dyn Board> {
    board: Box<D>,
    target: usize,
    until: VirtualNanos,
    // An instant the driver asked the run to pause at, which the models aren't told of.
    pause: Option<VirtualNanos>,
    // What the driver asked the emulator to watch, each with how many ask for it, and the changes the
    // emulator hasn't made yet: a watch to start, or one to stop.
    watches: BTreeMap<Watch, u32>,
    unsettled: Vec<(Watch, bool)>,
    // The addresses a register spans whose next access by the core fails, and whether the access just
    // served was one.
    failing: Vec<(u64, u64)>,
    failed_access: bool,
    // Whether the access just served was a debugger's that nothing answers.
    refused_access: bool,
    report: Report,
    // A settle inside an access cannot answer with its error, so it is kept for the driver.
    failed: Option<Error>,
    // Whether a reset, the core's own or a model's, was asked for inside the call the run stands in,
    // which the emulator carries out once the call returns.
    resetting: bool,
}

// What the bus refused or answered from a register file alone, kept for the run's report.
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(bound(deserialize = "'de: 'static"))]
struct Report {
    unmapped: BTreeMap<u64, u64>,
    unrouted: BTreeSet<(&'static str, u16)>,
    refused: BTreeMap<u64, Fault>,
    unimplemented: BTreeMap<u64, Unimplemented>,
    undefined: BTreeMap<u64, Undefined>,
    resets: Vec<Reset>,
    told: BTreeMap<(&'static str, &'static str, &'static str), Told>,
    // The lines the models hold high, which a pulse leaves as they are and a restart lowers.
    raised: BTreeSet<u16>,
}

/// Keeps the first telling of one condition: same instance, register and reason, by whichever came
/// first, the way [`answered_from_a_file_alone`] keeps the first program counter for one address.
fn note_told(report: &mut Report, told: impl Iterator<Item = Told>) {
    for told in told {
        report
            .told
            .entry((told.instance, told.register, told.reason))
            .or_insert(told);
    }
}

/// One restart the target took, in the order it took it.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(bound(deserialize = "'de: 'static"))]
pub struct Reset {
    /// The virtual clock at the restart.
    pub at: VirtualNanos,
    /// The emulator's instruction count at the restart.
    pub instruction_count: u64,
    /// The core's own request, or the instance that asked for one.
    pub cause: ResetCause,
}

/// An access to a register the part carries and no model implements, which the bus served from the
/// register file.
///
/// The instance is one the part carries and has no model for, so a read answers the register's
/// value and a write lands in it and nothing reacts. That is not a fault and the firmware is not
/// told: it is a gap between what the part describes and what it models, and a firmware that waits
/// on something only a model would do waits forever. A write is the sharper half: a firmware writes
/// to hardware to make something happen, and here nothing did.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Unimplemented {
    /// The instance, as the part names it.
    pub instance: &'static str,
    /// The register covering the address, as the part names it.
    pub register: &'static str,
    /// The program counter of the first access here.
    pub program_counter: u64,
    /// How many reads were answered from the register file.
    pub reads: u64,
    /// How many writes landed in it.
    pub writes: u64,
    /// What the last of those writes carried, so the report reads on its own without the bus.
    pub written: Option<u64>,
}

/// An access the firmware made to space in an instance's block that no register covers, on a part
/// whose description says such space reads zero and takes no write.
///
/// That's the part answering as the silicon does, so it isn't a gap and nothing is refused. It's named
/// per address all the same, because a register the vendor's file leaves out answers the same way, and
/// a firmware that reaches one reads zero where the part would have answered.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Undefined {
    /// The instance whose block holds the address, as the part names it.
    pub instance: &'static str,
    /// The program counter of the first access here.
    pub program_counter: u64,
    /// How many reads answered zero.
    pub reads: u64,
    /// How many writes landed nowhere.
    pub writes: u64,
}

/// An address the bus refused because no register covers it, or the access runs past the register
/// that does, and where the firmware was when it first asked. It prints in hex, which is how a person
/// reads an address: `unmapped at=0x40000100 pc=0x08000098` as a line, and the same two numbers as a
/// struct for `{:?}`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Unmapped {
    /// The address, which no model claims.
    pub address: u64,
    /// The program counter of the first access to it.
    pub program_counter: u64,
}

impl fmt::Display for Unmapped {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "unmapped at={:#010x} pc={:#010x}",
            self.address, self.program_counter
        )
    }
}

impl fmt::Debug for Unmapped {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("Unmapped")
            .field("address", &format_args!("{:#010x}", self.address))
            .field(
                "program_counter",
                &format_args!("{:#010x}", self.program_counter),
            )
            .finish()
    }
}

/// A session keeps its board and what it recorded. The target and which slots have a model are
/// what the board it's built with decides, and a session that's failed has nothing worth
/// keeping.
impl<D: Board + ?Sized> Store for Session<D> {
    fn store(&self, to: &mut Writer) -> Result<(), snapshot::Error> {
        let Self {
            board,
            target: _,
            until,
            pause,
            watches,
            unsettled: _,
            failing,
            failed_access: _,
            refused_access: _,
            report,
            failed,
            resetting,
        } = self;
        if failed.is_some() {
            return Err(snapshot::Error::Failed);
        }
        if *resetting {
            return Err(snapshot::Error::Resetting);
        }
        to.put(until)?;
        to.put(pause)?;
        to.put(watches)?;
        to.put(failing)?;
        to.put(report)?;
        board.store(to)
    }

    fn load(&mut self, from: &mut Reader) -> Result<(), snapshot::Error> {
        let Self {
            board,
            target: _,
            until,
            pause,
            watches,
            unsettled,
            failing,
            failed_access: _,
            refused_access: _,
            report,
            failed: _,
            resetting,
        } = self;
        // A session is kept only with no reset asked for, so none is once it's loaded.
        *resetting = false;
        *until = from.take()?;
        *pause = from.take()?;
        *watches = from.take()?;
        // The emulator starts from its own state, which keeps no watch, so every one starts again.
        *unsettled = watches.keys().map(|&watch| (watch, true)).collect();
        *failing = from.take()?;
        *report = from.take()?;
        board.load(from)
    }
}

impl<D: Board + ?Sized> Session<D> {
    /// A session on `board`'s target at `target`, asked to reach `until`.
    ///
    /// The board's targets are its MCUs in its own order, and a board with one has it at zero. A board
    /// whose edge declares what it doesn't carry, a field its types don't or a chip with no place, and
    /// a target whose spec aliases or boots from a region its MCU's memory map does not have, are
    /// refused here, before the core is made.
    pub fn new(mut board: Box<D>, target: usize, until: VirtualNanos) -> Result<Self, Error> {
        if let Some(edge) = board.edge() {
            edge.check()?;
        }
        let machine = board.name();
        let count = board.target_names().len();
        let (name, aliases, boot) = {
            let targets = board.targets();
            let chosen = targets.get(target).ok_or(Error::NoTarget {
                machine,
                target,
                count,
            })?;
            (
                chosen.name,
                chosen.core.aliases.clone(),
                chosen.core.boot.region,
            )
        };
        {
            let mcu = board.mcu(target).ok_or(Error::NoTarget {
                machine,
                target,
                count,
            })?;
            mcu.check()?;
            // The emulator would find nothing to put at the second address and start without it. Refuse it here.
            let regions = mcu.bus().memory();
            if let Some(alias) = aliases
                .iter()
                .find(|alias| regions.iter().all(|region| region.name != alias.region))
            {
                return Err(Error::NoRegion {
                    machine,
                    target: name,
                    region: alias.region,
                    at: alias.at,
                    regions: regions.iter().map(|region| region.name).collect(),
                });
            }
            if let Some(boot) = boot
                && regions.iter().all(|region| region.name != boot)
            {
                return Err(Error::NoBootRegion {
                    machine,
                    target: name,
                    region: boot,
                    regions: regions.iter().map(|region| region.name).collect(),
                });
            }
            // The core maps every region and alias, and where two meet it would answer from
            // whichever it mapped last, and an alias over an instance or a window would hide it.
            // Refuse it here too.
            let size = |name: &str| {
                regions
                    .iter()
                    .find(|region| region.name == name)
                    .map_or(0, |region| region.size)
            };
            let extent = |name, base, size| Extent { name, base, size };
            let spans: Vec<Extent> =
                regions
                    .iter()
                    .map(|region| extent(region.name, region.base, region.size))
                    .chain(mcu.bus().peripherals().map(|peripheral| {
                        extent(peripheral.name, peripheral.base, peripheral.size)
                    }))
                    .collect();
            let placed: Vec<Extent> = aliases
                .iter()
                .map(|alias| extent(alias.region, alias.at, size(alias.region)))
                .collect();
            for (index, (alias, at)) in aliases.iter().zip(&placed).enumerate() {
                let others = placed
                    .iter()
                    .enumerate()
                    .filter(|&(other, _)| other != index)
                    .map(|(_, span)| span);
                if let Some(clash) = spans.iter().chain(others).find(|span| span.overlaps(at)) {
                    return Err(Error::OverlappingAlias {
                        machine,
                        target: name,
                        region: alias.region,
                        at: alias.at,
                        other: clash.name,
                    });
                }
            }
        }
        Ok(Self {
            board,
            target,
            until,
            pause: None,
            watches: BTreeMap::new(),
            unsettled: Vec::new(),
            failing: Vec::new(),
            failed_access: false,
            refused_access: false,
            report: Report::default(),
            failed: None,
            resetting: false,
        })
    }

    /// The index of the target this session steps.
    #[must_use]
    pub fn target(&self) -> usize {
        self.target
    }

    /// The instant the run is asked to reach.
    #[must_use]
    pub fn until(&self) -> VirtualNanos {
        self.until
    }

    /// Asks the run to reach `until`: the next answer names the earlier of the next event and it.
    pub fn run_until(&mut self, until: VirtualNanos) {
        self.until = until;
    }

    /// Watches `watch` in the emulator from the next time the run hands it back, for as long as
    /// one more [`Session::unwatch`] of it hasn't come. What's watched is kept in a snapshot.
    pub fn watch(&mut self, watch: Watch) {
        let asked = self.watches.entry(watch).or_insert(0);
        *asked += 1;
        if *asked == 1 {
            self.unsettled.push((watch, true));
        }
    }

    /// Stops watching `watch` once every [`Session::watch`] of it has had its `unwatch`.
    pub fn unwatch(&mut self, watch: Watch) {
        let Some(asked) = self.watches.get_mut(&watch) else {
            return;
        };
        *asked -= 1;
        if *asked == 0 {
            self.watches.remove(&watch);
            self.unsettled.push((watch, false));
        }
    }

    /// Starts and stops in the emulator what [`Session::watch`] and [`Session::unwatch`] asked for
    /// since the last call. A watch the emulator can't start fails the run.
    pub fn settle_watches(&mut self, host: &mut dyn Host) {
        for (watch, start) in std::mem::take(&mut self.unsettled) {
            if !start {
                host.unwatch(watch);
            } else if !host.watch(watch) {
                self.failed.get_or_insert(Error::Unwatched(watch));
            }
        }
    }

    /// Fails the run, since the core can't start `watch`, which it answered only once the stretch
    /// it was asked in had ended.
    pub(crate) fn refuse_watch(&mut self, watch: Watch) {
        self.failed.get_or_insert(Error::Unwatched(watch));
    }

    /// Fails the core's next access to the addresses from `from` up to `to`, which the core takes as
    /// a bus error, and which no model sees.
    pub fn fail_next(&mut self, from: u64, to: u64) {
        self.failing.push((from, to));
    }

    /// Whether the access just served was one [`Session::fail_next`] failed.
    pub fn take_failure(&mut self) -> bool {
        std::mem::take(&mut self.failed_access)
    }

    /// Whether the access just served was a debugger's that nothing answers.
    pub(crate) fn take_refused(&mut self) -> bool {
        std::mem::take(&mut self.refused_access)
    }

    /// What a debugger's read of `width` at `address` answers at `at`, with nothing in the run
    /// changed and nothing told of it, or `None` where no register covers it. A shut gate doesn't
    /// stop it, since the register file holds its values all the same.
    pub(crate) fn peek(&mut self, at: VirtualNanos, address: u64, width: Width) -> Option<u64> {
        self.mcu().peek(at, address, width).ok()
    }

    /// The instant the driver asked the run to pause at, if one's still to come.
    #[must_use]
    pub fn pause(&self) -> Option<VirtualNanos> {
        self.pause
    }

    /// Asks the run to pause at `at` for the driver alone, or at no instant with `None`. The emulator
    /// stops there, between two instructions, and nothing of the models' is due, so they aren't told:
    /// the run is the one it would have been without the pause. Once it's reached, the driver asks for
    /// the next one or the run goes on to its next event.
    pub fn pause_at(&mut self, at: Option<VirtualNanos>) {
        self.pause = at;
    }

    /// Whether `stop` is the instant the run was asked to reach: at or past it, and not a reset.
    #[must_use]
    pub fn reached(&self, stop: &Stop) -> bool {
        stop.reason != StopReason::Reset && stop.at >= self.until
    }

    /// The next instant the models need control at: the earlier of the next event and where the run
    /// is asked to reach.
    pub fn next_deadline(&mut self) -> VirtualNanos {
        let horizon = horizon(self.until, self.pause);
        deadline_before(self.mcu().next_event(), horizon)
    }

    /// One access at its instant: the value read, or the value written back, and the next instant the
    /// models need control at.
    ///
    /// The models may drive a request or ask for an access of their own. Both happen at the instant
    /// of the access that caused them, through `host`, before the core has its answer, and so does
    /// every line they set. An access [`Session::fail_next`] fails reaches no model. It reads 0, and a
    /// write answers what the core wrote, since nothing took it. A debugger's access, one with no
    /// program counter, that a shut gate refuses reads 0 and is dropped, as the firmware's is, and
    /// isn't listed by [`Session::refused`].
    pub fn access(&mut self, access: Access, host: &mut dyn Host) -> (u64, VirtualNanos) {
        let failing = access
            .program_counter
            .is_some()
            .then(|| {
                self.failing
                    .iter()
                    .position(|&(from, to)| (from..to).contains(&access.address))
            })
            .flatten();
        if let Some(index) = failing {
            self.failing.remove(index);
            self.failed_access = true;
            let answer = match access.kind {
                AccessKind::Read => 0,
                AccessKind::Write => access.value,
            };
            return (answer, self.next_deadline());
        }
        let Self {
            board,
            target,
            until,
            pause,
            report,
            failed,
            resetting,
            refused_access,
            ..
        } = self;
        let until = horizon(*until, *pause);
        let mcu = board.mcu(*target).expect("checked at construction");
        let served = match access.kind {
            AccessKind::Read => mcu.read(access.virtual_time, access.address, access.width),
            AccessKind::Write => mcu
                .write(
                    access.virtual_time,
                    access.address,
                    access.width,
                    access.value,
                )
                .map(|()| access.value),
        };
        if let Some(address) = mcu.bus_mut().take_undefined() {
            answered_undefined(report, mcu.bus(), address, &access);
        }
        if let Some((slot, row)) = mcu.bus_mut().take_unbacked() {
            answered_from_a_file_alone(report, mcu.bus(), slot, row, &access);
        }
        let value = match served {
            Ok(value) => value,
            Err(fault) => {
                // A debugger's access is the person's, not a gap in the run. One a shut gate
                // refuses is dropped and answers zero, as the firmware's is.
                if access.program_counter.is_some() {
                    refuse(report, access.address, access.program_counter, fault);
                } else if !fault.is_shut_gate() {
                    *refused_access = true;
                }
                0
            }
        };
        match mcu.settle(host) {
            Ok(refusals) => note(report, refusals),
            Err(error) => {
                failed.get_or_insert(error);
            }
        }
        note_told(report, mcu.drain_told().into_iter());
        // device.rs's request_reset promises no core instruction between the ask and the reset.
        let restarted = take_model_reset(
            report,
            mcu,
            access.virtual_time,
            access.instruction_count,
            host,
        );
        *resetting = restarted;
        if !restarted {
            apply(mcu, host, report);
        }
        (value, deadline_before(mcu.next_event(), until))
    }

    /// The emulator stopped: the events due fire in order, every model gets a look outside, the part
    /// settles, and a reset restarts it. Answers the next instant the models need control at. A stop at
    /// the driver's pause with nothing of the models' due and short of where the run is asked to reach
    /// is the driver's alone, and the models aren't told of it.
    pub fn stop(&mut self, stop: Stop, host: &mut dyn Host) -> VirtualNanos {
        if self.pause.is_some_and(|pause| stop.at >= pause) {
            self.pause = None;
            let due = self.mcu().next_event().is_some_and(|next| next <= stop.at);
            if !due && stop.reason != StopReason::Reset && stop.at < self.until {
                return self.next_deadline();
            }
        }
        let Self {
            board,
            target,
            until,
            pause,
            report,
            failed,
            resetting,
            ..
        } = self;
        let until = horizon(*until, *pause);
        let mcu = board.mcu(*target).expect("checked at construction");
        mcu.advance_to(stop.at);
        let resets_before = report.resets.len();
        if let Some(error) = report.look_outside(mcu, stop.at, stop.instruction_count, host) {
            failed.get_or_insert(error);
        }
        if stop.reason == StopReason::Reset {
            // The core's own request. A model's reset taken at this same stop is the same restart,
            // with nothing run between them: the part's flags gain the core's cause as well, and the
            // recorded restart keeps the model's. A restart from an earlier stop is not this one, even
            // at the same instant, as with a debugger's reset of a halted core.
            mcu.reset(stop.at, ResetCause::Core);
            report.raised.clear();
            if report.resets.len() == resets_before {
                report.resets.push(Reset {
                    at: stop.at,
                    instruction_count: stop.instruction_count,
                    cause: ResetCause::Core,
                });
            }
        }
        // The emulator carries out a reset taken at this stop, the core's own or a model's, only once
        // the stop is over.
        *resetting = report.resets.len() > resets_before;
        deadline_before(mcu.next_event(), until)
    }

    /// Asks the run to reach `until` from the stop just taken at `at`, and answers the next deadline.
    ///
    /// Every model looks outside again at `at` first, and what that moved is settled and what it raised
    /// routed. So whatever a driver handed the board at the stop, a byte on a port or an input sensed,
    /// reaches the models at that same stop rather than at the next one.
    pub fn resume(
        &mut self,
        until: VirtualNanos,
        at: VirtualNanos,
        instructions: u64,
        host: &mut dyn Host,
    ) -> VirtualNanos {
        self.until = until;
        let until = horizon(until, self.pause);
        let Self {
            board,
            target,
            report,
            failed,
            resetting,
            ..
        } = self;
        let mcu = board.mcu(*target).expect("checked at construction");
        let resets_before = report.resets.len();
        if let Some(error) = report.look_outside(mcu, at, instructions, host) {
            failed.get_or_insert(error);
        }
        *resetting = report.resets.len() > resets_before;
        deadline_before(mcu.next_event(), until)
    }

    /// Raises `line` for an instant, so the core's interrupt controller takes it as pending once. A
    /// line a model holds high is pending already, and stays as it is.
    // ceiling: a controller that latches an edge takes the pulse; upgrade when a family whose
    // controller samples a level runs, with the pulse held until the core takes it
    pub fn pulse(&mut self, line: InterruptLine, host: &mut dyn Host) {
        if !self.report.raised.contains(&line.index()) {
            host.set_interrupt(line, Level::High);
            host.set_interrupt(line, Level::Low);
        }
    }

    /// Restarts the part at `at` as its reset pin driven low from outside it would, after
    /// `instructions`: the part's flags record the pin, the restart is one of
    /// [`Session::resets`], and the emulator restarts the core once the call the run stands in
    /// returns, with no instruction between. Inside a call that's already restarting the part, the
    /// core's own or a model's, it's the same restart, and the flags gain the pin as well.
    pub fn reset_by_pin(&mut self, at: VirtualNanos, instructions: u64, host: &mut dyn Host) {
        let Self {
            board,
            target,
            report,
            resetting,
            ..
        } = self;
        let mcu = board.mcu(*target).expect("checked at construction");
        if *resetting {
            mcu.reset(at, ResetCause::Pin);
            return;
        }
        let reset = Reset {
            at,
            instruction_count: instructions,
            cause: ResetCause::Pin,
        };
        restart(report, mcu, reset, host);
        *resetting = true;
    }

    /// The first error a settle met inside an access or a stop, which the answer could not carry.
    pub fn take_error(&mut self) -> Option<Error> {
        self.failed.take()
    }

    /// The addresses the bus refused because no register covers the address, or the access runs past
    /// the register that does, each with the program counter of its first access, by ascending
    /// address. An entry prints the way `rigless run` prints one.
    #[must_use]
    pub fn unmapped(&self) -> Vec<Unmapped> {
        self.report
            .unmapped
            .iter()
            .map(|(&address, &program_counter)| Unmapped {
                address,
                program_counter,
            })
            .collect()
    }

    /// The accesses the bus refused although it carries the register, and every access a model asked
    /// for that the bus wouldn't make, by ascending address, each with the fault of its first
    /// occurrence.
    ///
    /// This is the gated peripheral: the part declares what has to be true elsewhere for an instance to
    /// answer, and a firmware or a model that reaches one before it is true is refused. It is separate from
    /// [`Session::unmapped`] because the address is modeled and the part said no.
    #[must_use]
    pub fn refused(&self) -> &BTreeMap<u64, Fault> {
        &self.report.refused
    }

    /// The accesses the bus answered from a register file with no model behind it, by ascending address,
    /// each with the instance, the register, and the program counter of its first occurrence.
    ///
    /// An entry whose `writes` is not zero is the one to read first. A read of a reset value is
    /// often exactly right. A write is a firmware asking the part to do something, and here nothing
    /// did.
    ///
    /// Only the core's own accesses are counted. An access a model asks the runtime to make on its behalf
    /// reaches the bus through the settle rather than through an instruction, and the settle answers with
    /// what it refused and not with what it served, so a controller moving a word into an instance with no
    /// model is not reported here.
    #[must_use]
    pub fn unimplemented(&self) -> &BTreeMap<u64, Unimplemented> {
        &self.report.unimplemented
    }

    /// The accesses the firmware made to undefined space, by ascending address, each with the
    /// instance and the program counter of its first occurrence. They read zero and took no write, as
    /// the part does, so none is a gap. One is worth a look when the manual places a register there
    /// that the vendor's file leaves out.
    #[must_use]
    pub fn undefined(&self) -> &BTreeMap<u64, Undefined> {
        &self.report.undefined
    }

    /// The interrupts the target had no line for, as instance and number, by instance then number.
    #[must_use]
    pub fn unrouted(&self) -> &BTreeSet<(&'static str, u16)> {
        &self.report.unrouted
    }

    /// Every restart the target took, in order, with its instant, its instruction count and its cause.
    /// At each, the target restarted from its reset vector with every peripheral at its reset value,
    /// the board's chips as they stood, and its memory as the firmware left it.
    #[must_use]
    pub fn resets(&self) -> &[Reset] {
        &self.report.resets
    }

    /// What a model told about its own instance without an access to refuse, one entry per distinct
    /// instance, register and reason, each kept from the instant it was first told.
    pub fn told(&self) -> impl Iterator<Item = &Told> {
        self.report.told.values()
    }

    /// The board this session runs on.
    #[must_use]
    pub fn board(&self) -> &D {
        &self.board
    }

    /// The board, whole, to reach a model by field and ask what its boundary recorded.
    ///
    /// The session carries the board's own type, so where the caller built a concrete board this is a
    /// `&mut` to that type, and every model on it is a field path from here. Nothing is downcast.
    pub fn board_mut(&mut self) -> &mut D {
        &mut self.board
    }

    /// The target's MCU.
    pub fn mcu(&mut self) -> &mut dyn Mcu {
        self.board
            .mcu(self.target)
            .expect("checked at construction")
    }

    /// A run put back went a new way at `at`, which each host on the board's wires hears.
    pub(crate) fn new_way(&mut self, at: VirtualNanos) {
        for wire in self.board.hosts() {
            if let Some(host) = self.board.host_mut(wire.name) {
                host.forget_after(at);
            }
        }
    }
}

// An access the bus served from an instance with no model behind it. Remembered per address, with the
// program counter of the first one, so a polling loop is one line rather than thousands. A debugger's
// access isn't the firmware's, so it isn't remembered.
fn answered_from_a_file_alone(
    report: &mut Report,
    bus: &Bus,
    slot: usize,
    row: usize,
    access: &Access,
) {
    let Some(program_counter) = access.program_counter else {
        return;
    };
    let peripheral = bus.slots()[slot].peripheral();
    let entry = report
        .unimplemented
        .entry(access.address)
        .or_insert_with(|| Unimplemented {
            instance: peripheral.name,
            register: peripheral.registers[row].name,
            program_counter,
            reads: 0,
            writes: 0,
            written: None,
        });
    match access.kind {
        AccessKind::Read => entry.reads += 1,
        AccessKind::Write => {
            entry.writes += 1;
            entry.written = Some(access.value);
        }
    }
}

// An access the bus answered from undefined space. Remembered per address with the program counter of
// the first one, as an answer from a file alone is. A debugger's access isn't the firmware's.
fn answered_undefined(report: &mut Report, bus: &Bus, address: u64, access: &Access) {
    let (Some(slot), Some(program_counter)) = (bus.slot_at(address), access.program_counter) else {
        return;
    };
    let entry = report
        .undefined
        .entry(address)
        .or_insert_with(|| Undefined {
            instance: bus.slots()[slot].peripheral().name,
            program_counter,
            reads: 0,
            writes: 0,
        });
    match access.kind {
        AccessKind::Read => entry.reads += 1,
        AccessKind::Write => entry.writes += 1,
    }
}

// An access the bus refused: an address no register covers is a gap in the model, and one a gate shut is
// the part saying no. They are counted apart because the first is work to do and the second is the model.
fn refuse(report: &mut Report, address: u64, program_counter: Option<u64>, fault: Fault) {
    match program_counter {
        Some(program_counter) if !fault.is_shut_gate() => {
            report.unmapped.entry(address).or_insert(program_counter);
        }
        // A model's access carries no program counter, so it can't be read as a gap in the map.
        _ => {
            report.refused.entry(address).or_insert(fault);
        }
    }
}

impl Report {
    /// Lets every model of `mcu` look outside at `at`, settles what that moved, routes what it raised,
    /// and answers the error a settle met, if one did.
    fn look_outside(
        &mut self,
        mcu: &mut dyn Mcu,
        at: VirtualNanos,
        instructions: u64,
        host: &mut dyn Host,
    ) -> Option<Error> {
        mcu.poll(at);
        let error = match mcu.settle(host) {
            Ok(refusals) => {
                note(self, refusals);
                None
            }
            Err(error) => Some(error),
        };
        note_told(self, mcu.drain_told().into_iter());
        // device.rs's request_reset promises no core instruction between the ask and the reset.
        if !take_model_reset(self, mcu, at, instructions, host) {
            apply(mcu, host, self);
        }
        error
    }
}

// Whether a model's reset was taken, which tells the caller to leave this settle's interrupts undelivered.
fn take_model_reset(
    report: &mut Report,
    mcu: &mut dyn Mcu,
    at: VirtualNanos,
    instructions: u64,
    host: &mut dyn Host,
) -> bool {
    let Some(cause) = mcu.take_reset_request() else {
        return false;
    };
    let reset = Reset {
        at,
        instruction_count: instructions,
        cause,
    };
    restart(report, mcu, reset, host);
    true
}

// Restarts the part for `reset`'s cause and asks the emulator to restart the core, which lowers every
// line.
fn restart(report: &mut Report, mcu: &mut dyn Mcu, reset: Reset, host: &mut dyn Host) {
    mcu.reset(reset.at, reset.cause);
    report.resets.push(reset);
    report.raised.clear();
    host.request_reset();
}

fn note(report: &mut Report, refusals: Vec<Refused>) {
    for refusal in refusals {
        refuse(report, refusal.address, None, refusal.fault);
    }
}

/// The earlier of where the run is asked to reach and the driver's pause.
fn horizon(until: VirtualNanos, pause: Option<VirtualNanos>) -> VirtualNanos {
    pause.map_or(until, |pause| pause.min(until))
}

fn deadline_before(next_event: Option<VirtualNanos>, until: VirtualNanos) -> VirtualNanos {
    next_event.map_or(until, |at| at.min(until))
}

/// Drives the core's lines from what `mcu` raised: each interrupt on the line of its own number,
/// which a part with an interrupt controller has handed that controller already, and every level
/// the controller drove.
fn apply(mcu: &mut dyn Mcu, host: &mut dyn Host, report: &mut Report) {
    let mut drive = |line: InterruptLine, level: Level| {
        if level == Level::High {
            report.raised.insert(line.index());
        } else {
            report.raised.remove(&line.index());
        }
        host.set_interrupt(line, level);
    };
    let mut unrouted = Vec::new();
    for change in mcu.drain_interrupts() {
        match InterruptLine::new(change.interrupt) {
            Some(line) => drive(line, change.level),
            None => unrouted.push((change.instance, change.interrupt)),
        }
    }
    for LineChange { line, level } in mcu.drain_lines() {
        drive(line, level);
    }
    report.unrouted.extend(unrouted);
}