rigless 0.2.2

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
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The true random number generator of RM0433 rev 9 chapter 34, written against the `rng` layout.
//! Every citation is RM0433 rev 9 unless it names another document.
//!
//! **The words are not random, by design.** The hardware draws them from an analog noise source. A model
//! that did the same would make every run differ, which a rig that replays a flight can't have. This
//! one deals from a fixed-seed generator, SplitMix64, kept in the model's state, so a run is a function
//! of its inputs and a snapshot taken mid-stream goes on with the same words. [`Rng::seeded`] changes the
//! seed and [`SEED`] is the one [`Rng::new`] uses. The firmware sees 32 bits that never read zero, since
//! section 34.7.3 says a zero means a seed error, and that differ from word to word. It has no way to
//! tell them from noise.
//!
//! **What the model handles and what falls to the register file.** The model has an arm for every register
//! of the chapter. It answers every read of `RNG_DR` itself, since a read takes the word. The file keeps
//! `RNG_CR` as written, applies the access types, and clears `SEIS` and `CEIS` on a written zero once the
//! part has corrected them. The model keeps `RNG_SR` current with its flags, and holds the buffers, the
//! timing, the interrupt and the error flags.
//!
//! **The generation, from sections 34.3.3 to 34.5.** The output buffer holds four words, the conditioning
//! output register four more, and `DRDY` stands while the buffer has a word. Four words come out of a
//! conditioning round, and a round runs whenever the conditioning register is empty. After the first
//! round following the enable, the buffer is full and the next round fills the register. When the fourth
//! word is read the register moves into the buffer, so `DRDY` stays set and eight words can be read
//! without a pause, as 34.3.5 says. The times are the manual's:
//!
//! - A round takes the larger of 213 AHB cycles and `16 x fAHB / fRNG` of them, section 34.5.
//! - The first words after the enable take `128` RNG clock cycles and `426` AHB cycles when `fAHB` is below
//!   `213 x fRNG / 64`, and `192` and `213` otherwise, section 34.5. The page prints the counts without
//!   exponents, and the model reads them as plain numbers because only then do the two cases meet. At
//!   `fAHB = 213 x fRNG / 64` their times are equal, which needs the AHB counts to differ by 213, and
//!   `426 - 213` does.
//! - Each time is rounded up to a whole nanosecond.
//!
//! The manual says 213 in sections 34.3.3 and 34.5 and 216 in sections 34.3.8 and 34.7.3, for the same
//! round. The model takes 213, the figure of section 34.5, the one place the manual gives the rate as a
//! formula. The difference is three AHB cycles, 12.5 ns at 240 MHz.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | `RNG_CR`, `RNG_SR` and `RNG_DR` all zero | 34.7.1 to 34.7.3 | `every_register_resets_to_the_manuals_value` |
//! | `RNG_CR` `RNGEN` | Enables the generator. The first words come the manual's time after the enable. Disabling pauses the work, and enabling again resumes it where it stopped | 34.3.4, 34.3.8, 34.5, 34.7.1 | `the_first_word_comes_after_the_first_sample_time`, `a_slow_ahb_clock_takes_the_longer_first_sample`, `disabling_during_the_first_sample_resumes_where_it_stopped`, `a_stale_event_is_ignored` |
//! | `RNG_CR` `IE` | Enables the interrupt: it stands while the generator is enabled and any of `DRDY`, `SEIS` or `CEIS` is set | 34.4, Table 281, 34.7.1 | `the_interrupt_follows_drdy_while_enabled_and_unmasked`, `the_interrupt_is_line_80` |
//! | `RNG_CR` `CED` | Turns the clock error detection off. It can't be changed while the generator is enabled | 34.3.6, 34.7.1 | `ced_cannot_change_while_the_generator_is_enabled` |
//! | `RNG_SR` `DRDY` | Set while the buffer holds a word, and clear once the last is read until a round fills it again | 34.3.3, 34.7.2 | `each_read_takes_a_new_word_and_eight_words_drain_the_pipeline`, `drdy_returns_once_a_round_refills_the_buffer` |
//! | `RNG_DR` | Reads the next word and removes it. It reads zero when `DRDY` is clear. Read-only. A debugger's look takes nothing | 34.3.5, 34.7.3 | `each_read_takes_a_new_word_and_eight_words_drain_the_pipeline`, `dr_reads_zero_while_no_word_is_ready`, `a_peek_shows_the_next_word_and_takes_nothing` |
//! | A read the manual forbids | A read of `RNG_DR` with `DRDY` clear or an error flag set is told to the session once, and still answers as above | 34.3.5, 34.7.3 | `a_read_of_dr_the_manual_forbids_is_told_once` |
//! | The conditioning register | Four words held after the buffer is full. They move into the buffer when the last word is read, and `DRDY` stays set | 34.3.3, 34.3.5 | `each_read_takes_a_new_word_and_eight_words_drain_the_pipeline` |
//! | A round | Takes the larger of 213 AHB cycles and `16 x fAHB / fRNG` of them | 34.3.3, 34.5 | `drdy_returns_once_a_round_refills_the_buffer`, `a_slow_kernel_clock_stretches_the_round` |
//! | Disabled | The buffer and the register keep their words and can be read. No new round completes until the generator is enabled again, and enabling it then starts a fresh sample and round, not the one that was paused | 34.3.8 | `a_disabled_generator_keeps_its_words_and_makes_no_more` |
//! | `RNG_SR` `CECS`, `CEIS` | With `CED` clear and the kernel clock not above a thirty-second of the AHB clock, both are set while the generator runs. `CECS` clears when the clocks are right again, and `CEIS` stays until a written zero clears it. `CEIS` sets only at the transition | 34.3.6, 34.3.7, 34.7.2 | `a_slow_kernel_clock_raises_the_clock_error`, `ced_cannot_change_while_the_generator_is_enabled`, `a_change_of_the_ahb_clock_rescales_the_work_and_the_clock_check` |
//! | `RNG_SR` `SEIS`, `SECS` | A noise source error sets both, stops generation, and keeps the buffered words readable. `SEIS` clears by a written zero and a written one does nothing. `SECS` clears when the source is healthy again, and generation resumes. [`Rng::seed_error`] and [`Rng::seed_recovered`] are the inputs | 34.3.7, 34.7.2 | `a_seed_error_sets_both_flags_stops_generation_and_recovers` |
//! | The interrupt | One interrupt, position zero of the instance's table, which is line 80. It's a level, set by `DRDY` and the two interrupt flags, and lowered when the last word is read or a flag is written zero | 34.4, Table 281, Table 147 | `the_interrupt_follows_drdy_while_enabled_and_unmasked`, `the_interrupt_is_line_80` |
//! | A clock change | A change of the AHB rate keeps the fraction of the running work done, and re-evaluates the clock check | 34.5 | `a_change_of_the_ahb_clock_rescales_the_work_and_the_clock_check` |
//! | Events | An event the model doesn't expect is ignored, since there's no cancel | 34.5 | `a_stale_event_is_ignored` |
//! | Reset | The buffers, the work, the flags and the interrupt go back. The generator's stream goes on | 34.7 | `a_reset_empties_the_pipeline_and_goes_on_with_the_stream` |
//! | Determinism | The same seed gives the same words, another gives others, and a snapshot goes on from where it was | n/a | `the_stream_is_the_seeds_and_nothing_else`, `a_snapshot_goes_on_with_the_same_stream`, `no_word_is_zero`, `two_runs_of_one_script_are_the_same_run` |
//! | The driver | ArduPilot's `__late_init` enable and its blocking and nonblocking reads, register access by register access | 34.3.5 | `ardupilots_reads_get_their_words_within_their_timeouts` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | The analog noise source, the post-processing and the conditioning function | 34.3.3 | The words come from the seeded generator, deterministic by design, above |
//! | The health tests, the repetition count and the "too slow" check, as the manual describes them | 34.3.3, 34.3.7 | There's no sample stream to test. The flags they set are the model's inputs, [`Rng::seed_error`] for a noise source fault. The clock check is modeled from the two rates, the only part of it that has inputs |
//! | The recovery sequence's reads: clearing `SEIS`, reading twelve words, and finding `SEIS` still clear | 34.3.7 | Software's steps. The model keeps the buffered words readable after an error, and clears `SECS` when the source recovers |
//! | The kernel clock's source and rate | 34.3.6, 8.7.21 | The kernel clock is chosen by `RCC_D2CCIP2R.RNGSEL`, whose page names `hsi8_ck` as its default, a clock the manual has no other mention of. ChibiOS selects `HSI48`, `STM32_RNGSEL_HSI48_CK`, and section 34.6.2 validates 48 MHz, so [`KERNEL_HZ`] is that. A part has one clock to wire per instance, and it's the AHB's. The kernel rate is the one the model is built with |
//! | A different RNG clock from `RNGSEL` | 34.3.6 | Follows from the row above |
//! | A write not 32 bits wide corrupting the register | 34.2 | "Warning! any write not equal to 32 bits might corrupt the register content." The register file lands a narrow store in its lane |
//! | A bus error on a narrow write | 34.2 | "Else for write accesses an AHB bus error is generated". The model sees no width |
//! | The NIST configuration bits, `RNG_HTCR` and `RNG_NSCR` of Table 282 | 34.6.2 | Not registers of this layout. The chapter has three |
//! | ES0392 rev 15 | ES0392 rev 15 | No item touches this instance. The whole errata chapter was read for it |
//!
//! **Where this model departs from the vendor's file.** `RNG_SR`'s `SEIS` and `CEIS` are plain read-write
//! in the file where section 34.7.2 says "It is cleared by writing 0. Writing 1 has no effect", so
//! [`SEIS`] and [`CEIS`] restate them as clear-on-written-zero.
//!
//! **Determinism.** The model reads no clock. Its one source of numbers is the seeded generator, which
//! the state carries. `two_runs_of_one_script_are_the_same_run` drives two fresh instances through the same
//! accesses and checks every word, flag and interrupt agree.

