rigless 0.1.0

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model and unit of virtual time in Rust, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
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
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
A hardware-in-the-loop runtime for embedded firmware, in Rust, and the open half of [Rigless](https://riglesslabs.com). An unmodified firmware image runs on an emulated MCU: every peripheral, every chip on the board and every unit of virtual time is a Rust model, and the crate's own CPU core translates the firmware's instructions to host code and runs them, calling those models for every access.

It's written for any part and any board. An SoC is its vendor's SVD, the register tables generated from it, and the models written against those tables. A board is a struct that has its MCUs, its crystals and the chips on its buses. The traits each of those implements are this crate's public surface, documented in their modules and walked in the sections below. The STM32F427 and the Pixracer are the first part and the first board it carries, each behind a feature of its own, so wherever a name of either appears below it's an example and not the subject.

```text
rigless run --machine pixracer --until 2ms firmware/stm32f427/bear-bones/target/thumbv7em-none-eabihf/release/bear-bones
```

The core is the crate's own, in Rust: a decoder and translator per CPU family that lowers blocks of the firmware's instructions to host code with Cranelift. What to install and how to build it is in the [repository's README](https://gitlab.com/rigless-labs/rigless/-/blob/main/README.md), which is where setup is written down once rather than here as well.

## What is here

One crate, one tree, one version, under MIT or Apache-2.0 at your option. The register tables `rigless svd generate` wrote under `src/soc/` are the exception, and each carries the license of the vendor file it came from. The library is the runtime and the binary is how you drive it. A change to either is one merge request against this repository.

```text
src/
├── lib.rs
├── lib.md                   this page: the crate's documentation, which `lib.rs` includes
├── runtime.rs
├── runtime/                 what knows no SoC and no processor family, re-exported at the root
│   ├── device.rs
│   ├── mcu.rs
│   ├── time.rs
│   ├── session.rs
│   ├── snapshot.rs
│   ├── image.rs                 the images a run places, and where the core boots
│   ├── elf.rs
│   ├── symbols.rs               the firmware's own names, which a fault can name an instruction by
│   ├── coverage.rs
│   ├── emulator.rs
│   ├── emulator/                the runner, the code generator and memory, which every family shares
│   ├── link.rs
│   ├── link/                    the target's side of the link
│   ├── version.rs               what the runtime calls itself
│   └── version/                 the rule `build.rs` writes it by
├── soc.rs
├── soc/
│   ├── stm32f427.rs             one index per SoC, behind a Cargo feature
│   └── stm32f427/
│       ├── STM32F427.svd        the vendor's file, committed unchanged
│       ├── memory_map.rs        the map and the corrections, private to the module
│       ├── generated.rs, generated/
│       └── peripherals.rs, peripherals/     the MCU, and one model per peripheral: adc, dbg, dma, flash, gpio, i2c, iwdg, otg, pwr, rcc, rtc, sdio, spi, tim, usart
├── cpu.rs
├── cpu/                         one module per core family, the whole core: decode, translation, exceptions
├── chip.rs
├── chip/                        the chips a board puts on its buses, and the seam each kind attaches through
├── edge.rs                      the board's edge: what crosses it, and the `Edge` trait a board with a world implements
├── machine.rs
├── machine/
│   ├── armv7m.rs                one per core family
│   ├── pixracer.rs              one per board, behind a feature of its own: what it has, and what it is made of
│   └── stm32f427_bench.rs       one per SoC, for proving it before a board
├── svd.rs, svd/
├── cli.rs                       the `rigless` command, over any table of machines
└── main.rs                      the `rigless` binary: the command over `machine::all()`
```

Each area is a directory with its index file beside it, and `runtime` is private and re-exported, so the paths stay flat: `rigless::device`, not `rigless::runtime::device`.

| Module | Role |
|---|---|
| `soc` | The static description of a system on chip: its CPU, its memory map, its peripheral instances, the register layouts the generator emits, and the `Bus` that decodes an address to a slot and holds that slot's registers. A bus owns no models |
| `soc::stm32f427` | The STM32F427, behind the feature of that name: ST's own SVD, the tables generated from it, the memory map and the corrections from RM0090 rev 22 in `memory_map`, and `Stm32f427` itself in `peripherals`. That's the whole MCU, holding a model per instance as a field |
| `cpu` | One module per core family, each the whole core: its decoder and translator, its exception model and interrupt controller, the address space the processor answers itself, its floating-point unit and MPU, how it boots and what a debugger sees, and the blocks the architecture fixes at an address, each a `Device` like any peripheral |
| `chip` | The chips a board puts on a part's buses, each written against its own datasheet and naming no SoC, the seam each kind attaches through: `spi::Peer`, `i2c::Peer`, `uart::Peer` and `sd::Card`, and `Place`, where a controller holds a chip, a board silences it, and a trigger counts its transactions and fires a flip or a drop. `uart::Errors` is how often words arrive garbled on a line, which a board sets and a serial port's model asks about each word |
| `device` | The `Device` trait a model implements against one register layout, and the `DeviceContext` through which it sees time and its own interrupts |
| `mcu` | The `Mcu` trait that joins a bus to the fields holding its models, with `read`, `write` and `advance_to` written once against it. `Single` is one model on a bus of its own, and `Bare` is a bus with none |
| `time` | `VirtualNanos`, the virtual clock every access and every event is stamped with |
| `emulator` | The seam between the emulator and the models: `Access` and `Stop` as the emulator reports them, the `Host` it lends the models, the `Driver` a process lends the run, the `CoreSpec` a machine module fills in to say what one target's core is, the `Processor` a core family implements and the `CoreBus` it's lent, and `run`, which runs the target's core |
| `link` | The step protocol between an engine and a target it spawned, version 5: the target says what it carries in `Manifest`, its conditions among it, then the two meet once per plant step on stdin and stdout, a fault the engine arms fires at an exact point the target counts to, and at any stop the engine can have the target write a snapshot of itself. `serve` is the target's side, for any board with an edge |
| `snapshot` | A target's whole state at a stop, as a file a new process starts from: the core's own state, every writable region of memory, and every model, chip and board through the `Store` trait each implements. `take` writes one and `Snapshot` reads one back |
| `machine` | The `Board` trait a board implements, its `Target`s borrowed one per MCU, its edge, and every machine this build carries |
| `edge` | `Edge`, the trait a board that drives or senses something implements with its own outputs and signals types, the two halves of the seam a plant is stepped against, and its own conditions, what can be wrong with the board itself. `Coupled` is the same edge as bytes, `Condition`, `Sensed` and `Reading` are what a stuck, biased or noisy chip reads through, `draw` and `normal` are every random draw a board makes, keyed by the run's seed, and `Pulse` is what a timer's pin carries |
| `machine::armv7m` | The ARMv7-M machine: one typed field per property of a target's, and the vector table a run boots from |
| `machine::pixracer` | The Pixracer, behind the feature of that name: one STM32F427, its two crystals, its rail, the chips on its buses, and the six outputs and the mountings its description names |
| `machine::stm32f427_bench` | The board that proves the STM32F427 rather than being a board: the whole MCU, a rail, one crystal, a peer on every serial interface, serial port and the first I²C interface, known voltages on the converters' pins, and a memory card |
| `coverage` | Which of an MCU's registers a run reached, resolved from the accesses a session already reports |
| `session` | One firmware on one target: what the emulator calls at every access and every stop, and what the run records |
| `image` | The images a run places in memory, where each section goes, where the core boots from, and every refusal before the core runs |
| `elf` | The smallest ELF reader the runtime needs: the symbols, and each section's bytes with where the file stores them |
| `symbols` | The firmware's own names for its code and data, read from its ELF symbol table, which a trigger names an instruction by |
| `version` | What this runtime calls itself: its version alone for an official release, and `X.Y.Z+<commit>`, `X.Y.Z+<commit>.dirty` or `X.Y.Z+unknown` for any other build, so a result from one can't pass for a release |
| `svd` | The generator, behind the `svd` feature: a vendor's CMSIS-SVD file becomes the register tables an SoC is built from |
| `cli` | The `rigless` command, behind the `cli` feature. `cli::main` runs it over a table of machines, so a binary of your own gives your boards everything `rigless run` has |

| Feature | Default | What it adds |
|---|---|---|
| `cli` | yes | The `rigless` binary, `cli::main` for a binary of your own, and the argument parsing both need, with the kernels `rigless-rtos` walks for its debugger. It turns on `svd` too |
| `svd` | through `cli` | `rigless::svd`, so a build can generate tables as a library |
| `stm32f427` | through `pixracer` | The STM32F427 SoC, its models, and the `stm32f427-bench` machine |
| `pixracer` | yes | The `pixracer` machine, and the STM32F427 it's built on |

The bare-metal test firmware lives under `firmware/`, one directory per SoC. The plant interface and a reference plant for the commercial engine live in [rigless-labs/hil](https://gitlab.com/rigless-labs/hil), under the Rigless License. The API documentation of `main` is published at [rigless-labs.gitlab.io/rigless](https://rigless-labs.gitlab.io/rigless/).

## What you implement

Every seam is a public trait with the contract in its documentation. No model behind one is looked up by name, so what you write is a type the compiler checks against the runtime. The one downcast is a chip's: a controller holds its chip as the seam's trait object in a `chip::Place`, and its `chip_mut` hands the board back the type it hung, through `Any`.

| You are writing | Implement | Walked in |
|---|---|---|
| A peripheral of an SoC | `device::Device` against a `soc::Layout` the generator emits. The `DeviceContext` it's handed is its time, its events, its interrupt lines, its request lines, its clocks, the accesses it asks the runtime to make, a reset of the whole part, and what it tells the run about its own instance | [Modeling a peripheral](#modeling-a-peripheral), [Adding a model](#adding-a-model) |
| A block the processor architecture fixes | the same `Device`, beside its core family under `cpu` | [Adding a core family](#adding-a-core-family) |
| An SoC | `mcu::Mcu`: a struct holding one model per instance as a field, `bus`, `bus_mut` and `split`, over a `soc::Bus` built from the generated tables with the manual's corrections | [Adding an SoC](#adding-an-soc) |
| A chip on a bus | `chip::spi::Peer`, `chip::i2c::Peer`, `chip::uart::Peer` or `chip::sd::Card`, and setters in its datasheet's terms where the chip measures something | [A chip on a line of your own](#a-chip-on-a-line-of-your-own), [Adding a chip](#adding-a-chip) |
| A host on a wire | `chip::uart::Peer` too: `chip::uart::Host` is one over a socket, and a board names its wires through `machine::HostWire` | [The machine and the session](#the-machine-and-the-session) |
| A board | `machine::Board`: `name`, `targets`, `target_names` and `mcu`, then `hosts`, `attach_host` and `host_mut` where a host may sit on one of its wires, and `edge` where it has one. `edge::Edge` with the board's own `Outputs`, `Signals` and `Conditions` where it drives or senses something | [The board](#the-board), [The edge](#the-edge), [Adding a machine](#adding-a-machine) |
| What a snapshot keeps | `snapshot::Store`, on a board and on every chip, which `Board` and each chip seam require. Saving the MCU is one call, since every part and model this crate ships implements it already. `store!` implements it for your own part, model, chip or board, naming each field once as a value, a part or what construction built. `impl Store for MyBoard {}` says the board can't be kept, and its runs start from reset | the `snapshot` module |
| A core family | `emulator::Processor`, a function that makes one from a `CoreSpec`, and a machine module whose command carries that function, as `cpu::armv7m` and `machine::armv7m` do | [Adding a core family](#adding-a-core-family) |
| What a run does at every access, and at each instant it was asked to reach | `emulator::Driver`. `rigless run` prints, the engine's target answers the link, and a test records | the `emulator` module |
| What a plant reads and writes | nothing. The board's own `Edge::Outputs` and `Edge::Signals` are the two halves of the seam, and the board is what turns its pins and chips into them | [The machine and the session](#the-machine-and-the-session) |

A board written outside this crate runs from a binary of your own. `cli::main` is the whole `rigless` command over a table of machines you hand it, `--link` and `--gdb` included. `emulator::run_printed` prints what `rigless run` prints, `link::serve` runs a target for an engine, and `emulator::run` with a `Driver` of your own does anything else. `rigless run` itself carries the machines in the table `machine::all()` returns and no others, which is what keeps the runtime free of any registry. [Building your own device](#building-your-own-device) walks the whole path, from `cargo add` to a firmware running on your own part.

## The command line

`run` executes a firmware image on one MCU of a machine. It prints every peripheral access, then its stop, any resets with what caused each, the addresses no register covers, the accesses the bus refused for a reason other than a shut gate, the registers no model implements, the interrupts it has no line for, and what a model told the run about its own instance:

```text
rigless run --machine pixracer [--target MCU] [--until 10ms] [--gdb 3333] [--wait-for-debugger] [--coverage PATH] [--outputs 20ms] [--link [--seed N | --restore PATH]] [--host WIRE=PORT] [--image PATH[@ADDRESS]]... [--boot ADDRESS] firmware.elf
serial1 on :5760
```

`--until` is virtual time with a unit, `ns`, `us`, `ms`, or `s`. Without it the run has no deadline and ends when you interrupt it. Virtual time isn't wall-clock time. A firmware waiting for an interrupt jumps straight to its next event, so a deadline can pass in a blink, and one that keeps the core busy runs nearer the clock on the wall. `--gdb` serves GDB at that address, the loopback interface for a port alone, while the target runs, and `--wait-for-debugger` holds it before its first instruction instead.

The firmware and every `--image` go into memory before the core's first instruction, the way a probe writes them. An ELF file goes where it stores its sections: each section's bytes at the load address of the segment that holds it, as objcopy writes them into a binary. Nothing it only zeroes is written, and neither are bytes outside every section, like the file's own headers a linker can map in front of the first one. Any other file goes whole at the address after its `@`, like `app.bin@0x08004000`. The core boots where its machine says it does on its own, and `--boot ADDRESS` boots it from somewhere else. A fault names an instruction by the firmware's symbols, not an `--image`'s.

That gives you two ways to run an application linked to sit behind a bootloader. Put the bootloader in front of it, and the run boots the way the board does, bootloader first:

```text
rigless run --machine pixracer --image bootloader.elf app.elf
```

Or run the application alone, from its own vector table:

```text
rigless run --machine pixracer --boot 0x08004000 app.elf
```

A run is refused before anything runs when two images overlap, a segment is outside the memory map, the core can't boot from the address, or nothing is placed there. The refusal names what it hit, so you don't get a core running from empty flash.

`--coverage PATH` writes what the run reached when it ends: every instance a model is bound to with how many of its registers were touched, then every register of one that wasn't. After those it names every instance the run reached with no model behind it, and every address it reached outside every instance. It's what a bench run is read for, and `rigless run --machine stm32f427-bench` is that run.

A wire the board says a host may sit on gets a TCP socket on the loopback interface at the port the board names, and the run says so before it starts, `serial1 on :5760` above. Whatever connects there is the far end of that serial port: bytes it writes reach the firmware as frames at the port's baud, from the stop the runtime noticed them at, and bytes the firmware sends reach it as each frame ends. Nothing waits for it, and a run with no one connected is a run whose wire hears nothing. `--host WIRE=PORT` moves that socket to another port, and a port of 0 takes any free one and says which.

`--outputs PERIOD` reads the board's outputs every period of virtual time and prints each field that changed since the last read, in the unit the board declares it in. The period has to be above zero. The first read prints every field. Here's how the Pixracer's first read starts, with one pin driven:

```text
output t=20000000 PWM1.width=1000000
output t=20000000 PWM1.period=20000000
output t=20000000 PWM1.command=0
output t=20000000 PWM2.width=0
```

A run is one MCU. A machine with more than one names the one to run with `--target`. A machine with one needs no name. Every other MCU of such a machine is another run, in this process or another, since each run's core is a thread of its own.

`--link` makes the run an engine's target. The engine that spawned it speaks the `link` module's frames on its stdin and stdout, and nothing else is printed there. The target answers the engine's `Open` with `Manifest`, which names the board's inputs with their units and widths, its outputs, its ports, and its conditions, each resting at zero. Then once per plant step the engine sends `Advance`, asking it to reach an instant with some inputs and conditions changed and some bytes on its ports. The target answers `Reached` with every output, what the firmware sent, any resets, and each gap and each thing a model told, the first time the run meets it. `Shutdown` ends the run, and the target answers it with a `Report`. `--link` doesn't take `--until`, `--outputs`, `--coverage`, `--host`, `--semihosting`, `--trace` or `--rtt`. The target refuses an `Advance` for an instant behind where the run stands or past where the virtual clock runs, since it could never answer it. `--seed N` keys every random draw the board makes, 0 when it's left out. The target hands the board the seed and the `Advance`'s index once per `Advance`, after the conditions and the inputs, so the same seed under the same steps is the same run. The engine picks the instants and reads the outputs, and the board's ports cross the link and open no socket. The engine is the commercial `rigless-hil`, and `rigless-hil run` is the command that spawns this one.

Between a `Reached` and the next `Advance` the engine can send `Snapshot` with a path. The target writes a snapshot of itself there, the core's state, its memory, every model and chip, and what the link holds, and answers `Snapshotted` with its SHA-256. When it can't, at a stop where a reset the core or a model asked for hasn't happened yet or with a chip that can't be kept, it answers `Refused` with why, and the run goes on as it stood. `--restore PATH` starts a target from one instead of from reset. It has to be the same machine, target, images, boot address and runtime, and it names the snapshot in its `Manifest`. The engine's first `Advance` then carries on from the stop the snapshot was taken at, under the seed it was taken with, and from there it's the same run as one that never stopped. The core loads a snapshot as it stands, so only restore one you or your engine wrote. That's what lets cases that start the same way run the start once. The file is the target's part, and an engine appends its own after it: `rigless snapshot`, a `u16` format, then each part as a one-byte name length, the name, a `u64` length, the part's SHA-256 and its bytes.

An `Advance` can also arm faults, each with an id the engine picks, a trigger, and what fires. A trigger is a chain of links, each counting from where the one before it fired: `After` a length of time, the Nth `Access` to a register the `Manifest` lists, the Nth `Transaction` with a chip it lists, the Nth arrival at an instruction to `Execute` it, by its address or the firmware's own symbol for it, or the Nth instruction that makes a `Write` into a range of RAM. An access link counts the core's own reads or writes, optionally of a value under a mask, and fires right after the one before its count, so the one it counts to is the first to see the fault. A transaction link does the same with the transactions a chip's place counts: a select to its deselect, a card's command, or a word on a serial line. An `After` instant becomes a pause of the run's, between two instructions, which the models are never told of, and the step still ends where the engine asked. An instruction or a write is watched by the core only while a link counts it, and the core runs on through a hit short of the count as it would without the watch, so neither changes the run. An interrupt taken at a watched instruction before it runs brings the core back to it, and that's another arrival. A push of several registers into a watched range is one write. Write links of different faults watch one range between them, since the core reports a store to one range only. An instruction outside the part's memory, and a write range outside its RAM, are refused. What fires is the board's conditions, set right there, or a one-shot fault: the next word a chip answers flipped under a mask, its next transaction dropped, an interrupt line raised for an instant, a reset from the part's pin, or the core's next access to a register failed, which it takes as a bus error. `Reached` lists every fault that fired since the last answer, with its instant, its instruction count and the instruction the core was at, for one fired right after an access or on arriving at an instruction. A fault that never fires stays armed, and a snapshot keeps it with how far it has counted.

The crate is MIT or Apache-2.0, at your option, the core included. Cranelift and `rustc_apfloat`, which the core builds on, are Apache-2.0 with the LLVM exception. The register tables `rigless svd generate` wrote under `src/soc/` are the exception, and carry their vendor's copyright and license lines in their headers.

`svd generate` turns a vendor's SVD into the register tables of an SoC:

```text
rigless svd generate src/soc/stm32f427
```

## Modeling a peripheral

A model matches on the layout's registers. The register file has already stored the value, applied the access rules, and done the write-modify arithmetic, so the model writes only the side effects:

```rust
use rigless::device::{Device, DeviceContext, RegisterFile};
use rigless::mcu::{Mcu, Single};
use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
use rigless::time::VirtualNanos;

// What the generator emits for one register layout.
static REGISTERS: [RegisterSpec; 2] = [
    RegisterSpec { name: "CTRL", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
    RegisterSpec { name: "DATA", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Register { Ctrl, Data }
impl Layout for Register {
    const REGISTERS: &'static [RegisterSpec] = &REGISTERS;
    fn from_index(index: usize) -> Option<Self> { [Self::Ctrl, Self::Data].get(index).copied() }
    fn index(self) -> usize { self as usize }
}
const SERIAL: Peripheral = Peripheral { name: "SERIAL", base: 0x4000_0000, size: 0x100, interrupts: &[5], registers: &REGISTERS };
static MEMORY: [Region; 1] = [Region::ram("sram", 0x2000_0000, 64 << 10)];

// What a person writes.
#[derive(Default)]
struct Serial { sent: Vec<u8> }
impl Device for Serial {
    type Register = Register;
    fn write(&mut self, register: Register, value: u64, _registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) {
        if register == Register::Data { self.sent.push(value as u8); }
    }
}

// A bus describes where things answer, and the model goes on the MCU built over it. `Single` is one model
// on a bus of its own, which is what a test of one model wants.
let bus = Bus::builder(Cpu { name: "a-core", fpu: true, interrupt_lines: 16, endian: Endian::Little }, &MEMORY, [SERIAL]);
let mut soc = Single::new(bus, "SERIAL", Serial::default())?;
let data_register = SERIAL.base + u64::from(Register::Data.spec().offset);
let now = VirtualNanos(0);
soc.write(now, data_register, Width::W32, 0x5A)?;
assert_eq!(soc.read(now, data_register, Width::W32)?, 0x5A);
# Ok::<(), Box<dyn std::error::Error>>(())
```

Nothing in `soc` itself knows which processor family the SoC belongs to: addresses and values are 64-bit, a `Cpu` is a name, an FPU flag, an interrupt count and a byte order, and the tables are whatever the vendor's SVD says. A bus access names its width, so a byte store into a 32-bit register touches only its lane, and a lane of a write-only register has nothing to merge with, so the rest lands as zero. Where the vendor's file disagrees with the manual the SoC corrects it on the builder: `reset` for a reset value, `write_modify` for a write rule, `field` for a field's shape, and `declare` for a field the file leaves out. A write rule replaces the register's and every field's, as a status register whose flags clear on a written zero needs. A model is reached as a field of the MCU that has it, so there's nothing to look up and nothing to downcast. An input the bus doesn't carry goes in through the MCU's `drive_` method for that field, which runs a closure over the model, its registers, and a `DeviceContext` at the instant given. Your own MCU writes one with `Bus::drive`, which hands over the registers and the context for a slot, as an access does.

### Time and events

Every access reaches the bus at an instant of the virtual clock, which the core's instruction count runs, and a model sees that instant and nothing else about time through the `DeviceContext` every method receives. An event is a timer on that clock: nothing ticks and nothing polls. The `time` and `soc` modules document how events are ordered and how a model raises an interrupt it can't know the number of.

## The core

The core is the crate's own. A family's module under `cpu` decodes the firmware's instructions and translates a block of them at a time to host code with Cranelift, quickly the first time and again with better code once the block turns out to run often. What knows no instruction set, the page table the translated code reads memory through, the cache of blocks and the code generator, is private to the crate. A family meets the runner through `emulator::Processor` and the `emulator::CoreBus` it's lent, whose documentation is the contract. `emulator::run` makes the target's processor with the constructor its `CoreSpec` carries, and runs it on a thread of its own. A processor is a plain value, so a process runs as many as it likes, one after another or at once. The run maps the target's memory, places the images and boots, then runs the core in stretches to the next instant the models asked for. A load or store to plain memory runs inline in the translated code. Every other access, a peripheral's or one the memory map doesn't answer, is a call into `Session::access` at its instant, with its instruction count and its program counter, before the next instruction, and the core carries on afterwards by itself when the run has nothing to do between. Every instruction counts exactly, so an access lands at the same instruction count on every host. A watched instruction is a breakpoint the core stops before, a watched range has its pages sent through the same calls, and a debugger is served between stretches. What the processor answers itself, its interrupt controller, its system control space and how it boots, is the family's module under `cpu`. The core maps memory a page at a time, so every region and alias starts and ends on one of its pages, and a run whose map doesn't is refused before the first instruction. A model's write to memory the guest can't write is refused as the guest's would be. The core's half of a snapshot is the processor's architectural state, with nothing of the process or of how the run was split into stretches in it, so a snapshot is the same bytes from every process and every host, and loads into a new core before its first instruction. A saved state the core couldn't have been in, with an active exception its table doesn't have say, is refused. Nothing crosses a process boundary.

## The machine and the session

A machine module declares a struct whose fields are what is on the board and implements `Board`: its name, and one `Target` per MCU, each **borrowing** the MCU the board has, with the `CoreSpec` that describes its core and the route from that MCU's interrupt numbers to the emulator's lines, and `mcu`, the same MCU reached without building anything, which is how the session reaches it on every access. The board keeps ownership throughout, so once the run has ended a caller holding it reaches any model on it by field. A `Session` is one firmware on one of those targets: the emulator calls `Session::access` for every peripheral access and `Session::stop` at every instant the models asked for, and the answer to each names the next. The `machine` and `session` modules document the rest.

A board may also have wires a host sits on. `Board::hosts` names each with the port `rigless run` listens on for it, and `Board::attach_host` hangs whatever the caller puts there on the serial port the board's description wires it to: a `chip::uart::Host` over a socket from the command line, a test's stand-in from a test, or bytes at chosen instants from a test framework. A serial port's far end is a `chip::uart::Peer` whichever it is, the port keeps its own time against its baud whatever is behind it, and the session asks every model to look outside at every stop of its target, so a byte a host wrote lands at the stop it was noticed at and wakes a firmware asleep on the line. That is the whole of the runtime's part in it: what feeds a wire decides whether a run is reproducible, and the runtime makes no mode of it.

The board's other edge faces the world. A board that drives or senses something implements `Edge` with two types of its own. `Outputs` is what the firmware drives, which `Edge::drive` reads off the pins, and `Signals` is what the world does to the board's transducers, which `Edge::sense` hands to each chip through the chip's own setters on the chip's own axes. A flight controller's outputs are pulses off its timer pins and what each commands on its speed controllers' curve. A pump controller's could be a valve's position and a relay. `Edge::OUTPUT_FIELDS` and `Edge::FIELDS` say how each crosses the link, field by field. `Conditions` is the third type, what can be wrong with the board itself: a chip that's stopped answering, one that senses a stuck, biased or noisy value while the chip beside it senses the truth, or a serial line that garbles what arrives. `Edge::condition` takes it and it takes effect at once, `Edge::CONDITIONS` says how it crosses, and every condition rests at zero, the board as built. A silence goes on the chip's `chip::Place` through its controller, so the controller takes the path it takes with nothing there, and a wrong reading is `sense` handing the chip what an `edge::Sensed` answers. The chip never learns of either. Anything random comes from `edge::draw`, a function of the run's seed, the name of what it's for and an index. No generator holds state, so setting one condition never moves another's noise. `Edge::draw` is where a board draws for the step about to run, and a board with nothing random leaves it out. Whatever steps a plant, in lockstep or otherwise, holds the board as its own type, runs the session to an instant, reads the one and writes the other, and runs on. The runtime itself steps no plant and names none. The session and `rigless run` reach a board's edge only as bytes, through `edge::Coupled`. `Board::edge` answers it, and a board that drives and senses nothing has no edge.

`rigless run` is the session as a command. It builds the machine by name, starts the emulator on one of its MCUs, runs to `--until` of virtual time, or until you interrupt it when `--until` isn't given, and prints one line per access, then the stop, the resets, and the unmapped, refused, unimplemented, unrouted and told reports:

```text
read  t=20 n=5 pc=0x080001c0 at=0x40023830 w=32 v=0x00100000
write t=28 n=7 pc=0x080001c6 at=0x40023830 w=32 v=0x00100001
stop t=2000000 n=500000 reason=deadline
unmapped at=0x4002381c pc=0x080001e0
```

`t` is the virtual time in nanoseconds and `n` the instruction count, both as the core reported them. `pc` is the instruction that made the access, or `debugger` for a debugger's. The reports come from the session, and your own binary or test gets them the same way. `Session::unmapped` is a list of `Unmapped` entries that print as the line above, in hex, so an address no model claims reads as an address. A reset prints as `reset t=<ns> n=<count> cause=core` when the core asked for it, and names the instance when a model did, like `cause=IWDG` for the watchdog. A `told` line names the instance, the register, the instant and the manual's reason, once for each.

### The debugger

`--gdb [HOST:]PORT` serves GDB at that address and the target runs from reset, so a debugger attaches whenever it likes and detaches without ending the run. A port alone is on the loopback interface, and a host is an IP address, IPv6 in brackets like `[::1]:3333`. `--gdb 0.0.0.0:3333` serves every interface, and then anyone who can reach the machine can read and write the emulated core, so an SSH tunnel to the loopback is usually the better way in from another machine. `--wait-for-debugger` holds the core before its first instruction instead, until a debugger continues, steps, detaches or kills it. A connection that closes without a word, like a port probe's, leaves the core as it found it. The command says which it did and where once it's listening there, `serving GDB on 127.0.0.1:3333` or `waiting for a debugger on 127.0.0.1:3333`, so `--gdb 0` tells you the port the system picked.

```text
rigless run --machine <name> --gdb 3333 firmware.elf
gdb-multiarch firmware.elf
(gdb) target remote :3333
(gdb) break bear_bones::tim2_update
(gdb) continue
```

**Set a breakpoint before the first continue.** Virtual time isn't wall-clock time: the idle warp skips every idle wait instantly, so a `continue` with nothing to stop at reaches the deadline and ends the run before you can type again. That is why `--until` has no default. A breakpoint is the only thing that holds the CPU for you, and a name that doesn't resolve isn't one: GDB prints `Function ... not defined.` and continues anyway. The bear-bones firmware's symbols are `reset`, `bear_bones::tim2_update`, and `bear_bones::default_handler`. There's no `main`.

#### Connecting and detaching

The runtime serves the debugger itself, on the core's thread between stretches, so virtual time stands still while the debugger holds the core, and a debugger that attaches finds the core held where it was. It serves one debugger at a time, so one that connects while another is attached is refused and closed at once. Once that one goes, the next can connect straight away. `detach` lets the run go on. Under `target remote` that closes the connection, and under `target extended-remote` GDB stays connected, as it expects to, and `attach 1` holds the core again wherever it ran to. Until then the server answers nothing that asks about the core, so `monitor reset` asks you to attach first, while `monitor help`, `monitor halt`, `monitor peek on` and `monitor peek off` still answer. `disconnect` leaves the core held where it was for the next `target remote` to find, with your breakpoints and watchpoints taken out and its halting debug still on, as a probe that lost its host leaves it. A GDB that crashes leaves the core as it was too: held if it was stopped, and running on with its halting debug still on if it was running, so a `BKPT` it reaches then holds it for the next debugger. `kill` under `target remote` lets the run go on. Under `target extended-remote`, GDB's own `run` and `starti` reset the machine as `monitor reset` does and read everything again, and `kill` leaves the core held for the next `run` or `attach`, or lets it run on once GDB detaches or goes.

#### Breakpoints, watchpoints and stepping

Breakpoints, watchpoints on writes, reads and both (`watch`, `rwatch` and `awatch`), `stepi`, `finish`, `info registers` and `load` work as they would against a probe. The registers are the family's, its system registers among them and its floating-point registers when the core has them, and a step runs one instruction with interrupts held off, so one that comes due while you step is taken only once you continue. GDB's own `record full` works over the server, so `reverse-stepi` and `reverse-continue` go back through what GDB recorded.

**`next` over an idle loop never returns.** A loop around an idle wait is one source line, so `next` has no other line to reach and steps the CPU around it forever, and a step holds interrupts off, so the one that would take it somewhere else waits until the run goes on. `stepi` returns, and `next` doesn't until you press Ctrl-C, which stops it at its next step. Step into the handler with a breakpoint instead.

#### Halting debug

While a debugger is attached the core's halting debug is on, as a probe turns it on. The firmware reads `DHCSR.C_DEBUGEN` set, and a `BKPT` stops the core at itself, which GDB reports as `SIGTRAP`. Continue and it stops there again, so step past it with `set $pc = $pc + 2`. Once the debugger detaches or kills the run, a `BKPT` does what it does with no debugger, which is HardFault unless the firmware turned DebugMonitor on. One that disconnects leaves halting debug on, so a `BKPT` the core reaches after that holds it for the next debugger. Since that changes what the firmware does, the run prints `debugger attached t=<ns> n=<count>` where a debugger takes the core and `debugger detached t=<ns> n=<count>` where it lets it go or goes, and a run nobody debugs prints neither. In non-stop mode, a `detach` or a `kill` while the core runs holds it first, so it's let go at a stop, as it is in all-stop mode. A driver of your own hears both in `Driver::on_debugger_attached`.

#### Memory and registers

A debugger that stops the core and reads memory or the core's registers leaves the run the same run, a peripheral's registers included, since your reads are peeks. Writing a register or memory changes the run from that stop on, and the run's output doesn't show it, unless it reaches a peripheral, below. A run you can take back keeps it, to put it back where you made it. `load` writes the flash all the same, and from then on the run refuses a snapshot, since a snapshot keeps the flash only as the images placed it. `compare-sections` checks what's in memory against the ELF with a CRC the server works out, which takes milliseconds where reading it all back took seconds.

A peripheral write the debugger makes itself, through `set`, reaches the models like any other access but with no program counter, since no instruction made it. It appears in the run's output with `pc=debugger`. An address nothing covers is an error to GDB, `Cannot access memory`, and no gap in the run's report, since the run never went there.

#### Monitor commands

`monitor reset` resets the machine as the firmware's own request does and holds the core at its reset handler. GDB keeps the registers it had already read, so ask again with `maintenance flush register-cache`. `monitor reset halt` and `monitor reset init`, which IDEs' launch templates send, and `monitor system_reset`, which other emulators' scripts send, do the same. `monitor halt` says whether the core is held, which it is whenever GDB has it, and `monitor help` lists every command. None of them writes a file on your machine or starts a server, so the debugger's port reaches the emulated core and nothing else.

#### Watchpoints and instruction counts

Set as many watchpoints as you like, up to 64 MiB of memory between them, where silicon gives you four comparators. Every access to each page of memory a watchpoint touches is checked while it's in, so one over a lot of memory slows the run, as `monitor help` says.

A watchpoint stops the core before the access it watches happens, with the instruction that makes it not run yet. GDB then takes the watchpoint out, steps that instruction and tells you, so you land right after the instruction that made the access, as on any target that stops before it. Some instructions make more than one access: a load or store multiple, an `LDRD` or `STRD`, an unaligned access split into bytes, and a store through a bit-band alias, which reads the word before it writes it back. One whose later access is watched runs again from its first access when you go on, as the architecture lets a core restart a load or store multiple, so its earlier accesses to plain memory happen twice, with the same values, and nothing sees it. Once one of its earlier accesses has reached a peripheral, though, the core doesn't stop before the watched one. The instruction finishes and the core stops right after it, so a peripheral never sees an access twice and the run's output never gains a line. GDB steps once more from there, as it does after every watch stop, so you land one instruction further on. A core family that doesn't ask before an access stops after it the same way, as [Adding a core family](#adding-a-core-family) says.

`monitor icount` prints how many instructions the core has retired and the virtual time it's at, like `icount 57594 at 2230340 ns`. GDB reads the same count as `$icount`, a register of the run's own after the family's, so `p $icount`, `display $icount` and `break bear_bones::tim2_update if $icount > 57600` work. You can't write it. The run is deterministic, so a count is a place you can get back to. `monitor until-icount 57594` and then `continue` stops the core once it has retired exactly that many instructions, and every access in the run's output carries its count as `n`, so take one from there. `monitor until-time 5300us` stops it at that instant of virtual time, waiting or not. GDB hears either stop as `SIGTRAP`, and the run with the stop is the run without it. A count or an instant the run has passed is refused.

#### Peeks

A read you make, through `x`, `print` or an IDE's register view, is a peek. It answers what the firmware's read would answer at that instant and changes nothing. A flag a read clears stays set, the byte waiting in a serial port's data register stays there for the firmware, and no model and no driver hears of it, so the run is the one it would be with nobody looking. A peripheral whose clock is off shows you what its registers hold, where the firmware's own read of it answers zero, and a write you make to one is dropped, as the firmware's is. `monitor peek off` makes your reads the firmware's for the rest of the run, side effects and all, each in the run's output with `pc=debugger`, for when you want a read to do what it does on silicon. `monitor peek on` makes them peeks again. The server's own reads, like the CRC `compare-sections` asks for, always peek.

#### Vector catch and the memory map

`monitor vector-catch hard bus` has the core halt when it takes a HardFault or a BusFault, before the handler's first instruction, with the faulting context on the stack where the handler would find it, rather than you finding the core spinning in the handler later. The words are `DEMCR`'s `VC_` bits: `hard`, `int` for a fault on exception entry or return or on the vector read, `bus`, `state` for an undefined instruction or a bad state, `check` for an unaligned access or a divide by zero, `nocp`, `mm`, and `reset`, which halts the core at the reset handler after the firmware's own reset. `all` or `none` stand alone and do what they say, and with no words it says what's caught. Writing `DEMCR` yourself does the same, and GDB hears the halt as `SIGTRAP`. `DFSR` says what halted the core: `BKPT` for a `BKPT` or a breakpoint, `DWTTRAP` for a watchpoint, `VCATCH` for a vector catch and `HALTED` for your own halt or step. Write one to a bit to clear it.

GDB reads a memory map from the run: the flash in the blocks it erases in, the RAM, the system memory as read-only, and every peripheral and the core's own space, so `info mem` lists them all. `load` then erases the flash a block at a time and writes it with `vFlashErase` and `vFlashWrite`, as it would through a probe, and the run refuses a snapshot after it, as it does after any write to the flash. With the map GDB refuses an address it doesn't list, which is one nothing answers anyway, and writes the flash only through `load`, so a `set` there says writing to flash is forbidden.

You reach the bit-band aliases as the core does. A word at an alias reads as the one bit it stands for, and writing one sets or clears that bit. A write to a peripheral's alias peeks the register and writes it back once with the bit changed, so the model hears one write of the register, as it would from the firmware.

#### Host calls

`rigless run --semihosting` serves the firmware's semihosting calls, the ones of Arm's "Semihosting for AArch32 and AArch64" a firmware's console, clock, exit and files take: `SYS_OPEN`, `SYS_CLOSE`, `SYS_WRITEC`, `SYS_WRITE0`, `SYS_WRITE`, `SYS_READ`, `SYS_ISTTY`, `SYS_SEEK`, `SYS_FLEN`, `SYS_CLOCK`, `SYS_TIME`, `SYS_ERRNO`, `SYS_EXIT`, `SYS_EXIT_EXTENDED`, `SYS_ELAPSED` and `SYS_TICKFREQ`. Every other call, `SYS_READC`, `SYS_ISERROR`, `SYS_TMPNAM`, `SYS_REMOVE`, `SYS_RENAME`, `SYS_SYSTEM`, `SYS_GET_CMDLINE` and `SYS_HEAPINFO` among them, answers -1, as a call that failed does. What it writes to the console is printed as it runs, as `console t=<ns> n=<count> text=<bytes>` lines among the accesses, with a line break in what it wrote shown as `\n`, and with GDB attached GDB prints it too. Its clock and its time read virtual time, so two runs read the same, and its exit ends the run with its status, which the last line says as `exit code=N` and the process exits with. An attached GDB hears the firmware exit with that status. Its files are GDB's: with GDB attached, an open, read, write, seek or close happens on GDB's host, in GDB's working directory, and with no debugger attached it fails, so a firmware never reaches the files of the machine the emulator runs on. Without the flag a host call is the breakpoint instruction it is, which halts the core under a debugger and faults without one, as it does on a part with nothing serving it.

`Driver::on_console` hands a driver of your own each write to the console, and `Finished::exit_status` the status the firmware ended the run with. `Start::new` makes a run's start, and `Start::semihosting` turns host calls on.

#### Output ports

`--trace [HOST:]PORT` streams the core's own trace there, which on an ARMv7-M core is its ITM: what the firmware writes to a stimulus port it enabled goes out as the Instrumentation packet a viewer of the ITM's packet protocol reads, as it would from a probe on the trace pin. The port takes one client at a time, and with none connected the trace goes, so the run is the same run whether anyone's listening, and never waits on a slow client. A stimulus port reads ready whenever it's enabled, since the port takes every packet at once. `Start::trace` is the listener a run of your own streams it to. The ITM is the part's with or without the flag, so a firmware that turns it on and writes to it runs as it does on silicon, and the packets go nowhere.

`--rtt [HOST:]PORT` streams the firmware's RTT terminal, its up-channel 0, there. The run finds the control block by the firmware's symbol for it, or by its ID in RAM when the firmware has no symbols, and drains it every virtual millisecond, at whole milliseconds, writing the read offset back as a probe does. Writing the read offset is what makes room in the buffer, so a firmware that waits for room runs differently with the flag than without, as it does on a board with a probe attached and one without, and two runs with it are the same run. `Start::rtt` is the listener for a run of your own.

#### Conditions and dprintf

GDB hands the run a breakpoint's condition and a `dprintf`'s print to work out itself, as agent expressions, so the core never comes back to GDB for a pass that doesn't stop. `break bear_bones::tim2_update if (*(int *)0x40020014 & 0x20) == 0x20` stops only at the passes where the pin is high, and `info breakpoints` says `(target evals)`. Over 20 virtual seconds of bear-bones, about 20,000 passes, that took 1.6 s where GDB testing it at every pass took 4.8 s, and the run with no breakpoint at all 1.5 s. `set breakpoint condition-evaluation host` has GDB test it again. A condition reads registers, `$icount` among them, and memory through peeks, so it leaves the run the same run. One that can't be worked out, a read where nothing answers say, stops the core at the breakpoint and says why.

`set dprintf-style agent` then `dprintf bear_bones::tim2_update,"odr %#x\n", *(int *)0x40020014` prints at every pass and never stops the core. It takes `%d`, `%i`, `%u`, `%x`, `%X`, `%o`, `%c`, `%s`, `%p` and `%%`, with flags, a width, a precision and a length, and an `int` and a `long` are 32 bits, as on the targets that run here.

#### Non-stop and live reads

`set non-stop on` before `target remote` has GDB leave the core running while you look. `continue &` runs it in the background, and `x`, `print` and an IDE's live watch read memory while it runs, as peeks, so watching a value change leaves the run the same run. `interrupt` stops it, and a stop comes to GDB as it happens, without GDB waiting on the core. While the core runs, its registers, writes to memory and `monitor` wait for a stop, and a breakpoint you set goes in at once.

In non-stop mode the core is one thread, whatever kernel it runs, since the run can't stop one of a kernel's threads and leave the others going. The thread list comes back in all-stop mode. GDB doesn't wait on the core in non-stop mode, so it hears nothing of the firmware's host calls: the console still prints in the run's output, but not in GDB's, and a file call fails as it does with no debugger. What a `dprintf` prints, and why a breakpoint's condition couldn't be worked out, go to the run's output as `dprintf t=<ns> n=<count> text=<bytes>` lines, each once however often you take the run back over it. A driver of your own hears them in `Driver::on_debugger_print`, never in `Driver::on_console`.

#### More than one target

A run steps one target of a board, so each target of a board with more than one gets a run and a port of its own, `rigless run --target <name> --gdb <port>`, and one GDB holds them all as inferiors, each over its own connection:

```text
(gdb) target extended-remote :3333
(gdb) add-inferior -exec firmware.elf
(gdb) inferior 2
(gdb) target extended-remote :3334
(gdb) info inferiors
```

A breakpoint goes in every inferior whose program has it, and `continue` runs the inferior you're in. Under an engine's lockstep, with `--link`, a target you hold doesn't answer its step until you let it go, so the engine holds the others with it. One server that stops every core at the same instant needs a run of several targets, which the runtime doesn't do yet.

#### Going back

`monitor save NAME` keeps the run where it stands, in memory, for as long as your debugger's attached, and `monitor restore NAME` takes the run back there, or forward to it. `monitor snapshots` lists what you kept. GDB keeps the registers and memory it read across a monitor command, so after a restore ask again with `maintenance flush register-cache` and `maintenance flush dcache`.

`reverse-stepi`, `reverse-step`, `reverse-next`, `reverse-finish` and `reverse-continue` work with no `record` in GDB. The run keeps a checkpoint when you attach and every 4,194,304 instructions after, 64 at most, the older ones thinned out as it goes, and going back is putting the run back to one and running forward from there. `reverse-stepi` lands right before the instruction that came next, an exception the core took before it included. `reverse-continue` stops where going forward would have stopped: a breakpoint, a watchpoint, or a halt point, so a `monitor until-icount N` with an N the run has passed is a place it stops at. A watchpoint lands you on the store that last wrote the word, with its old value in it. With nothing to stop at, it goes back to where you attached, and GDB says it reached the end of the recorded history.

The run is deterministic, so going forward again is the same run, as long as what you did comes back where you did it: your writes, the registers you set, your steps, which hold interrupts off for their instruction, a reset, and what GDB answered the firmware's file calls with all do. So do the bytes a host wrote to a serial port, at the same instants. The run's output prints each instant once, so a session you went back and forth in prints what one that never went back prints, and the trace and RTT ports send nothing twice. Something the history doesn't have, a write or a step it never saw there, takes the run a new way from there: what came after it goes, the snapshots you saved after it with it, and the run's output carries on from there, its counts going back.

A run under `--link` can't go back, since the engine's inputs aren't kept, and the monitor says so. A driver of your own lets a debugger take the run back with `Driver::may_rewind`, which says so only when its calls change nothing in the run but which instant `on_stop` answers. A snapshot, the file an engine asks for, is still refused once a debugger has written memory the guest can't. A checkpoint keeps it as you wrote it, since it's yours and never a file.

#### Threads

`rigless run --gdb` shows GDB every thread of a firmware whose kernel [`rigless-rtos`](https://crates.io/crates/rigless-rtos) knows, ChibiOS/RT or FreeRTOS. GDB looks up the kernel's symbols for the run when it loads the firmware's, so there's nothing to set: `info threads` lists each thread with its name and what it's doing, like `(UART1, WTOREVT)` under ChibiOS or `(delay, Blocked)` under FreeRTOS, and `thread 3` then `bt` gives you that thread's stack. The thread that runs has the core's registers. Every other one has what its kernel saved when it switched away from it, and you can't write those, so a register the save doesn't keep reads as `<unavailable>` and `set $r4 = 1` there is refused. The core runs whichever thread the kernel runs, so `stepi`, `next` or `continue` in another thread steps or runs the one that runs, and the stop names it. The run walks the kernel's lists once a stop, through peeks, so looking at threads leaves the run the same run. Before the kernel starts, or when its memory doesn't check out, the core is the one thread, as it is with no kernel at all. GDB first meets the core as thread 1, before it's looked anything up, so it can show thread 1 beside the thread that runs until the next stop, when it says thread 1 exited.

The kernels are the binary's, not the runtime's, and `rigless-rtos`'s README says what each one needs of a build. A run of your own shows the threads of the kernels you put in `Start::kernels`, each a `WalkThreads`. A kernel names the symbols it asks GDB for, and from where GDB found them, the core's registers and a way to peek at memory, it walks its threads: each one's id, name and state, and what its kernel saved of its registers, by the names the target description gives them. The run asks for every kernel's symbols in order and walks the first whose required symbols GDB found. With no kernels the core is the one thread.

## Building your own device

Here's how to put your own part on rigless with nothing but the crate as a dependency: a peripheral, an SoC that holds it, a board, and a firmware that runs on it. The part is made up on purpose, so nothing here is specific to any real silicon. Its core is from the one family the crate carries today, ARMv7-M, so the board fills in `machine::armv7m::Core` and you build the firmware for that family's targets. Another family can live in a crate of your own, as [Adding a core family](#adding-a-core-family) says. Every Rust block for the host side is a doctest, so it compiles against this crate on every test run. The two that boot the firmware are `no_run`, so build the image first. The firmware blocks are `ignore`, since they're built for the core, not the host.

```text
cargo add rigless --no-default-features
```

Turn the default features off. That drops the CLI, the SVD generator and the SoCs and boards we ship, and leaves you the runtime. The core is always built in, and it's Rust, so there's nothing else to install.

### The peripheral

A peripheral is a `Device` written against a register layout. The register file already stores what the firmware wrote and applies the access rules, so your model only does the parts storage can't: what a write to the data register means, a status bit the firmware isn't allowed to write, and something happening later.

The register table here is written by hand, the way the generator would emit it, which is fine for a register or three. For a part with more, write an SVD for it, a made-up part included, and `rigless svd generate` writes the tables, the `Register` enums and the field constants for you, as [the generator](#the-generator) says.

```rust
use rigless::device::{Device, DeviceContext, Level, RegisterFile};
use rigless::mcu::{Mcu, Single};
use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
use rigless::time::{EventId, VirtualNanos};

// The register table, the way the generator would emit it from a vendor's SVD. Written by hand here
// because there's no vendor. `SR` is read-only to the firmware: the model sets and clears it.
static REGISTERS: [RegisterSpec; 3] = [
    RegisterSpec { name: "CR", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
    RegisterSpec { name: "SR", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
    RegisterSpec { name: "DR", offset: 0x8, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Register { Cr, Sr, Dr }
impl Layout for Register {
    const REGISTERS: &'static [RegisterSpec] = &REGISTERS;
    fn from_index(index: usize) -> Option<Self> { [Self::Cr, Self::Sr, Self::Dr].get(index).copied() }
    fn index(self) -> usize { self as usize }
}
const EN: u64 = 1;
const IE: u64 = 2;
const BUSY: u64 = 1;
const DONE: u64 = 2;
/// The beacon's one interrupt, by its position in the instance's list.
const FRAME_DONE: usize = 0;
/// The beacon's one request output, in its own numbering: high while a byte is on the wire.
const SENT: usize = 0;
/// One byte occupies the beacon for a millisecond of virtual time.
const FRAME: VirtualNanos = VirtualNanos(1_000_000);

/// The model keeps what the registers can't: the bytes it sent, and the event that ends the frame.
#[derive(Debug, Default)]
struct Beacon { sent: Vec<u8>, dropped: u32, frame_end: Option<EventId> }
impl Device for Beacon {
    type Register = Register;
    fn read(&mut self, register: Register, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) -> Option<u64> {
        if register != Register::Sr { return None; }
        let status = registers.get(Register::Sr);
        if status & DONE != 0 { registers.set(Register::Sr, status & !DONE); context.lower(FRAME_DONE); }
        Some(status)
    }
    fn write(&mut self, register: Register, value: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        if register != Register::Dr { return; }
        let enabled = registers.get(Register::Cr) & EN != 0;
        let busy = registers.get(Register::Sr) & BUSY != 0;
        if !enabled || busy { self.dropped += 1; return; }
        self.sent.push(value as u8);
        registers.set(Register::Sr, BUSY);
        context.request(SENT, Level::High);
        self.frame_end = Some(context.schedule(VirtualNanos(context.now().0 + FRAME.0)));
    }
    fn on_event(&mut self, event: EventId, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        if self.frame_end != Some(event) { return; }
        self.frame_end = None;
        registers.set(Register::Sr, DONE);
        context.request(SENT, Level::Low);
        if registers.get(Register::Cr) & IE != 0 { context.raise(FRAME_DONE); }
    }
    fn reset(&mut self) {
        self.frame_end = None;
    }
}

/// Where the one instance lives. A bus is a core, a memory map, and the instances at their addresses.
const BEACON: Peripheral = Peripheral { name: "BEACON", base: 0x4000_0000, size: 0x100, interrupts: &[3], registers: &REGISTERS };
const CPU: Cpu = Cpu { name: "lantern-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
static MEMORY: [Region; 2] = [Region::flash("flash", 0x0800_0000, 64 << 10), Region::ram("sram", 0x2000_0000, 16 << 10)];

// Drive it directly, like a unit test of one model would: a byte goes out, the beacon is busy for a
// frame, and a second byte written in the meantime gets dropped.
let mut soc = Single::new(Bus::builder(CPU, &MEMORY, [BEACON]), "BEACON", Beacon::default())?;
let at = |register: Register| BEACON.base + u64::from(register.spec().offset);
let start = VirtualNanos(0);
soc.write(start, at(Register::Cr), Width::W32, EN)?;
soc.write(start, at(Register::Dr), Width::W32, u64::from(b'h'))?;
assert_eq!(soc.read(start, at(Register::Sr), Width::W32)?, BUSY);
soc.write(start, at(Register::Dr), Width::W32, u64::from(b'i'))?;
soc.advance_to(FRAME);
// A read can have side effects too: `DONE` is set when the frame ends and cleared by reading `SR`.
assert_eq!(soc.read(FRAME, at(Register::Sr), Width::W32)?, DONE, "the frame is over");
assert_eq!(soc.read(FRAME, at(Register::Sr), Width::W32)?, 0, "reading SR cleared DONE");
{
    let (beacon, _registers) = soc.pair();
    assert_eq!((beacon.sent.as_slice(), beacon.dropped), (b"h".as_slice(), 1));
}

// With `IE` set, the end of a frame raises the beacon's interrupt, and reading `SR` lowers it. The
// read above already drove the line low once, and the runtime reports every level a model drives.
soc.drain_interrupts();
soc.write(FRAME, at(Register::Cr), Width::W32, EN | IE)?;
soc.write(FRAME, at(Register::Dr), Width::W32, u64::from(b'i'))?;
let second = VirtualNanos(2 * FRAME.0);
soc.advance_to(second);
let raised: Vec<(u16, Level)> = soc.drain_interrupts().into_iter().map(|change| (change.interrupt, change.level)).collect();
assert_eq!(raised, [(3, Level::High)], "line 3 of the part, as BEACON declares it");
soc.read(second, at(Register::Sr), Width::W32)?;
let lowered: Vec<Level> = soc.drain_interrupts().into_iter().map(|change| change.level).collect();
assert_eq!(lowered, [Level::Low]);
# Ok::<(), Box<dyn std::error::Error>>(())
```

Three things to notice. `SR` is `Access::ReadOnly`, so the register file rejects the firmware's writes and only `registers.set` can move the flag. `context.schedule` takes an absolute instant and gives you an `EventId`. `on_event` fires at exactly that instant with the same id, whether or not the firmware touched the peripheral in the meantime. There's no cancel. Keep the last id you scheduled and ignore anything else. And asking for an instant you already have an event at gives you the same id back, not a second event, so re-scheduling on every access is free. When the part resets, the reset drops every event, so `reset` forgets the frame the beacon was sending. Leave `reset` out of a model that keeps nothing a reset clears.

Reads work the same way. `Device::read` returns `Some(value)` to answer a read yourself, or `None` to let the register file serve what it holds. Return `None` for anything the file already has right, which is most registers. Where a read has a side effect on the whole register, say so in the layout instead: `ReadAction::Clear` or `ReadAction::Set`, and the file does it when the model returns `None`. Anything narrower is the model's job, like the `DONE` bit above, which clears on a read of `SR` while `BUSY` stays: read the file, change what changed, and hand back what the firmware should see.

A debugger's read is a peek instead, `Device::peek`, which answers what your `read` would with nothing changed. It takes `&self` and the register file shared, so it can't change a thing, and `None` serves what the file holds with no read action taken. The beacon needs none, since its `read` answers what `SR` holds. Write one only where your `read` answers something the file doesn't hold, like the byte at the front of a FIFO or a counter that runs on time.

### The SoC

An SoC is a struct with your models as fields, plus a `Bus` that knows which address belongs to which instance and holds the registers. The bus doesn't own models. `split` is where a slot number turns into a field, and `check` makes sure each field's layout matches its slot. If you wire a slot to the wrong field it fails when you build the part, not on the first access.

```rust
# use rigless::device::{Device, DeviceContext, Level, RegisterFile};
# use rigless::mcu::{Mcu, Single};
# use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
# use rigless::time::{EventId, VirtualNanos};
#
# // The register table, the way the generator would emit it from a vendor's SVD. Written by hand here
# // because there's no vendor. `SR` is read-only to the firmware: the model sets and clears it.
# static REGISTERS: [RegisterSpec; 3] = [
#     RegisterSpec { name: "CR", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "SR", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "DR", offset: 0x8, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum Register { Cr, Sr, Dr }
# impl Layout for Register {
#     const REGISTERS: &'static [RegisterSpec] = &REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { [Self::Cr, Self::Sr, Self::Dr].get(index).copied() }
#     fn index(self) -> usize { self as usize }
# }
# const EN: u64 = 1;
# const IE: u64 = 2;
# const BUSY: u64 = 1;
# const DONE: u64 = 2;
# /// The beacon's one interrupt, by its position in the instance's list.
# const FRAME_DONE: usize = 0;
# /// The beacon's one request output, in its own numbering: high while a byte is on the wire.
# const SENT: usize = 0;
# /// One byte occupies the beacon for a millisecond of virtual time.
# const FRAME: VirtualNanos = VirtualNanos(1_000_000);
#
# /// The model keeps what the registers can't: the bytes it sent, and the event that ends the frame.
# #[derive(Debug, Default)]
# struct Beacon { sent: Vec<u8>, dropped: u32, frame_end: Option<EventId> }
# impl Device for Beacon {
#     type Register = Register;
#     fn read(&mut self, register: Register, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) -> Option<u64> {
#         if register != Register::Sr { return None; }
#         let status = registers.get(Register::Sr);
#         if status & DONE != 0 { registers.set(Register::Sr, status & !DONE); context.lower(FRAME_DONE); }
#         Some(status)
#     }
#     fn write(&mut self, register: Register, value: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if register != Register::Dr { return; }
#         let enabled = registers.get(Register::Cr) & EN != 0;
#         let busy = registers.get(Register::Sr) & BUSY != 0;
#         if !enabled || busy { self.dropped += 1; return; }
#         self.sent.push(value as u8);
#         registers.set(Register::Sr, BUSY);
#         context.request(SENT, Level::High);
#         self.frame_end = Some(context.schedule(VirtualNanos(context.now().0 + FRAME.0)));
#     }
#     fn on_event(&mut self, event: EventId, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if self.frame_end != Some(event) { return; }
#         self.frame_end = None;
#         registers.set(Register::Sr, DONE);
#         context.request(SENT, Level::Low);
#         if registers.get(Register::Cr) & IE != 0 { context.raise(FRAME_DONE); }
#     }
# }
#
# /// Where the one instance lives. A bus is a core, a memory map, and the instances at their addresses.
# const BEACON: Peripheral = Peripheral { name: "BEACON", base: 0x4000_0000, size: 0x100, interrupts: &[3], registers: &REGISTERS };
# const CPU: Cpu = Cpu { name: "lantern-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
# static MEMORY: [Region; 2] = [Region::flash("flash", 0x0800_0000, 64 << 10), Region::ram("sram", 0x2000_0000, 16 << 10)];
#
use rigless::device::{ErasedDevice, RequestChange};
use rigless::soc::Error;

/// A second peripheral: one read-only register that counts the bytes the beacon has sent. It never
/// touches the beacon's registers. It listens to the beacon's request line, in the next sections.
static TALLY_REGISTERS: [RegisterSpec; 1] = [
    RegisterSpec { name: "COUNT", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TallyRegister { Count }
impl Layout for TallyRegister {
    const REGISTERS: &'static [RegisterSpec] = &TALLY_REGISTERS;
    fn from_index(index: usize) -> Option<Self> { (index == 0).then_some(Self::Count) }
    fn index(self) -> usize { 0 }
}
const TALLY: Peripheral = Peripheral { name: "TALLY", base: 0x4000_0100, size: 0x100, interrupts: &[], registers: &TALLY_REGISTERS };
#[derive(Debug, Default)]
struct Tally;
impl Device for Tally {
    type Register = TallyRegister;
    fn on_request(&mut self, change: RequestChange, registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) {
        // A sink hears every request on the part. This one wants the beacon's, rising.
        if change.instance == BEACON.name && change.request == SENT && change.level == Level::High {
            registers.set(TallyRegister::Count, registers.get(TallyRegister::Count) + 1);
        }
    }
}

/// The part, complete: the bus, and the models on it as fields.
#[derive(Debug)]
struct Lantern { bus: Bus, beacon: Beacon, beacon_slot: usize, tally: Tally, tally_slot: usize }
impl Lantern {
    fn new() -> Result<Self, Error> {
        // `reads_requests` is the wiring: the tally hears what the beacon drives.
        let bus = Bus::builder(CPU, &MEMORY, [BEACON, TALLY]).reads_requests(TALLY.name).build()?;
        let beacon_slot = bus.slot_of(BEACON.name).ok_or(Error::UnknownInstance { instance: BEACON.name })?;
        let tally_slot = bus.slot_of(TALLY.name).ok_or(Error::UnknownInstance { instance: TALLY.name })?;
        let mut chip = Self { bus, beacon: Beacon::default(), beacon_slot, tally: Tally, tally_slot };
        chip.check()?;
        Ok(chip)
    }
}
impl Mcu for Lantern {
    fn bus(&self) -> &Bus { &self.bus }
    fn bus_mut(&mut self) -> &mut Bus { &mut self.bus }
    fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
        let Self { bus, beacon, beacon_slot, tally, tally_slot } = self;
        let model: Option<&mut dyn ErasedDevice> = if slot == *beacon_slot { Some(beacon) } else if slot == *tally_slot { Some(tally) } else { None };
        (bus, model)
    }
}

let mut chip = Lantern::new()?;
let at = |register: Register| BEACON.base + u64::from(register.spec().offset);
chip.write(VirtualNanos(0), at(Register::Cr), Width::W32, EN)?;
chip.write(VirtualNanos(0), at(Register::Dr), Width::W32, u64::from(b'h'))?;
chip.advance_to(FRAME);
assert_eq!(chip.read(FRAME, at(Register::Sr), Width::W32)?, DONE);
assert_eq!(chip.beacon.sent, b"h");
# Ok::<(), Box<dyn std::error::Error>>(())
```

`Mcu` gives you `read`, `write` and `advance_to` at whatever instant you pick, which is all a unit test of the part needs. The tally is what a second peripheral costs: another field, another slot, another arm in `split`. That's it.

A flash `Region` is erased whole unless you say how it erases. `Region::flash(...).erased_in(&[...])` gives its erase blocks, which add up to its size, and GDB's `load` then erases a block at a time, as it does through a probe. Without them the memory map a debugger reads has the flash as one block, so a `load` of a few bytes erases all of it.

### The board

A board is what a `Session` runs. The chip is complete on its own. The board adds whatever sits around it: crystals, a supply rail, chips on the buses. This one adds nothing. The one thing every board has to provide is the description of its core, and that comes from the core family module, `armv7m::Core`: one typed field for each thing the family needs to make a processor, so a board can't leave one out, and the family checks each value when the run starts.

```rust
# use rigless::device::{Device, DeviceContext, Level, RegisterFile};
# use rigless::mcu::{Mcu, Single};
# use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
# use rigless::time::{EventId, VirtualNanos};
#
# // The register table, the way the generator would emit it from a vendor's SVD. Written by hand here
# // because there's no vendor. `SR` is read-only to the firmware: the model sets and clears it.
# static REGISTERS: [RegisterSpec; 3] = [
#     RegisterSpec { name: "CR", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "SR", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "DR", offset: 0x8, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum Register { Cr, Sr, Dr }
# impl Layout for Register {
#     const REGISTERS: &'static [RegisterSpec] = &REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { [Self::Cr, Self::Sr, Self::Dr].get(index).copied() }
#     fn index(self) -> usize { self as usize }
# }
# const EN: u64 = 1;
# const IE: u64 = 2;
# const BUSY: u64 = 1;
# const DONE: u64 = 2;
# /// The beacon's one interrupt, by its position in the instance's list.
# const FRAME_DONE: usize = 0;
# /// The beacon's one request output, in its own numbering: high while a byte is on the wire.
# const SENT: usize = 0;
# /// One byte occupies the beacon for a millisecond of virtual time.
# const FRAME: VirtualNanos = VirtualNanos(1_000_000);
#
# /// The model keeps what the registers can't: the bytes it sent, and the event that ends the frame.
# #[derive(Debug, Default)]
# struct Beacon { sent: Vec<u8>, dropped: u32, frame_end: Option<EventId> }
# impl Device for Beacon {
#     type Register = Register;
#     fn read(&mut self, register: Register, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) -> Option<u64> {
#         if register != Register::Sr { return None; }
#         let status = registers.get(Register::Sr);
#         if status & DONE != 0 { registers.set(Register::Sr, status & !DONE); context.lower(FRAME_DONE); }
#         Some(status)
#     }
#     fn write(&mut self, register: Register, value: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if register != Register::Dr { return; }
#         let enabled = registers.get(Register::Cr) & EN != 0;
#         let busy = registers.get(Register::Sr) & BUSY != 0;
#         if !enabled || busy { self.dropped += 1; return; }
#         self.sent.push(value as u8);
#         registers.set(Register::Sr, BUSY);
#         context.request(SENT, Level::High);
#         self.frame_end = Some(context.schedule(VirtualNanos(context.now().0 + FRAME.0)));
#     }
#     fn on_event(&mut self, event: EventId, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if self.frame_end != Some(event) { return; }
#         self.frame_end = None;
#         registers.set(Register::Sr, DONE);
#         context.request(SENT, Level::Low);
#         if registers.get(Register::Cr) & IE != 0 { context.raise(FRAME_DONE); }
#     }
# }
#
# /// Where the one instance lives. A bus is a core, a memory map, and the instances at their addresses.
# const BEACON: Peripheral = Peripheral { name: "BEACON", base: 0x4000_0000, size: 0x100, interrupts: &[3], registers: &REGISTERS };
# const CPU: Cpu = Cpu { name: "lantern-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
# static MEMORY: [Region; 2] = [Region::flash("flash", 0x0800_0000, 64 << 10), Region::ram("sram", 0x2000_0000, 16 << 10)];
#
# use rigless::device::{ErasedDevice, RequestChange};
# use rigless::soc::Error;
#
# /// A second peripheral: one read-only register that counts the bytes the beacon has sent. It never
# /// touches the beacon's registers. It listens to the beacon's request line, in the next sections.
# static TALLY_REGISTERS: [RegisterSpec; 1] = [
#     RegisterSpec { name: "COUNT", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum TallyRegister { Count }
# impl Layout for TallyRegister {
#     const REGISTERS: &'static [RegisterSpec] = &TALLY_REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { (index == 0).then_some(Self::Count) }
#     fn index(self) -> usize { 0 }
# }
# const TALLY: Peripheral = Peripheral { name: "TALLY", base: 0x4000_0100, size: 0x100, interrupts: &[], registers: &TALLY_REGISTERS };
# #[derive(Debug, Default)]
# struct Tally;
# impl Device for Tally {
#     type Register = TallyRegister;
#     fn on_request(&mut self, change: RequestChange, registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) {
#         // A sink hears every request on the part. This one wants the beacon's, rising.
#         if change.instance == BEACON.name && change.request == SENT && change.level == Level::High {
#             registers.set(TallyRegister::Count, registers.get(TallyRegister::Count) + 1);
#         }
#     }
# }
#
# /// The part, complete: the bus, and the models on it as fields.
# #[derive(Debug)]
# struct Lantern { bus: Bus, beacon: Beacon, beacon_slot: usize, tally: Tally, tally_slot: usize }
# impl Lantern {
#     fn new() -> Result<Self, Error> {
#         // `reads_requests` is the wiring: the tally hears what the beacon drives.
#         let bus = Bus::builder(CPU, &MEMORY, [BEACON, TALLY]).reads_requests(TALLY.name).build()?;
#         let beacon_slot = bus.slot_of(BEACON.name).ok_or(Error::UnknownInstance { instance: BEACON.name })?;
#         let tally_slot = bus.slot_of(TALLY.name).ok_or(Error::UnknownInstance { instance: TALLY.name })?;
#         let mut chip = Self { bus, beacon: Beacon::default(), beacon_slot, tally: Tally, tally_slot };
#         chip.check()?;
#         Ok(chip)
#     }
# }
# impl Mcu for Lantern {
#     fn bus(&self) -> &Bus { &self.bus }
#     fn bus_mut(&mut self) -> &mut Bus { &mut self.bus }
#     fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
#         let Self { bus, beacon, beacon_slot, tally, tally_slot } = self;
#         let model: Option<&mut dyn ErasedDevice> = if slot == *beacon_slot { Some(beacon) } else if slot == *tally_slot { Some(tally) } else { None };
#         (bus, model)
#     }
# }
#
use std::num::{NonZeroU32, NonZeroU64};
use rigless::cpu;
use rigless::emulator::InterruptLine;
use rigless::machine::{Board, Target, armv7m};
use rigless::session::Session;
use rigless::snapshot::Store;

/// The board: one Lantern and nothing else.
#[derive(Debug)]
struct LanternBoard { chip: Lantern }
// A board says how a snapshot keeps it. This one doesn't, so a snapshot of it is refused and its
// runs start from reset.
impl Store for LanternBoard {}
impl Board for LanternBoard {
    fn name(&self) -> &'static str { "lantern" }
    fn target_names(&self) -> Vec<&'static str> { vec!["lantern-core"] }
    fn mcu(&mut self, target: usize) -> Option<&mut dyn Mcu> { (target == 0).then_some(&mut self.chip) }
    fn targets(&mut self) -> Vec<Target<'_>> {
        let core = armv7m::Core {
            variant: armv7m::Variant::M3,                               // the part declares no FPU
            clock_hz: NonZeroU64::new(16_000_000).unwrap(),             // drives the core's own timers
            interrupt_lines: NonZeroU32::new(u32::from(Mcu::cpu(&self.chip).interrupt_lines)).unwrap(),
            priority_bits: 0,                                           // zero leaves it to the core
            refclk_divider: NonZeroU32::new(8).unwrap(),
            instructions_per_second: NonZeroU64::new(250_000_000).unwrap(), // four nanoseconds each
            boot: "flash",                                              // the region at the reset vector
        }
        .spec(&cpu::armv7m::windows(Mcu::bus(&self.chip)));
        vec![Target { name: "lantern-core", mcu: &mut self.chip, core, interrupt_line: InterruptLine::new }]
    }
}

// A session on the board's one target. Nothing runs yet, but the core's description is built and
// checked against the part: a `boot` that isn't in `MEMORY` gets rejected right here.
let board = LanternBoard { chip: Lantern::new()? };
let session = Session::new(Box::new(board), 0, VirtualNanos(3 * FRAME.0))?;
assert_eq!(session.until(), VirtualNanos(3 * FRAME.0));
# Ok::<(), Box<dyn std::error::Error>>(())
```

`boot` is the region the emulator also maps at address zero, where the core fetches its reset vector. That's why the firmware below links its vector table at the start of `flash`. Name a region that isn't in `MEMORY` and `Session::new` rejects it and tells you which names exist. The interrupt count comes from the `Cpu`, so you write it once. `instructions_per_second` is how fast the core runs against the run's virtual clock: 250 million is four nanoseconds an instruction, which every target we ship uses. The part's own clock, one instruction a cycle, is the other natural pick, and the virtual clock counts whole nanoseconds, rounding each instant down. `spec` gives the core an MPU of eight regions and the floating-point unit its variant has by default. When the part's manual says otherwise, call `spec_with_parts` with what it says. The core runs only what the variant and the part have, so a variant without the DSP extension takes its instructions as undefined, and a part without an FPU faults on every floating-point instruction. A unit or an MPU the variant can't have is refused when the run starts.

### Interrupts

A peripheral's interrupt lines are declared on the instance, not on the model: `interrupts: &[3]` on `BEACON` says the beacon's one interrupt is line 3 of the part. The model raises it by position in that list, `context.raise(FRAME_DONE)` with `FRAME_DONE` at 0, so a model written once serves every instance of its layout whatever lines those instances have. Two beacons at two addresses are two `Peripheral`s with two `interrupts` tables and one `Beacon` type.

An interrupt is a level, not a pulse. The model raises it when the flag goes up and lowers it when the firmware clears the flag, which for the beacon is a read of `SR`. Drive the same level twice and the runtime reports it twice, so a model that should only report changes keeps the last level it drove. `Mcu::drain_interrupts` is how a unit test sees what a model drove, as the peripheral block above does.

On the board, `Target::interrupt_line` turns the part's interrupt number into the emulator's line. `InterruptLine::new` is the identity, which is right when the core's interrupt controller numbers its inputs the way the part does. An interrupt the route returns `None` for isn't dropped silently: `Session::unrouted` lists it. In the firmware, line 3 is row 3 of the interrupt table, right after the sixteen exceptions. To take the beacon's interrupt instead of polling for it, enable the line in the core's interrupt controller, set `IE`, and put a handler in that row:

```rust,ignore
const NVIC_ISER0: *mut u32 = 0xE000_E100 as *mut u32;
const BEACON_LINE: u32 = 3;

extern "C" fn beacon_done() {
    read(BEACON_SR); // clears DONE, which lowers the line
}

#[unsafe(link_section = ".vector_table.interrupts")]
#[used]
static INTERRUPTS: [extern "C" fn(); INTERRUPT_POSITIONS] = {
    let mut table = [default_handler as extern "C" fn(); INTERRUPT_POSITIONS];
    table[BEACON_LINE as usize] = beacon_done;
    table
};

// In `reset`, before the first byte:
write(NVIC_ISER0, 1 << BEACON_LINE);
write(BEACON_CR, EN | IE);
```

Interrupts go from a peripheral to the core. When one peripheral needs to tell another something, that's a request line, which is the next section.

### Request lines

A request line is how one peripheral tells another something. It's a level the driving model owns, like an interrupt, but it goes to other instances on the part instead of to the core. The beacon drives its request 0 high while a byte is on the wire and low when the frame ends: `context.request(SENT, Level::High)` in `write`, `Level::Low` in `on_event`, in the beacon's own numbering.

Who hears it is the bus description's business, not the beacon's. `reads_requests(TALLY.name)` in `Lantern::new` makes the tally a sink, and a sink gets every request on the part in `Device::on_request` as a `RequestChange`: the driving instance's name, its request number, and the level. The tally checks for the beacon's request 0 going high, counts it, and ignores everything else. That's how a DMA controller on a real part knows which of its channels fired: it knows its own wiring and reads the instance name off the change. Nothing reaches into another model's registers.

Requests are delivered by `Mcu::settle`, which carries out everything the models asked for after an access or an event: requests to their sinks, clocks to their readers, and any bus access a model asked for, which is why it takes the emulator's memory. A session settles after every access and every stop. In a unit test you call it yourself:

```rust
# use rigless::device::{Device, DeviceContext, Level, RegisterFile};
# use rigless::mcu::{Mcu, Single};
# use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
# use rigless::time::{EventId, VirtualNanos};
#
# // The register table, the way the generator would emit it from a vendor's SVD. Written by hand here
# // because there's no vendor. `SR` is read-only to the firmware: the model sets and clears it.
# static REGISTERS: [RegisterSpec; 3] = [
#     RegisterSpec { name: "CR", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "SR", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "DR", offset: 0x8, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum Register { Cr, Sr, Dr }
# impl Layout for Register {
#     const REGISTERS: &'static [RegisterSpec] = &REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { [Self::Cr, Self::Sr, Self::Dr].get(index).copied() }
#     fn index(self) -> usize { self as usize }
# }
# const EN: u64 = 1;
# const IE: u64 = 2;
# const BUSY: u64 = 1;
# const DONE: u64 = 2;
# /// The beacon's one interrupt, by its position in the instance's list.
# const FRAME_DONE: usize = 0;
# /// The beacon's one request output, in its own numbering: high while a byte is on the wire.
# const SENT: usize = 0;
# /// One byte occupies the beacon for a millisecond of virtual time.
# const FRAME: VirtualNanos = VirtualNanos(1_000_000);
#
# /// The model keeps what the registers can't: the bytes it sent, and the event that ends the frame.
# #[derive(Debug, Default)]
# struct Beacon { sent: Vec<u8>, dropped: u32, frame_end: Option<EventId> }
# impl Device for Beacon {
#     type Register = Register;
#     fn read(&mut self, register: Register, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) -> Option<u64> {
#         if register != Register::Sr { return None; }
#         let status = registers.get(Register::Sr);
#         if status & DONE != 0 { registers.set(Register::Sr, status & !DONE); context.lower(FRAME_DONE); }
#         Some(status)
#     }
#     fn write(&mut self, register: Register, value: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if register != Register::Dr { return; }
#         let enabled = registers.get(Register::Cr) & EN != 0;
#         let busy = registers.get(Register::Sr) & BUSY != 0;
#         if !enabled || busy { self.dropped += 1; return; }
#         self.sent.push(value as u8);
#         registers.set(Register::Sr, BUSY);
#         context.request(SENT, Level::High);
#         self.frame_end = Some(context.schedule(VirtualNanos(context.now().0 + FRAME.0)));
#     }
#     fn on_event(&mut self, event: EventId, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if self.frame_end != Some(event) { return; }
#         self.frame_end = None;
#         registers.set(Register::Sr, DONE);
#         context.request(SENT, Level::Low);
#         if registers.get(Register::Cr) & IE != 0 { context.raise(FRAME_DONE); }
#     }
# }
#
# /// Where the one instance lives. A bus is a core, a memory map, and the instances at their addresses.
# const BEACON: Peripheral = Peripheral { name: "BEACON", base: 0x4000_0000, size: 0x100, interrupts: &[3], registers: &REGISTERS };
# const CPU: Cpu = Cpu { name: "lantern-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
# static MEMORY: [Region; 2] = [Region::flash("flash", 0x0800_0000, 64 << 10), Region::ram("sram", 0x2000_0000, 16 << 10)];
#
# use rigless::device::{ErasedDevice, RequestChange};
# use rigless::soc::Error;
#
# /// A second peripheral: one read-only register that counts the bytes the beacon has sent. It never
# /// touches the beacon's registers. It listens to the beacon's request line, in the next sections.
# static TALLY_REGISTERS: [RegisterSpec; 1] = [
#     RegisterSpec { name: "COUNT", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum TallyRegister { Count }
# impl Layout for TallyRegister {
#     const REGISTERS: &'static [RegisterSpec] = &TALLY_REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { (index == 0).then_some(Self::Count) }
#     fn index(self) -> usize { 0 }
# }
# const TALLY: Peripheral = Peripheral { name: "TALLY", base: 0x4000_0100, size: 0x100, interrupts: &[], registers: &TALLY_REGISTERS };
# #[derive(Debug, Default)]
# struct Tally;
# impl Device for Tally {
#     type Register = TallyRegister;
#     fn on_request(&mut self, change: RequestChange, registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) {
#         // A sink hears every request on the part. This one wants the beacon's, rising.
#         if change.instance == BEACON.name && change.request == SENT && change.level == Level::High {
#             registers.set(TallyRegister::Count, registers.get(TallyRegister::Count) + 1);
#         }
#     }
# }
#
# /// The part, complete: the bus, and the models on it as fields.
# #[derive(Debug)]
# struct Lantern { bus: Bus, beacon: Beacon, beacon_slot: usize, tally: Tally, tally_slot: usize }
# impl Lantern {
#     fn new() -> Result<Self, Error> {
#         // `reads_requests` is the wiring: the tally hears what the beacon drives.
#         let bus = Bus::builder(CPU, &MEMORY, [BEACON, TALLY]).reads_requests(TALLY.name).build()?;
#         let beacon_slot = bus.slot_of(BEACON.name).ok_or(Error::UnknownInstance { instance: BEACON.name })?;
#         let tally_slot = bus.slot_of(TALLY.name).ok_or(Error::UnknownInstance { instance: TALLY.name })?;
#         let mut chip = Self { bus, beacon: Beacon::default(), beacon_slot, tally: Tally, tally_slot };
#         chip.check()?;
#         Ok(chip)
#     }
# }
# impl Mcu for Lantern {
#     fn bus(&self) -> &Bus { &self.bus }
#     fn bus_mut(&mut self) -> &mut Bus { &mut self.bus }
#     fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
#         let Self { bus, beacon, beacon_slot, tally, tally_slot } = self;
#         let model: Option<&mut dyn ErasedDevice> = if slot == *beacon_slot { Some(beacon) } else if slot == *tally_slot { Some(tally) } else { None };
#         (bus, model)
#     }
# }
#
use rigless::emulator::{Error as EmulatorError, Memory};

// Nothing here asks for a memory access, so the memory a settle is handed can refuse everything.
struct NoMemory;
impl Memory for NoMemory {
    fn read(&mut self, address: u64, width: Width) -> Result<u64, EmulatorError> { Err(EmulatorError::Memory { address, width }) }
    fn write(&mut self, address: u64, width: Width, _value: u64) -> Result<(), EmulatorError> { Err(EmulatorError::Memory { address, width }) }
}

let mut chip = Lantern::new()?;
let at = |register: Register| BEACON.base + u64::from(register.spec().offset);
let count = TALLY.base + u64::from(TallyRegister::Count.spec().offset);
chip.write(VirtualNanos(0), at(Register::Cr), Width::W32, EN)?;
chip.write(VirtualNanos(0), at(Register::Dr), Width::W32, u64::from(b'h'))?;
let _refused = chip.settle(&mut NoMemory)?;
assert_eq!(chip.read(VirtualNanos(0), count, Width::W32)?, 1, "the tally saw the beacon's request go high");
chip.advance_to(FRAME);
let _refused = chip.settle(&mut NoMemory)?;
chip.write(FRAME, at(Register::Dr), Width::W32, u64::from(b'i'))?;
let _refused = chip.settle(&mut NoMemory)?;
assert_eq!(chip.read(FRAME, count, Width::W32)?, 2, "and go high again for the second byte");
# Ok::<(), Box<dyn std::error::Error>>(())
```

Clocks work the same way: `Builder::clocked_by` says which instance runs on which output of another, `context.clock` drives a rate, and `Device::on_clock` receives it. A bit of one instance that another answers behind is `Builder::gate`, or `Builder::gate_writes` for writes to named registers. The `soc` module docs cover all three.

### The firmware

The firmware is a normal bare-metal image: `no_std`, your own vector table, your own linker script, built for the core's target. It's in the repository as [`firmware/template/`](https://gitlab.com/rigless-labs/rigless/-/tree/main/firmware/template), ready to copy. This one sends "hi" on the beacon the way a real driver would: wait until the last byte is done, write the next. Then it sleeps. That wait is the whole point of the test. A model that just stored registers would never clear `BUSY`, and the firmware would spin forever.

```rust,ignore
#![no_std]
#![no_main]

use core::arch::asm;
use core::ptr::{read_volatile, write_volatile};

// The beacon's registers, at the address the SoC put them.
const BEACON_CR: *mut u32 = 0x4000_0000 as *mut u32;
const BEACON_SR: *mut u32 = 0x4000_0004 as *mut u32;
const BEACON_DR: *mut u32 = 0x4000_0008 as *mut u32;
const EN: u32 = 1;
const BUSY: u32 = 1;
/// One row per interrupt line the core declares. None is used, but the table has them.
const INTERRUPT_POSITIONS: usize = 16;

fn read(register: *mut u32) -> u32 {
    // SAFETY: a word-aligned register on the part's map, and this is the only code running.
    unsafe { read_volatile(register) }
}
fn write(register: *mut u32, value: u32) {
    // SAFETY: as for the read.
    unsafe { write_volatile(register, value) };
}

/// Sends "hi" the way a driver would: wait until the last byte is done, write the next. Then sleeps.
#[unsafe(no_mangle)]
pub extern "C" fn reset() -> ! {
    write(BEACON_CR, EN);
    for &byte in b"hi" {
        while read(BEACON_SR) & BUSY != 0 {
            core::hint::spin_loop();
        }
        write(BEACON_DR, u32::from(byte));
    }
    loop {
        // SAFETY: WFI sleeps until an interrupt. It touches no memory or register.
        unsafe { asm!("wfi", options(nomem, nostack, preserves_flags)) };
    }
}

extern "C" fn default_handler() {
    loop { core::hint::spin_loop(); }
}

#[unsafe(link_section = ".vector_table.reset")]
#[used]
static RESET_VECTOR: extern "C" fn() -> ! = reset;
/// The fourteen exception vectors after the reset vector.
#[unsafe(link_section = ".vector_table.exceptions")]
#[used]
static EXCEPTIONS: [extern "C" fn(); 14] = [default_handler; 14];
#[unsafe(link_section = ".vector_table.interrupts")]
#[used]
static INTERRUPTS: [extern "C" fn(); INTERRUPT_POSITIONS] = [default_handler; INTERRUPT_POSITIONS];

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo<'_>) -> ! {
    loop { core::hint::spin_loop(); }
}
```

The interrupt table needs one entry per line the core declares (`CPU.interrupt_lines`). The linker script puts the vector table at the start of flash, and its `MEMORY` has to use the same origins and lengths as the SoC's, because the emulator loads the image by the SoC's map:

```text
MEMORY
{
  FLASH : ORIGIN = 0x08000000, LENGTH = 64K   /* as MEMORY declares them, or the loader and the */
  SRAM  : ORIGIN = 0x20000000, LENGTH = 16K   /* linker disagree about where the code is */
}

ENTRY(reset);
_stack_start = ORIGIN(SRAM) + LENGTH(SRAM);

SECTIONS
{
  .vector_table ORIGIN(FLASH) :
  {
    LONG(_stack_start);
    KEEP(*(.vector_table.reset));
    KEEP(*(.vector_table.exceptions));
    KEEP(*(.vector_table.interrupts));
  } > FLASH
  .text : { *(.text .text.*); } > FLASH
  .rodata : { *(.rodata .rodata.*); } > FLASH
  .bss (NOLOAD) : { *(.bss .bss.*); } > SRAM
  /DISCARD/ : { *(.ARM.exidx .ARM.exidx.*); }
}
```

The firmware is its own Cargo project, not a workspace member, because it's built for a different target. `Cargo.toml` gets an empty `[workspace]` and `panic = "abort"` in both profiles. `.cargo/config.toml` sets the target and the linker script:

```toml
[build]
target = "thumbv7m-none-eabi"            # a core without an FPU. thumbv7em-none-eabihf for one with

[target.thumbv7m-none-eabi]
rustflags = ["-C", "link-arg=-Tlink.x", "-C", "link-arg=--nmagic"]
```

```text
rustup target add thumbv7m-none-eabi
cargo build --release
```

### Running it

`rigless run` only knows the machines in `machine::all()`, so for your own board you run it from a binary of your own. The shortest is the whole command over a table of your own machines. Turn on the `cli` feature, `cargo add rigless --no-default-features --features cli`, and the binary is one line:

```rust,no_run
# use rigless::device::{Device, DeviceContext, Level, RegisterFile};
# use rigless::mcu::{Mcu, Single};
# use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
# use rigless::time::{EventId, VirtualNanos};
#
# // The register table, the way the generator would emit it from a vendor's SVD. Written by hand here
# // because there's no vendor. `SR` is read-only to the firmware: the model sets and clears it.
# static REGISTERS: [RegisterSpec; 3] = [
#     RegisterSpec { name: "CR", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "SR", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "DR", offset: 0x8, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum Register { Cr, Sr, Dr }
# impl Layout for Register {
#     const REGISTERS: &'static [RegisterSpec] = &REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { [Self::Cr, Self::Sr, Self::Dr].get(index).copied() }
#     fn index(self) -> usize { self as usize }
# }
# const EN: u64 = 1;
# const IE: u64 = 2;
# const BUSY: u64 = 1;
# const DONE: u64 = 2;
# /// The beacon's one interrupt, by its position in the instance's list.
# const FRAME_DONE: usize = 0;
# /// The beacon's one request output, in its own numbering: high while a byte is on the wire.
# const SENT: usize = 0;
# /// One byte occupies the beacon for a millisecond of virtual time.
# const FRAME: VirtualNanos = VirtualNanos(1_000_000);
#
# /// The model keeps what the registers can't: the bytes it sent, and the event that ends the frame.
# #[derive(Debug, Default)]
# struct Beacon { sent: Vec<u8>, dropped: u32, frame_end: Option<EventId> }
# impl Device for Beacon {
#     type Register = Register;
#     fn read(&mut self, register: Register, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) -> Option<u64> {
#         if register != Register::Sr { return None; }
#         let status = registers.get(Register::Sr);
#         if status & DONE != 0 { registers.set(Register::Sr, status & !DONE); context.lower(FRAME_DONE); }
#         Some(status)
#     }
#     fn write(&mut self, register: Register, value: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if register != Register::Dr { return; }
#         let enabled = registers.get(Register::Cr) & EN != 0;
#         let busy = registers.get(Register::Sr) & BUSY != 0;
#         if !enabled || busy { self.dropped += 1; return; }
#         self.sent.push(value as u8);
#         registers.set(Register::Sr, BUSY);
#         context.request(SENT, Level::High);
#         self.frame_end = Some(context.schedule(VirtualNanos(context.now().0 + FRAME.0)));
#     }
#     fn on_event(&mut self, event: EventId, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if self.frame_end != Some(event) { return; }
#         self.frame_end = None;
#         registers.set(Register::Sr, DONE);
#         context.request(SENT, Level::Low);
#         if registers.get(Register::Cr) & IE != 0 { context.raise(FRAME_DONE); }
#     }
# }
#
# /// Where the one instance lives. A bus is a core, a memory map, and the instances at their addresses.
# const BEACON: Peripheral = Peripheral { name: "BEACON", base: 0x4000_0000, size: 0x100, interrupts: &[3], registers: &REGISTERS };
# const CPU: Cpu = Cpu { name: "lantern-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
# static MEMORY: [Region; 2] = [Region::flash("flash", 0x0800_0000, 64 << 10), Region::ram("sram", 0x2000_0000, 16 << 10)];
#
# use rigless::device::{ErasedDevice, RequestChange};
#
# /// A second peripheral: one read-only register that counts the bytes the beacon has sent. It never
# /// touches the beacon's registers. It listens to the beacon's request line, in the next sections.
# static TALLY_REGISTERS: [RegisterSpec; 1] = [
#     RegisterSpec { name: "COUNT", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum TallyRegister { Count }
# impl Layout for TallyRegister {
#     const REGISTERS: &'static [RegisterSpec] = &TALLY_REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { (index == 0).then_some(Self::Count) }
#     fn index(self) -> usize { 0 }
# }
# const TALLY: Peripheral = Peripheral { name: "TALLY", base: 0x4000_0100, size: 0x100, interrupts: &[], registers: &TALLY_REGISTERS };
# #[derive(Debug, Default)]
# struct Tally;
# impl Device for Tally {
#     type Register = TallyRegister;
#     fn on_request(&mut self, change: RequestChange, registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) {
#         // A sink hears every request on the part. This one wants the beacon's, rising.
#         if change.instance == BEACON.name && change.request == SENT && change.level == Level::High {
#             registers.set(TallyRegister::Count, registers.get(TallyRegister::Count) + 1);
#         }
#     }
# }
#
# /// The part, complete: the bus, and the models on it as fields.
# #[derive(Debug)]
# struct Lantern { bus: Bus, beacon: Beacon, beacon_slot: usize, tally: Tally, tally_slot: usize }
# impl Lantern {
#     fn new() -> Result<Self, Error> {
#         // `reads_requests` is the wiring: the tally hears what the beacon drives.
#         let bus = Bus::builder(CPU, &MEMORY, [BEACON, TALLY]).reads_requests(TALLY.name).build()?;
#         let beacon_slot = bus.slot_of(BEACON.name).ok_or(Error::UnknownInstance { instance: BEACON.name })?;
#         let tally_slot = bus.slot_of(TALLY.name).ok_or(Error::UnknownInstance { instance: TALLY.name })?;
#         let mut chip = Self { bus, beacon: Beacon::default(), beacon_slot, tally: Tally, tally_slot };
#         chip.check()?;
#         Ok(chip)
#     }
# }
# impl Mcu for Lantern {
#     fn bus(&self) -> &Bus { &self.bus }
#     fn bus_mut(&mut self) -> &mut Bus { &mut self.bus }
#     fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
#         let Self { bus, beacon, beacon_slot, tally, tally_slot } = self;
#         let model: Option<&mut dyn ErasedDevice> = if slot == *beacon_slot { Some(beacon) } else if slot == *tally_slot { Some(tally) } else { None };
#         (bus, model)
#     }
# }
#
# use std::num::{NonZeroU32, NonZeroU64};
# use rigless::cpu;
# use rigless::emulator::InterruptLine;
# use rigless::machine::{Target, armv7m};
# use rigless::session::Session;
#
# /// The board: one Lantern and nothing else.
# #[derive(Debug)]
# struct LanternBoard { chip: Lantern }
# impl rigless::snapshot::Store for LanternBoard {}
# impl Board for LanternBoard {
#     fn name(&self) -> &'static str { "lantern" }
#     fn target_names(&self) -> Vec<&'static str> { vec!["lantern-core"] }
#     fn mcu(&mut self, target: usize) -> Option<&mut dyn Mcu> { (target == 0).then_some(&mut self.chip) }
#     fn targets(&mut self) -> Vec<Target<'_>> {
#         let core = armv7m::Core {
#             variant: armv7m::Variant::M3,                               // the part declares no FPU
#             clock_hz: NonZeroU64::new(16_000_000).unwrap(),             // drives the core's own timers
#             interrupt_lines: NonZeroU32::new(u32::from(Mcu::cpu(&self.chip).interrupt_lines)).unwrap(),
#             priority_bits: 0,                                           // zero leaves it to the core
#             refclk_divider: NonZeroU32::new(8).unwrap(),
#             instructions_per_second: NonZeroU64::new(250_000_000).unwrap(), // four nanoseconds each
#             boot: "flash",                                              // the region at the reset vector
#         }
#         .spec(&cpu::armv7m::windows(Mcu::bus(&self.chip)));
#         vec![Target { name: "lantern-core", mcu: &mut self.chip, core, interrupt_line: InterruptLine::new }]
#     }
# }
#
use std::process::ExitCode;
use rigless::machine::{Board, Constructor};
use rigless::soc::Error;

fn lantern() -> Result<Box<dyn Board>, Error> {
    Ok(Box::new(LanternBoard { chip: Lantern::new()? }))
}

/// Each board by the name `--machine` takes, which is the name it calls itself.
const MACHINES: &[(&str, Constructor)] = &[("lantern", lantern)];

# #[cfg(feature = "cli")]
fn main() -> ExitCode {
    rigless::cli::main(MACHINES)
}
# #[cfg(not(feature = "cli"))]
# fn main() -> ExitCode { let _ = MACHINES; ExitCode::SUCCESS }
```

That binary is `rigless` for your boards: `run --machine lantern --until 3ms firmware.elf` prints what `rigless run` prints, `--gdb 3333` serves a debugger, and `--coverage` writes the report. Once the board implements `Edge`, `--link` makes it an engine's target, and an engine spawns this binary wherever it would spawn `rigless`. `link::serve` is that target as a function, for a program that isn't a command.

To keep your own `main`, `emulator::run_printed` does what the command does: it takes a `Session` on one of the board's targets and a `Start` naming the image, and prints every access, the stop and the reports, the same lines `rigless run` prints, to whatever you hand it:

```rust,no_run
# use rigless::device::{Device, DeviceContext, Level, RegisterFile};
# use rigless::mcu::{Mcu, Single};
# use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
# use rigless::time::{EventId, VirtualNanos};
#
# // The register table, the way the generator would emit it from a vendor's SVD. Written by hand here
# // because there's no vendor. `SR` is read-only to the firmware: the model sets and clears it.
# static REGISTERS: [RegisterSpec; 3] = [
#     RegisterSpec { name: "CR", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "SR", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "DR", offset: 0x8, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum Register { Cr, Sr, Dr }
# impl Layout for Register {
#     const REGISTERS: &'static [RegisterSpec] = &REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { [Self::Cr, Self::Sr, Self::Dr].get(index).copied() }
#     fn index(self) -> usize { self as usize }
# }
# const EN: u64 = 1;
# const IE: u64 = 2;
# const BUSY: u64 = 1;
# const DONE: u64 = 2;
# /// The beacon's one interrupt, by its position in the instance's list.
# const FRAME_DONE: usize = 0;
# /// The beacon's one request output, in its own numbering: high while a byte is on the wire.
# const SENT: usize = 0;
# /// One byte occupies the beacon for a millisecond of virtual time.
# const FRAME: VirtualNanos = VirtualNanos(1_000_000);
#
# /// The model keeps what the registers can't: the bytes it sent, and the event that ends the frame.
# #[derive(Debug, Default)]
# struct Beacon { sent: Vec<u8>, dropped: u32, frame_end: Option<EventId> }
# impl Device for Beacon {
#     type Register = Register;
#     fn read(&mut self, register: Register, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) -> Option<u64> {
#         if register != Register::Sr { return None; }
#         let status = registers.get(Register::Sr);
#         if status & DONE != 0 { registers.set(Register::Sr, status & !DONE); context.lower(FRAME_DONE); }
#         Some(status)
#     }
#     fn write(&mut self, register: Register, value: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if register != Register::Dr { return; }
#         let enabled = registers.get(Register::Cr) & EN != 0;
#         let busy = registers.get(Register::Sr) & BUSY != 0;
#         if !enabled || busy { self.dropped += 1; return; }
#         self.sent.push(value as u8);
#         registers.set(Register::Sr, BUSY);
#         context.request(SENT, Level::High);
#         self.frame_end = Some(context.schedule(VirtualNanos(context.now().0 + FRAME.0)));
#     }
#     fn on_event(&mut self, event: EventId, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if self.frame_end != Some(event) { return; }
#         self.frame_end = None;
#         registers.set(Register::Sr, DONE);
#         context.request(SENT, Level::Low);
#         if registers.get(Register::Cr) & IE != 0 { context.raise(FRAME_DONE); }
#     }
# }
#
# /// Where the one instance lives. A bus is a core, a memory map, and the instances at their addresses.
# const BEACON: Peripheral = Peripheral { name: "BEACON", base: 0x4000_0000, size: 0x100, interrupts: &[3], registers: &REGISTERS };
# const CPU: Cpu = Cpu { name: "lantern-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
# static MEMORY: [Region; 2] = [Region::flash("flash", 0x0800_0000, 64 << 10), Region::ram("sram", 0x2000_0000, 16 << 10)];
#
# use rigless::device::{ErasedDevice, RequestChange};
# use rigless::soc::Error;
#
# /// A second peripheral: one read-only register that counts the bytes the beacon has sent. It never
# /// touches the beacon's registers. It listens to the beacon's request line, in the next sections.
# static TALLY_REGISTERS: [RegisterSpec; 1] = [
#     RegisterSpec { name: "COUNT", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum TallyRegister { Count }
# impl Layout for TallyRegister {
#     const REGISTERS: &'static [RegisterSpec] = &TALLY_REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { (index == 0).then_some(Self::Count) }
#     fn index(self) -> usize { 0 }
# }
# const TALLY: Peripheral = Peripheral { name: "TALLY", base: 0x4000_0100, size: 0x100, interrupts: &[], registers: &TALLY_REGISTERS };
# #[derive(Debug, Default)]
# struct Tally;
# impl Device for Tally {
#     type Register = TallyRegister;
#     fn on_request(&mut self, change: RequestChange, registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) {
#         // A sink hears every request on the part. This one wants the beacon's, rising.
#         if change.instance == BEACON.name && change.request == SENT && change.level == Level::High {
#             registers.set(TallyRegister::Count, registers.get(TallyRegister::Count) + 1);
#         }
#     }
# }
#
# /// The part, complete: the bus, and the models on it as fields.
# #[derive(Debug)]
# struct Lantern { bus: Bus, beacon: Beacon, beacon_slot: usize, tally: Tally, tally_slot: usize }
# impl Lantern {
#     fn new() -> Result<Self, Error> {
#         // `reads_requests` is the wiring: the tally hears what the beacon drives.
#         let bus = Bus::builder(CPU, &MEMORY, [BEACON, TALLY]).reads_requests(TALLY.name).build()?;
#         let beacon_slot = bus.slot_of(BEACON.name).ok_or(Error::UnknownInstance { instance: BEACON.name })?;
#         let tally_slot = bus.slot_of(TALLY.name).ok_or(Error::UnknownInstance { instance: TALLY.name })?;
#         let mut chip = Self { bus, beacon: Beacon::default(), beacon_slot, tally: Tally, tally_slot };
#         chip.check()?;
#         Ok(chip)
#     }
# }
# impl Mcu for Lantern {
#     fn bus(&self) -> &Bus { &self.bus }
#     fn bus_mut(&mut self) -> &mut Bus { &mut self.bus }
#     fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
#         let Self { bus, beacon, beacon_slot, tally, tally_slot } = self;
#         let model: Option<&mut dyn ErasedDevice> = if slot == *beacon_slot { Some(beacon) } else if slot == *tally_slot { Some(tally) } else { None };
#         (bus, model)
#     }
# }
#
# use std::num::{NonZeroU32, NonZeroU64};
# use rigless::cpu;
# use rigless::emulator::InterruptLine;
# use rigless::machine::{Board, Target, armv7m};
# use rigless::session::Session;
#
# /// The board: one Lantern and nothing else.
# #[derive(Debug)]
# struct LanternBoard { chip: Lantern }
# impl rigless::snapshot::Store for LanternBoard {}
# impl Board for LanternBoard {
#     fn name(&self) -> &'static str { "lantern" }
#     fn target_names(&self) -> Vec<&'static str> { vec!["lantern-core"] }
#     fn mcu(&mut self, target: usize) -> Option<&mut dyn Mcu> { (target == 0).then_some(&mut self.chip) }
#     fn targets(&mut self) -> Vec<Target<'_>> {
#         let core = armv7m::Core {
#             variant: armv7m::Variant::M3,                               // the part declares no FPU
#             clock_hz: NonZeroU64::new(16_000_000).unwrap(),             // drives the core's own timers
#             interrupt_lines: NonZeroU32::new(u32::from(Mcu::cpu(&self.chip).interrupt_lines)).unwrap(),
#             priority_bits: 0,                                           // zero leaves it to the core
#             refclk_divider: NonZeroU32::new(8).unwrap(),
#             instructions_per_second: NonZeroU64::new(250_000_000).unwrap(), // four nanoseconds each
#             boot: "flash",                                              // the region at the reset vector
#         }
#         .spec(&cpu::armv7m::windows(Mcu::bus(&self.chip)));
#         vec![Target { name: "lantern-core", mcu: &mut self.chip, core, interrupt_line: InterruptLine::new }]
#     }
# }
#
use std::io;
use std::path::Path;
use rigless::emulator::{self, Start};
use rigless::image::Image;

let session = Session::new(Box::new(LanternBoard { chip: Lantern::new()? }), 0, VirtualNanos(3 * FRAME.0))?;
let firmware = Image::read(Path::new("firmware/template/target/thumbv7m-none-eabi/release/template"), None)?;
let images = [firmware];
let start = Start::new(&images);
let printed = emulator::run_printed(session, &start, None, io::stdout())?;
assert_eq!(printed.session.board().chip.beacon.sent, b"hi");
# Ok::<(), Box<dyn std::error::Error>>(())
```

That's a whole `main`. The flags map straight across: the session's `until` is `--until`, and `emulator::NO_DEADLINE` means run until interrupted. `debugger: Some(&listener)` in `Start` is `--gdb`, with a `TcpListener` you bound where the debugger connects, `127.0.0.1` and the port for a port alone. The third argument is `--outputs`, the period to read the board's outputs at. `printed.coverage` is what `--coverage` writes, so `fs::write(path, printed.coverage.to_string())` gives you the same file. A host on one of the board's wires is `board.attach_host(...)` before you make the session, which `rigless run` does for every wire `Board::hosts` names, at the board's port or the one `--host` gives. `printed.session` hands the board back, so you can look at your models once the run is over.

To do something other than print, write your own `Driver` and call `emulator::run`. A `Driver` gets every access as it happens, and every stop at an instant the run was asked to reach. That's what a test wants to assert on:

```rust,no_run
# use rigless::device::{Device, DeviceContext, Level, RegisterFile};
# use rigless::mcu::{Mcu, Single};
# use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
# use rigless::time::{EventId, VirtualNanos};
#
# // The register table, the way the generator would emit it from a vendor's SVD. Written by hand here
# // because there's no vendor. `SR` is read-only to the firmware: the model sets and clears it.
# static REGISTERS: [RegisterSpec; 3] = [
#     RegisterSpec { name: "CR", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "SR", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
#     RegisterSpec { name: "DR", offset: 0x8, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum Register { Cr, Sr, Dr }
# impl Layout for Register {
#     const REGISTERS: &'static [RegisterSpec] = &REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { [Self::Cr, Self::Sr, Self::Dr].get(index).copied() }
#     fn index(self) -> usize { self as usize }
# }
# const EN: u64 = 1;
# const IE: u64 = 2;
# const BUSY: u64 = 1;
# const DONE: u64 = 2;
# /// The beacon's one interrupt, by its position in the instance's list.
# const FRAME_DONE: usize = 0;
# /// The beacon's one request output, in its own numbering: high while a byte is on the wire.
# const SENT: usize = 0;
# /// One byte occupies the beacon for a millisecond of virtual time.
# const FRAME: VirtualNanos = VirtualNanos(1_000_000);
#
# /// The model keeps what the registers can't: the bytes it sent, and the event that ends the frame.
# #[derive(Debug, Default)]
# struct Beacon { sent: Vec<u8>, dropped: u32, frame_end: Option<EventId> }
# impl Device for Beacon {
#     type Register = Register;
#     fn read(&mut self, register: Register, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) -> Option<u64> {
#         if register != Register::Sr { return None; }
#         let status = registers.get(Register::Sr);
#         if status & DONE != 0 { registers.set(Register::Sr, status & !DONE); context.lower(FRAME_DONE); }
#         Some(status)
#     }
#     fn write(&mut self, register: Register, value: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if register != Register::Dr { return; }
#         let enabled = registers.get(Register::Cr) & EN != 0;
#         let busy = registers.get(Register::Sr) & BUSY != 0;
#         if !enabled || busy { self.dropped += 1; return; }
#         self.sent.push(value as u8);
#         registers.set(Register::Sr, BUSY);
#         context.request(SENT, Level::High);
#         self.frame_end = Some(context.schedule(VirtualNanos(context.now().0 + FRAME.0)));
#     }
#     fn on_event(&mut self, event: EventId, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
#         if self.frame_end != Some(event) { return; }
#         self.frame_end = None;
#         registers.set(Register::Sr, DONE);
#         context.request(SENT, Level::Low);
#         if registers.get(Register::Cr) & IE != 0 { context.raise(FRAME_DONE); }
#     }
# }
#
# /// Where the one instance lives. A bus is a core, a memory map, and the instances at their addresses.
# const BEACON: Peripheral = Peripheral { name: "BEACON", base: 0x4000_0000, size: 0x100, interrupts: &[3], registers: &REGISTERS };
# const CPU: Cpu = Cpu { name: "lantern-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
# static MEMORY: [Region; 2] = [Region::flash("flash", 0x0800_0000, 64 << 10), Region::ram("sram", 0x2000_0000, 16 << 10)];
#
# use rigless::device::{ErasedDevice, RequestChange};
# use rigless::soc::Error;
#
# /// A second peripheral: one read-only register that counts the bytes the beacon has sent. It never
# /// touches the beacon's registers. It listens to the beacon's request line, in the next sections.
# static TALLY_REGISTERS: [RegisterSpec; 1] = [
#     RegisterSpec { name: "COUNT", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
# ];
# #[derive(Debug, Clone, Copy, PartialEq, Eq)]
# enum TallyRegister { Count }
# impl Layout for TallyRegister {
#     const REGISTERS: &'static [RegisterSpec] = &TALLY_REGISTERS;
#     fn from_index(index: usize) -> Option<Self> { (index == 0).then_some(Self::Count) }
#     fn index(self) -> usize { 0 }
# }
# const TALLY: Peripheral = Peripheral { name: "TALLY", base: 0x4000_0100, size: 0x100, interrupts: &[], registers: &TALLY_REGISTERS };
# #[derive(Debug, Default)]
# struct Tally;
# impl Device for Tally {
#     type Register = TallyRegister;
#     fn on_request(&mut self, change: RequestChange, registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) {
#         // A sink hears every request on the part. This one wants the beacon's, rising.
#         if change.instance == BEACON.name && change.request == SENT && change.level == Level::High {
#             registers.set(TallyRegister::Count, registers.get(TallyRegister::Count) + 1);
#         }
#     }
# }
#
# /// The part, complete: the bus, and the models on it as fields.
# #[derive(Debug)]
# struct Lantern { bus: Bus, beacon: Beacon, beacon_slot: usize, tally: Tally, tally_slot: usize }
# impl Lantern {
#     fn new() -> Result<Self, Error> {
#         // `reads_requests` is the wiring: the tally hears what the beacon drives.
#         let bus = Bus::builder(CPU, &MEMORY, [BEACON, TALLY]).reads_requests(TALLY.name).build()?;
#         let beacon_slot = bus.slot_of(BEACON.name).ok_or(Error::UnknownInstance { instance: BEACON.name })?;
#         let tally_slot = bus.slot_of(TALLY.name).ok_or(Error::UnknownInstance { instance: TALLY.name })?;
#         let mut chip = Self { bus, beacon: Beacon::default(), beacon_slot, tally: Tally, tally_slot };
#         chip.check()?;
#         Ok(chip)
#     }
# }
# impl Mcu for Lantern {
#     fn bus(&self) -> &Bus { &self.bus }
#     fn bus_mut(&mut self) -> &mut Bus { &mut self.bus }
#     fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
#         let Self { bus, beacon, beacon_slot, tally, tally_slot } = self;
#         let model: Option<&mut dyn ErasedDevice> = if slot == *beacon_slot { Some(beacon) } else if slot == *tally_slot { Some(tally) } else { None };
#         (bus, model)
#     }
# }
#
# use std::num::{NonZeroU32, NonZeroU64};
# use rigless::cpu;
# use rigless::emulator::InterruptLine;
# use rigless::machine::{Board, Target, armv7m};
# use rigless::session::Session;
#
# /// The board: one Lantern and nothing else.
# #[derive(Debug)]
# struct LanternBoard { chip: Lantern }
# impl rigless::snapshot::Store for LanternBoard {}
# impl Board for LanternBoard {
#     fn name(&self) -> &'static str { "lantern" }
#     fn target_names(&self) -> Vec<&'static str> { vec!["lantern-core"] }
#     fn mcu(&mut self, target: usize) -> Option<&mut dyn Mcu> { (target == 0).then_some(&mut self.chip) }
#     fn targets(&mut self) -> Vec<Target<'_>> {
#         let core = armv7m::Core {
#             variant: armv7m::Variant::M3,                               // the part declares no FPU
#             clock_hz: NonZeroU64::new(16_000_000).unwrap(),             // drives the core's own timers
#             interrupt_lines: NonZeroU32::new(u32::from(Mcu::cpu(&self.chip).interrupt_lines)).unwrap(),
#             priority_bits: 0,                                           // zero leaves it to the core
#             refclk_divider: NonZeroU32::new(8).unwrap(),
#             instructions_per_second: NonZeroU64::new(250_000_000).unwrap(), // four nanoseconds each
#             boot: "flash",                                              // the region at the reset vector
#         }
#         .spec(&cpu::armv7m::windows(Mcu::bus(&self.chip)));
#         vec![Target { name: "lantern-core", mcu: &mut self.chip, core, interrupt_line: InterruptLine::new }]
#     }
# }
#
use std::path::Path;
use rigless::emulator::{self, Driver, Finished, Host, Settled, Start, Stop, StopReason};
use rigless::image::Image;

/// What the run does on every access and every stop. This one records accesses and lets the run
/// reach its deadline. `rigless run` prints them instead, a test asserts on them, and the link's
/// target counts toward the faults it armed after each one and takes a snapshot at a stop when the
/// engine asks, with the host it's lent there.
struct Recording(Vec<(emulator::Access, u64)>);
impl<D: Board + ?Sized> Driver<D> for Recording {
    fn on_settled(&mut self, _session: &mut Session<D>, _host: &mut dyn Host, settled: Settled) {
        if let Settled::Access { access, value } = settled { self.0.push((access, value)); }
    }
    fn on_stop(&mut self, _session: &mut Session<D>, _stop: Stop, _host: &mut dyn Host) -> Option<VirtualNanos> { None }
}

let session = Session::new(Box::new(LanternBoard { chip: Lantern::new()? }), 0, VirtualNanos(3 * FRAME.0))?;
let firmware = Image::read(Path::new("firmware/template/target/thumbv7m-none-eabi/release/template"), None)?;
let images = [firmware];
let start = Start::new(&images);
let Finished { session, driver, stop, .. } = emulator::run(session, &start, Recording(Vec::new()))?;

// What the beacon saw, and what the core did.
assert!(session.unmapped().is_empty(), "an address no model claims: {:?}", session.unmapped());
assert_eq!(session.board().chip.beacon.sent, b"hi");
let writes = driver.0.iter().filter(|(access, _)| access.address == BEACON.base + 0x8).count();
assert_eq!(writes, 2, "one write per byte, the second only once the first's frame had passed");
let stop = stop.expect("the driver let the run reach its deadline");
assert_eq!(stop.reason, StopReason::Idle, "the firmware sleeps once it has said hi");
# Ok::<(), Box<dyn std::error::Error>>(())
```

`Driver::on_settled` sees every access, every hit on what the emulator watches and every stop once the models have settled after it. `Driver::on_stop` returns the next instant to run to, or `None` to stop. `Finished` gives you back the session (board included), the driver (with whatever it recorded), and the stop that ended the run. `StopReason::Idle` means the core is halted waiting for an interrupt with nothing pending, and the emulator skips ahead instead of waiting.

**Runs share nothing.** Each `emulator::run` makes a core of its own on a thread of its own, so a process runs as many as you like, one after another or at once, and a test that boots a firmware runs under `cargo test` as well as `cargo nextest`.

### The edge

An engine steps a board in lockstep through its edge: what the firmware drives, read off the board, and what the world does to the board's transducers, handed to them. If your board drives or senses something a plant cares about, implement `edge::Edge` for it and answer `Board::edge` with it. The lantern drives nothing a plant reads, so here's a board that does: a made-up heater, whose firmware reads the water's temperature and drives a heating element. It's the heater the engine's own docs step against a tank of water, field for field.

```rust
use std::num::{NonZeroU32, NonZeroU64};
use rigless::cpu;
use rigless::device::{Device, DeviceContext, ErasedDevice, RegisterFile};
use rigless::edge::{Coupled, Edge, Field};
use rigless::emulator::InterruptLine;
use rigless::machine::{Board, Target, armv7m};
use rigless::mcu::Mcu;
use rigless::session::Session;
use rigless::snapshot::Store;
use rigless::soc::{Access, Bus, Cpu, Endian, Error, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
use rigless::time::VirtualNanos;

// Two instances of one register each: the water the firmware reads, in thousandths of a degree, and
// the element's duty it writes, in thousandths of full power.
static THERMOMETER_REGISTERS: [RegisterSpec; 1] = [
    RegisterSpec { name: "DATA", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
];
static ELEMENT_REGISTERS: [RegisterSpec; 1] = [
    RegisterSpec { name: "DUTY", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Thermometer { Data }
impl Layout for Thermometer {
    const REGISTERS: &'static [RegisterSpec] = &THERMOMETER_REGISTERS;
    fn from_index(index: usize) -> Option<Self> { (index == 0).then_some(Self::Data) }
    fn index(self) -> usize { 0 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Element { Duty }
impl Layout for Element {
    const REGISTERS: &'static [RegisterSpec] = &ELEMENT_REGISTERS;
    fn from_index(index: usize) -> Option<Self> { (index == 0).then_some(Self::Duty) }
    fn index(self) -> usize { 0 }
}
const THERMOMETER: Peripheral = Peripheral { name: "THERMOMETER", base: 0x4000_0000, size: 0x100, interrupts: &[], registers: &THERMOMETER_REGISTERS };
const ELEMENT: Peripheral = Peripheral { name: "ELEMENT", base: 0x4000_0100, size: 0x100, interrupts: &[], registers: &ELEMENT_REGISTERS };
const CPU: Cpu = Cpu { name: "heater-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
static MEMORY: [Region; 2] = [Region::flash("flash", 0x0800_0000, 64 << 10), Region::ram("sram", 0x2000_0000, 16 << 10)];

/// The probe: a read of `DATA` answers the water the board last handed it.
#[derive(Debug, Default)]
struct Probe { water: i32 }
impl Device for Probe {
    type Register = Thermometer;
    fn read(&mut self, _register: Thermometer, _registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) -> Option<u64> {
        Some(u64::from(self.water.cast_unsigned()))
    }
}

/// The part: the probe on its bus, and the element, which is its register and nothing more.
#[derive(Debug)]
struct Boiler { bus: Bus, probe: Probe, probe_slot: usize, element_slot: usize }
impl Boiler {
    fn new() -> Result<Self, Error> {
        let bus = Bus::builder(CPU, &MEMORY, [THERMOMETER, ELEMENT]).build()?;
        let slot = |name| bus.slot_of(name).ok_or(Error::UnknownInstance { instance: name });
        let (probe_slot, element_slot) = (slot(THERMOMETER.name)?, slot(ELEMENT.name)?);
        let mut part = Self { bus, probe: Probe::default(), probe_slot, element_slot };
        part.check()?;
        Ok(part)
    }
}
impl Mcu for Boiler {
    fn bus(&self) -> &Bus { &self.bus }
    fn bus_mut(&mut self) -> &mut Bus { &mut self.bus }
    fn split(&mut self, slot: usize) -> (&mut Bus, Option<&mut dyn ErasedDevice>) {
        let model: Option<&mut dyn ErasedDevice> = if slot == self.probe_slot { Some(&mut self.probe) } else { None };
        (&mut self.bus, model)
    }
}

/// The board: the part and nothing else.
#[derive(Debug)]
struct Heater { part: Boiler }
impl Heater {
    /// The heater as built, its probe in water at rest.
    fn new() -> Result<Self, Error> {
        let mut board = Self { part: Boiler::new()? };
        board.sense(&Self::REST);
        Ok(board)
    }
}
impl Store for Heater {}
impl Board for Heater {
    fn name(&self) -> &'static str { "heater" }
    fn target_names(&self) -> Vec<&'static str> { vec!["heater-core"] }
    fn mcu(&mut self, target: usize) -> Option<&mut dyn Mcu> { (target == 0).then_some(&mut self.part) }
    fn targets(&mut self) -> Vec<Target<'_>> {
        let core = armv7m::Core {
            variant: armv7m::Variant::M3,
            clock_hz: NonZeroU64::new(16_000_000).unwrap(),
            interrupt_lines: NonZeroU32::new(u32::from(CPU.interrupt_lines)).unwrap(),
            priority_bits: 0,
            refclk_divider: NonZeroU32::new(8).unwrap(),
            instructions_per_second: NonZeroU64::new(250_000_000).unwrap(),
            boot: "flash",
        }
        .spec(&cpu::armv7m::windows(Mcu::bus(&self.part)));
        vec![Target { name: "heater-core", mcu: &mut self.part, core, interrupt_line: InterruptLine::new }]
    }
    fn edge(&mut self) -> Option<&mut dyn Coupled> { Some(self) }
}

impl Edge for Heater {
    /// The water, in thousandths of a degree.
    type Signals = i32;
    /// The element's duty, in thousandths of full power.
    type Outputs = u16;
    /// Nothing about the heater itself can go wrong.
    type Conditions = ();
    const FIELDS: &'static [Field] = &[Field::new("water", "m°C", 4, true)];
    const OUTPUT_FIELDS: &'static [Field] = &[Field::new("ELEMENT.duty", "‰", 2, false)];
    const REST: i32 = 20_000;
    fn sense(&mut self, water: &i32) {
        self.part.probe.water = *water;
    }
    fn condition(&mut self, _conditions: &()) {}
    fn drive(&mut self) -> u16 {
        let duty = self.part.bus.slots()[self.part.element_slot].registers().get(Element::Duty);
        u16::try_from(duty.min(1_000)).unwrap_or(1_000)
    }
}

// The session holds each declaration to its type before anything runs.
let session = Session::new(Box::new(Heater::new()?), 0, VirtualNanos(1_000_000))?;
assert_eq!(session.board().part.probe.water, 20_000);
# Ok::<(), Box<dyn std::error::Error>>(())
```

Five things to get right:

- **The types cross the link as bytes**, so each implements `link::Wire`. The integers and `()` already do. A struct of your own writes its fields in order, each as its integer.
- **Build the board sensing `REST`**, as `Heater::new` does. Nothing senses it for you, and an engine only sends what changes from it.
- **The fields describe those bytes.** `FIELDS`, `OUTPUT_FIELDS` and `CONDITIONS` list each value in the order its type writes it, with its unit, its width in bytes and its sign. `Session::new` holds every list to its type and refuses one that doesn't add up, naming the list and both numbers. An engine holds `FIELDS` and `OUTPUT_FIELDS` against its own declaration of the board, so a field renamed on one side is a run refused rather than a wrong result. It offers `CONDITIONS` as they're listed.
- **`sense` hands each value to what measures it**, a model or a chip, on the part's own field, and the firmware finds it there the next time it reads. A model that has to act at the instant an input changes, like a pin raising an interrupt, takes it through a `drive_` method of the part's, which `Bus::drive` writes, from a test or a driver that knows the instant.
- **`drive` reads what the firmware drives** off the registers or the models that hold it, as the board would read its pins.

If your board's chips can go wrong, list what can as its `Conditions`, each resting at zero: a chip that stops answering is a `chip::Place` its controller silences, and a reading that's stuck, biased or noisy is the board handing the chip what an `edge::Sensed` answers under an `edge::Condition`. Name those chips in `CHIPS` and hand out each one's place in `place`, so a fault can reach it. A session refuses a board that names a chip and has no place for it. The Pixracer's module, `machine::pixracer`, is a whole board that does all of it.

To run it, put the heater in the table of the binary from [Running it](#running-it), and `--link` makes it an engine's target. A plant on the engine's side declares the same machine and the same fields from its own types.

### A chip on a line of your own

The chips this crate ships hang on the controllers its parts ship. A part of your own hangs a chip on a controller of its own, and the controller holds it in a `chip::Place`, which knows whether the chip answers, counts its transactions for a trigger, and takes a flipped word or a lost one. Here's a serial line's controller with a made-up gauge on its far end, which says its reading every 10 ms:

```rust
use std::any::Any;
use rigless::chip::Place;
use rigless::chip::uart::Peer;
use rigless::device::{Device, DeviceContext, RegisterFile};
use rigless::mcu::{Mcu, Single};
use rigless::snapshot::Store;
use rigless::soc::{Access, Bus, Cpu, Endian, Layout, Peripheral, ReadAction, Region, RegisterSpec, Width, WriteModify};
use rigless::time::{EventId, VirtualNanos};

// `DR` takes a word to send and answers the last word received, which the controller holds apart.
// `SR` says one is waiting.
static LINE_REGISTERS: [RegisterSpec; 2] = [
    RegisterSpec { name: "DR", offset: 0x0, width: Width::W32, reset: 0, access: Access::ReadWrite, read: ReadAction::None, write: WriteModify::None, fields: &[] },
    RegisterSpec { name: "SR", offset: 0x4, width: Width::W32, reset: 0, access: Access::ReadOnly, read: ReadAction::None, write: WriteModify::None, fields: &[] },
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Line { Dr, Sr }
impl Layout for Line {
    const REGISTERS: &'static [RegisterSpec] = &LINE_REGISTERS;
    fn from_index(index: usize) -> Option<Self> { [Self::Dr, Self::Sr].get(index).copied() }
    fn index(self) -> usize { self as usize }
}
const WAITING: u64 = 1;
/// The controller's one interrupt, by its position in the instance's list.
const RECEIVED: usize = 0;
/// A word on the line takes a millisecond.
const FRAME: VirtualNanos = VirtualNanos(1_000_000);

/// The controller, what's on the far end of its line, the word it's sending and the last it received.
#[derive(Debug, Default)]
struct Uart { far_end: Option<Place<dyn Peer>>, sending: Option<(EventId, u16)>, received: u16 }

impl Uart {
    /// The chip on the far end as the type the board hung there, for the board to hand it what it
    /// measures. This is the one downcast.
    fn chip_mut<C: Peer>(&mut self) -> Option<&mut C> {
        let chip: &mut dyn Any = self.far_end.as_mut()?.chip_mut();
        chip.downcast_mut()
    }
}

impl Device for Uart {
    type Register = Line;
    fn read(&mut self, register: Line, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) -> Option<u64> {
        if register != Line::Dr { return None; }
        registers.set(Line::Sr, 0);
        context.lower(RECEIVED);
        Some(u64::from(self.received))
    }
    fn write(&mut self, register: Line, value: u64, _registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        if register == Line::Dr {
            let end = context.schedule(VirtualNanos(context.now().0 + FRAME.0));
            self.sending = Some((end, u16::from(value.to_le_bytes()[0])));
        }
    }
    fn on_event(&mut self, event: EventId, _registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let Some((_, word)) = self.sending.filter(|(end, _)| *end == event) else { return };
        self.sending = None;
        // The far end hears the word as its stop bit ends, unless it's silent.
        if let Some(chip) = self.far_end.as_mut().and_then(Place::answering) {
            chip.receive(word, context);
        }
    }
    fn poll(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let Some(place) = &mut self.far_end else { return };
        let now = context.now();
        // A silent far end's words are lost, as down a cut line.
        place.lose_while_silent(now);
        while let Some(word) = place.answering().and_then(|chip| chip.transmit(now)) {
            // Each word is a transaction: a trigger counts it, and a flip or a drop lands on it.
            if let Some(word) = place.sent(u32::from(word)) {
                self.received = u16::try_from(word & 0xFFFF).unwrap_or_default();
                registers.set(Line::Sr, WAITING);
                context.raise(RECEIVED);
            }
        }
    }
    fn reset(&mut self) {
        self.sending = None;
    }
}

/// The gauge: it says its reading once every 10 ms, and keeps what it hears.
#[derive(Debug, Default)]
struct Gauge { reading: u16, due: u64, heard: Vec<u16> }
impl Store for Gauge {}
impl Peer for Gauge {
    fn receive(&mut self, word: u16, _context: &DeviceContext<'_>) {
        self.heard.push(word);
    }
    fn transmit(&mut self, now: VirtualNanos) -> Option<u16> {
        (now.0 >= self.due).then(|| {
            self.due += 10_000_000;
            self.reading
        })
    }
    fn received(&self) -> &[u16] {
        &self.heard
    }
}

const LINE: Peripheral = Peripheral { name: "LINE", base: 0x4000_0000, size: 0x100, interrupts: &[7], registers: &LINE_REGISTERS };
static MEMORY: [Region; 1] = [Region::ram("sram", 0x2000_0000, 16 << 10)];
let cpu = Cpu { name: "a-core", fpu: false, interrupt_lines: 16, endian: Endian::Little };
let mut part = Single::new(Bus::builder(cpu, &MEMORY, [LINE]), "LINE", Uart::default())?;
let data = LINE.base + u64::from(Line::Dr.spec().offset);

// The board hangs the gauge, and reaches it again as a gauge to hand it its reading.
part.pair().0.far_end = Some(Place::new(Box::new(Gauge::default())));
part.pair().0.chip_mut::<Gauge>().unwrap().reading = 43;
// At a stop the controller asks the far end for what it has said by then.
part.poll(VirtualNanos(0));
assert_eq!(part.read(VirtualNanos(0), data, Width::W32)?, 43);
// The firmware's word reaches the gauge as its frame ends.
part.write(VirtualNanos(0), data, Width::W32, u64::from(b'?'))?;
part.advance_to(FRAME);
assert_eq!(part.pair().0.chip_mut::<Gauge>().unwrap().heard, [u16::from(b'?')]);
// Silenced, as a board condition does, the gauge's next word is lost on the line.
part.pair().0.far_end.as_mut().unwrap().silence(true);
part.pair().0.chip_mut::<Gauge>().unwrap().reading = 44;
part.poll(VirtualNanos(10_000_000));
assert_eq!(part.read(VirtualNanos(10_000_000), data, Width::W32)?, 43);
# Ok::<(), Box<dyn std::error::Error>>(())
```

The controller asks the far end at every stop of the session, through `Device::poll`, and the far end hands over what it has said by then. So a word a chip says lands at the first stop at or after its instant: a host on a socket at the stop its bytes are noticed at, and a chip's word at the next event of the part or the next step of an engine. A run with more stops, like one reading `--outputs` often, hands the firmware a chip's words sooner. Keep a chip's words on instants the run stops at anyway, or give its controller an event of its own to ask at.

Your board does the rest. Its `condition` silences the place with `Place::silence`, its `place` hands it out as a `chip::Placed`, so a trigger counts its words and a fault flips or drops one, and its `sense` reaches the chip through `chip_mut` to hand it what it measures. A snapshot keeps the place and the chip through their own `Store`, which a chip that can't be kept leaves empty, as the gauge does.

### What goes wrong, and what it says

| What happened | What you see | What it means |
|---|---|---|
| `boot` names a region that isn't in `MEMORY` | `lantern puts a region called rom at 0x0, but lantern-core has no such region (it has flash, sram)` from `Session::new` | `armv7m::Core::boot` has to be one of the `MEMORY` names |
| The firmware hit an address no model claims | `unmapped at=0x40000200 pc=0x08000098` in `Session::unmapped` | Wrong address in the firmware, or the instance isn't on the bus |
| `link.x` and `MEMORY` disagree | The core fetches something that isn't the vector table | `link.x` has to use the SoC's origins and lengths |
| A table names a board by something other than what the board calls itself | `the machine built for lantern calls itself lamp, and a run names a machine by what it calls itself` from `cli::main` | The name in the table has to be what `Board::name` answers |
| `--link` on a board with no edge | `lantern has no edge a plant couples to, so it can't be an engine's target` | An engine steps a board through its `Edge`. Implement it, or run the board without `--link` |
| `--outputs` on a board with no edge | `lantern has no edge, so there are no outputs to read. ...` from `emulator::run_printed` | The outputs are the edge's. Implement `Edge`, or leave `--outputs` out |
| An edge's fields don't add up to its types | `heater's FIELDS add up to 2 bytes, and its Signals type takes 4 on the wire` from `Session::new` or `link::serve` | Each `Field::width` has to be the bytes its value takes on the wire, in the order the type writes them |
| A chip in `CHIPS` has no place | ``heater's CHIPS name gauge, and its `place` gives none for it`` from `Session::new` or `link::serve` | `Edge::place` answers the place of every chip `CHIPS` names, in that order |

## The SoCs and the machines

Everything a firmware runs on: the SoCs, the models of their peripherals, the machines built from them, and each core family.

| What | Where | Now |
|---|---|---|
| An SoC | one module under `soc`, behind a Cargo feature its boards turn on | `soc::stm32f427`: the STM32F427, RM0090 rev 22 and ES0206 rev 24 |
| Its models | one module each under the SoC's `peripherals` | `soc::stm32f427::peripherals::gpio` is the GPIO port of chapter 8. `pwr` is the power controller of section 5.5, and `rcc` the reset and clock controller of chapter 6. `spi` is the serial peripheral interface of chapter 28. `tim` is the general-purpose timer of chapter 18 against the `tim2` layout, answering for all four of them, and through its `Advanced` family it's the advanced-control timer of chapter 17, answering for TIM1 and TIM8. `dma` is the direct memory access controller of chapter 10, answering for both of them. `sdio` is the card interface of chapter 31. `usart` is the serial port of chapter 30, answering for all eight from the chapter's two tables. `otg` is the reset register of the USB core of chapter 34 and nothing else, which is a partial and says so. `adc` is the analogue-to-digital converter of chapter 13, answering for all three of them and for the common block they share. `rtc` is the backup domain of chapter 26 as far as the reset that clears it, a partial for the same reason and saying so. `i2c` is the inter-integrated circuit interface of chapter 27 as a controller, answering for all three, with target mode left out and saying so. `flash` is the flash memory interface of chapter 3, its registers and not the array behind them. `iwdg` is the independent watchdog of chapter 21, whose end of count restarts the part. `dbg` is the MCU debug component of section 38.16 |
| A chip | one module under `chip`, naming no SoC | `chip::sd` is the card end of a secure digital bus, and `chip::memory_card` is a card that answers it. `chip::fram` is a ferroelectric memory. `chip::mpu9250` with the `chip::ak8963` compass die inside it, `chip::icm20608`, `chip::lis3mdl` and `chip::ms5611` are the inertial units, the magnetometer and the barometer of the first board, each written against its own datasheet. `chip::nmea` is a receiver that reports a position as NMEA sentences on a serial port |
| A core family | one module under `cpu` and one under `machine`, compiled always, naming no SoC | `cpu::armv7m` is the ARMv7-M core and the blocks the architecture fixes at an address, and `machine::armv7m` the typed fields a target of it fills in |
| A machine | one module under `machine` declaring a board struct that implements `Board`. A board is behind a feature of its own that turns on its SoC's, and an SoC's bench is behind the SoC's | `machine::pixracer` is one STM32F427 with its crystals, its rail, the chips on its buses and its six outputs. `machine::stm32f427_bench` is the part alone, for proving it |

An SoC module holds the vendor's SVD, the register tables `rigless svd generate` emits from it, the memory map from the reference manual with every correction where the vendor's file disagrees with it, and **the MCU those add up to**. A model is written against one of the generated layouts. The MCU holds one per instance as a field, and reaching it is a field path:

```rust
# #[cfg(feature = "stm32f427")]
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use rigless::mcu::Mcu;
use rigless::soc::{Layout, Width, stm32f427};
use rigless::soc::stm32f427::Stm32f427;
use rigless::time::VirtualNanos;

let mut mcu = Stm32f427::new()?;
assert_eq!(mcu.cpu(), stm32f427::CPU);

// An instance answers behind whatever the part says has to be true elsewhere. Here that is the port's
// clock enable, RM0090 rev 22 section 2.1.11, so a firmware turns it on first and so does this.
use stm32f427::generated::{GPIOA, RCC, rcc};
let enables = RCC.base + u64::from(rcc::Register::Ahb1enr.spec().offset);
mcu.write(VirtualNanos(0), enables, Width::W32, rcc::ahb1enr::GPIOAEN.mask())?;
mcu.write(VirtualNanos(0), GPIOA.base, Width::W32, 0)?;

// The models are fields. No name, no lookup, nothing that can be absent.
let (clocks, registers) = mcu.clocks();
assert_eq!(clocks.sysclk(registers), 16_000_000, "the part wakes on its own oscillator");
assert_eq!(mcu.spi.len(), stm32f427::SERIAL_INTERFACES.len());
# Ok(())
# }
# #[cfg(not(feature = "stm32f427"))]
# fn main() {}
```

### The models

Each model's module opens with **two** tables. The first is what it covers: `Register or section | Behaviour | Source | Test`, one row per register and per functional section of its chapter, with the section number it was read from and the test that proves it, and one row per erratum of the errata sheet that the model implements. The second is what it doesn't: `Behaviour | Source | Reason`, one row for each clause of the chapter and each applicable erratum the model leaves out. Each row says why. That's a wire the runtime doesn't have, an analogue effect with no register behaviour, or a simplification named as the model's own. A row belongs in the first table only if a test proves it, so no row there is ever empty.

A targeted peripheral is implemented in full, so nothing inside one is register-only. Where that is departed from, the departure goes in the second table with its reason, and the model refuses to pretend: `spi` is the SPI half of a register block the I2S shares, and in the other mode it stores every write, starts nothing, and raises nothing.

**Two instances of one part are joined by four things and nothing else**, because a model may not reach another model. A **gate** is a bit of one instance that another answers behind, which is how a peripheral whose clock is off stops answering its registers. A **request line** is a level one drives and another reads, which is how a serial interface tells a DMA controller it has a byte. A **clock** is a rate one drives and another runs on, which is how the tree tells a timer what it counts, and the timer keeps its count across the change. A **requested access** is a model asking the runtime to make a bus or memory access on its behalf and being handed the answer, which is how that controller then moves the byte. The first three are declared in the part's own description beside its corrections, and `rigless::soc`'s documentation is where all four are written down. A model that needs none of them declares none.

A model's boundary to the world **outside** the part is still methods, because the runtime carries no pin levels. What the outside world puts in takes a `drive_` name and a level. What a board attaches takes an `attach_` or `set_` name and is given after the part is built. What nothing consumes yet is answered by a method, so the logic is complete and only the wiring waits.

`Gpio` takes `drive(pin, level)` for what the outside world puts on a pin and `drive_function(pin, level)` for what a peripheral puts on one, and answers `level(pin)` and `function(pin)`. `Tim<General>` and `Tim<Advanced>` take `with_clock(hz)` from the part for the rate they count until the tree drives another, `drive_input`, `drive_etr`, `drive_internal_trigger`, and on the advanced family `drive_break` for the `BKIN` pin, and answer `output(channel)`, `complementary_output(channel)`, `trigger_output`, and `dma_requests`. The counter itself is read the way the firmware reads it, through `CNT`. `Rcc` takes `attach_crystals(hse_hz, lse_hz)` for the two a board fits and `fail_hse` for one of them stopping. It answers the whole clock tree: `sysclk`, `hclk`, `pclk1`, `pclk2`, `timer_clock`, `pll48`, `rtc_clock`, `mco1`, `mco2`, `plli2s_clock` and `sai_clock`. It drives the seven outputs something on the part runs on as clocks, so a timer, a serial interface or port, an I²C interface, the converters, the card interface, the flash interface, the watchdog and the processor's counter follow a firmware's retuning without naming the tree. The board's rail goes in through the MCU's own `set_supply(millivolts)`, which is also how it moves, and feeds both the regulator, which answers `scale`, and the flash interface's latency check. `Spi` takes `with_clock(hz)`, `drive_miso(words)` for what the device on the other side of the wires puts on them, and `drive_nss(level)`, and answers `shifted_out`. A test reaches a model as a field of the MCU that has it, like `mcu.rcc` or `board.mcu.spi[0]`, and drives one of those inputs at an instant through the MCU's `drive_` method for that field.

### Machines

A machine is a module declaring a board struct that **has** its parts: `Pixracer { mcu: Stm32f427, hse: Crystal, lse: Crystal, rail: Supply }`. It implements `Board`, which hands the session one `Target` per MCU. A target is the MCU borrowed, the `CoreSpec` its emulator takes, and the route from that MCU's interrupt numbers to the emulator's lines. `Board::mcu` is the same MCU by index, which is how the session reaches it on every access.

**A machine doesn't choose what the part has.** Which of a part's own peripherals are modelled is the part's business: `Stm32f427::new()` gives a part with a model on every instance it has one for, each its own field, and that's the MCU, complete and runnable on its own. A board adds the crystals on the oscillator pins, the rail the regulator sees, and the integrated circuits on its buses, and puts them on the part's fields when the board is built. A board that fits no crystal has no crystal. The part still has its whole clock tree.

The whole of it is a tree of concrete types, so every step of reaching anything is a field, up to a chip on a wire, which the interface's `chip_mut` hands back as the type that was hung:

```text
Pixracer ── mcu ──▶ Stm32f427 ── rcc   ──▶ Rcc
         ├─ hse ──▶ Crystal    ├─ pwr  ──▶ Pwr
         ├─ lse ──▶ Crystal    ├─ spi  ──▶ [Spi; 6] ── chip_mut::<P>(n) ──▶ P
         └─ rail ─▶ Supply     └─ tim  ──▶ [Tim<General>; 4]
```

The bus underneath decodes an address to a slot and holds that slot's registers. It owns no models. `Mcu::split` is the one place a slot becomes a field, a `match` each SoC writes once, and it is why nothing after construction looks a peripheral up by name or downcasts to its type.

The core's description comes from the module of the MCU's core family. `armv7m` is the first: one typed field for each thing the family needs to make a processor, with `NonZero` where a zero makes no core, plus the core it runs, the instruction rate, and the region the SoC's boot configuration puts where the core fetches its reset vector. An SoC's numbers are therefore typed and named once, and nothing is written twice: the Pixracer's interrupt count is its SoC's own.

`Pixracer` is the first board: one whole STM32F427 in its `mcu` field, the board's 24 MHz and 32.768 kHz crystals, and its 3.3 V rail, so a firmware can take the part from the internal oscillator it wakes on up to 168 MHz on the over-drive rail, the way the manual sets that out. Its FRAM and its barometer hang on the second serial interface and its two inertial units and its magnetometer on the first, each behind the select the board description names. Its `serial1`, the telemetry port a ground station talks to, is `USART2`, and `rigless run` listens for one on port 5760. Its `serial3`, `UART4`, has a satellite receiver on the far end that reports where the world says the board is, in NMEA sentences. Its six outputs are the description's `PWM(1)` to `PWM(6)`, four channels of the first advanced-control timer and two of a general-purpose one, read on the speed controllers' curve. Its chips read the body's axes as their own, since the firmware's driver undoes the description's rotation on the way in, and it's built sensing a level vehicle at rest.

`Stm32f427Bench` is the same MCU with as little fitted as still lets something happen: a rail, one crystal, a peer on each serial interface and each serial port, and a seven-bit and a ten-bit one on the first I²C interface, each answering a known pattern and recording every word it was sent, known voltages on the converters' pins, and a memory card on the card interface. It's the part run standalone, which is what a conformance run wants. A board's crystal and its sensors are things that run shouldn't depend on, and a part has to be provable before there's a board to put it on.

### Adding an SoC

1. Make the directory `src/soc/<name>/` and put the vendor's SVD in it, unchanged, with its origin, version, and license in `src/soc/<name>/README.md`.
2. Generate the tables from the repository root. The directory must hold exactly one `.svd` file, and `generated.rs` and `generated/` land beside it:

   ```text
   rigless svd generate src/soc/<name>
   ```

3. Write the two files beside it as `stm32f427` does: `src/soc/<name>.rs` is the index, declaring `generated` and `peripherals` publicly, `memory_map` privately, and re-exporting the surface a board uses. `src/soc/<name>/memory_map.rs` holds the map and every correction, each a call on the builder or a struct update over a generated constant, with the manual's section named, and exposes `CPU`, `MEMORY`, and `builder()`.
4. Add the feature to `Cargo.toml`, `<name> = []`, in `default` only when no board there turns it on, and the module to `src/soc.rs` under `#[cfg(feature = "<name>")]`.
5. Add the feature to the two places that name every SoC: the `extern crate self as rigless;` gate at the top of `src/lib.rs`, which is what the generated tables import through, and the feature checks in the `lint` job of `.gitlab-ci.yml`, which build each feature on its own.

CI regenerates every SoC and fails when the committed tables differ from the result. The tables are never edited by hand.

### Adding a model

1. Put it under its SoC's `peripherals`, `src/soc/<name>/peripherals/<peripheral>.rs`, written against one generated layout and named for the peripheral rather than for that layout: `tim.rs` is written against `tim2`, `gpio.rs` against `gpioa`.
2. Open it with the two tables above: what it covers, each row naming the section it was read from and the test that proves it, and what it leaves out, each row naming why. Every erratum of the errata sheet that applies to the part goes in one table or the other. The manual is the source of truth and a citation names the section it was read from, never memory.
3. Give the inputs the runtime can't yet deliver an explicit method, and the outputs nobody consumes yet a method as well, so the logic is complete and only the wiring waits.
4. Give the MCU a field for it in `src/soc/<name>/peripherals.rs`, a `Field` variant, a slot in `Slots`, a row in `Field::all`, and an arm in `Field::instance`, in the slot-resolving match, and in `split`. Only `Field::all` isn't a match the compiler checks, so it's the one to remember. `Mcu::check` catches a field wired to the wrong slot when the part is built.
5. Where the vendor gives one instance a shorter table than the layout the model is written against, and that shorter table is a prefix of it, override `Device::layout` to return the instance's own. That is what lets `tim` answer for all four of chapter 18's timers across two tables, and it is what the wiring check holds each slot to.
6. Add the model and its test file to `every_test_a_models_table_names_is_a_test_that_exists`, which is what holds the tables of step 2 to naming tests that exist. A model missing from that list is a model whose tables nothing checks.
7. Add the instance's register offsets to the bench firmware's walk. **Binding a model claims the whole instance**, so the coverage gate wants every register of it reached, not only the ones the model answers for. A partial model is still walked whole and accounts for the rest in its second table.
8. Expect the part's own checks to fire, and answer each with a reason rather than by loosening it. A model with no clock enable behind it, or an instance the manual gives no interrupt line, is a real thing the part does. Each has an exception table next to the rule it breaks, so the reason is found where the rule is.
9. A controller that chips hang on marks what a trigger counts on each chip's `chip::Place`: `begin` before it reaches the chip for a transaction, `release` when the transaction ends, and every word the chip answers through `answered`, or through `sent` on a line where each word is a transaction of its own. Hand the place out with a `place_mut`. A controller that marks nothing has chips no trigger counts and no flip reaches.

### Adding a chip

A chip is a board's, not a part's. It lives under `src/chip`, is written against its own datasheet, and names no SoC. It implements the seam the controller it hangs on calls: `chip::spi::Peer` for a serial interface, `chip::uart::Peer` for a serial port, `chip::sd::Card` for the card interface, and `chip::i2c::Peer` for an I²C bus, whether the part's or one inside another chip. The seam lives beside the chips, since a chip names no SoC. Its own state is its account of what it was sent. A stand-in has no state of its own, so it keeps a record instead. It opens with the two tables above, and the first has the same four columns, so the same check that holds a peripheral's tables to naming real tests holds a chip's. Add it to that check's list. The board hangs it on its controller when the board is built. On a serial interface it hangs behind the select the board's description names, a port, a pin and the level that selects it, and the pin's level reaches the interface as the port's request line. On an I²C bus its address is what selects it. A test drives it directly through the seam, taking the instant from a controller's context. The firmware the board runs is what proves the chip and the select together. A chip that measures something has setters in its datasheet's terms, like `set_rate`, `set_field` and `set_pressure`, each on its own axes, and keeps what its transducer measures from then on. The board reaches them through the interface's `chip_mut`, asking for the chip back as the type it hung, and it's the board that turns the body's axes into the chip's.

### Adding a machine

Declare a struct whose fields are what's on the board: its MCU or MCUs, its crystals, its rail and the chips on its buses. Put each on the part's own fields when the board is built. Implement `Board`: `targets` hands the session one `Target` per MCU, *borrowing* it, with a `CoreSpec` from its core family's module and the interrupt route, which is `InterruptLine::new` wherever the controller is inside the core, and `mcu` hands the same MCU back by index. Give it a feature of its own in `Cargo.toml` that turns on its SoC's, and name it in `default` if the default build should carry it. Gate its module, its row and every test that builds it on that feature, add the feature to the checks in the `lint` job of `.gitlab-ci.yml`, and add the row to the table `machine::all()` returns, which is how `rigless run` and `rigless run --link` both find it. An SoC's bench rides on the SoC's own feature instead. You hand a board outside this crate to `cli::main` in a table of your own instead. A board with two MCUs is two targets. A process runs one of them, `--target` says which, and `targets` answers with the same MCUs in the same order every time. A board that drives or senses something implements `Edge` with its own `Outputs`, `Signals` and `Conditions`. Its `REST` is what the board senses when it's built, and all three types implement `link::Wire`, which is what lets `edge` answer with the board itself. `condition` turns each silence onto the chip's place with its controller's `silence`, which answers whether a chip hangs there, and senses what the board last sensed again, so a condition takes effect at once. `sense` hands each chip what its `Sensed` answers under its `Condition`, so one chip can read wrong while the one beside it reads the truth. `draw` has each `Sensed` draw its noise for the step, keyed by the noise condition's own name, and senses again when any moved. A serial line's error rates go on the port's `chip::uart::Errors` in `condition`, and its seed in `draw`. `drive` reads each output off the pin its description wires it to, as whatever that pin carries means to the thing on it, and `sense` turns the body's axes into each chip's own before handing the signals over. `CHIPS` names every chip a trigger or a one-shot fault can reach, and `place` hands out each one's place by that name, through its controller's `place_mut`. Its `edge` answers itself.

### Adding a core family

A family is a core and the spec that makes it, and neither has to live in this crate. The core implements `emulator::Processor`: it runs a stretch of the family's instructions on the `emulator::CoreBus` it's lent, and between stretches it does what the runner, the models and a debugger ask of it. The trait's documentation says what the runner relies on, from how instructions are counted to when the core hands control back. A function makes the core from a `CoreSpec`, and refuses one that's missing a property the core reads. A machine module renders the spec: that function in `processor`, a typed field per property the core reads back by name, an enum of the variants it accepts, and the rate the core retires instructions at. `emulator::run` calls whatever constructor the spec carries, so no generic module changes and nothing in the runtime names your family. `cpu::armv7m::processor` and `machine::armv7m` are the family this crate carries. Its code generator, page table and block cache stay private, so a family of your own brings its own way to run instructions, an interpreter or a translator. A family that maps memory into its core says how big its pages are, and one that maps nothing takes regions of any size, every access reaching it through the bus. A family answers, address by address, whether it can stop before an instruction there, and the run refuses a debugger's breakpoint or a watch on an instruction it can't keep. One that never stops at an instruction writes none of the breakpoint methods. A family that asks `CoreBus::stops_before` before each of an instruction's accesses has a debugger's watchpoint stop the core before the access, and one that doesn't still stops for it, right after the instruction that made the access. A kernel's walk reads its switched-out threads back by the names the family's target description gives its registers, so a family writes nothing for GDB to see them. A board whose MCUs are of two families runs each as its own target.

`cpu::armv7m::processor` also runs the ARMv7-M core without a session: make a spec with `machine::armv7m::Core`, map pages of your own memory into the processor, its `page_size` bytes each, boot it and run it on a `CoreBus` of your own.

## The generator

`rigless::svd` turns a vendor's CMSIS-SVD file into plain Rust a person can implement a peripheral against. For each register layout it emits a `Register` enum to match on, a constant per field, and the table the runtime serves reads and writes from. `generated.rs` names every instance at its address. All of it uses the types in `soc`, and none of it knows which processor family the SoC belongs to.

What a layout file looks like, trimmed:

```rust,ignore
/// The registers by name, where a variant's index is its row in [`REGISTERS`].
pub enum Register {
    /// `CTRL` at offset `0x0`, resets to `0x0000_0000`, read-write.
    Ctrl = 0,
    /// `STATUS` at offset `0x8`, resets to `0x0000_0002`, read-write.
    Status = 2,
}

/// The fields of `CTRL`.
pub mod ctrl {
    /// `EN`, bit 6.
    pub const EN: &FieldSpec = &super::REGISTERS[0].fields[4];
}

pub static REGISTERS: [RegisterSpec; 9] = [ /* one register per line */ ];
```

Register and peripheral names that are Rust keywords get a trailing underscore, so a register called `LOOP` is the module `loop_` and the variant `Loop`. Not emitted: descriptions, enumerated values. The provenance line at the top of each file names the source file and its SHA-256.

The library is [`svd::generate`] for the tables of one SVD as strings, and [`svd::write_module`] for a whole SoC directory. The fixture under `tests/fixture` is an SoC directory of its own. After changing the emitter, regenerate it the same way and review the diff:

```text
rigless svd generate tests/fixture
```

## Building and testing

`.gitlab-ci.yml` is the source of truth for how this is built and tested. Every job's `script` is a command you can run yourself, so neither this page nor the README copies them, and neither can then tell you something the pipeline has stopped doing. What belongs here is what the suite proves.

**Sixteen test binaries boot a firmware** on the core built in, and they need one built under `firmware/`, bear-bones, conformance or the template, failing rather than skipping without it and saying so. `bear_bones_runs` is the proof of the whole stack: the test firmware's setup writes checked against the register file, its TIM2 handler firing inside the window the manual puts the update in, a twin with the interrupt misrouted that proves the first would notice, two runs of the binary that print the same thing to the byte, three runs at once in one process that are the same run, a debugger that stops it at a breakpoint and a watchpoint without changing it, and one that steps it and writes its flash, after which the run takes no snapshot. `conformance` is the bench, every check the conformance firmware declares, held to a budget the job enforces. `debugger` holds bear-bones on its read and access watchpoints and restarts it with extended mode's `run`, and runs a program a few instructions long to show a `BKPT` halting the core for a debugger, a step over a faulting load stopping at its handler, memory nothing answers refused, `monitor reset`, the interrupt reaching a core that waits, the registers against the target description with and without a floating-point unit, the protocol's framing, and one debugger at a time. It also runs GDB itself, `gdb-multiarch`, against the binary on bear-bones, and fails rather than skipping when that isn't installed. `debugger_protocol` holds the server to "Debugging with GDB" for GDB 18.1, appendix E where it's particular, thread-ids in both forms among it, and runs GDB itself for what only GDB's use of the protocol shows, like its own `record full`. `debugger_stops` holds where a debugger stops the core and what that costs the run: a run serving GDB with nobody attached printing what a run without it prints, the interrupt reaching a core that has waited or slept a long way, watchpoints well past silicon's four that stop the core before the access they watch, or right after the instruction once a model has seen part of it, and halt points at an exact instruction count or instant, with GDB itself reading and breaking on `$icount`. `debugger_peeks` holds a debugger's reads to changing nothing: every model peeked on a busy part, bear-bones read in its TIM2 handler with the run it makes and the run it prints unchanged, `monitor peek off` bringing the firmware's read back, a peripheral whose clock is off, and a stop by the interrupt, a step or a halt point finding every model settled at its instant. `debugger_memory` holds what a debugger reads and writes of memory and flash, and what `DFSR` says halted the core. `debugger_live` holds conditions and `dprintf` worked out in the target, and non-stop mode reading memory while the firmware runs. `debugger_reverse` holds `monitor save`, `monitor restore`, `reverse-stepi` and `reverse-continue` to the run that never went back, by what it hears and what it prints. `two_targets` debugs both MCUs of a board with two, one connection each. `gdb_sessions` runs GDB's whole sessions under `tests/gdb` against bear-bones and its other programs and holds each to the transcript beside it. `semihosting` runs bear-bones' programs that print, exit, and write and read files on GDB's host, and `output_ports` the ones that write to the ITM and to RTT's terminal. `link` spawns the binary as an engine would and steps it across the link. `serve` serves a board from outside the crate's table of machines as an engine's target, on the template firmware. `snapshot` stops bear-bones and the conformance bench, saves each, and restores it into a new core that runs on as the run that never stopped did. `core` needs no firmware: it runs programs a few instructions long, to show a core that locked up resets and runs on, a floating-point exception pends no interrupt line, an exception's entry stores at the instruction it interrupts, and a stop saves the same bytes however the run was split into stretches on the way there. `outside_family` and `core_alone` need none either, and see only the public API: a toy family the test writes runs on the runner as this crate's own does, a debugger's watchpoint stopping it though it never asks before an access, and the ARMv7-M core runs on a bus of the test's own with no session around it.

**Everything else runs with no core**: the session driven by hand as the core drives it, the link's frames read back as written and refused when they aren't, and every model against its manual. The runtime alone, with no SoC and no command line, is what the `--no-default-features` checks build. Miri runs it too, with the core absent, since it can't execute the code the core generates.

**The fuzz targets** under `fuzz/` take what comes from outside a run and the core itself: arbitrary code on the core, a processor's saved state, a snapshot file, what a debugger sends, the link's frames, a firmware image and its symbols, and an SVD for the generator. Each has to answer or refuse its input, and never panic, hang or touch memory it doesn't own. The `fuzz` job runs them on every merge request with `cargo fuzz` and its address sanitizer. To run one yourself, on the nightly toolchain `cargo fuzz` needs and naming your host's target, since the `cargo fuzz` you install may default to another:

```text
cargo +nightly fuzz run --target x86_64-unknown-linux-gnu core
```

## What it is not

The open runtime boots firmware, runs unit tests in CI, serves GDB, prints every peripheral access, and carries the seam a plant is stepped against. Stepping a plant in lockstep, injecting faults and replaying runs bit-for-bit is the Rigless engine, `rigless-hil`, a separate, closed, commercial binary. The `Plant` trait it steps is in `rigless-plant`, in [rigless-labs/hil](https://gitlab.com/rigless-labs/hil), under the Rigless License. Nor is this an emulator of one board. A part and a board are each a module behind a feature of its own, and the runtime's generic modules know neither by name.