use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::soc::stm32h743::generated::rng::{Register, cr, sr};
use crate::soc::{Access, FieldSpec, Layout, WriteModify};
use crate::time::{EventId, VirtualNanos};

// The RNG interrupt, position zero of its instance's table. Table 147 gives it line 80.
const IT: usize = 0;

/// The rate of the RNG kernel clock the firmware selects, 48 MHz, which is `HSI48`: ChibiOS's
/// `STM32_RNGSEL_HSI48_CK`, and the clock section 34.6.2 validates the generator at. The page of
/// section 8.7.21 that gives `RNGSEL`'s reset names its default `hsi8_ck`, a clock RM0433 has no other
/// mention of.
pub const KERNEL_HZ: u64 = 48_000_000;

/// The AHB clock of the AHB2 bus the RNG sits on after reset, 64 MHz, which is `HSI` undivided,
/// RM0433 rev 9 sections 8.7.2 and 8.7.8. A part wires the clock to the RCC's own output and passes the
/// rate it sees before the firmware touches the clock tree.
pub const RESET_AHB_HZ: u64 = 64_000_000;

/// The seed [`Rng::new`] deals from.
pub const SEED: u64 = 0x0123_4567_89AB_CDEF;

const NANOS_PER_SECOND: u128 = 1_000_000_000;
// The floor of a round, in AHB cycles, section 34.5.
const ROUND_FLOOR_CYCLES: u128 = 213;
const ROUND_RNG_CYCLES: u128 = 16;
// The first sample's RNG cycles and AHB cycles below the threshold, section 34.5.
const FIRST_SLOW: (u128, u128) = (128, 426);
// The first sample's RNG cycles and AHB cycles at or above the threshold, section 34.5.
const FIRST_FAST: (u128, u128) = (192, 213);
const THRESHOLD: (u128, u128) = (213, 64);
// A clock error is a kernel clock not above `fAHB / 32`, section 34.3.6.
const CLOCK_RATIO: u128 = 32;
const WORDS: usize = 4;

/// `SEIS`, section 34.7.2: cleared by writing zero, and a written one has no effect.
pub static SEIS: FieldSpec = FieldSpec {
    access: Access::ReadWrite,
    write: WriteModify::ZeroToClear,
    ..*sr::SEIS
};

/// `CEIS`, section 34.7.2: the same rule as [`SEIS`].
pub static CEIS: FieldSpec = FieldSpec {
    access: Access::ReadWrite,
    write: WriteModify::ZeroToClear,
    ..*sr::CEIS
};

const FORBIDDEN_READ: &str = "RNG_DR read with DRDY clear or an error flag set: it must not be read, RM0433 rev 9 section 34.3.5";

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Work {
    // The first words after the enable, section 34.5.
    First,
    // A conditioning round, section 34.5.
    Round,
    // Enabling again after the first words: the noise source was off, so it samples anew and then
    // runs a round, 34.3.8.
    Resample,
}

#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Job {
    work: Work,
    left: u64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Stage {
    Starting,
    Steady,
}

#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Faults {
    seed: bool,
    clock: bool,
}

/// The true random number generator.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Rng {
    ahb_hz: u64,
    kernel_hz: u64,
    stream: u64,
    enabled: bool,
    ced: bool,
    stage: Stage,
    faults: Faults,
    buffer: [u32; WORDS],
    waiting: usize,
    register: Option<[u32; WORDS]>,
    job: Option<Job>,
    run: Option<(EventId, VirtualNanos)>,
    raised: Level,
    told: bool,
}

impl Rng {
    /// A generator on an AHB clock of `ahb_hz` and an RNG kernel clock of `kernel_hz`, dealing from
    /// [`SEED`]. A part wires the AHB rate to the RCC, so `ahb_hz` is where it starts, and
    /// [`KERNEL_HZ`] is the kernel's.
    #[must_use]
    pub const fn new(ahb_hz: u64, kernel_hz: u64) -> Self {
        Self::seeded(ahb_hz, kernel_hz, SEED)
    }

    /// A generator that deals from `seed`.
    #[must_use]
    pub const fn seeded(ahb_hz: u64, kernel_hz: u64, seed: u64) -> Self {
        Self {
            ahb_hz,
            kernel_hz,
            stream: seed,
            enabled: false,
            ced: false,
            stage: Stage::Starting,
            faults: Faults {
                seed: false,
                clock: false,
            },
            buffer: [0; WORDS],
            waiting: 0,
            register: None,
            job: None,
            run: None,
            raised: Level::Low,
            told: false,
        }
    }

    /// A fault of the noise source, 34.3.7: `SEIS` and `SECS` are set and generation stops. The words
    /// already buffered stay readable. It stands until [`Rng::seed_recovered`].
    pub fn seed_error(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        if self.faults.seed {
            return;
        }
        self.pause(context.now());
        self.faults.seed = true;
        registers.set(Register::Sr, sr::SEIS.set(registers.get(Register::Sr), 1));
        self.sync(registers, context);
    }

    /// The noise source healthy again, 34.3.7: `SECS` clears, `SEIS` stays until software clears it, and
    /// generation resumes.
    pub fn seed_recovered(
        &mut self,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        if !self.faults.seed {
            return;
        }
        self.faults.seed = false;
        self.resume(context);
        self.sync(registers, context);
    }

    // The next word of the stream: 32 bits of SplitMix64, never zero, since a zero in `RNG_DR` means
    // a seed error, section 34.7.3.
    fn deal(&mut self) -> u32 {
        loop {
            self.stream = self.stream.wrapping_add(0x9E37_79B9_7F4A_7C15);
            let mut mixed = self.stream;
            mixed = (mixed ^ (mixed >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
            mixed = (mixed ^ (mixed >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
            mixed ^= mixed >> 31;
            let word = u32::try_from(mixed >> 32).expect("the top half is 32 bits");
            if word != 0 {
                return word;
            }
        }
    }

    fn deal_round(&mut self) -> [u32; WORDS] {
        std::array::from_fn(|_| self.deal())
    }

    fn clocks_run(&self) -> bool {
        self.ahb_hz > 0 && self.kernel_hz > 0
    }

    fn duration(&self, work: Work) -> u64 {
        if !self.clocks_run() {
            return 0;
        }
        let (ahb, kernel) = (u128::from(self.ahb_hz), u128::from(self.kernel_hz));
        let nanos = match work {
            Work::Round | Work::Resample => {
                let cycles = (ROUND_RNG_CYCLES * ahb)
                    .div_ceil(kernel)
                    .max(ROUND_FLOOR_CYCLES);
                let sampling = if work == Work::Resample {
                    ROUND_RNG_CYCLES
                } else {
                    0
                };
                ((sampling * ahb + cycles * kernel) * NANOS_PER_SECOND).div_ceil(kernel * ahb)
            }
            Work::First => {
                let (rng_cycles, ahb_cycles) = if ahb * THRESHOLD.1 < THRESHOLD.0 * kernel {
                    FIRST_SLOW
                } else {
                    FIRST_FAST
                };
                ((rng_cycles * ahb + ahb_cycles * kernel) * NANOS_PER_SECOND).div_ceil(kernel * ahb)
            }
        };
        u64::try_from(nanos).expect("a few cycles at a clock fit a u64 of nanoseconds")
    }

    fn begin(&mut self, work: Work, context: &mut DeviceContext<'_>) {
        self.job = Some(Job {
            work,
            left: self.duration(work),
        });
        self.resume(context);
    }

    fn resume(&mut self, context: &mut DeviceContext<'_>) {
        if !self.enabled || self.faults.seed || !self.clocks_run() || self.run.is_some() {
            return;
        }
        if let Some(job) = self.job {
            let due = VirtualNanos(context.now().0 + job.left);
            self.run = Some((context.schedule(due), due));
        }
    }

    fn pause(&mut self, now: VirtualNanos) {
        if let Some((_, due)) = self.run.take()
            && let Some(job) = &mut self.job
        {
            job.left = due.0.saturating_sub(now.0);
        }
    }

    fn finish(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let Some(job) = self.job.take() else {
            return;
        };
        if job.work == Work::First {
            self.stage = Stage::Steady;
        }
        let round = self.deal_round();
        if self.waiting == 0 {
            self.buffer = round;
            self.waiting = WORDS;
        } else {
            self.register = Some(round);
        }
        if self.register.is_none() {
            self.begin(Work::Round, context);
        }
        self.sync(registers, context);
    }

    fn take(&mut self, context: &mut DeviceContext<'_>) -> u32 {
        if self.waiting == 0 {
            return 0;
        }
        let word = self.buffer[WORDS - self.waiting];
        self.waiting -= 1;
        if self.waiting == 0
            && let Some(round) = self.register.take()
        {
            self.buffer = round;
            self.waiting = WORDS;
            self.begin(Work::Round, context);
        }
        word
    }

    // Whether the clock check fails at the rates now, section 34.3.6: the kernel clock at or
    // under a thirty-second of the AHB clock, with the check on and the generator running.
    fn clock_check_fails(&self) -> bool {
        self.enabled
            && !self.ced
            && u128::from(self.kernel_hz) * CLOCK_RATIO <= u128::from(self.ahb_hz)
    }

    fn sync(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let failing = self.clock_check_fails();
        let mut status = registers.get(Register::Sr);
        if failing && !self.faults.clock {
            status = sr::CEIS.set(status, 1);
        }
        self.faults.clock = failing;
        status = sr::DRDY.set(status, u64::from(self.waiting > 0));
        status = sr::CECS.set(status, u64::from(self.faults.clock));
        status = sr::SECS.set(status, u64::from(self.faults.seed));
        registers.set(Register::Sr, status);
        let pending = sr::DRDY.is_set(status) || sr::SEIS.is_set(status) || sr::CEIS.is_set(status);
        let wanted =
            Level::from(self.enabled && cr::IE.is_set(registers.get(Register::Cr)) && pending);
        if wanted != self.raised {
            self.raised = wanted;
            context.interrupt(IT, wanted);
        }
    }

    fn control(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let landed = registers.get(Register::Cr);
        let enable = cr::RNGEN.is_set(landed);
        if self.enabled && cr::CED.is_set(landed) != self.ced {
            registers.set(Register::Cr, cr::CED.set(landed, u64::from(self.ced)));
        } else {
            self.ced = cr::CED.is_set(landed);
        }
        if enable && !self.enabled {
            self.enabled = true;
            match self.stage {
                Stage::Starting if self.job.is_none() => self.begin(Work::First, context),
                Stage::Starting => self.resume(context),
                Stage::Steady if self.register.is_none() => self.begin(Work::Resample, context),
                Stage::Steady => {}
            }
        } else if !enable && self.enabled {
            self.pause(context.now());
            self.enabled = false;
        }
        self.sync(registers, context);
    }
}

impl Device for Rng {
    type Register = Register;

    fn read(
        &mut self,
        register: Register,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) -> Option<u64> {
        (register == Register::Dr).then(|| {
            let status = registers.get(Register::Sr);
            let unfit = self.waiting == 0 || sr::SEIS.is_set(status) || sr::CEIS.is_set(status);
            if unfit && !self.told {
                context.tell(Register::Dr.spec().name, FORBIDDEN_READ);
            }
            self.told = unfit;
            let word = self.take(context);
            self.sync(registers, context);
            u64::from(word)
        })
    }

    fn peek(
        &self,
        register: Register,
        _registers: &RegisterFile,
        _now: VirtualNanos,
    ) -> Option<u64> {
        (register == Register::Dr).then(|| {
            if self.waiting == 0 {
                0
            } else {
                u64::from(self.buffer[WORDS - self.waiting])
            }
        })
    }

    fn write(
        &mut self,
        register: Register,
        _value: u64,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        match register {
            // 34.7.1: the enables.
            Register::Cr => self.control(registers, context),
            // 34.7.2: a written zero cleared an interrupt flag, so the interrupt follows.
            Register::Sr => self.sync(registers, context),
            // 34.7.3: read-only.
            Register::Dr => {}
        }
    }

    fn on_event(
        &mut self,
        event: EventId,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        if self.run.map(|(id, _)| id) != Some(event) {
            return;
        }
        self.run = None;
        self.finish(registers, context);
    }

    // The AHB clock changed, 34.5: the work keeps the fraction of it that was done.
    fn on_clock(&mut self, hz: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let before = self.job.map(|job| (job.work, self.duration(job.work)));
        self.pause(context.now());
        self.ahb_hz = hz;
        if let Some((work, was)) = before {
            let now = self.duration(work);
            if let Some(job) = &mut self.job
                && was > 0
                && now > 0
            {
                let left = u128::from(job.left) * u128::from(now) / u128::from(was);
                job.left = u64::try_from(left).expect("a fraction of a duration fits");
            }
        }
        self.resume(context);
        self.sync(registers, context);
    }

    // The pipeline and the work go back, and the flags with the registers. The stream goes on, since
    // the generator's state isn't something a reset restores.
    fn reset(&mut self) {
        *self = Self {
            stream: self.stream,
            ..Self::new(self.ahb_hz, self.kernel_hz)
        };
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::device::ResetCause;
    use crate::mcu::{Mcu, Operate, Single};
    use crate::soc::Width;
    use crate::soc::stm32h743::builder;
    use crate::soc::stm32h743::generated::RNG;

    /// The AHB2 clock of the board's 480 MHz configuration: 240 MHz, above the threshold of 159.75 MHz.
    const FAST_AHB: u64 = 240_000_000;
    /// The first sample at 240 MHz and 48 MHz: 192 RNG cycles, 4000 ns, and 213 AHB cycles, 887.5 ns, 34.5.
    const FIRST_FAST_NANOS: u64 = 4888;
    /// A round at 240 MHz: 16 x 5 = 80 cycles is under the floor, so 213 cycles, 887.5 ns.
    const ROUND_FAST_NANOS: u64 = 888;
    /// Sampling again after a re-enable: 16 RNG cycles, 333.3 ns, and the round, 887.5 ns, 34.3.8.
    const RESAMPLE_FAST_NANOS: u64 = 1221;
    /// The first sample at the reset's 64 MHz: below the threshold, so 128 RNG cycles, 2666.7 ns, and 426 AHB
    /// cycles, 6656.25 ns.
    const FIRST_SLOW_NANOS: u64 = 9323;

    fn rng_at(ahb_hz: u64, kernel_hz: u64) -> Single<Rng> {
        Single::new(builder(), "RNG", Rng::new(ahb_hz, kernel_hz)).expect("the bus builds")
    }

    fn rng() -> Single<Rng> {
        rng_at(FAST_AHB, KERNEL_HZ)
    }

    fn address(register: Register) -> u64 {
        RNG.base + u64::from(register.spec().offset)
    }

    fn at(nanos: u64) -> VirtualNanos {
        VirtualNanos(nanos)
    }

    fn read(soc: &mut Single<Rng>, now: u64, register: Register) -> u64 {
        soc.advance_to(at(now));
        soc.read(at(now), address(register), Width::W32)
            .expect("the register answers")
    }

    fn write(soc: &mut Single<Rng>, now: u64, register: Register, value: u64) {
        soc.advance_to(at(now));
        soc.write(at(now), address(register), Width::W32, value)
            .expect("the register takes the write");
    }

    fn enable(soc: &mut Single<Rng>, now: u64) {
        write(soc, now, Register::Cr, cr::RNGEN.mask());
    }

    fn drdy(soc: &mut Single<Rng>, now: u64) -> bool {
        sr::DRDY.is_set(read(soc, now, Register::Sr))
    }

    fn lines(soc: &mut Single<Rng>) -> Vec<(u16, Level)> {
        soc.drain_interrupts()
            .into_iter()
            .map(|change| (change.interrupt, change.level))
            .collect()
    }

    #[test]
    fn every_register_resets_to_the_manuals_value() {
        let mut soc = rng();
        for register in Register::ALL {
            assert_eq!(
                read(&mut soc, 0, register),
                0,
                "{}, 34.7",
                register.spec().name
            );
        }
    }

    #[test]
    fn the_first_word_comes_after_the_first_sample_time() {
        let mut soc = rng();
        enable(&mut soc, 1000);
        let ready = 1000 + FIRST_FAST_NANOS;
        assert!(!drdy(&mut soc, ready - 1), "34.5: not before");
        assert_eq!(read(&mut soc, ready - 1, Register::Dr), 0);
        assert!(drdy(&mut soc, ready), "192 RNG cycles and 213 AHB cycles");
        assert_ne!(read(&mut soc, ready, Register::Dr), 0);
    }

    #[test]
    fn a_slow_ahb_clock_takes_the_longer_first_sample() {
        let mut soc = rng_at(64_000_000, KERNEL_HZ);
        enable(&mut soc, 0);
        assert!(
            !drdy(&mut soc, FIRST_SLOW_NANOS - 1),
            "128 RNG cycles and 426 AHB cycles, 34.5"
        );
        assert!(drdy(&mut soc, FIRST_SLOW_NANOS));
    }

    #[test]
    fn each_read_takes_a_new_word_and_eight_words_drain_the_pipeline() {
        let mut soc = rng();
        enable(&mut soc, 0);
        let both = FIRST_FAST_NANOS + ROUND_FAST_NANOS;
        let mut words = Vec::new();
        for _ in 0..8 {
            assert!(
                drdy(&mut soc, both),
                "DRDY holds through the register's four, 34.3.5"
            );
            words.push(read(&mut soc, both, Register::Dr));
        }
        assert!(
            !drdy(&mut soc, both),
            "the pipeline is empty after eight, 34.3.3"
        );
        assert_eq!(read(&mut soc, both, Register::Dr), 0, "34.7.3");
        words.sort_unstable();
        words.dedup();
        assert_eq!(words.len(), 8, "every word is new");
        assert!(words.iter().all(|&word| word != 0));
    }

    #[test]
    fn drdy_returns_once_a_round_refills_the_buffer() {
        let mut soc = rng();
        enable(&mut soc, 0);
        for _ in 0..4 {
            read(&mut soc, FIRST_FAST_NANOS, Register::Dr);
        }
        assert!(
            !drdy(&mut soc, FIRST_FAST_NANOS),
            "the register's round is still running"
        );
        assert!(
            !drdy(&mut soc, FIRST_FAST_NANOS + ROUND_FAST_NANOS - 1),
            "213 AHB cycles, 34.3.3"
        );
        assert!(drdy(&mut soc, FIRST_FAST_NANOS + ROUND_FAST_NANOS));
    }

    #[test]
    fn a_slow_kernel_clock_stretches_the_round() {
        let mut soc = rng_at(FAST_AHB, 1_000_000);
        write(&mut soc, 0, Register::Cr, cr::RNGEN.mask() | cr::CED.mask());
        let first = 192_000 + ROUND_FAST_NANOS;
        assert!(
            drdy(&mut soc, first),
            "192 RNG cycles at 1 MHz and 213 AHB cycles"
        );
        for _ in 0..4 {
            read(&mut soc, first, Register::Dr);
        }
        let round = 16_000;
        assert!(
            !drdy(&mut soc, first + round - 1),
            "16 x fAHB / fRNG is 3840 AHB cycles, past the floor of 213, 34.5"
        );
        assert!(drdy(&mut soc, first + round));
    }

    #[test]
    fn dr_reads_zero_while_no_word_is_ready() {
        let mut soc = rng();
        assert_eq!(read(&mut soc, 0, Register::Dr), 0, "disabled");
        enable(&mut soc, 0);
        assert_eq!(
            read(&mut soc, 100, Register::Dr),
            0,
            "34.7.3: zero when DRDY is clear"
        );
    }

    #[test]
    fn a_read_of_dr_the_manual_forbids_is_told_once() {
        let mut soc = rng();
        enable(&mut soc, 0);
        read(&mut soc, 100, Register::Dr);
        read(&mut soc, 200, Register::Dr);
        let told = soc.drain_told();
        assert_eq!(told.len(), 1, "DRDY was clear, 34.3.5: {told:?}");
        assert_eq!(told[0].instance, "RNG");
        assert_eq!(told[0].register, "DR");
        read(&mut soc, FIRST_FAST_NANOS, Register::Dr);
        assert!(soc.drain_told().is_empty(), "a read with DRDY set is fine");
        for _ in 0..3 {
            read(&mut soc, FIRST_FAST_NANOS, Register::Dr);
        }
        read(&mut soc, FIRST_FAST_NANOS, Register::Dr);
        read(&mut soc, FIRST_FAST_NANOS, Register::Dr);
        assert_eq!(
            soc.drain_told().len(),
            1,
            "told again on the next transition"
        );
        let mut soc = rng_at(FAST_AHB, 7_000_000);
        enable(&mut soc, 0);
        read(&mut soc, FIRST_FAST_NANOS + ROUND_FAST_NANOS, Register::Dr);
        assert_eq!(
            soc.drain_told().len(),
            1,
            "a word is ready, but CEIS stands: it must not be read either"
        );
    }

    #[test]
    fn a_peek_shows_the_next_word_and_takes_nothing() {
        let mut soc = rng();
        enable(&mut soc, 0);
        soc.advance_to(at(FIRST_FAST_NANOS));
        let peeked = soc
            .peek(at(FIRST_FAST_NANOS), address(Register::Dr), Width::W32)
            .expect("the register answers");
        assert_ne!(peeked, 0);
        let again = soc
            .peek(at(FIRST_FAST_NANOS), address(Register::Dr), Width::W32)
            .expect("the register answers");
        assert_eq!(again, peeked, "a second look sees the same word");
        assert_eq!(
            read(&mut soc, FIRST_FAST_NANOS, Register::Dr),
            peeked,
            "and the read takes it"
        );
    }

    #[test]
    fn a_disabled_generator_keeps_its_words_and_makes_no_more() {
        let mut soc = rng();
        enable(&mut soc, 0);
        let ready = FIRST_FAST_NANOS;
        read(&mut soc, ready, Register::Dr);
        write(&mut soc, ready, Register::Cr, 0);
        for _ in 0..3 {
            assert_ne!(
                read(&mut soc, ready + 10_000, Register::Dr),
                0,
                "34.3.8: the buffer stays readable"
            );
        }
        assert!(!drdy(&mut soc, ready + 10_000), "and empties");
        assert!(
            !drdy(&mut soc, ready + 1_000_000),
            "no round completes while it's disabled, 34.3.8"
        );
        enable(&mut soc, ready + 1_000_000);
        assert!(
            !drdy(&mut soc, ready + 1_000_000 + RESAMPLE_FAST_NANOS - 1),
            "re-enabled, it samples anew and runs a round, 34.3.8"
        );
        assert!(drdy(&mut soc, ready + 1_000_000 + RESAMPLE_FAST_NANOS));
    }

    #[test]
    fn disabling_during_the_first_sample_resumes_where_it_stopped() {
        let mut soc = rng();
        enable(&mut soc, 0);
        write(&mut soc, 2000, Register::Cr, 0);
        assert!(!drdy(&mut soc, 100_000), "disabled, it doesn't progress");
        enable(&mut soc, 100_000);
        let remaining = FIRST_FAST_NANOS - 2000;
        assert!(
            !drdy(&mut soc, 100_000 + remaining - 1),
            "34.3.4: it resumes from where it stopped"
        );
        assert!(drdy(&mut soc, 100_000 + remaining));
    }

    #[test]
    fn a_stale_event_is_ignored() {
        let mut soc = rng();
        enable(&mut soc, 0);
        write(&mut soc, 1000, Register::Cr, 0);
        enable(&mut soc, 3000);
        let second = 3000 + FIRST_FAST_NANOS - 1000;
        assert!(
            !drdy(&mut soc, FIRST_FAST_NANOS),
            "the first run's event fires here and is no longer the work's end"
        );
        assert!(drdy(&mut soc, second));
    }

    #[test]
    fn the_interrupt_follows_drdy_while_enabled_and_unmasked() {
        let mut soc = rng();
        write(&mut soc, 0, Register::Cr, cr::RNGEN.mask() | cr::IE.mask());
        soc.advance_to(at(FIRST_FAST_NANOS - 1));
        assert!(lines(&mut soc).is_empty(), "no word, no interrupt");
        soc.advance_to(at(FIRST_FAST_NANOS));
        assert_eq!(
            lines(&mut soc),
            [(80, Level::High)],
            "an interrupt when DRDY rises, 34.4"
        );
        let both = FIRST_FAST_NANOS + ROUND_FAST_NANOS;
        soc.advance_to(at(both));
        assert!(
            lines(&mut soc).is_empty(),
            "the register filling changes nothing"
        );
        for _ in 0..7 {
            read(&mut soc, both, Register::Dr);
        }
        assert!(lines(&mut soc).is_empty(), "a word still waits");
        read(&mut soc, both, Register::Dr);
        assert_eq!(
            lines(&mut soc),
            [(80, Level::Low)],
            "the interrupt clears itself when the last word is read, Table 281"
        );
        let next = both + ROUND_FAST_NANOS;
        soc.advance_to(at(next));
        assert_eq!(lines(&mut soc), [(80, Level::High)]);
        write(&mut soc, next, Register::Cr, cr::RNGEN.mask());
        assert_eq!(lines(&mut soc), [(80, Level::Low)], "IE clear lowers it");
        write(&mut soc, next, Register::Cr, cr::IE.mask());
        assert!(
            lines(&mut soc).is_empty(),
            "34.4: interrupts are generated only when the generator is enabled, though DRDY stands"
        );
    }

    #[test]
    fn the_interrupt_is_line_80() {
        let soc = builder().build().unwrap();
        assert_eq!(
            soc.peripheral("RNG").expect("an instance").interrupts[IT],
            80,
            "Table 147"
        );
    }

    #[test]
    fn ced_cannot_change_while_the_generator_is_enabled() {
        let mut soc = rng();
        enable(&mut soc, 0);
        write(
            &mut soc,
            10,
            Register::Cr,
            cr::RNGEN.mask() | cr::CED.mask(),
        );
        assert!(
            !cr::CED.is_set(read(&mut soc, 10, Register::Cr)),
            "34.7.1: the clock error detection can't be disabled on the fly"
        );
        write(&mut soc, 20, Register::Cr, 0);
        write(&mut soc, 30, Register::Cr, cr::CED.mask());
        assert!(
            cr::CED.is_set(read(&mut soc, 30, Register::Cr)),
            "disabled, it can"
        );
        write(
            &mut soc,
            40,
            Register::Cr,
            cr::RNGEN.mask() | cr::CED.mask(),
        );
        write(&mut soc, 50, Register::Cr, cr::RNGEN.mask());
        assert!(
            cr::CED.is_set(read(&mut soc, 50, Register::Cr)),
            "and enabled it can't be enabled either"
        );
    }

    #[test]
    fn a_slow_kernel_clock_raises_the_clock_error() {
        let mut soc = rng_at(FAST_AHB, 7_000_000);
        write(&mut soc, 0, Register::Cr, cr::RNGEN.mask() | cr::IE.mask());
        let status = read(&mut soc, 0, Register::Sr);
        assert!(
            sr::CECS.is_set(status),
            "7 MHz x 32 is under 240 MHz, 34.3.6"
        );
        assert!(sr::CEIS.is_set(status), "34.3.7: both are set");
        assert_eq!(
            lines(&mut soc),
            [(80, Level::High)],
            "an interrupt for the error"
        );
        write(&mut soc, 5, Register::Sr, !sr::CEIS.mask());
        let status = read(&mut soc, 5, Register::Sr);
        assert!(!sr::CEIS.is_set(status), "a written zero clears CEIS");
        assert!(
            sr::CECS.is_set(status),
            "while the condition stands, CECS does"
        );
        assert_eq!(lines(&mut soc), [(80, Level::Low)]);
        write(&mut soc, 6, Register::Sr, sr::CEIS.mask());
        assert!(
            !sr::CEIS.is_set(read(&mut soc, 6, Register::Sr)),
            "a written one does nothing"
        );
        let mut soc = rng_at(FAST_AHB, 7_000_000);
        write(&mut soc, 0, Register::Cr, cr::RNGEN.mask() | cr::CED.mask());
        assert_eq!(
            read(&mut soc, 0, Register::Sr) & (sr::CECS.mask() | sr::CEIS.mask()),
            0,
            "CED turns the check off"
        );
    }

    #[test]
    fn a_change_of_the_ahb_clock_rescales_the_work_and_the_clock_check() {
        let mut soc = rng_at(FAST_AHB, 7_000_000);
        write(&mut soc, 0, Register::Cr, cr::RNGEN.mask());
        assert!(sr::CECS.is_set(read(&mut soc, 0, Register::Sr)));
        soc.drive(at(1000), |rng, registers, context| {
            rng.on_clock(200_000_000, registers, context);
        });
        let status = read(&mut soc, 1000, Register::Sr);
        assert!(
            !sr::CECS.is_set(status),
            "7 MHz x 32 is 224 MHz, over 200 MHz: CECS clears itself, 34.3.6"
        );
        assert!(
            sr::CEIS.is_set(status),
            "and CEIS stays until software clears it"
        );

        let mut soc = rng();
        enable(&mut soc, 0);
        soc.drive(at(2000), |rng, registers, context| {
            rng.on_clock(200_000_000, registers, context);
        });
        // At 200 MHz the first sample is 192 RNG cycles and 213 AHB cycles, 5065 ns, where it was 4888 ns.
        // The fraction still to do, 2888 of 4888, applies to the new total.
        let due = 2000 + 2888 * 5065 / 4888;
        assert!(
            !drdy(&mut soc, due - 1),
            "a slower clock stretches what was left"
        );
        assert!(drdy(&mut soc, due), "by the new total's fraction");
    }

    #[test]
    fn a_seed_error_sets_both_flags_stops_generation_and_recovers() {
        let mut soc = rng();
        write(&mut soc, 0, Register::Cr, cr::RNGEN.mask() | cr::IE.mask());
        let ready = FIRST_FAST_NANOS;
        soc.advance_to(at(ready));
        lines(&mut soc);
        for _ in 0..4 {
            read(&mut soc, ready, Register::Dr);
        }
        soc.drive(at(ready), |rng, registers, context| {
            rng.seed_error(registers, context);
        });
        let status = read(&mut soc, ready, Register::Sr);
        assert!(
            sr::SEIS.is_set(status) && sr::SECS.is_set(status),
            "34.3.7: both set"
        );
        assert!(
            !drdy(&mut soc, ready + 100_000),
            "generation stopped: the round that was running never lands"
        );
        write(&mut soc, ready + 200_000, Register::Sr, !sr::SEIS.mask());
        let status = read(&mut soc, ready + 200_000, Register::Sr);
        assert!(!sr::SEIS.is_set(status), "SEIS clears by a written zero");
        assert!(sr::SECS.is_set(status), "SECS stays while the fault does");
        soc.drive(at(ready + 300_000), |rng, registers, context| {
            rng.seed_recovered(registers, context);
        });
        let status = read(&mut soc, ready + 300_000, Register::Sr);
        assert!(!sr::SECS.is_set(status), "recovered");
        assert!(!drdy(&mut soc, ready + 300_000 + ROUND_FAST_NANOS - 1));
        assert!(
            drdy(&mut soc, ready + 300_000 + ROUND_FAST_NANOS),
            "and generation resumes"
        );
    }

    #[test]
    fn a_reset_empties_the_pipeline_and_goes_on_with_the_stream() {
        let mut soc = rng();
        enable(&mut soc, 0);
        let first = read(&mut soc, FIRST_FAST_NANOS, Register::Dr);
        soc.reset(at(FIRST_FAST_NANOS), ResetCause::Pin);
        assert_eq!(read(&mut soc, FIRST_FAST_NANOS, Register::Cr), 0);
        assert!(!drdy(&mut soc, FIRST_FAST_NANOS));
        enable(&mut soc, 100_000);
        let after = read(&mut soc, 100_000 + FIRST_FAST_NANOS, Register::Dr);
        assert_eq!(first, 0x157A_3807, "the first word of the stream");
        assert_eq!(
            after, 0x0140_4CE9,
            "the stream goes on past the four words the first round dealt, so the fifth follows"
        );
    }

    #[test]
    fn the_stream_is_the_seeds_and_nothing_else() {
        let words = |seed: u64| {
            let mut rng = Rng::seeded(FAST_AHB, KERNEL_HZ, seed);
            (0..12).map(|_| rng.deal()).collect::<Vec<u32>>()
        };
        assert_eq!(words(SEED), words(SEED), "the same seed, the same words");
        assert_ne!(words(SEED), words(SEED + 1), "another seed, other words");
        assert_eq!(
            words(SEED)[..5],
            [
                0x157A_3807,
                0xD573_529B,
                0x2F90_B72E,
                0xA2D4_1933,
                0x0140_4CE9
            ],
            "SplitMix64's top halves from the seed, which a port of the generator reproduces"
        );
    }

    #[test]
    fn no_word_is_zero() {
        let mut rng = Rng::seeded(FAST_AHB, KERNEL_HZ, 0);
        assert!(
            (0..100_000).all(|_| rng.deal() != 0),
            "a zero in RNG_DR means a seed error, 34.7.3"
        );
        // The state that deals a zero first: the generator adds its increment, and a mix of zero is zero.
        rng.stream = 0_u64.wrapping_sub(0x9E37_79B9_7F4A_7C15);
        let mut next = Rng::seeded(FAST_AHB, KERNEL_HZ, 0);
        next.stream = 0;
        assert_eq!(
            rng.deal(),
            next.deal(),
            "the zero is skipped and the word after it is dealt"
        );
    }

    #[test]
    fn a_snapshot_goes_on_with_the_same_stream() {
        let mut one = Rng::new(FAST_AHB, KERNEL_HZ);
        let first: Vec<u32> = (0..5).map(|_| one.deal()).collect();
        let bytes = postcard::to_stdvec(&one).expect("the state serializes");
        let mut two: Rng = postcard::from_bytes(&bytes).expect("the state deserializes");
        let rest_one: Vec<u32> = (0..7).map(|_| one.deal()).collect();
        let rest_two: Vec<u32> = (0..7).map(|_| two.deal()).collect();
        assert_eq!(
            rest_one, rest_two,
            "a restored generator deals the words that follow"
        );
        assert_ne!(first, rest_one);
    }

    #[test]
    fn two_runs_of_one_script_are_the_same_run() {
        fn run() -> (Vec<u64>, Vec<(u16, Level)>, u64) {
            let mut soc = rng();
            write(&mut soc, 0, Register::Cr, cr::RNGEN.mask() | cr::IE.mask());
            let mut words = Vec::new();
            for step in 1..=12_u64 {
                let now = step * 600;
                if drdy(&mut soc, now) {
                    words.push(read(&mut soc, now, Register::Dr));
                }
            }
            let status = read(&mut soc, 8000, Register::Sr);
            (words, lines(&mut soc), status)
        }
        let (first, second) = (run(), run());
        assert!(!first.0.is_empty());
        assert_eq!(first, second);
    }

    /// ArduPilot's `__late_init` enables the interrupt and then the generator, and `stm32_rand_generate`
    /// reads `RNG->SR` for the error bits and `DRDY` before it takes `RNG->DR`. The blocking call asks for
    /// bytes against a deadline in microseconds and the nonblocking call gives up at once when `DRDY` is clear.
    #[test]
    fn ardupilots_reads_get_their_words_within_their_timeouts() {
        let mut soc = rng();
        let current = read(&mut soc, 0, Register::Cr);
        write(&mut soc, 0, Register::Cr, cr::IE.set(current, 1));
        let current = read(&mut soc, 0, Register::Cr);
        write(&mut soc, 0, Register::Cr, cr::RNGEN.set(current, 1));
        let generate = |soc: &mut Single<Rng>, now: u64| {
            let status = read(soc, now, Register::Sr);
            let errors = sr::SEIS.mask() | sr::CEIS.mask();
            (status & errors == 0 && sr::DRDY.is_set(status)).then(|| read(soc, now, Register::Dr))
        };
        assert_eq!(
            generate(&mut soc, 100),
            None,
            "the nonblocking call before the first sample returns none"
        );
        let timeout_nanos = 10_000_u64;
        let mut now = 100;
        let mut words = Vec::new();
        while words.len() < 4 && now < timeout_nanos {
            if let Some(word) = generate(&mut soc, now) {
                words.push(word);
            }
            now += 100;
        }
        assert_eq!(
            words.len(),
            4,
            "four words, a blocking call for sixteen bytes, within ten microseconds"
        );
        assert!(words.iter().all(|&word| word != 0));
        // By 7 us the buffer and the register are both full, so the nonblocking call takes eight words
        // while DRDY stands and then none.
        let mut taken = 0;
        while generate(&mut soc, 7000).is_some() {
            taken += 1;
        }
        assert_eq!(
            taken, 8,
            "34.3.5: the buffer's four and the register's four"
        );
    }
}