mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
2073 Commits
2021-08-09
...
2023-05-08
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
066e42145e | ||
|
|
efe1425e8e | ||
|
|
55e0742232 | ||
|
|
abf92cd09e | ||
|
|
73a214913a | ||
|
|
5453840d8c | ||
|
|
77078e2b7a | ||
|
|
3f1ae986cb | ||
|
|
d39d2a88f8 | ||
|
|
6556865615 | ||
|
|
44779e68ee | ||
|
|
54f5bae59e | ||
|
|
e94b9f695a | ||
|
|
3797968870 | ||
|
|
9c079a6c50 | ||
|
|
561e2b774e | ||
|
|
0cb4fec504 | ||
|
|
ec81cdd388 | ||
|
|
8f0dc9d9a2 | ||
|
|
4ebc3344cb | ||
|
|
6552d962ab | ||
|
|
5f151c07ea | ||
|
|
1f4d526ea5 | ||
|
|
19d03dd4fd | ||
|
|
6c0feeedb4 | ||
|
|
b5d9586362 | ||
|
|
8cd38094fc | ||
|
|
e49e98d309 | ||
|
|
1b4df01a28 | ||
|
|
dbddcd109c | ||
|
|
efa7d659bc | ||
|
|
5daec050dd | ||
|
|
f5c8eba843 | ||
|
|
e5b0e666cc | ||
|
|
96896f838c | ||
|
|
f22aa6eb36 | ||
|
|
6d092e0633 | ||
|
|
6651a9c323 | ||
|
|
d40bc58e8b | ||
|
|
4b53082774 | ||
|
|
8a5b7e9f47 | ||
|
|
12bcc2dee7 | ||
|
|
d587d80616 | ||
|
|
0070a271f8 | ||
|
|
d641a9c2b1 | ||
|
|
ed2d4ebb0c | ||
|
|
32597b4e95 | ||
|
|
9f198f6392 | ||
|
|
107cb18df4 | ||
|
|
e66c015b43 | ||
|
|
9d99cc6115 | ||
|
|
383770515e | ||
|
|
024b7960cb | ||
|
|
e0a5d9f31c | ||
|
|
224c79c492 | ||
|
|
278e7ba9b0 | ||
|
|
20c1c6fdcd | ||
|
|
514022204e | ||
|
|
564ee1a5cb | ||
|
|
f3c2c0ffa9 | ||
|
|
931d2373a4 | ||
|
|
de3cd9c286 | ||
|
|
655638656f | ||
|
|
7d63a50f3e | ||
|
|
2bf2abf4b2 | ||
|
|
235d54bb67 | ||
|
|
e66a92d6cb | ||
|
|
a6251f436a | ||
|
|
6d49b2e66b | ||
|
|
363fd0f781 | ||
|
|
345e519e6a | ||
|
|
315e0b4545 | ||
|
|
4a5b2fd9ba | ||
|
|
a5a36cb08e | ||
|
|
aa4582956f | ||
|
|
c9543d0b36 | ||
|
|
d36c8df0c9 | ||
|
|
f26dee16bf | ||
|
|
131784d007 | ||
|
|
e703fa9cf8 | ||
|
|
201a7c17ae | ||
|
|
e0125e0177 | ||
|
|
cc04349618 | ||
|
|
d46f869276 | ||
|
|
9836a108da | ||
|
|
da944fde92 | ||
|
|
c9124f13cd | ||
|
|
ca7d34ad04 | ||
|
|
2913368a06 | ||
|
|
82659e7924 | ||
|
|
f3a84021ed | ||
|
|
555d883227 | ||
|
|
020d9604c6 | ||
|
|
6845008fd4 | ||
|
|
cc7b209e1a | ||
|
|
e8404bdcc0 | ||
|
|
f8eb2199c2 | ||
|
|
a13905acf9 | ||
|
|
5471979f8d | ||
|
|
5b8a5755f0 | ||
|
|
caaba836ba | ||
|
|
8fc043247c | ||
|
|
e58a488add | ||
|
|
0ea1da10d6 | ||
|
|
3381e6b5aa | ||
|
|
4c1973adc8 | ||
|
|
318cfab67d | ||
|
|
1ef34b1f18 | ||
|
|
12bd71cfe1 | ||
|
|
c7128f4206 | ||
|
|
95fd5a52ba | ||
|
|
bfa167fcdf | ||
|
|
b948761f30 | ||
|
|
dd65074bbf | ||
|
|
9debed25e8 | ||
|
|
1bdf9a50e6 | ||
|
|
2592dfd78f | ||
|
|
ef5d05db53 | ||
|
|
6a2cda7074 | ||
|
|
62d381065e | ||
|
|
091c1b0f45 | ||
|
|
a28b5bdeea | ||
|
|
2e45422b03 | ||
|
|
3606f5befe | ||
|
|
44ac948bb2 | ||
|
|
4bac782121 | ||
|
|
318109b7d5 | ||
|
|
6990ba9242 | ||
|
|
54be424159 | ||
|
|
8d900cf636 | ||
|
|
6db939f48f | ||
|
|
5eae11434a | ||
|
|
7aa8728b39 | ||
|
|
270c5dfe85 | ||
|
|
d6e4f12fdc | ||
|
|
4e3e4806b9 | ||
|
|
ebc596820e | ||
|
|
b62e899039 | ||
|
|
a123ef151c | ||
|
|
5b31db700b | ||
|
|
8af0a2313c | ||
|
|
88eaa4ff02 | ||
|
|
c140f370fe | ||
|
|
023da1970a | ||
|
|
211e145230 | ||
|
|
bbccc5d6d6 | ||
|
|
dbfc9a14aa | ||
|
|
9630a1bc39 | ||
|
|
3847c9c281 | ||
|
|
3f2a5929a3 | ||
|
|
9b71f42375 | ||
|
|
9c43776392 | ||
|
|
35a0a1447e | ||
|
|
bf0ed2813c | ||
|
|
1edf747f9f | ||
|
|
f38cf91ea7 | ||
|
|
5c7367b262 | ||
|
|
c1457cc5e0 | ||
|
|
169d7a7418 | ||
|
|
5143960970 | ||
|
|
40894964bc | ||
|
|
927e61484f | ||
|
|
dce04e7219 | ||
|
|
f5814b4c75 | ||
|
|
815a75d9b6 | ||
|
|
4ad84e5047 | ||
|
|
8665dca7f0 | ||
|
|
41d57e03a6 | ||
|
|
914a9e0c84 | ||
|
|
8768ee1504 | ||
|
|
8e6c36bb15 | ||
|
|
c6401f0444 | ||
|
|
3c3efe3e22 | ||
|
|
7028bdd05d | ||
|
|
7cb51c021b | ||
|
|
a3df106f92 | ||
|
|
b538407386 | ||
|
|
c04d292c8e | ||
|
|
f9c88fd598 | ||
|
|
7fcb1b29dd | ||
|
|
6786e3e78c | ||
|
|
67755c3811 | ||
|
|
c6372295c5 | ||
|
|
a2786e6266 | ||
|
|
a892523c09 | ||
|
|
ab595f5e8d | ||
|
|
38950fe241 | ||
|
|
46d009f27b | ||
|
|
34722bae89 | ||
|
|
d41081c59f | ||
|
|
ec227ce021 | ||
|
|
83f6d1cda3 | ||
|
|
6d315b4660 | ||
|
|
debdad350d | ||
|
|
0d4dc214fb | ||
|
|
5d4c49c913 | ||
|
|
8f5c7fcabc | ||
|
|
115acf835e | ||
|
|
c0fec8db15 | ||
|
|
f0e70f18fd | ||
|
|
9d2841bf6a | ||
|
|
ce6dd188a4 | ||
|
|
4cc34fd557 | ||
|
|
3636383b1f | ||
|
|
1264600bab | ||
|
|
002d27d9c2 | ||
|
|
90e8ce3253 | ||
|
|
a315384e30 | ||
|
|
c5c722ae56 | ||
|
|
872b9e5021 | ||
|
|
492a170b20 | ||
|
|
30a2b1611f | ||
|
|
29af5542f8 | ||
|
|
bc4c54800e | ||
|
|
4c93d01fe2 | ||
|
|
8f20cb93e9 | ||
|
|
73e79b14ea | ||
|
|
3142f5c21d | ||
|
|
6d7f189ce7 | ||
|
|
a91a5b8d07 | ||
|
|
4cdcd3ac7d | ||
|
|
0576451102 | ||
|
|
3f12a28f4f | ||
|
|
41ba883fb6 | ||
|
|
1e646eb57b | ||
|
|
2d6afe1013 | ||
|
|
d3c446d91b | ||
|
|
975ead5d01 | ||
|
|
c6cda7c401 | ||
|
|
7e69e33ec2 | ||
|
|
95e00dd958 | ||
|
|
8a673a697b | ||
|
|
75ad4cdb67 | ||
|
|
d42b6df570 | ||
|
|
81b00c6d97 | ||
|
|
1e5f751bc0 | ||
|
|
9a65fffe16 | ||
|
|
515fa22bfe | ||
|
|
66ac089cc2 | ||
|
|
5d5098acb2 | ||
|
|
1bf8406e7e | ||
|
|
75acbd2d6c | ||
|
|
baa6f9b3cd | ||
|
|
1c6a0ad3f7 | ||
|
|
fbfa26ad5e | ||
|
|
b12fd00145 | ||
|
|
0c8815d6a0 | ||
|
|
700470915a | ||
|
|
f8b42d4107 | ||
|
|
63bd0f918d | ||
|
|
445b34933a | ||
|
|
6b85ee9607 | ||
|
|
bbd8b011ba | ||
|
|
15fbf4cb7f | ||
|
|
7293f9dc10 | ||
|
|
8567c934b1 | ||
|
|
2744a9b6b0 | ||
|
|
91047e5b3a | ||
|
|
c6dd7d4726 | ||
|
|
b7d80f5ed1 | ||
|
|
a5765abbad | ||
|
|
696ec12516 | ||
|
|
c9734df65c | ||
|
|
13e490e7d7 | ||
|
|
cefcc1d443 | ||
|
|
d1f929e6f7 | ||
|
|
c9643c4145 | ||
|
|
e289e6e757 | ||
|
|
a726c9d97a | ||
|
|
c77e7c268f | ||
|
|
9c57bfd58d | ||
|
|
4efda108c6 | ||
|
|
191cf4829b | ||
|
|
9b7a925816 | ||
|
|
392b0acb58 | ||
|
|
4b7606894e | ||
|
|
1fb94d15ab | ||
|
|
348c42bdea | ||
|
|
e450e53c4e | ||
|
|
355ee7fbc7 | ||
|
|
339086d597 | ||
|
|
f0b1c34db2 | ||
|
|
7b25fe5f61 | ||
|
|
194b5bc36a | ||
|
|
0951c50e40 | ||
|
|
9588c9bee2 | ||
|
|
6f973fc605 | ||
|
|
eb51ed9ae8 | ||
|
|
83cf4497dd | ||
|
|
f6e601daff | ||
|
|
bb6ceafe0e | ||
|
|
55e73cb812 | ||
|
|
f0db676a10 | ||
|
|
32b29bd63b | ||
|
|
bfe94eb268 | ||
|
|
20ec192129 | ||
|
|
055e9cdf8d | ||
|
|
a5b9bdc18c | ||
|
|
eb51ff5cdf | ||
|
|
1769c24531 | ||
|
|
1a58ddaa67 | ||
|
|
183cb519e7 | ||
|
|
68361913ee | ||
|
|
ced002125e | ||
|
|
1e17fc71ab | ||
|
|
f57c2a961f | ||
|
|
48a4355592 | ||
|
|
3bc38d35c9 | ||
|
|
4d67360702 | ||
|
|
18def0c97d | ||
|
|
84cb7df1be | ||
|
|
97d93ad55c | ||
|
|
5f85074caa | ||
|
|
f0a4d1d8ec | ||
|
|
fb0241cf6e | ||
|
|
50b5122969 | ||
|
|
9f450b3ccb | ||
|
|
4190d25698 | ||
|
|
befc81743a | ||
|
|
23ff3fc366 | ||
|
|
78ce439b9b | ||
|
|
ce440d52b3 | ||
|
|
2e7e5ea12b | ||
|
|
0d8c014099 | ||
|
|
fee82d3baa | ||
|
|
76ad465030 | ||
|
|
483ee8a74f | ||
|
|
520ae7f2b2 | ||
|
|
ae5b81c0ab | ||
|
|
6bd261b222 | ||
|
|
53bb17c848 | ||
|
|
6e0f260478 | ||
|
|
19e333d117 | ||
|
|
3352feb21b | ||
|
|
73549eb38c | ||
|
|
dbff7592f5 | ||
|
|
4d96122884 | ||
|
|
9085ba4081 | ||
|
|
f1f16d1f9a | ||
|
|
fd14829992 | ||
|
|
c0fe88a5bb | ||
|
|
4d9d684618 | ||
|
|
a0a835cf10 | ||
|
|
ef67205ce8 | ||
|
|
794adf470b | ||
|
|
8cc20844a9 | ||
|
|
b522d65c50 | ||
|
|
cb19c2ffb0 | ||
|
|
5f6ddf8557 | ||
|
|
72e0bfecc1 | ||
|
|
cdf547ac82 | ||
|
|
dd5b4b484a | ||
|
|
56831e02fc | ||
|
|
5d2d3944ef | ||
|
|
f9e21df701 | ||
|
|
bb436204f6 | ||
|
|
de45536b5c | ||
|
|
ebc1264c2c | ||
|
|
4875148617 | ||
|
|
7a82b76911 | ||
|
|
27d37f71ec | ||
|
|
c4a5a9763e | ||
|
|
a9f97ac871 | ||
|
|
475440dc70 | ||
|
|
dc3f8f5e42 | ||
|
|
459ef39b08 | ||
|
|
27812fd0e2 | ||
|
|
38eb4d36de | ||
|
|
2bd20a0cf8 | ||
|
|
da61909ec5 | ||
|
|
5729ece7bb | ||
|
|
151f60958e | ||
|
|
180045ada6 | ||
|
|
11542e7a7f | ||
|
|
71598250ea | ||
|
|
e8aab1fd2a | ||
|
|
ffb0b2ce0b | ||
|
|
b7c315058f | ||
|
|
7d6eac2895 | ||
|
|
d79aac3081 | ||
|
|
8d5547dc9e | ||
|
|
5d89293c92 | ||
|
|
5ba97da6cd | ||
|
|
37f07f349e | ||
|
|
711f7b2d75 | ||
|
|
dca8c51384 | ||
|
|
462b7dcbfa | ||
|
|
2ab4b351ca | ||
|
|
4eedec50fb | ||
|
|
ee22a98c17 | ||
|
|
99ced5476f | ||
|
|
cdbd821913 | ||
|
|
8808014a85 | ||
|
|
6832cbeb31 | ||
|
|
08b3c42a5c | ||
|
|
95c526d957 | ||
|
|
3f3c9f7491 | ||
|
|
6aa8400996 | ||
|
|
8ccb803b08 | ||
|
|
7ce8326c8c | ||
|
|
28b4f51cb3 | ||
|
|
2fe6253ca8 | ||
|
|
e6ae35638b | ||
|
|
812234f695 | ||
|
|
b921e893a2 | ||
|
|
9b235a8f64 | ||
|
|
c8a82933bc | ||
|
|
5813e2b6c6 | ||
|
|
005f38dbff | ||
|
|
b77fb50e89 | ||
|
|
ae8f0d339e | ||
|
|
ccadf69630 | ||
|
|
bbd2cd47ea | ||
|
|
63ad2e8263 | ||
|
|
23e4a47f8b | ||
|
|
255d2f3486 | ||
|
|
6ad1d74ddd | ||
|
|
12ca79e645 | ||
|
|
85df54ee7d | ||
|
|
2b220659dd | ||
|
|
8a8c044976 | ||
|
|
e79388fc02 | ||
|
|
f6a72dc2b4 | ||
|
|
041eb79bf8 | ||
|
|
7d82b2ad12 | ||
|
|
c2b8cbfefc | ||
|
|
53140c016e | ||
|
|
adbd23eaea | ||
|
|
3f80df1feb | ||
|
|
cabf1a052c | ||
|
|
8ff9f27b91 | ||
|
|
ae2419e283 | ||
|
|
c1f0eed0a3 | ||
|
|
4e5a80e23a | ||
|
|
46fee9c53a | ||
|
|
8ddf20b36a | ||
|
|
7ba6c78d14 | ||
|
|
fd20323c25 | ||
|
|
83b9fc3318 | ||
|
|
1ceabb30b0 | ||
|
|
f8cb3ca8b5 | ||
|
|
d8a11eaba7 | ||
|
|
ab37b00356 | ||
|
|
b4fcf92a62 | ||
|
|
38c531fd5a | ||
|
|
8c670d2105 | ||
|
|
9a56d053f8 | ||
|
|
79224d9383 | ||
|
|
7c328edd4a | ||
|
|
cb0e259339 | ||
|
|
9be9e1ab0c | ||
|
|
149c940a29 | ||
|
|
ec728ad573 | ||
|
|
df7f94f362 | ||
|
|
bc9ddacb8d | ||
|
|
979bf42541 | ||
|
|
d09473b66f | ||
|
|
b31b4a5d10 | ||
|
|
5560a0ed39 | ||
|
|
a1ae7c28b2 | ||
|
|
a364499d17 | ||
|
|
fb2b7969a2 | ||
|
|
abb19e6670 | ||
|
|
555250dbd9 | ||
|
|
8148397f62 | ||
|
|
f095bba1ca | ||
|
|
ee3a3df0b5 | ||
|
|
aff1caed15 | ||
|
|
da03cd58c1 | ||
|
|
ce98ca4bdd | ||
|
|
cc55f0586d | ||
|
|
47e8f3c0f1 | ||
|
|
d5ceb934d2 | ||
|
|
17c1e51231 | ||
|
|
fee072b404 | ||
|
|
0a9c392371 | ||
|
|
06dbb7167b | ||
|
|
eff9a09b9f | ||
|
|
1f19141746 | ||
|
|
28093196b9 | ||
|
|
eb206a08d9 | ||
|
|
b2f005da1b | ||
|
|
8305a3b46a | ||
|
|
f3f23f90a3 | ||
|
|
77bc60bf86 | ||
|
|
ec5d57fefe | ||
|
|
58396f0c52 | ||
|
|
c0377f074f | ||
|
|
4d3221fc55 | ||
|
|
59388230a6 | ||
|
|
1ba4363802 | ||
|
|
d17fadbe0b | ||
|
|
9cba56237d | ||
|
|
ea9411b21c | ||
|
|
38e85a340a | ||
|
|
fea8fecf11 | ||
|
|
c4091a4cdb | ||
|
|
d826532031 | ||
|
|
beca7a01c2 | ||
|
|
2d8e260671 | ||
|
|
04f5d29ed9 | ||
|
|
5ed60f9153 | ||
|
|
2f78a1c7af | ||
|
|
dc35ec8fa0 | ||
|
|
7e3dbbbf0a | ||
|
|
0f017302ce | ||
|
|
36c3cb1f70 | ||
|
|
6773a321c1 | ||
|
|
ffdf44ad4f | ||
|
|
cb7f1e42ff | ||
|
|
84a6c89a92 | ||
|
|
451b730c8e | ||
|
|
98d3da62b5 | ||
|
|
45dc99fb9d | ||
|
|
2edbbfbe37 | ||
|
|
dad1d7744e | ||
|
|
de8ce3380c | ||
|
|
b848b1389a | ||
|
|
2c44ddfa95 | ||
|
|
72b6ab4389 | ||
|
|
1a7509e860 | ||
|
|
0fe94b2e6d | ||
|
|
93c1f7fc90 | ||
|
|
b6da1019bd | ||
|
|
effe8c102d | ||
|
|
cee3f78059 | ||
|
|
68f810883d | ||
|
|
cbfd8e18e8 | ||
|
|
8dc1aca67c | ||
|
|
acc82546c4 | ||
|
|
df29a50738 | ||
|
|
d460f40b13 | ||
|
|
7996fe6dab | ||
|
|
f50ce7f137 | ||
|
|
6fa4e379d2 | ||
|
|
3c954e76ed | ||
|
|
cd7671e8fa | ||
|
|
54ca168db5 | ||
|
|
330d852686 | ||
|
|
303ea496f1 | ||
|
|
e7b213604e | ||
|
|
c3007bffc9 | ||
|
|
0499dbd4cf | ||
|
|
20d685ec5c | ||
|
|
4df2a29a1f | ||
|
|
722e3a141d | ||
|
|
91e9248ecc | ||
|
|
22a3f4de2c | ||
|
|
d57464a02a | ||
|
|
1346bf6fff | ||
|
|
4ff2e7f546 | ||
|
|
cf356c59aa | ||
|
|
1555b51d99 | ||
|
|
64c5b84b8b | ||
|
|
017f55390a | ||
|
|
6010c971a1 | ||
|
|
ea4bf5f31a | ||
|
|
f4c242d5e9 | ||
|
|
0595773355 | ||
|
|
f89ca84902 | ||
|
|
e9771ce540 | ||
|
|
02f65cb7db | ||
|
|
246bd5a6ac | ||
|
|
1a68df6765 | ||
|
|
1b197d0bb2 | ||
|
|
3c2d01451a | ||
|
|
4c38fa8ad3 | ||
|
|
c2c81162a1 | ||
|
|
3d234147a6 | ||
|
|
38a509bc20 | ||
|
|
f30f13f0bc | ||
|
|
8e7f53751d | ||
|
|
bfc77f1606 | ||
|
|
a6b8285d9c | ||
|
|
a675dd8c24 | ||
|
|
7d13768d51 | ||
|
|
ce46ec4d3e | ||
|
|
1ffd65b7af | ||
|
|
43c6db3610 | ||
|
|
175314cd16 | ||
|
|
837acdcf60 | ||
|
|
7289192130 | ||
|
|
e84e94ef61 | ||
|
|
bb54ac14b8 | ||
|
|
dcd66b93fd | ||
|
|
856e3d97bf | ||
|
|
c6aa83e5f2 | ||
|
|
1b19f93965 | ||
|
|
c7373a5d3e | ||
|
|
fb83603133 | ||
|
|
94231ca3e3 | ||
|
|
e2a8b26b57 | ||
|
|
b6f45d9a90 | ||
|
|
69f92963f9 | ||
|
|
6b001e3106 | ||
|
|
6d1c954623 | ||
|
|
bdb35b6191 | ||
|
|
892580c183 | ||
|
|
4c90a4ec93 | ||
|
|
f58f7102f7 | ||
|
|
adf3405e6b | ||
|
|
8d34d9a06a | ||
|
|
0d540fd211 | ||
|
|
025c79ca65 | ||
|
|
868d179132 | ||
|
|
cfccfd48e5 | ||
|
|
2f3dfdcc67 | ||
|
|
d4b7d73fc4 | ||
|
|
867769f6e7 | ||
|
|
7f423e39ed | ||
|
|
e6505dc985 | ||
|
|
bcdb2d135d | ||
|
|
c5d1cffad2 | ||
|
|
54b4a0771d | ||
|
|
85f75ab1f3 | ||
|
|
668332f6c7 | ||
|
|
021ddb3565 | ||
|
|
6981bc8a82 | ||
|
|
7030646671 | ||
|
|
3781b5eb0e | ||
|
|
e897cd99f9 | ||
|
|
cc9b6bbc61 | ||
|
|
318cea4ccd | ||
|
|
45892f3584 | ||
|
|
612413cb1c | ||
|
|
511ec5a736 | ||
|
|
4fb9dec381 | ||
|
|
82476bdabe | ||
|
|
58ee8e2460 | ||
|
|
94a90b7a89 | ||
|
|
5d992758f8 | ||
|
|
27b8c29096 | ||
|
|
93d2a612ee | ||
|
|
03d4960a03 | ||
|
|
1ac0a4e924 | ||
|
|
d85d70a133 | ||
|
|
76979c8059 | ||
|
|
86246e4f45 | ||
|
|
2c95dea4db | ||
|
|
804c12034c | ||
|
|
ce7f57f251 | ||
|
|
af7c56d313 | ||
|
|
3e4044c7a0 | ||
|
|
88a22fdbf8 | ||
|
|
146e739390 | ||
|
|
f204162986 | ||
|
|
8679854c91 | ||
|
|
0383d0333e | ||
|
|
eb0b6e9df9 | ||
|
|
426eb0f79b | ||
|
|
0b2d92048d | ||
|
|
6beca141d5 | ||
|
|
b67790df7d | ||
|
|
f29d305597 | ||
|
|
89abf7faeb | ||
|
|
57186c3c14 | ||
|
|
feee6afe0f | ||
|
|
cb42ee3ade | ||
|
|
830704b4a9 | ||
|
|
0c6d7e07ee | ||
|
|
b28a3ebb4d | ||
|
|
6579c12053 | ||
|
|
28a7dc194c | ||
|
|
a943a0b59a | ||
|
|
80bc530d17 | ||
|
|
68480530fe | ||
|
|
eadfa71b49 | ||
|
|
9c43470c43 | ||
|
|
8f2e94a1d8 | ||
|
|
52c3e0592a | ||
|
|
637161157c | ||
|
|
76d5e53094 | ||
|
|
b6f40fdcc7 | ||
|
|
3de1e762b7 | ||
|
|
ee7ef81054 | ||
|
|
bae47fca20 | ||
|
|
41af76bed8 | ||
|
|
a7515fe156 | ||
|
|
60f997a52c | ||
|
|
f465fe65f4 | ||
|
|
3d6ce6c13f | ||
|
|
bf03bda314 | ||
|
|
126838e7c7 | ||
|
|
8310b40812 | ||
|
|
9133e25a7b | ||
|
|
ddfc2e4ca4 | ||
|
|
5aa129fbd3 | ||
|
|
18f01bcd48 | ||
|
|
4c031bd335 | ||
|
|
79f8cab5e2 | ||
|
|
92efad4970 | ||
|
|
6a509c1280 | ||
|
|
dcb68c16fe | ||
|
|
75f3f1a77f | ||
|
|
10108303e7 | ||
|
|
b7ad94c676 | ||
|
|
1c537a877e | ||
|
|
0270997acd | ||
|
|
4b9d92929a | ||
|
|
5b69324ee9 | ||
|
|
cce449ba8f | ||
|
|
df15d60b9e | ||
|
|
a0e01d4c34 | ||
|
|
59da143e6a | ||
|
|
4ddbf095f3 | ||
|
|
4e9ae65459 | ||
|
|
d16dc3a5d7 | ||
|
|
a1544f3033 | ||
|
|
f2fb9cf596 | ||
|
|
6dabdaca45 | ||
|
|
51ed3f2ed0 | ||
|
|
b097b1296b | ||
|
|
b03d91d5dd | ||
|
|
cf5c6c2144 | ||
|
|
3a2d27a636 | ||
|
|
5c3084c37c | ||
|
|
07ce0f0133 | ||
|
|
96189bde4b | ||
|
|
fc0dc4e5e2 | ||
|
|
3e2a6ef3f4 | ||
|
|
01a309909b | ||
|
|
7886c2df7a | ||
|
|
18735ee571 | ||
|
|
1ce07e2ee8 | ||
|
|
7cbee172b2 | ||
|
|
fca974723f | ||
|
|
6a2d4ae11d | ||
|
|
6da634b79f | ||
|
|
c85ca09236 | ||
|
|
a5b7ef5498 | ||
|
|
970087eefb | ||
|
|
11305c2e6b | ||
|
|
5da16023d8 | ||
|
|
b1d8a45339 | ||
|
|
c133f80c73 | ||
|
|
58b04cdfa4 | ||
|
|
1e149d0add | ||
|
|
f7e75da4bd | ||
|
|
c2938a4f63 | ||
|
|
825136b168 | ||
|
|
5a9eb58d33 | ||
|
|
beb4993548 | ||
|
|
48e8bfbb0e | ||
|
|
5dfbc58959 | ||
|
|
924de35cf3 | ||
|
|
7cf9e08948 | ||
|
|
60d3519993 | ||
|
|
c6b4570424 | ||
|
|
f5d56cc473 | ||
|
|
4e52572b03 | ||
|
|
6abc317986 | ||
|
|
22c0b588c4 | ||
|
|
6c9fc0ac75 | ||
|
|
ef322dc705 | ||
|
|
94fcc90886 | ||
|
|
d0df156b05 | ||
|
|
7aeaa4a485 | ||
|
|
823c7765f8 | ||
|
|
5cb0aebdf4 | ||
|
|
ef40a81be2 | ||
|
|
21842052cf | ||
|
|
686dccb48d | ||
|
|
1f7700edac | ||
|
|
5adc656066 | ||
|
|
e0ec3c986d | ||
|
|
827b137c86 | ||
|
|
9cf64ea643 | ||
|
|
fc1952bf42 | ||
|
|
9888f079fa | ||
|
|
f2c2027a8c | ||
|
|
4467eb1c41 | ||
|
|
ef5ac1442f | ||
|
|
1c1ce625a7 | ||
|
|
a442077eac | ||
|
|
6c638712f3 | ||
|
|
069a057a94 | ||
|
|
4ed3b21bf3 | ||
|
|
2e7afb13c7 | ||
|
|
a23b0f5122 | ||
|
|
da552abf75 | ||
|
|
380b5141fb | ||
|
|
66775b2c4e | ||
|
|
2c12a7d968 | ||
|
|
5a97c09238 | ||
|
|
3112376943 | ||
|
|
65140b341d | ||
|
|
ecfd17a259 | ||
|
|
a72dd96dc6 | ||
|
|
944e5ebbfa | ||
|
|
76767110b7 | ||
|
|
2f684ee66d | ||
|
|
7dcfa9eb65 | ||
|
|
ec98736bd7 | ||
|
|
ab0c290489 | ||
|
|
15ac2c3e5a | ||
|
|
0c24a27ba6 | ||
|
|
eb82e06fab | ||
|
|
f8e6954739 | ||
|
|
586ef4810b | ||
|
|
b62f484d93 | ||
|
|
07b600ccaf | ||
|
|
907dc75e5b | ||
|
|
ea0d2971eb | ||
|
|
a0bc332fe6 | ||
|
|
b0ab5b7b62 | ||
|
|
a3fc8dbf42 | ||
|
|
37516e6f6b | ||
|
|
9fde7c0f89 | ||
|
|
dc8103ea82 | ||
|
|
e248092014 | ||
|
|
f343635cab | ||
|
|
60daf9678f | ||
|
|
5d5bd6791b | ||
|
|
fe748507f0 | ||
|
|
cb7230e7b7 | ||
|
|
cb162b6755 | ||
|
|
7d00b50e13 | ||
|
|
12b058867e | ||
|
|
8ff09a1923 | ||
|
|
62fa0991ed | ||
|
|
52a8566e7f | ||
|
|
3bfcf252a8 | ||
|
|
f4ae58b1e5 | ||
|
|
12277d9305 | ||
|
|
c9d3f4492d | ||
|
|
f23c5cc6df | ||
|
|
24823233ff | ||
|
|
bd056973ba | ||
|
|
5420fd5aa3 | ||
|
|
44cdb4726e | ||
|
|
698cc182ae | ||
|
|
93615f6647 | ||
|
|
fba709f46d | ||
|
|
bb34aaa0c7 | ||
|
|
5661c3317a | ||
|
|
733ffc0eee | ||
|
|
6cc41d6dda | ||
|
|
d91f8a264e | ||
|
|
0ace9634ce | ||
|
|
48d51759cd | ||
|
|
bfd0b683bf | ||
|
|
61e0f60e94 | ||
|
|
7fa715e37a | ||
|
|
e066546c13 | ||
|
|
7dc66128c2 | ||
|
|
e484e4c9d7 | ||
|
|
4a75691005 | ||
|
|
8ada73b283 | ||
|
|
f316cbcf94 | ||
|
|
2a9a05785c | ||
|
|
0a6b2b7d32 | ||
|
|
c3345dd839 | ||
|
|
917b7fbf80 | ||
|
|
97715e7ccc | ||
|
|
43c0dea1bd | ||
|
|
2e4652209b | ||
|
|
aec4bf9d45 | ||
|
|
e2d811a7a0 | ||
|
|
f8643a62e6 | ||
|
|
dd5c903fd6 | ||
|
|
2e1675066d | ||
|
|
be84ce657b | ||
|
|
64053d697f | ||
|
|
a59ad06438 | ||
|
|
5af03d74ec | ||
|
|
ba2803c807 | ||
|
|
fdcbf617d8 | ||
|
|
cc7a4f7f91 | ||
|
|
2e42bda0a3 | ||
|
|
da8e6737c6 | ||
|
|
670201fcc2 | ||
|
|
168dc12e27 | ||
|
|
fd1955e15b | ||
|
|
ab35016aae | ||
|
|
f4f93f4836 | ||
|
|
6efb9b24e0 | ||
|
|
dd0a7533ab | ||
|
|
079c3fd263 | ||
|
|
8cbf929671 | ||
|
|
50130b7004 | ||
|
|
ab52c5cef2 | ||
|
|
c7fa93a5bc | ||
|
|
400b73b5a2 | ||
|
|
788b026cf5 | ||
|
|
9009645cea | ||
|
|
c4ae5d4c8d | ||
|
|
ca8dd61045 | ||
|
|
ac037bcffd | ||
|
|
d429dec2e5 | ||
|
|
d779bc3784 | ||
|
|
a4baa33e2f | ||
|
|
56aa182fb6 | ||
|
|
9818c7e78c | ||
|
|
5495f30329 | ||
|
|
6aa599a17c | ||
|
|
57858b2fa5 | ||
|
|
d4c1e92b1c | ||
|
|
403eda7024 | ||
|
|
1671827d24 | ||
|
|
578c3e21a5 | ||
|
|
87ef0d9ab3 | ||
|
|
cfafbfd141 | ||
|
|
9289a6c1bb | ||
|
|
4a740fbd14 | ||
|
|
7eb00c131f | ||
|
|
5ae461eb0b | ||
|
|
542126194a | ||
|
|
a61f7e38b6 | ||
|
|
3d059cb751 | ||
|
|
74e96b881c | ||
|
|
9848fa9a4d | ||
|
|
c24a7a8b58 | ||
|
|
71e38a6781 | ||
|
|
7b3cf6e747 | ||
|
|
676e4a6112 | ||
|
|
fd66a9b396 | ||
|
|
640b04e59e | ||
|
|
1625796cfe | ||
|
|
93749cd650 | ||
|
|
02b6ea6c46 | ||
|
|
6fcaf3571e | ||
|
|
10b9b13673 | ||
|
|
6cb559f65e | ||
|
|
c3b436fe96 | ||
|
|
aaac777651 | ||
|
|
103de74063 | ||
|
|
7f33a5ca0c | ||
|
|
e389dcb912 | ||
|
|
9d278d80f1 | ||
|
|
e994910ff6 | ||
|
|
e7b3705060 | ||
|
|
f17502fe81 | ||
|
|
8e7df5c1b1 | ||
|
|
8ba1b4e0cf | ||
|
|
93679f8d48 | ||
|
|
a292483344 | ||
|
|
90d720ca28 | ||
|
|
f8e933438e | ||
|
|
6dd89eb0d7 | ||
|
|
2bd20446bb | ||
|
|
659e4f6987 | ||
|
|
cd5f3c90c2 | ||
|
|
91a6911a51 | ||
|
|
0857dd0ae5 | ||
|
|
8c242fa2dd | ||
|
|
5a4f117a12 | ||
|
|
62ed1ca2fd | ||
|
|
d1298c8863 | ||
|
|
75e85b80aa | ||
|
|
73815ba1dd | ||
|
|
e1abf431cb | ||
|
|
8e0fa3bb5f | ||
|
|
8ffaf1a8e4 | ||
|
|
7788a109b0 | ||
|
|
9eea471e72 | ||
|
|
3ef53315a2 | ||
|
|
2a40e419fc | ||
|
|
d6f72d9862 | ||
|
|
3da720c789 | ||
|
|
dbf7909b85 | ||
|
|
57aa8d2f17 | ||
|
|
a318a49c72 | ||
|
|
35e73b77f4 | ||
|
|
698d1a7111 | ||
|
|
1365fca161 | ||
|
|
d17d77714f | ||
|
|
e8dd8215ba | ||
|
|
e11990e453 | ||
|
|
165ebe8ae3 | ||
|
|
e746637bee | ||
|
|
0e6370d467 | ||
|
|
512cd333e5 | ||
|
|
f599a78cad | ||
|
|
7601dab464 | ||
|
|
a8623eab4a | ||
|
|
c367ddff1b | ||
|
|
67b340fa5e | ||
|
|
c97245e626 | ||
|
|
79e2c17f93 | ||
|
|
5937737bb7 | ||
|
|
5f030edea4 | ||
|
|
88e33353a1 | ||
|
|
f3c0c62c79 | ||
|
|
866787c5d3 | ||
|
|
367ad8079a | ||
|
|
64491525b4 | ||
|
|
68b184885f | ||
|
|
06f3c716f5 | ||
|
|
22714b8c7f | ||
|
|
80c1bedffb | ||
|
|
56ad6d24ee | ||
|
|
4ad0e04c23 | ||
|
|
f9d1c554b7 | ||
|
|
ee58301a46 | ||
|
|
f2a7660390 | ||
|
|
d4c7ce2d6f | ||
|
|
4961e39fb6 | ||
|
|
0bedf608c0 | ||
|
|
1ab831f571 | ||
|
|
b90f1a48ce | ||
|
|
72425fc2e1 | ||
|
|
a5f2dfbc0c | ||
|
|
5db6a937cb | ||
|
|
9709b9b1b1 | ||
|
|
2c6b9b4c9d | ||
|
|
463fbb07f9 | ||
|
|
b6e473a515 | ||
|
|
24f7b5806c | ||
|
|
5872e0ea4a | ||
|
|
04d2d6012a | ||
|
|
f43d27541b | ||
|
|
c8d3d980ba | ||
|
|
f93bf06b99 | ||
|
|
0f7cb2fa5a | ||
|
|
01e93ba916 | ||
|
|
780954f27b | ||
|
|
19d69bdbb5 | ||
|
|
27fac7af86 | ||
|
|
6f048de973 | ||
|
|
a611a745e7 | ||
|
|
0dfaa7d9cf | ||
|
|
eab720f6ea | ||
|
|
8ad1d6b813 | ||
|
|
be684d66fd | ||
|
|
a7e8aef9d3 | ||
|
|
4b07c41df9 | ||
|
|
df54f1f1b7 | ||
|
|
9e3c2b68d7 | ||
|
|
3349bcaaed | ||
|
|
3a4fb81242 | ||
|
|
1df3ad0671 | ||
|
|
523cdd859b | ||
|
|
b037c76da6 | ||
|
|
9cac4ca317 | ||
|
|
34e5f39571 | ||
|
|
e0a279344c | ||
|
|
e2f4db3e45 | ||
|
|
cdb9eae1ee | ||
|
|
c1837af84a | ||
|
|
a87f6a28c9 | ||
|
|
98325325b1 | ||
|
|
26bf66e3f8 | ||
|
|
363cd97154 | ||
|
|
5eb19da91f | ||
|
|
c6b3281274 | ||
|
|
1e8adc2bd9 | ||
|
|
c73021cf3c | ||
|
|
1b3acf9cd8 | ||
|
|
1a26d4e409 | ||
|
|
c8ede400eb | ||
|
|
269263eecf | ||
|
|
4e21cdfc63 | ||
|
|
faef5633f8 | ||
|
|
7d1f1a3175 | ||
|
|
4e34727195 | ||
|
|
1dd6ed6ae3 | ||
|
|
cb4d6710df | ||
|
|
3b68b9a83b | ||
|
|
4279ce87ea | ||
|
|
3c1c4f89e9 | ||
|
|
4a6512f5d5 | ||
|
|
284f23c6ea | ||
|
|
11a9a5c126 | ||
|
|
4993801741 | ||
|
|
4b35899a12 | ||
|
|
1304e930eb | ||
|
|
94288d5a94 | ||
|
|
3811ab1b82 | ||
|
|
c869eb1eec | ||
|
|
f97d2a0eb9 | ||
|
|
176c8355cb | ||
|
|
2258434326 | ||
|
|
e46a3c4046 | ||
|
|
0e4cfde657 | ||
|
|
4bd9c36922 | ||
|
|
256da43fe5 | ||
|
|
6a442e0136 | ||
|
|
a818650027 | ||
|
|
9d79e64f5c | ||
|
|
c7c12f9638 | ||
|
|
ee942c5c17 | ||
|
|
d157819c49 | ||
|
|
2d91fb5441 | ||
|
|
81431a5453 | ||
|
|
6d7ec07216 | ||
|
|
b4978d1452 | ||
|
|
cb77519af8 | ||
|
|
45e9648b8c | ||
|
|
ce32957d9d | ||
|
|
ba8592ceae | ||
|
|
4327af3760 | ||
|
|
860cc63e21 | ||
|
|
452dd3ccfd | ||
|
|
e5c1621382 | ||
|
|
af3518dc1f | ||
|
|
6cfc0e80d9 | ||
|
|
1ee9c585ca | ||
|
|
efe5a5ac26 | ||
|
|
334e3ec529 | ||
|
|
84c165459f | ||
|
|
282c4121d6 | ||
|
|
6c2eee0e44 | ||
|
|
eeb6a088b8 | ||
|
|
22b63fe1f8 | ||
|
|
7ef526e2d3 | ||
|
|
ce7f94559b | ||
|
|
0471decfc8 | ||
|
|
e4c0a89889 | ||
|
|
084d6ca11d | ||
|
|
274902c3c1 | ||
|
|
f46e7c65c5 | ||
|
|
c6c6213460 | ||
|
|
29f6b02c04 | ||
|
|
1bf7c0ae5f | ||
|
|
1b87626b82 | ||
|
|
44ae084794 | ||
|
|
13a1809101 | ||
|
|
c35200fbd0 | ||
|
|
da9fb216b1 | ||
|
|
bef12f3d65 | ||
|
|
aa9e7eb7a2 | ||
|
|
f3d3e588fd | ||
|
|
4a40581deb | ||
|
|
eed2672db5 | ||
|
|
84071ac6d0 | ||
|
|
1a27eea46c | ||
|
|
d0b6451f02 | ||
|
|
2147c5a5f2 | ||
|
|
c7aa4d8b6d | ||
|
|
e94efe887c | ||
|
|
3db2de7478 | ||
|
|
345f7c3c62 | ||
|
|
13848ddbbc | ||
|
|
6f6e466c08 | ||
|
|
b0518040b5 | ||
|
|
29c872d867 | ||
|
|
acb63a1307 | ||
|
|
341bf2e480 | ||
|
|
20a191f144 | ||
|
|
81f4581f41 | ||
|
|
dfaf8ce64e | ||
|
|
f60f1932f2 | ||
|
|
ff8e4754d7 | ||
|
|
27c4d19455 | ||
|
|
7f704fdae1 | ||
|
|
dd63a6b61e | ||
|
|
1935d968c5 | ||
|
|
f83954f5b7 | ||
|
|
77b56c50e6 | ||
|
|
002a8c061f | ||
|
|
4299334e24 | ||
|
|
4d03c73222 | ||
|
|
84cfbaa0a4 | ||
|
|
7a2fd93d08 | ||
|
|
0d81992f6a | ||
|
|
5b67c9bf4a | ||
|
|
6594b38567 | ||
|
|
6c854e8ecc | ||
|
|
2e796f31d4 | ||
|
|
3d8f5d4302 | ||
|
|
2fa6b2301b | ||
|
|
4ba20132b9 | ||
|
|
a6e4d23c29 | ||
|
|
6d43576db7 | ||
|
|
b7d1bff0c7 | ||
|
|
79c5af755f | ||
|
|
c6d84e7e60 | ||
|
|
192513656a | ||
|
|
41dc728c9b | ||
|
|
f3c1b1f052 | ||
|
|
bd61c72007 | ||
|
|
0efeea1294 | ||
|
|
56ce1ec6e8 | ||
|
|
de168956e4 | ||
|
|
5b80844d81 | ||
|
|
a9902fc817 | ||
|
|
ed75688251 | ||
|
|
17add4b585 | ||
|
|
d492156453 | ||
|
|
96af3d5ec5 | ||
|
|
69ba14e34e | ||
|
|
943c924382 | ||
|
|
4b97427937 | ||
|
|
ab8e1fdcbf | ||
|
|
477979c275 | ||
|
|
c635720a09 | ||
|
|
f2a6a12f79 | ||
|
|
7445c617bc | ||
|
|
8e7340860e | ||
|
|
2ca1eb4cf8 | ||
|
|
0af8660181 | ||
|
|
330ec1b848 | ||
|
|
2f7cff84d9 | ||
|
|
8e5650fde9 | ||
|
|
539932dc56 | ||
|
|
5ab5e1270e | ||
|
|
e35de357fa | ||
|
|
0818fd7828 | ||
|
|
98cb9cc1eb | ||
|
|
bf8c97abbb | ||
|
|
ad6cf5e401 | ||
|
|
2b3900fd14 | ||
|
|
1defeca1ad | ||
|
|
ac6a9ab631 | ||
|
|
8176bb6f79 | ||
|
|
9c266d4316 | ||
|
|
d478a1b448 | ||
|
|
190a351a29 | ||
|
|
607ddd2f78 | ||
|
|
fed79a116f | ||
|
|
5db0ea0236 | ||
|
|
06fe320cc0 | ||
|
|
f7991e18de | ||
|
|
d7d0a5c15e | ||
|
|
47f4bbeec6 | ||
|
|
9ab70b340c | ||
|
|
70cdc2ca9f | ||
|
|
f63a872387 | ||
|
|
67462c2f92 | ||
|
|
4a4e786060 | ||
|
|
665f2d4c00 | ||
|
|
64586ca7ba | ||
|
|
46686b4b9c | ||
|
|
15c90e546f | ||
|
|
5aabe01b6d | ||
|
|
5d1d94848c | ||
|
|
7d10976e08 | ||
|
|
d3b55a74a5 | ||
|
|
de58ec71fd | ||
|
|
052ba80fd7 | ||
|
|
39f0ec7536 | ||
|
|
af973138df | ||
|
|
5a87506f3d | ||
|
|
90f0005cf2 | ||
|
|
d8b3748d24 | ||
|
|
1b224c961e | ||
|
|
b6ffff5bbd | ||
|
|
5ebae85a16 | ||
|
|
b3cf13775b | ||
|
|
c61809f0c4 | ||
|
|
2f2d6bc08b | ||
|
|
1bb809098c | ||
|
|
17a2ce0464 | ||
|
|
011506f00d | ||
|
|
25ab478461 | ||
|
|
fc9a35dd04 | ||
|
|
7efe30f34c | ||
|
|
ef28d5512b | ||
|
|
3827ecd6d3 | ||
|
|
fa49737538 | ||
|
|
14532867a4 | ||
|
|
73f340586d | ||
|
|
56fe00c5fb | ||
|
|
3c26177239 | ||
|
|
fe8f0d960d | ||
|
|
c72caef4fd | ||
|
|
0720a391e8 | ||
|
|
d16ac70a50 | ||
|
|
fc8e020436 | ||
|
|
6b073c6067 | ||
|
|
0b19bbff8d | ||
|
|
42927c1e32 | ||
|
|
df999978f1 | ||
|
|
43cd740a7b | ||
|
|
52f355db24 | ||
|
|
a86c5ccdc9 | ||
|
|
e532562108 | ||
|
|
4293ab2acb | ||
|
|
8d24c00df2 | ||
|
|
f4074e0bba | ||
|
|
e4426dc952 | ||
|
|
9359f6477b | ||
|
|
a103f30d51 | ||
|
|
78b60dbd1a | ||
|
|
cde75a1c00 | ||
|
|
b9d243552c | ||
|
|
85242ba896 | ||
|
|
d16dab6f62 | ||
|
|
8066b19f93 | ||
|
|
abd2a831a3 | ||
|
|
824d3ae3f7 | ||
|
|
727a14c6f9 | ||
|
|
13d20137d3 | ||
|
|
9680566595 | ||
|
|
33c9ea2cf7 | ||
|
|
1d8d2b373b | ||
|
|
611b472b12 | ||
|
|
bb73eb0db3 | ||
|
|
8a18685902 | ||
|
|
872b941b20 | ||
|
|
39261436c8 | ||
|
|
5e355383df | ||
|
|
90bfec8c04 | ||
|
|
866b6c6129 | ||
|
|
649fe7a1ec | ||
|
|
9cbbb6e508 | ||
|
|
9908769bb3 | ||
|
|
8902bb1af0 | ||
|
|
baf1bd354d | ||
|
|
539c2985aa | ||
|
|
5c356e15b5 | ||
|
|
8ff0b71b29 | ||
|
|
4e5a6c89b9 | ||
|
|
8f8f201186 | ||
|
|
0c688757b0 | ||
|
|
5778e92e70 | ||
|
|
3268ea42ff | ||
|
|
1538500903 | ||
|
|
6ca30a16ca | ||
|
|
e6dc2e0d31 | ||
|
|
9bbd1390c1 | ||
|
|
27f8db6e8b | ||
|
|
dda0c0e097 | ||
|
|
f5ea5c26a3 | ||
|
|
d01fa96177 | ||
|
|
03caa53863 | ||
|
|
4f4a2e6d92 | ||
|
|
87178ed725 | ||
|
|
94e5436f6e | ||
|
|
b965f2053a | ||
|
|
959db77b88 | ||
|
|
edee078f0a | ||
|
|
d4b766bf3f | ||
|
|
72772c9a83 | ||
|
|
4c806d7c51 | ||
|
|
c16a60c5ea | ||
|
|
96afcb7a43 | ||
|
|
e5a8d8b9ad | ||
|
|
efeee5160e | ||
|
|
06fb502047 | ||
|
|
977192f480 | ||
|
|
cf66d9d38d | ||
|
|
25eeff8fc5 | ||
|
|
d342cdad2b | ||
|
|
c899ee0d55 | ||
|
|
220408fcaa | ||
|
|
f4e99be7e1 | ||
|
|
bf9fc0ae96 | ||
|
|
9697e666b7 | ||
|
|
a8a1a74b79 | ||
|
|
216ca7cbc9 | ||
|
|
549e440f7c | ||
|
|
45c02c31f8 | ||
|
|
b6b092d124 | ||
|
|
d346d4a9b6 | ||
|
|
c84e98774a | ||
|
|
e1f4187430 | ||
|
|
3af93ada6f | ||
|
|
fa4dee8cfd | ||
|
|
3888492f0d | ||
|
|
dc16928f74 | ||
|
|
a4e440527b | ||
|
|
80ff146620 | ||
|
|
d4fe9d8166 | ||
|
|
85a0af03c1 | ||
|
|
dc43f5605b | ||
|
|
e0d2baae58 | ||
|
|
437de19ecb | ||
|
|
fab064641f | ||
|
|
cc69d01bdc | ||
|
|
461a95d7ff | ||
|
|
316e9681cc | ||
|
|
4181313cc6 | ||
|
|
aa1665acce | ||
|
|
6aabc5e7b0 | ||
|
|
343a8e0192 | ||
|
|
2707887a65 | ||
|
|
ef87d09cfa | ||
|
|
d21c67f237 | ||
|
|
de0432b317 | ||
|
|
de40fed248 | ||
|
|
76d7e0e1f8 | ||
|
|
bfa551ec08 | ||
|
|
740e564bc7 | ||
|
|
1f585d67b6 | ||
|
|
5b22e94a4b | ||
|
|
7749aef6b6 | ||
|
|
5de8fb0d08 | ||
|
|
19f7335926 | ||
|
|
9b61830a55 | ||
|
|
99f4cd867d | ||
|
|
f29fec33a2 | ||
|
|
93fe3459fd | ||
|
|
1abd3bd7f3 | ||
|
|
5509f20025 | ||
|
|
fc4fd41be4 | ||
|
|
3ffca20001 | ||
|
|
7c29305788 | ||
|
|
41fb18e573 | ||
|
|
e4c6251ef5 | ||
|
|
7aa250eaf7 | ||
|
|
ff380b686a | ||
|
|
d2452f4b68 | ||
|
|
deb9c32a38 | ||
|
|
440f45b996 | ||
|
|
7d64c4ec66 | ||
|
|
7fe0d530c1 | ||
|
|
c944767554 | ||
|
|
fde5a1c507 | ||
|
|
0fbfb41fa8 | ||
|
|
1991ed0804 | ||
|
|
e782b92a80 | ||
|
|
07635ea2be | ||
|
|
fb3de9ce9d | ||
|
|
efff91ea3d | ||
|
|
1916bd3bd0 | ||
|
|
4eb752b000 | ||
|
|
bfb29a58f3 | ||
|
|
f86e455a87 | ||
|
|
faa35fe9fc | ||
|
|
89b8b59658 | ||
|
|
de55a1adc4 | ||
|
|
d1613025ee | ||
|
|
cc4431c409 | ||
|
|
3d5986c55d | ||
|
|
9aeb6ee532 | ||
|
|
e7f6cc598d | ||
|
|
cd465dd121 | ||
|
|
174b48a14a | ||
|
|
bca18e7aba | ||
|
|
17e761d6c6 | ||
|
|
c50556dde4 | ||
|
|
dd5bdd67d7 | ||
|
|
21ac9363e9 | ||
|
|
8e3cccf4d6 | ||
|
|
945e935312 | ||
|
|
bb5cf570e5 | ||
|
|
a5ed288db2 | ||
|
|
7002d6d306 | ||
|
|
1b8d8f3a04 | ||
|
|
284440336d | ||
|
|
140ae7a513 | ||
|
|
21328d9e37 | ||
|
|
5177fe1db7 | ||
|
|
7de50b5e2e | ||
|
|
4652a84b43 | ||
|
|
9e0755bc86 | ||
|
|
da0f7d7907 | ||
|
|
88d72bf31d | ||
|
|
aac2f7dd73 | ||
|
|
1f44ad1723 | ||
|
|
4ab1857a11 | ||
|
|
d23c714ec7 | ||
|
|
ac524532e7 | ||
|
|
59a1fde2a1 | ||
|
|
31276de5c3 | ||
|
|
c581aef11d | ||
|
|
7f6a955a71 | ||
|
|
125d97cc41 | ||
|
|
de7d9ba471 | ||
|
|
ad54b44235 | ||
|
|
42532ec0f5 | ||
|
|
b84fa619da | ||
|
|
8a1409184f | ||
|
|
8a3c16a5bc | ||
|
|
6343c65ce2 | ||
|
|
20b4736a1f | ||
|
|
d5967f7834 | ||
|
|
d5f7650ac1 | ||
|
|
6330caffde | ||
|
|
8f580c256c | ||
|
|
4671b8db5c | ||
|
|
7c8f044380 | ||
|
|
8efd506471 | ||
|
|
e83267751e | ||
|
|
a3b110aee5 | ||
|
|
84f0b0a84c | ||
|
|
c9c5adc650 | ||
|
|
52e7226655 | ||
|
|
b89c8decd4 | ||
|
|
d783975597 | ||
|
|
5ec291df5c | ||
|
|
0a45355055 | ||
|
|
e696624da0 | ||
|
|
99ad40f3e0 | ||
|
|
b9c8016aca | ||
|
|
8ad1f2d4f5 | ||
|
|
dc30581be0 | ||
|
|
2e56b606fa | ||
|
|
d84c72afe5 | ||
|
|
2d69896f64 | ||
|
|
b3dd2db815 | ||
|
|
290dd3993b | ||
|
|
4f6a9917c6 | ||
|
|
3d48183753 | ||
|
|
33c31eb798 | ||
|
|
73ae7ad82f | ||
|
|
ee6470708b | ||
|
|
61f25926b5 | ||
|
|
1a5d3bb69c | ||
|
|
7d4fe55d63 | ||
|
|
089e03afe8 | ||
|
|
8e019f01ab | ||
|
|
77bdaf3c78 | ||
|
|
0b6828c895 | ||
|
|
d4704c656f | ||
|
|
c01192c784 | ||
|
|
8adb611edf | ||
|
|
e5af5b57ad | ||
|
|
f05d3e6af3 | ||
|
|
5963d038ef | ||
|
|
bfd28a04ba | ||
|
|
359ec257c0 | ||
|
|
88767e402c | ||
|
|
88c7a6d053 | ||
|
|
e698cbf092 | ||
|
|
f2ce646d8d | ||
|
|
cbf9b345ff | ||
|
|
1725894fe9 | ||
|
|
fd4f85eb19 | ||
|
|
f1c4864016 | ||
|
|
e6bd265729 | ||
|
|
c22e8112e7 | ||
|
|
44252984c2 | ||
|
|
4b4f92780e | ||
|
|
f694620087 | ||
|
|
dc1d1f132e | ||
|
|
9b4048ec6e | ||
|
|
727342134c | ||
|
|
c744a97e3c | ||
|
|
40cafb95ed | ||
|
|
91d75d7704 | ||
|
|
dc8cff364f | ||
|
|
572dc40e6b | ||
|
|
f92ffddb82 | ||
|
|
641e0c1afc | ||
|
|
bf7faa80c1 | ||
|
|
a2ae3771eb | ||
|
|
673ffc50da | ||
|
|
6dc9973754 | ||
|
|
cf6a910630 | ||
|
|
520baa6ec8 | ||
|
|
c1cc4f96df | ||
|
|
bbf925a27e | ||
|
|
381fd5dbe4 | ||
|
|
ead8b7437e | ||
|
|
9f2d18b7ba | ||
|
|
acd9df6745 | ||
|
|
f96c051932 | ||
|
|
67b2e40fae | ||
|
|
081a2acd61 | ||
|
|
de79acc790 | ||
|
|
a125bc7242 | ||
|
|
ebed4cd728 | ||
|
|
21d4838322 | ||
|
|
926a373591 | ||
|
|
0cbb481fa4 | ||
|
|
a954f23642 | ||
|
|
41a104cc10 | ||
|
|
f0b4971c7b | ||
|
|
8e669a32a3 | ||
|
|
0e16e7935e | ||
|
|
7ea84d9a4e | ||
|
|
7313c89dec | ||
|
|
35a66c03c2 | ||
|
|
bbb3168bae | ||
|
|
1ea9d3faf8 | ||
|
|
4479be4fd0 | ||
|
|
e7aaf4dd2e | ||
|
|
91a6bf671d | ||
|
|
49b5889d9e | ||
|
|
ede61ae130 | ||
|
|
7a79111767 | ||
|
|
6432521b9d | ||
|
|
65f578fe61 | ||
|
|
3a8eb4a4f0 | ||
|
|
eb180656bb | ||
|
|
1afcbba218 | ||
|
|
8a0902a83b | ||
|
|
dfb312fee6 | ||
|
|
11bb594fa2 | ||
|
|
8e3ae2c78f | ||
|
|
8080d1d961 | ||
|
|
4b4135e35a | ||
|
|
d1148c4cab | ||
|
|
8ee62b4789 | ||
|
|
5e7a142ff1 | ||
|
|
2c816db45e | ||
|
|
b920507f34 | ||
|
|
d8601ef01f | ||
|
|
afbc57cc0c | ||
|
|
9f12c009d6 | ||
|
|
84ac68a58b | ||
|
|
27d1df4699 | ||
|
|
0d7a7dc7c9 | ||
|
|
b8bff0e7f5 | ||
|
|
60bf1ef7ea | ||
|
|
dc37b692cf | ||
|
|
95976d8b58 | ||
|
|
ecb20cc29b | ||
|
|
b6183e86eb | ||
|
|
229af0380c | ||
|
|
b968a662d3 | ||
|
|
159e869fe6 | ||
|
|
76814588b8 | ||
|
|
1934c7faa2 | ||
|
|
9e9e160c43 | ||
|
|
546b4edbf1 | ||
|
|
63d8a88e2f | ||
|
|
75d2d64e7c | ||
|
|
a5113998e2 | ||
|
|
4d2e8cd71d | ||
|
|
30b355fd6f | ||
|
|
c257b91552 | ||
|
|
12df7112da | ||
|
|
cd5ca3f65b | ||
|
|
0bd63cf00f | ||
|
|
7ceb3369eb | ||
|
|
ae21726287 | ||
|
|
a4da1b6eb0 | ||
|
|
85bfd2eba3 | ||
|
|
2d543590dc | ||
|
|
18b6f17e86 | ||
|
|
f37179d9f2 | ||
|
|
3e0b7d71d4 | ||
|
|
58d10943ed | ||
|
|
dc920a04f6 | ||
|
|
d031381e70 | ||
|
|
ed1b0b90f7 | ||
|
|
38dd3c5c60 | ||
|
|
d3189acaa6 | ||
|
|
350c98ab4d | ||
|
|
4f3c754771 | ||
|
|
dc994f001d | ||
|
|
9b6ccbcc95 | ||
|
|
9d3cf9c73c | ||
|
|
28572d4392 | ||
|
|
0433db0370 | ||
|
|
a6b326da48 | ||
|
|
e457ce66ea | ||
|
|
c118dd8afe | ||
|
|
dba3a3d942 | ||
|
|
6c606b5506 | ||
|
|
55dbeefeb2 | ||
|
|
4d9589af7c | ||
|
|
ee625cb8a8 | ||
|
|
f20940a37b | ||
|
|
32e0a66610 | ||
|
|
d9598b35c2 | ||
|
|
acba357df6 | ||
|
|
7ce335d9da | ||
|
|
3caf9ca914 | ||
|
|
fd569201ef | ||
|
|
f094aa946a | ||
|
|
a17c192a9e | ||
|
|
1916a9b99c | ||
|
|
9796b308dc | ||
|
|
bdf0a1941c | ||
|
|
d0e3024bec | ||
|
|
d2ad149e56 | ||
|
|
ad602a4722 | ||
|
|
348840a2aa | ||
|
|
3a719633eb | ||
|
|
bd69948d37 | ||
|
|
54aa211f56 | ||
|
|
f118891970 | ||
|
|
c4055fde97 | ||
|
|
dbae3fc9a5 | ||
|
|
7c73ed7ed5 | ||
|
|
c834960bfb | ||
|
|
600abc55b5 | ||
|
|
f3ec7d54bb | ||
|
|
090760e526 | ||
|
|
cccde7dc89 | ||
|
|
849e48f519 | ||
|
|
1c3935eb40 | ||
|
|
466bed3163 | ||
|
|
641a9c72e9 | ||
|
|
5138216ba1 | ||
|
|
de1f5686a8 | ||
|
|
c983678fcd | ||
|
|
2b0415d552 | ||
|
|
066e4421e8 | ||
|
|
f02a241249 | ||
|
|
a5fe1e4259 | ||
|
|
9b80563443 | ||
|
|
91b5da06e3 | ||
|
|
7320f96ae7 | ||
|
|
fdf2b9cd7b | ||
|
|
bfc70a1b60 | ||
|
|
aff7a93106 | ||
|
|
3b027c4593 | ||
|
|
42d3bdd373 | ||
|
|
57789092c1 | ||
|
|
6bc5268cbd | ||
|
|
887ab705d1 | ||
|
|
ff6ddaed2e | ||
|
|
e6fe36f45c | ||
|
|
3a26f6b8bf | ||
|
|
06b6f85d55 | ||
|
|
d6f1ea50a6 | ||
|
|
9554869886 | ||
|
|
364059551c | ||
|
|
06340b1ad7 | ||
|
|
d23511860d | ||
|
|
a8dd4660b2 | ||
|
|
eb3a0eb3c7 | ||
|
|
cd0148e0bc | ||
|
|
8584ee609f | ||
|
|
373847e2b7 | ||
|
|
84f7d8dfc2 | ||
|
|
e057a7d0dd | ||
|
|
7bab15bf99 | ||
|
|
dac40630fd | ||
|
|
33bfa1b81c | ||
|
|
8fc27dc292 | ||
|
|
f8e8f18be5 | ||
|
|
8b38c567d2 | ||
|
|
cd53e42d79 | ||
|
|
bea6cf2038 | ||
|
|
eca80f1425 | ||
|
|
1c0962e53c | ||
|
|
4b21549ff4 | ||
|
|
30d7b0129b | ||
|
|
ce6877d6e4 | ||
|
|
0ab5177637 | ||
|
|
276cbfa505 | ||
|
|
610c85a354 | ||
|
|
012084b37b | ||
|
|
55af6681af | ||
|
|
2a7a42ff8f | ||
|
|
7af5737ec5 | ||
|
|
0ad1529f3f | ||
|
|
0df8173536 | ||
|
|
b517811e2f | ||
|
|
83d3a9c6dd | ||
|
|
d0402261e6 | ||
|
|
6f6e09d200 | ||
|
|
24e2fd4184 | ||
|
|
1aada996dc | ||
|
|
f5d3d6bcea | ||
|
|
a8a99f647f | ||
|
|
ff68b26c44 | ||
|
|
a94b4f62fd | ||
|
|
bcc959d938 | ||
|
|
cf25d8a378 | ||
|
|
c750bdafd5 | ||
|
|
693d46f8ea | ||
|
|
3496ebd1d7 | ||
|
|
be763cf7fe | ||
|
|
c3b4bee210 | ||
|
|
6df0227ab1 | ||
|
|
2a3a7fa8a0 | ||
|
|
50a6496399 | ||
|
|
c99dee86dd | ||
|
|
0c5bb9626b | ||
|
|
a9971917f5 | ||
|
|
4c62611da3 | ||
|
|
47f36f08fb | ||
|
|
f906bab1a5 | ||
|
|
fffc03c4e4 | ||
|
|
0f6934a131 | ||
|
|
0a94184d6b | ||
|
|
7be3578497 | ||
|
|
eeaccb8ac0 | ||
|
|
8ef9a932aa | ||
|
|
31e22e4cfb | ||
|
|
4fc25fb798 | ||
|
|
941d9a46a2 | ||
|
|
ecfe68d70f | ||
|
|
c0c2b5e3a9 | ||
|
|
f102d8a4b4 | ||
|
|
471e13efbc | ||
|
|
6d34432988 | ||
|
|
d3f0d15732 | ||
|
|
b827b9e33e | ||
|
|
29e5ecc282 | ||
|
|
c9bf2dda16 | ||
|
|
3ceb378b9b | ||
|
|
1cf1c90511 | ||
|
|
491b9f83f2 | ||
|
|
d989825216 | ||
|
|
3976420b88 | ||
|
|
2f1ce5fe43 | ||
|
|
42145a5b8a | ||
|
|
04f4536cb2 | ||
|
|
4e66017205 | ||
|
|
2c1f2edcf2 | ||
|
|
299d517449 | ||
|
|
561e73dbd7 | ||
|
|
9e6ffaad7d | ||
|
|
9cded1e92c | ||
|
|
4c1ab6ff25 | ||
|
|
16f31cab6a | ||
|
|
02c88e6826 | ||
|
|
9ecd43238f | ||
|
|
5ffe71346c | ||
|
|
d25804f4a2 | ||
|
|
edb75e69cb | ||
|
|
f3e895f17c | ||
|
|
b952d73e83 | ||
|
|
07facc0636 | ||
|
|
da1a69be27 | ||
|
|
7e31658932 | ||
|
|
5ebc59dd1f | ||
|
|
b10f5ab110 | ||
|
|
b4286bb42b | ||
|
|
4d7ce3792f | ||
|
|
76767da300 | ||
|
|
dc8701a929 | ||
|
|
139d35c6f9 | ||
|
|
cb24457b4a | ||
|
|
9f3efb7f05 | ||
|
|
e6001e0f22 | ||
|
|
c6535bf035 | ||
|
|
7118a515e0 | ||
|
|
952451c9b8 | ||
|
|
610327a04e | ||
|
|
2121e32409 | ||
|
|
7ec21edc2f | ||
|
|
003162f710 | ||
|
|
040ac93042 | ||
|
|
b489ba3d0d | ||
|
|
c5e8b547af | ||
|
|
e67de90ad0 | ||
|
|
c3c84c88a1 | ||
|
|
0dc9c4cee1 | ||
|
|
544c137cb0 | ||
|
|
b312a61a81 | ||
|
|
4917556a99 | ||
|
|
15ed4a0d09 | ||
|
|
aa6b0f07b7 | ||
|
|
d9d20d9d30 | ||
|
|
689bfbbdb3 | ||
|
|
e27a10bde4 | ||
|
|
253a199f27 | ||
|
|
61e5702520 | ||
|
|
b12c640807 | ||
|
|
9be23ecc34 | ||
|
|
8960f471a0 | ||
|
|
955cb6411c | ||
|
|
fc4ca4f8e3 | ||
|
|
eec068914e | ||
|
|
a1f02d0cd8 | ||
|
|
39b8285ba5 | ||
|
|
7733fef3bd | ||
|
|
6acddfdb98 | ||
|
|
ec3d5c0b32 | ||
|
|
99492c2ec2 | ||
|
|
addf9f9af4 | ||
|
|
846b505d27 | ||
|
|
c4cfcfab8e | ||
|
|
5e083426c5 | ||
|
|
8d43b4a98d | ||
|
|
aeaea073c6 | ||
|
|
6b0dd19442 | ||
|
|
9336ffe216 | ||
|
|
eb157f15f3 | ||
|
|
d6e2a3f425 | ||
|
|
b47ca13ed3 | ||
|
|
67546c4d6e | ||
|
|
f72deb0a5c | ||
|
|
616ccbb878 | ||
|
|
5899af0038 | ||
|
|
ed303310bb | ||
|
|
33ff4f3b5c | ||
|
|
20bad38d42 | ||
|
|
92a07398cd | ||
|
|
ce8f782577 | ||
|
|
e961d0b4a3 | ||
|
|
2253ff656a | ||
|
|
18631399ad | ||
|
|
ad4afcdcd5 | ||
|
|
2cf5bcc5db | ||
|
|
1180ad7662 | ||
|
|
5463cd1ae3 | ||
|
|
647ec770ce | ||
|
|
e47bec2e65 | ||
|
|
6566936be9 | ||
|
|
674941abdf | ||
|
|
b3f0ca39ed | ||
|
|
5ccb512883 | ||
|
|
da286d5ae8 | ||
|
|
73e45511dc | ||
|
|
a282a51673 | ||
|
|
b7b13e20d1 | ||
|
|
ad90c6b6ce | ||
|
|
402fa41bc0 | ||
|
|
0b9ebafc0f | ||
|
|
140e24ef15 | ||
|
|
0c998d60cb | ||
|
|
ffcd2ea10c | ||
|
|
cb460de94d | ||
|
|
f6624bf776 | ||
|
|
b4b6c4d86f | ||
|
|
759689ff31 | ||
|
|
1dfc36f311 | ||
|
|
1c03ff1d37 | ||
|
|
19dd2f92bd | ||
|
|
acfaa016a0 | ||
|
|
732761433a | ||
|
|
9012a7f5e1 | ||
|
|
e957b471b2 | ||
|
|
e5a5faa417 | ||
|
|
313dbe05e0 | ||
|
|
adf7124e2c | ||
|
|
c4ab2bbeed | ||
|
|
42ef459e20 | ||
|
|
cad1a9e0f1 | ||
|
|
f1d514470d | ||
|
|
9a7a54f22f | ||
|
|
137d1c61bd | ||
|
|
adc071ed7a | ||
|
|
e06f470044 | ||
|
|
ab69fe56c9 | ||
|
|
60bad22a91 | ||
|
|
7092429f7c | ||
|
|
fa800bb809 | ||
|
|
e15f1103a0 | ||
|
|
a4263b5a8c | ||
|
|
3d85f820f4 | ||
|
|
245b7baa61 | ||
|
|
0eeaaa150a | ||
|
|
692d87f446 | ||
|
|
6572efe2a7 | ||
|
|
8aac2bd029 | ||
|
|
add11db369 | ||
|
|
e47eab1d40 | ||
|
|
2f86dfdf2b | ||
|
|
fa71ae3174 | ||
|
|
dfcd1508c9 | ||
|
|
0ca4631279 | ||
|
|
7e5fc4444a | ||
|
|
a6221ca322 | ||
|
|
d8e42c4379 | ||
|
|
3bf109ae0b | ||
|
|
dd37fa49a0 | ||
|
|
3227ec72a2 | ||
|
|
ee324c3d89 | ||
|
|
863971f944 | ||
|
|
fd70f7ad43 | ||
|
|
6e034c9b7f | ||
|
|
52e375a985 | ||
|
|
635c1eacd5 | ||
|
|
f49ba18627 | ||
|
|
6dbce96781 | ||
|
|
9ec42f0f8f | ||
|
|
10a5e7313f | ||
|
|
ec9cb21fae | ||
|
|
fdd02ad6a6 | ||
|
|
76e9fcc94a | ||
|
|
e412927415 | ||
|
|
dda154c7c6 | ||
|
|
9215535bee | ||
|
|
27726fd2d1 | ||
|
|
86c6248b48 | ||
|
|
f8380d2d4c | ||
|
|
5cc25d0846 | ||
|
|
1502c4530e | ||
|
|
c1df4d1c0b | ||
|
|
1f9e41e9cb | ||
|
|
e402e690b0 | ||
|
|
6a15bb15ca | ||
|
|
3255fc91fa | ||
|
|
7f2610c4fc | ||
|
|
79bd3eb6ae | ||
|
|
b11dd6950c | ||
|
|
98bd6fc240 | ||
|
|
8be053fd35 | ||
|
|
99fee22a9f | ||
|
|
084d002353 | ||
|
|
dcbc9847a3 | ||
|
|
db3c158215 | ||
|
|
25e2bd307a | ||
|
|
b9f78f5d33 | ||
|
|
b4ec9d70da | ||
|
|
738999a8b7 | ||
|
|
dd91d793d9 | ||
|
|
8e51e8eb77 | ||
|
|
6210605bc7 | ||
|
|
0245b040b0 | ||
|
|
34c1cc5693 | ||
|
|
8795719c18 | ||
|
|
6bbbf43341 | ||
|
|
f0ef45f0ca | ||
|
|
ee6039bfa5 | ||
|
|
ef58ce6277 | ||
|
|
15de5e98c4 | ||
|
|
38848ca2db | ||
|
|
77c627e822 | ||
|
|
c640132699 | ||
|
|
60b09d9bb0 | ||
|
|
57dd38aef2 | ||
|
|
460a6cb6fe | ||
|
|
26aaddaa33 | ||
|
|
e51151e558 | ||
|
|
f576baf214 | ||
|
|
5c1ac05170 | ||
|
|
1bae4973bc | ||
|
|
3d9f86c584 | ||
|
|
3514e537ca | ||
|
|
3d160ce85f | ||
|
|
b78090ec76 | ||
|
|
759007ffc1 | ||
|
|
37a55c3a77 | ||
|
|
69ae9d72c8 | ||
|
|
604232acd9 | ||
|
|
82205d71cc | ||
|
|
402eab10f8 | ||
|
|
b6bf4d73ad | ||
|
|
5425b5c423 | ||
|
|
29cd8504ca | ||
|
|
3544746934 | ||
|
|
d8f814f1c4 | ||
|
|
a43175125a | ||
|
|
1d03bc560a | ||
|
|
3832acf6e3 | ||
|
|
7894b50321 | ||
|
|
ffded619e6 | ||
|
|
bcb7bb5cce | ||
|
|
87dcd82f69 | ||
|
|
e671cc6056 | ||
|
|
5da89b88a6 | ||
|
|
5d60c1f20b | ||
|
|
7fd00165c9 | ||
|
|
34d4420e8c | ||
|
|
20da194fab | ||
|
|
8d2d4c850f | ||
|
|
b7bed027d7 | ||
|
|
fcd6b7b0ea | ||
|
|
ceca32ceb3 | ||
|
|
e3bb9fc1d7 | ||
|
|
77a8ddb95c | ||
|
|
c733a4dbf8 | ||
|
|
d898a43dff | ||
|
|
6216d53b1a | ||
|
|
86c30769d9 | ||
|
|
956a6dbd64 | ||
|
|
68fe19818e | ||
|
|
de208ead4e | ||
|
|
69d62560b4 | ||
|
|
87d2fc1491 | ||
|
|
2bc9af09e1 | ||
|
|
26f4758523 | ||
|
|
6123349b79 | ||
|
|
d1ac54fe92 | ||
|
|
9468adf737 | ||
|
|
e85db40b0f | ||
|
|
b3d55cc16d | ||
|
|
56b62a5e49 | ||
|
|
3ee1fc544f | ||
|
|
5401744dc0 | ||
|
|
fe10a10ac2 | ||
|
|
ba2e5a97a9 | ||
|
|
4515d1220c | ||
|
|
486959bce8 | ||
|
|
e1a410bf3d | ||
|
|
3767cc7c0b | ||
|
|
96b0ce9ef2 | ||
|
|
22dd8a8847 | ||
|
|
b2ae8e7a4a | ||
|
|
3e2bac8129 | ||
|
|
50b9d0e86d | ||
|
|
a030d9935e | ||
|
|
c425dec4d5 | ||
|
|
67d53601d5 | ||
|
|
622cca0acf | ||
|
|
48999c03a5 | ||
|
|
377cc7bdcd | ||
|
|
a5d0976c2d | ||
|
|
ae05010255 | ||
|
|
66cacbd0e0 | ||
|
|
b1616be4b8 | ||
|
|
a0a9a72d8f | ||
|
|
0cfc7f732c | ||
|
|
f7de6f790c | ||
|
|
d1f3b5ed80 | ||
|
|
7925dcc5a2 | ||
|
|
6ade36bf09 | ||
|
|
c52945aab5 | ||
|
|
2b0a4055f7 | ||
|
|
7cb16a3fc5 | ||
|
|
0b80c1988b |
2
.github/workflows/ccpp.yml
vendored
2
.github/workflows/ccpp.yml
vendored
@@ -8,7 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get --allow-releaseinfo-change update && sudo apt-get --fix-missing install libsdl2-dev scons
|
||||
- name: Make
|
||||
|
||||
@@ -47,7 +47,7 @@ template <typename MachineType> class MultiInterface {
|
||||
std::recursive_mutex &machines_mutex_;
|
||||
|
||||
private:
|
||||
std::vector<Concurrency::AsyncTaskQueue> queues_;
|
||||
std::vector<Concurrency::AsyncTaskQueue<true>> queues_;
|
||||
};
|
||||
|
||||
class MultiTimedMachine: public MultiInterface<MachineTypes::TimedMachine>, public MachineTypes::TimedMachine {
|
||||
|
||||
@@ -17,6 +17,7 @@ enum class Machine {
|
||||
AppleIIgs,
|
||||
Atari2600,
|
||||
AtariST,
|
||||
Amiga,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
|
||||
25
Analyser/Static/Amiga/StaticAnalyser.cpp
Normal file
25
Analyser/Static/Amiga/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Amiga::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// As there is at least one usable media image, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
using Target = Analyser::Static::Amiga::Target;
|
||||
auto *const target = new Target();
|
||||
target->media = media;
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
}
|
||||
27
Analyser/Static/Amiga/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Amiga/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Amiga_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Amiga_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Amiga_StaticAnalyser_hpp */
|
||||
48
Analyser/Static/Amiga/Target.hpp
Normal file
48
Analyser/Static/Amiga/Target.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Amiga_Target_h
|
||||
#define Analyser_Static_Amiga_Target_h
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Amiga {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(ChipRAM,
|
||||
FiveHundredAndTwelveKilobytes,
|
||||
OneMegabyte,
|
||||
TwoMegabytes);
|
||||
ReflectableEnum(FastRAM,
|
||||
None,
|
||||
OneMegabyte,
|
||||
TwoMegabytes,
|
||||
FourMegabytes,
|
||||
EightMegabytes);
|
||||
|
||||
ChipRAM chip_ram = ChipRAM::FiveHundredAndTwelveKilobytes;
|
||||
FastRAM fast_ram = FastRAM::EightMegabytes;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Amiga) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(fast_ram);
|
||||
DeclareField(chip_ram);
|
||||
AnnounceEnum(FastRAM);
|
||||
AnnounceEnum(ChipRAM);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Amiga_Target_h */
|
||||
@@ -13,8 +13,17 @@ Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &
|
||||
auto target = std::make_unique<Target>();
|
||||
target->media = media;
|
||||
|
||||
if(!target->media.disks.empty())
|
||||
// If any disks are present, attach a Disk II.
|
||||
if(!target->media.disks.empty()) {
|
||||
target->disk_controller = Target::DiskController::SixteenSector;
|
||||
}
|
||||
|
||||
// The emulated SCSI card requires a IIe, so upgrade to that if
|
||||
// any mass storage is present.
|
||||
if(!target->media.mass_storage_devices.empty()) {
|
||||
target->model = Target::Model::EnhancedIIe;
|
||||
target->scsi_controller = Target::SCSIController::AppleSCSI;
|
||||
}
|
||||
|
||||
TargetList targets;
|
||||
targets.push_back(std::move(target));
|
||||
|
||||
@@ -29,16 +29,24 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
SixteenSector,
|
||||
ThirteenSector
|
||||
);
|
||||
ReflectableEnum(SCSIController,
|
||||
None,
|
||||
AppleSCSI
|
||||
);
|
||||
|
||||
Model model = Model::IIe;
|
||||
DiskController disk_controller = DiskController::None;
|
||||
SCSIController scsi_controller = SCSIController::None;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleII) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(model);
|
||||
DeclareField(disk_controller);
|
||||
DeclareField(scsi_controller);
|
||||
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(DiskController);
|
||||
AnnounceEnum(SCSIController);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
EightMB
|
||||
);
|
||||
|
||||
Model model = Model::ROM03;
|
||||
Model model = Model::ROM01;
|
||||
MemoryModel memory_model = MemoryModel::EightMB;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AppleIIgs) {
|
||||
|
||||
@@ -17,7 +17,18 @@ namespace Static {
|
||||
namespace AtariST {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {}
|
||||
ReflectableEnum(MemorySize,
|
||||
FiveHundredAndTwelveKilobytes,
|
||||
OneMegabyte,
|
||||
FourMegabytes);
|
||||
MemorySize memory_size = MemorySize::OneMegabyte;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::AtariST) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(memory_size);
|
||||
AnnounceEnum(MemorySize);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ static std::unique_ptr<Analyser::Static::Target> CartridgeTarget(
|
||||
auto target = std::make_unique<Analyser::Static::MSX::Target>();
|
||||
target->confidence = confidence;
|
||||
|
||||
// Observation: all ROMs of 48kb or less are from the MSX 1 era.
|
||||
if(segment.data.size() < 48*1024) {
|
||||
target->model = Analyser::Static::MSX::Target::Model::MSX1;
|
||||
}
|
||||
|
||||
if(type == Analyser::Static::MSX::Cartridge::Type::None) {
|
||||
target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments));
|
||||
} else {
|
||||
@@ -100,6 +105,7 @@ static Analyser::Static::TargetList CartridgeTargetsFrom(
|
||||
// TODO: check for a rational init address?
|
||||
|
||||
// If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on.
|
||||
// Bonus observation: all such ROMs are from the MSX 1 era.
|
||||
if(data_size <= 0xc000) {
|
||||
targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0));
|
||||
continue;
|
||||
|
||||
@@ -22,6 +22,12 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
bool has_disk_drive = false;
|
||||
std::string loading_command;
|
||||
|
||||
ReflectableEnum(Model,
|
||||
MSX1,
|
||||
MSX2
|
||||
);
|
||||
Model model = Model::MSX2;
|
||||
|
||||
ReflectableEnum(Region,
|
||||
Japan,
|
||||
USA,
|
||||
@@ -34,6 +40,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
DeclareField(has_disk_drive);
|
||||
DeclareField(region);
|
||||
AnnounceEnum(Region);
|
||||
DeclareField(model);
|
||||
AnnounceEnum(Model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,7 +48,9 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
}
|
||||
};
|
||||
|
||||
#define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem
|
||||
constexpr bool is_master_system(Analyser::Static::Sega::Target::Model model) {
|
||||
return model >= Analyser::Static::Sega::Target::Model::MasterSystem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
// Analysers
|
||||
#include "Acorn/StaticAnalyser.hpp"
|
||||
#include "Amiga/StaticAnalyser.hpp"
|
||||
#include "AmstradCPC/StaticAnalyser.hpp"
|
||||
#include "AppleII/StaticAnalyser.hpp"
|
||||
#include "AppleIIgs/StaticAnalyser.hpp"
|
||||
@@ -38,25 +39,27 @@
|
||||
// Disks
|
||||
#include "../../Storage/Disk/DiskImage/Formats/2MG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AcornADF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AmigaADF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/AppleDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/CPCDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/D64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/ST.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/STX.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HDV.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// State Snapshots
|
||||
@@ -78,6 +81,8 @@
|
||||
// Target Platform Types
|
||||
#include "../../Storage/TargetPlatforms.hpp"
|
||||
|
||||
template<class> inline constexpr bool always_false_v = false;
|
||||
|
||||
using namespace Analyser::Static;
|
||||
|
||||
namespace {
|
||||
@@ -101,8 +106,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
#define InsertInstance(list, instance, platforms) \
|
||||
list.emplace_back(instance);\
|
||||
potential_platforms |= platforms;\
|
||||
TargetPlatform::TypeDistinguisher *distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
|
||||
if(distinguisher) potential_platforms &= distinguisher->target_platform_type(); \
|
||||
TargetPlatform::TypeDistinguisher *const distinguisher = dynamic_cast<TargetPlatform::TypeDistinguisher *>(list.back().get());\
|
||||
if(distinguisher) potential_platforms &= distinguisher->target_platform_type();
|
||||
|
||||
#define Insert(list, class, platforms, ...) \
|
||||
InsertInstance(list, new Storage::class(__VA_ARGS__), platforms);
|
||||
@@ -121,14 +126,29 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
if(extension == "2mg") {
|
||||
// 2MG uses a factory method; defer to it.
|
||||
try {
|
||||
InsertInstance(result.disks, Storage::Disk::Disk2MG::open(file_name), TargetPlatform::DiskII)
|
||||
const auto media = Storage::Disk::Disk2MG::open(file_name);
|
||||
std::visit([&result, &potential_platforms](auto &&arg) {
|
||||
using Type = typename std::decay<decltype(arg)>::type;
|
||||
|
||||
if constexpr (std::is_same<Type, nullptr_t>::value) {
|
||||
// It's valid for no media to be returned.
|
||||
} else if constexpr (std::is_same<Type, Storage::Disk::DiskImageHolderBase *>::value) {
|
||||
InsertInstance(result.disks, arg, TargetPlatform::DiskII);
|
||||
} else if constexpr (std::is_same<Type, Storage::MassStorage::MassStorageDevice *>::value) {
|
||||
// TODO: or is it Apple IIgs?
|
||||
InsertInstance(result.mass_storage_devices, arg, TargetPlatform::AppleII);
|
||||
} else {
|
||||
static_assert(always_false_v<Type>, "Unexpected type encountered.");
|
||||
}
|
||||
}, media);
|
||||
} catch(...) {}
|
||||
}
|
||||
|
||||
Format("80", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 80
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF (Acorn)
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AmigaADF>, TargetPlatform::Amiga) // ADF (Amiga)
|
||||
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
@@ -151,6 +171,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format("hdv", result.mass_storage_devices, MassStorage::HDV, TargetPlatform::AppleII) // HDV (Apple II, hard disk, single volume image)
|
||||
Format( "hfe",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::HFE>,
|
||||
@@ -158,7 +179,11 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
|
||||
Format( "ipf",
|
||||
result.disks,
|
||||
Disk::DiskImageHolder<Storage::Disk::IPF>,
|
||||
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum) // IPF
|
||||
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
@@ -191,7 +216,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format("sg", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SG
|
||||
Format("sms", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Sega) // SMS
|
||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::ST>, TargetPlatform::AtariST) // ST
|
||||
Format("st", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::AtariST) // ST
|
||||
Format("stx", result.disks, Disk::DiskImageHolder<Storage::Disk::STX>, TargetPlatform::AtariST) // STX
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
@@ -253,6 +278,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
Append(AmstradCPC);
|
||||
Append(AppleII);
|
||||
Append(AppleIIgs);
|
||||
Append(Amiga);
|
||||
Append(Atari2600);
|
||||
Append(AtariST);
|
||||
Append(Coleco);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
@@ -138,8 +139,11 @@ template <class T> class WrappedInt {
|
||||
forceinline constexpr bool operator !() const { return !length_; }
|
||||
// bool operator () is not supported because it offers an implicit cast to int, which is prone silently to permit misuse
|
||||
|
||||
/// @returns The underlying int, cast to an integral type of your choosing.
|
||||
template<typename Type = IntType> forceinline constexpr Type as() const { return Type(length_); }
|
||||
/// @returns The underlying int, converted to an integral type of your choosing, clamped to that int's range.
|
||||
template<typename Type = IntType> forceinline constexpr Type as() const {
|
||||
const auto clamped = std::clamp(length_, IntType(std::numeric_limits<Type>::min()), IntType(std::numeric_limits<Type>::max()));
|
||||
return Type(clamped);
|
||||
}
|
||||
|
||||
/// @returns The underlying int, in its native form.
|
||||
forceinline constexpr IntType as_integral() const { return length_; }
|
||||
@@ -222,6 +226,15 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Equivalent to @c divide_cycles(Cycles(1)) but faster.
|
||||
*/
|
||||
forceinline Cycles divide_cycles() {
|
||||
const Cycles result(length_ >> 1);
|
||||
length_ &= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
friend WrappedInt;
|
||||
void fill(Cycles &result) {
|
||||
|
||||
48
ClockReceiver/DeferredValue.hpp
Normal file
48
ClockReceiver/DeferredValue.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// DeferredValue.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/08/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DeferredValue_h
|
||||
#define DeferredValue_h
|
||||
|
||||
/*!
|
||||
Provides storage for a single deferred value: one with a current value and a certain number
|
||||
of future values.
|
||||
*/
|
||||
template <int DeferredDepth, typename ValueT> class DeferredValue {
|
||||
private:
|
||||
static_assert(sizeof(ValueT) <= 4);
|
||||
|
||||
constexpr int elements_per_uint32 = sizeof(uint32_t) / sizeof(ValueT);
|
||||
constexpr int unit_shift = sizeof(ValueT) * 8;
|
||||
constexpr int insert_shift = (DeferredDepth & (elements_per_uint32 - 1)) * unit_shift;
|
||||
constexpr uint32_t insert_mask = ~(0xffff'ffff << insert_shift);
|
||||
|
||||
std::array<uint32_t, (DeferredDepth + elements_per_uint32 - 1) / elements_per_uint32> backlog;
|
||||
|
||||
public:
|
||||
/// @returns the current value.
|
||||
ValueT value() const {
|
||||
return uint8_t(backlog[0]);
|
||||
}
|
||||
|
||||
/// Advances to the next enqueued value.
|
||||
void advance() {
|
||||
for(size_t c = 0; c < backlog.size() - 1; c--) {
|
||||
backlog[c] = (backlog[c] >> unit_shift) | (backlog[c+1] << (32 - unit_shift));
|
||||
}
|
||||
backlog[backlog.size() - 1] >>= unit_shift;
|
||||
}
|
||||
|
||||
/// Inserts a new value, replacing whatever is currently at the end of the queue.
|
||||
void insert(ValueT value) {
|
||||
backlog[DeferredDepth / elements_per_uint32] =
|
||||
(backlog[DeferredDepth / elements_per_uint32] & insert_mask) | (value << insert_shift);
|
||||
}
|
||||
};
|
||||
|
||||
#endif /* DeferredValue_h */
|
||||
@@ -9,10 +9,13 @@
|
||||
#ifndef JustInTime_h
|
||||
#define JustInTime_h
|
||||
|
||||
#include "ClockReceiver.hpp"
|
||||
#include "../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "ClockingHintSource.hpp"
|
||||
#include "ForceInline.hpp"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
/*!
|
||||
A JustInTimeActor holds (i) an embedded object with a run_for method; and (ii) an amount
|
||||
of time since run_for was last called.
|
||||
@@ -120,7 +123,13 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// If this object provides sequence points, checks for changes to the next
|
||||
/// sequence point upon deletion of the pointer.
|
||||
[[nodiscard]] forceinline auto operator->() {
|
||||
#ifndef NDEBUG
|
||||
assert(!flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
flush();
|
||||
#ifndef NDEBUG
|
||||
flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(this));
|
||||
}
|
||||
|
||||
@@ -129,7 +138,13 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// Despite being const, this will flush the object and, if relevant, update the next sequence point.
|
||||
[[nodiscard]] forceinline auto operator -> () const {
|
||||
auto non_const_this = const_cast<JustInTimeActor<T, LocalTimeScale, multiplier, divider> *>(this);
|
||||
#ifndef NDEBUG
|
||||
assert(!non_const_this->flush_concurrency_check_.test_and_set());
|
||||
#endif
|
||||
non_const_this->flush();
|
||||
#ifndef NDEBUG
|
||||
non_const_this->flush_concurrency_check_.clear();
|
||||
#endif
|
||||
return std::unique_ptr<const T, SequencePointAwareDeleter>(&object_, SequencePointAwareDeleter(non_const_this));
|
||||
}
|
||||
|
||||
@@ -263,6 +278,10 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference clocking) {
|
||||
clocking_preference_ = clocking;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
std::atomic_flag flush_concurrency_check_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -314,7 +333,7 @@ template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = Lo
|
||||
LocalTimeScale time_since_update_;
|
||||
TargetTimeScale threshold_;
|
||||
bool is_flushed_ = true;
|
||||
Concurrency::AsyncTaskQueue task_queue_;
|
||||
Concurrency::AsyncTaskQueue<true> task_queue_;
|
||||
};
|
||||
|
||||
#endif /* JustInTime_h */
|
||||
|
||||
@@ -20,6 +20,11 @@ inline Nanos nanos_now() {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
inline Seconds seconds(Nanos nanos) {
|
||||
return double(nanos) / 1e9;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* TimeTypes_h */
|
||||
|
||||
|
||||
@@ -8,8 +8,18 @@
|
||||
|
||||
#include "ncr5380.hpp"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
#define LOG_PREFIX "[5380] "
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
// TODO:
|
||||
//
|
||||
// end_of_dma_ should be set if: /EOP && /DACK && (/RD || /WR); for at least 100ns.
|
||||
|
||||
|
||||
using namespace NCR::NCR5380;
|
||||
using SCSI::Line;
|
||||
|
||||
@@ -28,20 +38,16 @@ NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
|
||||
void NCR5380::write(int address, uint8_t value, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
|
||||
data_bus_ = value;
|
||||
LOG("[0] Set current SCSI bus state to " << PADHEX(2) << int(value));
|
||||
|
||||
data_bus_ = value;
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
|
||||
// printf("w %02x\n", value);
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
dma_acknowledge(value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: {
|
||||
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
|
||||
LOG("[1] Initiator command register set: " << PADHEX(2) << int(value));
|
||||
initiator_command_ = value;
|
||||
|
||||
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
|
||||
@@ -57,7 +63,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
} break;
|
||||
|
||||
case 2:
|
||||
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
|
||||
LOG("[2] Set mode: " << PADHEX(2) << int(value));
|
||||
mode_ = value;
|
||||
|
||||
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
|
||||
@@ -69,6 +75,7 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
// bit 1: 1 = use DMA mode
|
||||
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
|
||||
arbitration_in_progress_ = false;
|
||||
phase_mismatch_ = false;
|
||||
switch(mode_ & 0x3) {
|
||||
case 0x0:
|
||||
bus_output_ &= ~SCSI::Line::Busy;
|
||||
@@ -88,31 +95,36 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
bus_.update_observers();
|
||||
break;
|
||||
}
|
||||
|
||||
// "[The End of DMA Transfer] bit is reset when the DMA MODE bit
|
||||
// is reset (0) in the Mode Register".
|
||||
end_of_dma_ &= bool(value & 0x2);
|
||||
|
||||
update_control_output();
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
|
||||
LOG("[3] Set target command: " << PADHEX(2) << int(value));
|
||||
target_command_ = value;
|
||||
update_control_output();
|
||||
} break;
|
||||
|
||||
case 4:
|
||||
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
|
||||
LOG("[4] Set select enabled: " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
|
||||
LOG("[5] Start DMA send: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::Send;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
|
||||
LOG("[6] Start DMA target receive: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::TargetReceive;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
|
||||
LOG("[7] Start DMA initiator receive: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::InitiatorReceive;
|
||||
break;
|
||||
}
|
||||
@@ -136,18 +148,15 @@ void NCR5380::write(int address, uint8_t value, bool) {
|
||||
uint8_t NCR5380::read(int address, bool) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
|
||||
LOG("[0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
|
||||
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
return dma_acknowledge();
|
||||
}
|
||||
return uint8_t(bus_.get_state());
|
||||
|
||||
case 1:
|
||||
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
|
||||
LOG("[1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
|
||||
return
|
||||
// Bits repeated as they were set.
|
||||
(initiator_command_ & ~0x60) |
|
||||
@@ -159,11 +168,11 @@ uint8_t NCR5380::read(int address, bool) {
|
||||
(lost_arbitration_ ? 0x20 : 0x00);
|
||||
|
||||
case 2:
|
||||
// LOG("[SCSI 2] Get mode");
|
||||
LOG("[2] Get mode");
|
||||
return mode_;
|
||||
|
||||
case 3:
|
||||
// LOG("[SCSI 3] Get target command");
|
||||
LOG("[3] Get target command");
|
||||
return target_command_;
|
||||
|
||||
case 4: {
|
||||
@@ -177,41 +186,38 @@ uint8_t NCR5380::read(int address, bool) {
|
||||
((bus_state & Line::Input) ? 0x04 : 0x00) |
|
||||
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Parity) ? 0x01 : 0x00);
|
||||
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
|
||||
LOG("[4] Get current bus state: " << PADHEX(2) << int(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
case 5: {
|
||||
const auto bus_state = bus_.get_state();
|
||||
const bool phase_matches =
|
||||
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
|
||||
(bus_state & (Line::Message | Line::Control | Line::Input));
|
||||
|
||||
const uint8_t result =
|
||||
/* b7 = end of DMA */
|
||||
(end_of_dma_ ? 0x80 : 0x00) |
|
||||
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
|
||||
/* b5 = parity error */
|
||||
/* b4 = IRQ active */
|
||||
(phase_matches ? 0x08 : 0x00) |
|
||||
(irq_ ? 0x10 : 0x00) |
|
||||
(phase_matches() ? 0x08 : 0x00) |
|
||||
/* b2 = busy error */
|
||||
((bus_state & Line::Attention) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
|
||||
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
|
||||
LOG("[5] Get bus and status: " << PADHEX(2) << int(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
case 6:
|
||||
// LOG("[SCSI 6] Get input data");
|
||||
LOG("[6] Get input data");
|
||||
return 0xff;
|
||||
|
||||
case 7:
|
||||
// LOG("[SCSI 7] Reset parity/interrupt");
|
||||
LOG("[7] Reset parity/interrupt");
|
||||
irq_ = false;
|
||||
return 0xff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SCSI::BusState NCR5380::target_output() {
|
||||
SCSI::BusState NCR5380::target_output() const {
|
||||
SCSI::BusState output = SCSI::DefaultBusState;
|
||||
if(target_command_ & 0x08) output |= Line::Request;
|
||||
if(target_command_ & 0x04) output |= Line::Message;
|
||||
@@ -236,6 +242,17 @@ void NCR5380::update_control_output() {
|
||||
}
|
||||
|
||||
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
|
||||
/*
|
||||
When connected as an Initiator with DMA Mode True,
|
||||
if the phase lines I//O, C//D, and /MSG do not match the
|
||||
phase bits in the Target Command Register, a phase mismatch
|
||||
interrupt is generated when /REQ goes active.
|
||||
*/
|
||||
if((mode_ & 0x42) == 0x02 && new_state & SCSI::Line::Request && !phase_matches()) {
|
||||
irq_ = true;
|
||||
phase_mismatch_ = true;
|
||||
}
|
||||
|
||||
switch(state_) {
|
||||
default: break;
|
||||
|
||||
@@ -296,7 +313,13 @@ void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double
|
||||
dma_request_ = false;
|
||||
break;
|
||||
case SCSI::Line::Request:
|
||||
dma_request_ = true;
|
||||
// Don't issue a new DMA request if a phase mismatch has
|
||||
// been detected and this is an intiator receiving.
|
||||
// This is a bit of reading between the lines.
|
||||
// (i.e. guesswork, partly)
|
||||
dma_request_ =
|
||||
!phase_mismatch_ ||
|
||||
(dma_operation_ != DMAOperation::InitiatorReceive);
|
||||
break;
|
||||
case SCSI::Line::Request | SCSI::Line::Acknowledge:
|
||||
dma_request_ = false;
|
||||
@@ -316,3 +339,38 @@ void NCR5380::set_execution_state(ExecutionState state) {
|
||||
state_ = state;
|
||||
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
|
||||
}
|
||||
|
||||
size_t NCR5380::scsi_id() {
|
||||
return device_id_;
|
||||
}
|
||||
|
||||
bool NCR5380::dma_request() {
|
||||
return dma_request_;
|
||||
}
|
||||
|
||||
uint8_t NCR5380::dma_acknowledge() {
|
||||
const uint8_t bus_state = uint8_t(bus_.get_state());
|
||||
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
|
||||
return bus_state;
|
||||
}
|
||||
|
||||
void NCR5380::dma_acknowledge(uint8_t value) {
|
||||
data_bus_ = value;
|
||||
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
}
|
||||
|
||||
bool NCR5380::phase_matches() const {
|
||||
const auto bus_state = bus_.get_state();
|
||||
return
|
||||
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
|
||||
(bus_state & (Line::Message | Line::Control | Line::Input));
|
||||
}
|
||||
|
||||
@@ -30,6 +30,18 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
/*! Reads from @c address. */
|
||||
uint8_t read(int address, bool dma_acknowledge = false);
|
||||
|
||||
/*! @returns The SCSI ID assigned to this device. */
|
||||
size_t scsi_id();
|
||||
|
||||
/*! @return @c true if DMA request is active; @c false otherwise. */
|
||||
bool dma_request();
|
||||
|
||||
/*! Signals DMA acknowledge with a simultaneous read. */
|
||||
uint8_t dma_acknowledge();
|
||||
|
||||
/*! Signals DMA acknowledge with a simultaneous write. */
|
||||
void dma_acknowledge(uint8_t);
|
||||
|
||||
private:
|
||||
SCSI::Bus &bus_;
|
||||
|
||||
@@ -46,6 +58,10 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
bool assert_data_bus_ = false;
|
||||
bool dma_request_ = false;
|
||||
bool dma_acknowledge_ = false;
|
||||
bool end_of_dma_ = false;
|
||||
|
||||
bool irq_ = false;
|
||||
bool phase_mismatch_ = false;
|
||||
|
||||
enum class ExecutionState {
|
||||
None,
|
||||
@@ -63,10 +79,11 @@ class NCR5380 final: public SCSI::Bus::Observer {
|
||||
|
||||
void set_execution_state(ExecutionState state);
|
||||
|
||||
SCSI::BusState target_output();
|
||||
SCSI::BusState target_output() const;
|
||||
void update_control_output();
|
||||
|
||||
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
|
||||
bool phase_matches() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
#define _522_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <typeinfo>
|
||||
#include <cstdio>
|
||||
|
||||
#include "Implementation/6522Storage.hpp"
|
||||
|
||||
@@ -37,22 +35,24 @@ enum Line {
|
||||
class PortHandler {
|
||||
public:
|
||||
/// Requests the current input value of @c port from the port handler.
|
||||
uint8_t get_port_input([[maybe_unused]] Port port) { return 0xff; }
|
||||
uint8_t get_port_input([[maybe_unused]] Port port) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
/// Sets the current output value of @c port and provides @c direction_mask, indicating which pins are marked as output.
|
||||
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
|
||||
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value, [[maybe_unused]] uint8_t direction_mask) {}
|
||||
|
||||
/// Sets the current logical output level for line @c line on port @c port.
|
||||
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
|
||||
void set_control_line_output([[maybe_unused]] Port port, [[maybe_unused]] Line line, [[maybe_unused]] bool value) {}
|
||||
|
||||
/// Sets the current logical value of the interrupt line.
|
||||
void set_interrupt_status([[maybe_unused]] bool status) {}
|
||||
void set_interrupt_status([[maybe_unused]] bool status) {}
|
||||
|
||||
/// Provides a measure of time elapsed between other calls.
|
||||
void run_for([[maybe_unused]] HalfCycles duration) {}
|
||||
void run_for([[maybe_unused]] HalfCycles duration) {}
|
||||
|
||||
/// Receives passed-on flush() calls from the 6522.
|
||||
void flush() {}
|
||||
void flush() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -88,9 +88,9 @@ class IRQDelegatePortHandler: public PortHandler {
|
||||
Consumers should derive their own curiously-recurring-template-pattern subclass,
|
||||
implementing bus communications as required.
|
||||
*/
|
||||
template <class T> class MOS6522: public MOS6522Storage {
|
||||
template <class BusHandlerT> class MOS6522: public MOS6522Storage {
|
||||
public:
|
||||
MOS6522(T &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
MOS6522(BusHandlerT &bus_handler) noexcept : bus_handler_(bus_handler) {}
|
||||
MOS6522(const MOS6522 &) = delete;
|
||||
|
||||
/*! Sets a register value. */
|
||||
@@ -100,7 +100,7 @@ template <class T> class MOS6522: public MOS6522Storage {
|
||||
uint8_t read(int address);
|
||||
|
||||
/*! @returns the bus handler. */
|
||||
T &bus_handler();
|
||||
BusHandlerT &bus_handler();
|
||||
|
||||
/// Sets the input value of line @c line on port @c port.
|
||||
void set_control_line_input(Port port, Line line, bool value);
|
||||
@@ -123,7 +123,7 @@ template <class T> class MOS6522: public MOS6522Storage {
|
||||
void shift_in();
|
||||
void shift_out();
|
||||
|
||||
T &bus_handler_;
|
||||
BusHandlerT &bus_handler_;
|
||||
HalfCycles time_since_bus_handler_call_;
|
||||
|
||||
void access(int address);
|
||||
|
||||
94
Components/6526/6526.hpp
Normal file
94
Components/6526/6526.hpp
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// 6526.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _526_h
|
||||
#define _526_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "Implementation/6526Storage.hpp"
|
||||
#include "../Serial/Line.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
|
||||
enum Port {
|
||||
A = 0,
|
||||
B = 1
|
||||
};
|
||||
|
||||
struct PortHandler {
|
||||
/// Requests the current input value of @c port from the port handler.
|
||||
uint8_t get_port_input([[maybe_unused]] Port port) {
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
/// Sets the current output value of @c port; any bits marked as input will be supplied as 1s.
|
||||
void set_port_output([[maybe_unused]] Port port, [[maybe_unused]] uint8_t value) {}
|
||||
};
|
||||
|
||||
enum class Personality {
|
||||
// The 6526, used in machines such as the C64, has a BCD time-of-day clock.
|
||||
P6526,
|
||||
// The 8250, used in the Amiga, provides a binary time-of-day clock.
|
||||
P8250,
|
||||
};
|
||||
|
||||
template <typename PortHandlerT, Personality personality> class MOS6526:
|
||||
private MOS6526Storage,
|
||||
private Serial::Line<true>::ReadDelegate
|
||||
{
|
||||
public:
|
||||
MOS6526(PortHandlerT &port_handler) noexcept : port_handler_(port_handler) {
|
||||
serial_input.set_read_delegate(this);
|
||||
}
|
||||
MOS6526(const MOS6526 &) = delete;
|
||||
|
||||
/// Writes @c value to the register at @c address. Only the low two bits of the address are decoded.
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/// Fetches the value of the register @c address. Only the low two bits of the address are decoded.
|
||||
uint8_t read(int address);
|
||||
|
||||
/// Pulses Phi2 to advance by the specified number of half cycles.
|
||||
void run_for(const HalfCycles half_cycles);
|
||||
|
||||
/// Pulses the TOD input the specified number of times.
|
||||
void advance_tod(int count);
|
||||
|
||||
/// @returns @c true if the interrupt output is active, @c false otherwise.
|
||||
bool get_interrupt_line();
|
||||
|
||||
/// Sets the current state of the CNT input.
|
||||
void set_cnt_input(bool active);
|
||||
|
||||
/// Provides both the serial input bit and an additional source of CNT.
|
||||
Serial::Line<true> serial_input;
|
||||
|
||||
/// Sets the current state of the FLG input.
|
||||
void set_flag_input(bool low);
|
||||
|
||||
private:
|
||||
PortHandlerT &port_handler_;
|
||||
TODStorage<personality == Personality::P8250> tod_;
|
||||
|
||||
template <int port> void set_port_output();
|
||||
template <int port> uint8_t get_port_input();
|
||||
void update_interrupts();
|
||||
void posit_interrupt(uint8_t mask);
|
||||
void advance_counters(int);
|
||||
|
||||
bool serial_line_did_produce_bit(Serial::Line<true> *line, int bit) final;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/6526Implementation.hpp"
|
||||
|
||||
#endif /* _526_h */
|
||||
244
Components/6526/Implementation/6526Implementation.hpp
Normal file
244
Components/6526/Implementation/6526Implementation.hpp
Normal file
@@ -0,0 +1,244 @@
|
||||
//
|
||||
// 6526Implementation.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _526Implementation_h
|
||||
#define _526Implementation_h
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
|
||||
enum Interrupts: uint8_t {
|
||||
TimerA = 1 << 0,
|
||||
TimerB = 1 << 1,
|
||||
Alarm = 1 << 2,
|
||||
SerialPort = 1 << 3,
|
||||
Flag = 1 << 4,
|
||||
};
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
template <int port> void MOS6526<BusHandlerT, personality>::set_port_output() {
|
||||
const uint8_t output = output_[port] | (~data_direction_[port]);
|
||||
port_handler_.set_port_output(Port(port), output);
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
template <int port> uint8_t MOS6526<BusHandlerT, personality>::get_port_input() {
|
||||
// Avoid bothering the port handler if there's no input active.
|
||||
const uint8_t input_mask = ~data_direction_[port];
|
||||
const uint8_t input = input_mask ? port_handler_.get_port_input(Port(port)) : 0x00;
|
||||
return (input & input_mask) | (output_[port] & data_direction_[port]);
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::posit_interrupt(uint8_t mask) {
|
||||
if(!mask) {
|
||||
return;
|
||||
}
|
||||
interrupt_state_ |= mask;
|
||||
update_interrupts();
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::update_interrupts() {
|
||||
if(interrupt_state_ & interrupt_control_) {
|
||||
pending_ |= InterruptInOne;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
bool MOS6526<BusHandlerT, personality>::get_interrupt_line() {
|
||||
return interrupt_state_ & 0x80;
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::set_cnt_input(bool active) {
|
||||
cnt_edge_ = active && !cnt_state_;
|
||||
cnt_state_ = active;
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::set_flag_input(bool low) {
|
||||
if(low && !flag_state_) {
|
||||
posit_interrupt(Interrupts::Flag);
|
||||
}
|
||||
flag_state_ = low;
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::write(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
// Port output.
|
||||
case 0:
|
||||
output_[0] = value;
|
||||
set_port_output<0>();
|
||||
break;
|
||||
case 1:
|
||||
output_[1] = value;
|
||||
set_port_output<1>();
|
||||
break;
|
||||
|
||||
// Port direction.
|
||||
case 2:
|
||||
data_direction_[0] = value;
|
||||
set_port_output<0>();
|
||||
break;
|
||||
case 3:
|
||||
data_direction_[1] = value;
|
||||
set_port_output<1>();
|
||||
break;
|
||||
|
||||
// Counters; writes set the reload values.
|
||||
case 4: counter_[0].template set_reload<0, personality == Personality::P8250>(value); break;
|
||||
case 5: counter_[0].template set_reload<8, personality == Personality::P8250>(value); break;
|
||||
case 6: counter_[1].template set_reload<0, personality == Personality::P8250>(value); break;
|
||||
case 7: counter_[1].template set_reload<8, personality == Personality::P8250>(value); break;
|
||||
|
||||
// Time-of-day clock.
|
||||
case 8: tod_.template write<0>(value); break;
|
||||
case 9: tod_.template write<1>(value); break;
|
||||
case 10: tod_.template write<2>(value); break;
|
||||
case 11: tod_.template write<3>(value); break;
|
||||
|
||||
// Interrupt control.
|
||||
case 13: {
|
||||
if(value & 0x80) {
|
||||
interrupt_control_ |= value & 0x7f;
|
||||
} else {
|
||||
interrupt_control_ &= ~(value & 0x7f);
|
||||
}
|
||||
update_interrupts();
|
||||
} break;
|
||||
|
||||
// Control. Posted to both the counters and the clock as it affects both.
|
||||
case 14:
|
||||
counter_[0].template set_control<false>(value);
|
||||
tod_.template set_control<false>(value);
|
||||
if(shifter_is_output_ != bool(value & 0x40)) {
|
||||
shifter_is_output_ = value & 0x40;
|
||||
shift_bits_ = 0;
|
||||
}
|
||||
break;
|
||||
case 15:
|
||||
counter_[1].template set_control<true>(value);
|
||||
tod_.template set_control<true>(value);
|
||||
break;
|
||||
|
||||
// Shift control.
|
||||
case 12:
|
||||
printf("TODO: write to shift register\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("Unhandled 6526 write: %02x to %d\n", value, address);
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
uint8_t MOS6526<BusHandlerT, personality>::read(int address) {
|
||||
address &= 0xf;
|
||||
switch(address) {
|
||||
case 0: return get_port_input<0>();
|
||||
case 1: return get_port_input<1>();
|
||||
|
||||
case 2: case 3:
|
||||
return data_direction_[address - 2];
|
||||
|
||||
// Counters; reads obtain the current values.
|
||||
case 4: return uint8_t(counter_[0].value >> 0);
|
||||
case 5: return uint8_t(counter_[0].value >> 8);
|
||||
case 6: return uint8_t(counter_[1].value >> 0);
|
||||
case 7: return uint8_t(counter_[1].value >> 8);
|
||||
|
||||
// Interrupt state.
|
||||
case 13: {
|
||||
const uint8_t result = interrupt_state_;
|
||||
interrupt_state_ = 0;
|
||||
pending_ &= ~(InterruptNow | InterruptInOne);
|
||||
update_interrupts();
|
||||
return result;
|
||||
} break;
|
||||
|
||||
case 14: case 15:
|
||||
return counter_[address - 14].control;
|
||||
|
||||
// Time-of-day clock.
|
||||
case 8: return tod_.template read<0>();
|
||||
case 9: return tod_.template read<1>();
|
||||
case 10: return tod_.template read<2>();
|
||||
case 11: return tod_.template read<3>();
|
||||
|
||||
// Shift register.
|
||||
case 12: return shift_data_;
|
||||
|
||||
default:
|
||||
printf("Unhandled 6526 read from %d\n", address);
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::run_for(const HalfCycles half_cycles) {
|
||||
half_divider_ += half_cycles;
|
||||
int sub = half_divider_.divide_cycles().template as<int>();
|
||||
|
||||
while(sub--) {
|
||||
pending_ <<= 1;
|
||||
if(pending_ & InterruptNow) {
|
||||
interrupt_state_ |= 0x80;
|
||||
}
|
||||
pending_ &= PendingClearMask;
|
||||
|
||||
// TODO: use CNT potentially to clock timer A, elimiante conditional above.
|
||||
const bool timer1_did_reload = counter_[0].template advance<false>(false, cnt_state_, cnt_edge_);
|
||||
|
||||
const bool timer1_carry = timer1_did_reload && (counter_[1].control & 0x60) == 0x40;
|
||||
const bool timer2_did_reload = counter_[1].template advance<true>(timer1_carry, cnt_state_, cnt_edge_);
|
||||
posit_interrupt((timer1_did_reload ? Interrupts::TimerA : 0x00) | (timer2_did_reload ? Interrupts::TimerB : 0x00));
|
||||
|
||||
cnt_edge_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
void MOS6526<BusHandlerT, personality>::advance_tod(int count) {
|
||||
if(!count) return;
|
||||
if(tod_.advance(count)) {
|
||||
posit_interrupt(Interrupts::Alarm);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BusHandlerT, Personality personality>
|
||||
bool MOS6526<BusHandlerT, personality>::serial_line_did_produce_bit(Serial::Line<true> *, int bit) {
|
||||
// TODO: post CNT change; might affect timer.
|
||||
|
||||
if(!shifter_is_output_) {
|
||||
shift_register_ = uint8_t((shift_register_ << 1) | bit);
|
||||
++shift_bits_;
|
||||
|
||||
if(shift_bits_ == 8) {
|
||||
shift_bits_ = 0;
|
||||
shift_data_ = shift_register_;
|
||||
posit_interrupt(Interrupts::SerialPort);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _526Implementation_h */
|
||||
339
Components/6526/Implementation/6526Storage.hpp
Normal file
339
Components/6526/Implementation/6526Storage.hpp
Normal file
@@ -0,0 +1,339 @@
|
||||
//
|
||||
// 6526Storage.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 18/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef _526Storage_h
|
||||
#define _526Storage_h
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace MOS {
|
||||
namespace MOS6526 {
|
||||
|
||||
class TODBase {
|
||||
public:
|
||||
template <bool is_timer2> void set_control(uint8_t value) {
|
||||
if constexpr (is_timer2) {
|
||||
write_alarm = value & 0x80;
|
||||
} else {
|
||||
is_50Hz = value & 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write_alarm = false, is_50Hz = false;
|
||||
};
|
||||
|
||||
template <bool is_8250> class TODStorage {};
|
||||
|
||||
template <> class TODStorage<false>: public TODBase {
|
||||
private:
|
||||
bool increment_ = true, latched_ = false;
|
||||
int divider_ = 0;
|
||||
std::array<uint8_t, 4> value_;
|
||||
std::array<uint8_t, 4> latch_;
|
||||
std::array<uint8_t, 4> alarm_;
|
||||
|
||||
static constexpr uint8_t masks[4] = {0xf, 0x3f, 0x3f, 0x1f};
|
||||
|
||||
void bcd_increment(uint8_t &value) {
|
||||
++value;
|
||||
if((value&0x0f) > 0x09) value += 0x06;
|
||||
}
|
||||
|
||||
public:
|
||||
template <int byte> void write(uint8_t v) {
|
||||
if(write_alarm) {
|
||||
alarm_[byte] = v & masks[byte];
|
||||
} else {
|
||||
value_[byte] = v & masks[byte];
|
||||
|
||||
if constexpr (byte == 0) {
|
||||
increment_ = true;
|
||||
}
|
||||
if constexpr (byte == 3) {
|
||||
increment_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <int byte> uint8_t read() {
|
||||
if(latched_) {
|
||||
const uint8_t result = latch_[byte];
|
||||
if constexpr (byte == 0) {
|
||||
latched_ = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if constexpr (byte == 3) {
|
||||
latched_ = true;
|
||||
latch_ = value_;
|
||||
}
|
||||
return value_[byte];
|
||||
}
|
||||
|
||||
bool advance(int count) {
|
||||
if(!increment_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while(count--) {
|
||||
// Increment the pre-10ths divider.
|
||||
++divider_;
|
||||
if(divider_ < 5) continue;
|
||||
if(divider_ < 6 && !is_50Hz) continue;
|
||||
divider_ = 0;
|
||||
|
||||
// Increments 10ths of a second. One BCD digit.
|
||||
++value_[0];
|
||||
if(value_[0] < 10) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Increment seconds. Actual BCD needed from here onwards.
|
||||
bcd_increment(value_[1]);
|
||||
if(value_[1] != 60) {
|
||||
continue;
|
||||
}
|
||||
value_[1] = 0;
|
||||
|
||||
// Increment minutes.
|
||||
bcd_increment(value_[2]);
|
||||
if(value_[2] != 60) {
|
||||
continue;
|
||||
}
|
||||
value_[2] = 0;
|
||||
|
||||
// TODO: increment hours, keeping AM/PM separate?
|
||||
}
|
||||
|
||||
return false; // TODO: test against alarm.
|
||||
}
|
||||
};
|
||||
|
||||
template <> class TODStorage<true>: public TODBase {
|
||||
private:
|
||||
uint32_t increment_mask_ = uint32_t(~0);
|
||||
uint32_t latch_ = 0;
|
||||
uint32_t value_ = 0;
|
||||
uint32_t alarm_ = 0xff'ffff;
|
||||
|
||||
public:
|
||||
template <int byte> void write(uint8_t v) {
|
||||
if constexpr (byte == 3) {
|
||||
return;
|
||||
}
|
||||
constexpr int shift = byte << 3;
|
||||
|
||||
// Write to either the alarm or the current value as directed;
|
||||
// writing to any part of the current value other than the LSB
|
||||
// pauses incrementing until the LSB is written.
|
||||
const uint32_t mask = uint32_t(~(0xff << shift));
|
||||
if(write_alarm) {
|
||||
alarm_ = (alarm_ & mask) | uint32_t(v << shift);
|
||||
} else {
|
||||
value_ = (value_ & mask) | uint32_t(v << shift);
|
||||
increment_mask_ = (byte == 0) ? uint32_t(~0) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <int byte> uint8_t read() {
|
||||
if constexpr (byte == 3) {
|
||||
return 0xff; // Assumed. Just a guess.
|
||||
}
|
||||
constexpr int shift = byte << 3;
|
||||
|
||||
if constexpr (byte == 2) {
|
||||
latch_ = value_ | 0xff00'0000;
|
||||
}
|
||||
|
||||
const uint32_t source = latch_ ? latch_ : value_;
|
||||
const uint8_t result = uint8_t((source >> shift) & 0xff);
|
||||
|
||||
if constexpr (byte == 0) {
|
||||
latch_ = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool advance(int count) {
|
||||
// The 8250 uses a simple binary counter to replace the
|
||||
// 6526's time-of-day clock. So this is easy.
|
||||
const uint32_t distance_to_alarm = (alarm_ - value_) & 0xff'ffff;
|
||||
const auto increment = uint32_t(count) & increment_mask_;
|
||||
value_ = (value_ + increment) & 0xff'ffff;
|
||||
return distance_to_alarm <= increment;
|
||||
}
|
||||
};
|
||||
|
||||
struct MOS6526Storage {
|
||||
bool cnt_state_ = false; // Inactive by default.
|
||||
bool cnt_edge_ = false;
|
||||
bool flag_state_ = false;
|
||||
HalfCycles half_divider_;
|
||||
|
||||
uint8_t output_[2] = {0, 0};
|
||||
uint8_t data_direction_[2] = {0, 0};
|
||||
|
||||
uint8_t interrupt_control_ = 0;
|
||||
uint8_t interrupt_state_ = 0;
|
||||
|
||||
uint8_t shift_data_ = 0;
|
||||
uint8_t shift_register_ = 0;
|
||||
int shift_bits_ = 0;
|
||||
bool shifter_is_output_ = false;
|
||||
|
||||
struct Counter {
|
||||
uint16_t reload = 0;
|
||||
uint16_t value = 0;
|
||||
uint8_t control = 0;
|
||||
|
||||
template <int shift, bool is_8250> void set_reload(uint8_t v) {
|
||||
reload = (reload & (0xff00 >> shift)) | uint16_t(v << shift);
|
||||
|
||||
if constexpr (shift == 8) {
|
||||
// This seems to be a special 8250 feature per the Amiga
|
||||
// Hardware Reference Manual; cf. Appendix F.
|
||||
if(is_8250) {
|
||||
control |= 1;
|
||||
pending |= ReloadInOne;
|
||||
} else {
|
||||
if(!(control&1)) {
|
||||
pending |= ReloadInOne;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If this write has hit during a reload cycle, reload.
|
||||
if(pending & ReloadNow) {
|
||||
value = reload;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_counter_2> void set_control(uint8_t v) {
|
||||
control = v;
|
||||
|
||||
if(v&2) {
|
||||
printf("UNIMPLEMENTED: PB strobe\n");
|
||||
}
|
||||
}
|
||||
|
||||
template <bool is_counter_2> bool advance(bool chained_input, bool cnt_state, bool cnt_edge) {
|
||||
// TODO: remove most of the conditionals here in favour of bit shuffling.
|
||||
|
||||
pending = (pending & PendingClearMask) << 1;
|
||||
|
||||
//
|
||||
// Apply feeder states inputs: anything that
|
||||
// will take effect in the future.
|
||||
//
|
||||
|
||||
// Schedule a force reload if requested.
|
||||
if(control & 0x10) {
|
||||
pending |= ReloadInOne;
|
||||
control &= ~0x10;
|
||||
}
|
||||
|
||||
// Keep a history of the one-shot bit.
|
||||
if(control & 0x08) {
|
||||
pending |= OneShotInOne;
|
||||
}
|
||||
|
||||
// Determine whether an input clock is applicable.
|
||||
if constexpr(is_counter_2) {
|
||||
switch(control&0x60) {
|
||||
case 0x00: // Count Phi2 pulses.
|
||||
pending |= TestInputNow;
|
||||
break;
|
||||
case 0x20: // Count negative CNTs, with an extra cycle of delay.
|
||||
pending |= cnt_edge ? TestInputInOne : 0;
|
||||
break;
|
||||
case 0x40: // Count timer A reloads.
|
||||
pending |= chained_input ? TestInputNow : 0;
|
||||
break;
|
||||
case 0x60: // Count timer A transitions when CNT is low.
|
||||
pending |= chained_input && cnt_state ? TestInputNow : 0;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if(!(control&0x20)) {
|
||||
pending |= TestInputNow;
|
||||
} else if (cnt_edge) {
|
||||
pending |= TestInputInOne;
|
||||
}
|
||||
}
|
||||
if(pending&TestInputNow && control&1) {
|
||||
pending |= ApplyClockInTwo;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Perform a timer tick and decide whether a reload is prompted.
|
||||
//
|
||||
if(pending & ApplyClockNow) {
|
||||
--value;
|
||||
}
|
||||
|
||||
const bool should_reload = !value && (pending & ApplyClockInOne);
|
||||
|
||||
// Schedule a reload if so ordered.
|
||||
if(should_reload) {
|
||||
pending |= ReloadNow; // Combine this decision with a deferred
|
||||
// input from the force-reoad test above.
|
||||
|
||||
// If this was one-shot, stop.
|
||||
if(pending&(OneShotInOne | OneShotNow)) {
|
||||
control &= ~1;
|
||||
pending &= ~(ApplyClockInOne|ApplyClockInTwo); // Cancel scheduled ticks.
|
||||
}
|
||||
}
|
||||
|
||||
// Reload if scheduled.
|
||||
if(pending & ReloadNow) {
|
||||
value = reload;
|
||||
pending &= ~ApplyClockInOne; // Skip next decrement.
|
||||
}
|
||||
|
||||
|
||||
return should_reload;
|
||||
}
|
||||
|
||||
private:
|
||||
int pending = 0;
|
||||
|
||||
static constexpr int ReloadInOne = 1 << 0;
|
||||
static constexpr int ReloadNow = 1 << 1;
|
||||
|
||||
static constexpr int OneShotInOne = 1 << 2;
|
||||
static constexpr int OneShotNow = 1 << 3;
|
||||
|
||||
static constexpr int ApplyClockInTwo = 1 << 4;
|
||||
static constexpr int ApplyClockInOne = 1 << 5;
|
||||
static constexpr int ApplyClockNow = 1 << 6;
|
||||
|
||||
static constexpr int TestInputInOne = 1 << 7;
|
||||
static constexpr int TestInputNow = 1 << 8;
|
||||
|
||||
static constexpr int PendingClearMask = ~(ReloadNow | OneShotNow | ApplyClockNow);
|
||||
|
||||
bool active_ = false;
|
||||
} counter_[2];
|
||||
|
||||
static constexpr int InterruptInOne = 1 << 0;
|
||||
static constexpr int InterruptNow = 1 << 1;
|
||||
static constexpr int PendingClearMask = ~(InterruptNow);
|
||||
int pending_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* _526Storage_h */
|
||||
@@ -12,18 +12,18 @@
|
||||
|
||||
using namespace MOS::MOS6560;
|
||||
|
||||
AudioGenerator::AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
AudioGenerator::AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
|
||||
void AudioGenerator::set_volume(uint8_t volume) {
|
||||
audio_queue_.defer([this, volume]() {
|
||||
audio_queue_.enqueue([this, volume]() {
|
||||
volume_ = int16_t(volume) * range_multiplier_;
|
||||
});
|
||||
}
|
||||
|
||||
void AudioGenerator::set_control(int channel, uint8_t value) {
|
||||
audio_queue_.defer([this, channel, value]() {
|
||||
audio_queue_.enqueue([this, channel, value]() {
|
||||
control_registers_[channel] = value;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace MOS6560 {
|
||||
// audio state
|
||||
class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
AudioGenerator(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
AudioGenerator(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
void set_volume(uint8_t volume);
|
||||
void set_control(int channel, uint8_t value);
|
||||
@@ -33,7 +33,7 @@ class AudioGenerator: public ::Outputs::Speaker::SampleSource {
|
||||
static constexpr bool get_is_stereo() { return false; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
||||
unsigned int counters_[4] = {2, 1, 0, 0}; // create a slight phase offset for the three channels
|
||||
unsigned int shift_registers_[4] = {0, 0, 0, 0};
|
||||
@@ -433,9 +433,9 @@ template <class BusHandler> class MOS6560 {
|
||||
BusHandler &bus_handler_;
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Concurrency::AsyncTaskQueue<false> audio_queue_;
|
||||
AudioGenerator audio_generator_;
|
||||
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
|
||||
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
|
||||
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
|
||||
@@ -148,7 +148,7 @@ uint8_t ACIA::parity(uint8_t value) {
|
||||
return value ^ (parity_ == Parity::Even);
|
||||
}
|
||||
|
||||
bool ACIA::serial_line_did_produce_bit(Serial::Line *, int bit) {
|
||||
bool ACIA::serial_line_did_produce_bit(Serial::Line<false> *, int bit) {
|
||||
// Shift this bit into the 11-bit input register; this is big enough to hold
|
||||
// the largest transmission symbol.
|
||||
++bits_received_;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
namespace Motorola {
|
||||
namespace ACIA {
|
||||
|
||||
class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
|
||||
class ACIA: public ClockingHint::Source, private Serial::Line<false>::ReadDelegate {
|
||||
public:
|
||||
static constexpr const HalfCycles SameAsTransmit = HalfCycles(0);
|
||||
|
||||
@@ -77,13 +77,13 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
|
||||
void reset();
|
||||
|
||||
// Input lines.
|
||||
Serial::Line receive;
|
||||
Serial::Line clear_to_send;
|
||||
Serial::Line data_carrier_detect;
|
||||
Serial::Line<false> receive;
|
||||
Serial::Line<false> clear_to_send;
|
||||
Serial::Line<false> data_carrier_detect;
|
||||
|
||||
// Output lines.
|
||||
Serial::Line transmit;
|
||||
Serial::Line request_to_send;
|
||||
Serial::Line<false> transmit;
|
||||
Serial::Line<false> request_to_send;
|
||||
|
||||
// ClockingHint::Source.
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
@@ -118,7 +118,7 @@ class ACIA: public ClockingHint::Source, private Serial::Line::ReadDelegate {
|
||||
HalfCycles transmit_clock_rate_;
|
||||
HalfCycles receive_clock_rate_;
|
||||
|
||||
bool serial_line_did_produce_bit(Serial::Line *line, int bit) final;
|
||||
bool serial_line_did_produce_bit(Serial::Line<false> *line, int bit) final;
|
||||
|
||||
bool interrupt_line_ = false;
|
||||
void update_interrupt_line();
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
#ifndef MFP68901_hpp
|
||||
#define MFP68901_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Motorola {
|
||||
namespace MFP68901 {
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,12 +12,34 @@
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include "Implementation/9918Base.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace TI {
|
||||
namespace TMS {
|
||||
namespace TI::TMS {
|
||||
|
||||
enum Personality {
|
||||
TMS9918A, // includes the 9928 and 9929; set TV standard and output device as desired.
|
||||
V9938,
|
||||
V9958,
|
||||
|
||||
// Sega extensions.
|
||||
SMSVDP,
|
||||
SMS2VDP,
|
||||
GGVDP,
|
||||
MDVDP,
|
||||
};
|
||||
|
||||
enum class TVStandard {
|
||||
/*! i.e. 50Hz output at around 312.5 lines/field */
|
||||
PAL,
|
||||
/*! i.e. 60Hz output at around 262.5 lines/field */
|
||||
NTSC
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#include "Implementation/9918Base.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/*!
|
||||
Provides emulation of the TMS9918a, TMS9928 and TMS9929. Likely in the future to be the
|
||||
@@ -30,13 +52,10 @@ namespace TMS {
|
||||
These chips have only one non-on-demand interaction with the outside world: an interrupt line.
|
||||
See get_time_until_interrupt and get_interrupt_line for asynchronous operation options.
|
||||
*/
|
||||
class TMS9918: public Base {
|
||||
template <Personality personality> class TMS9918: private Base<personality> {
|
||||
public:
|
||||
/*!
|
||||
Constructs an instance of the drive controller that behaves according to personality @c p.
|
||||
@param p The type of controller to emulate.
|
||||
*/
|
||||
TMS9918(Personality p);
|
||||
/*! Constructs an instance of the VDP that behaves according to the templated personality. */
|
||||
TMS9918();
|
||||
|
||||
/*! Sets the TV standard for this TMS, if that is hard-coded in hardware. */
|
||||
void set_tv_standard(TVStandard standard);
|
||||
@@ -44,18 +63,24 @@ class TMS9918: public Base {
|
||||
/*! Sets the scan target this TMS will post content to. */
|
||||
void set_scan_target(Outputs::Display::ScanTarget *);
|
||||
|
||||
/// Gets the current scan status.
|
||||
/*! Gets the current scan status. */
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/*! Sets the type of display the CRT will request. */
|
||||
/*! Sets the type of CRT display. */
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/*! Gets the type of display the CRT will request. */
|
||||
/*! Gets the type of CRT display. */
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/*!
|
||||
Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code
|
||||
that the input clock rate is 3579545 Hz, the NTSC colour clock rate.
|
||||
Runs the VDP for the number of cycles indicate; the input clock rate is implicitly assumed.
|
||||
|
||||
For everything except the Mega Drive VDP:
|
||||
* the input clock rate should be 3579545 Hz, the NTSC colour clock rate.
|
||||
|
||||
For the Mega Drive:
|
||||
* the input clock rate should be around 7.6MHz; 15/7ths of the NTSC colour
|
||||
clock rate for NTSC output and 12/7ths of the PAL colour clock rate for PAL output.
|
||||
*/
|
||||
void run_for(const HalfCycles cycles);
|
||||
|
||||
@@ -65,11 +90,11 @@ class TMS9918: public Base {
|
||||
/*! Gets a register value. */
|
||||
uint8_t read(int address);
|
||||
|
||||
/*! Gets the current scan line; provided by the Master System only. */
|
||||
uint8_t get_current_line();
|
||||
/*! Gets the current scan line; provided by the Sega VDPs only. */
|
||||
uint8_t get_current_line() const;
|
||||
|
||||
/*! Gets the current latched horizontal counter; provided by the Master System only. */
|
||||
uint8_t get_latched_horizontal_counter();
|
||||
/*! Gets the current latched horizontal counter; provided by the Sega VDPs only. */
|
||||
uint8_t get_latched_horizontal_counter() const;
|
||||
|
||||
/*! Latches the current horizontal counter. */
|
||||
void latch_horizontal_counter();
|
||||
@@ -81,7 +106,7 @@ class TMS9918: public Base {
|
||||
If get_interrupt_line is true now of if get_interrupt_line would
|
||||
never return true, returns HalfCycles::max().
|
||||
*/
|
||||
HalfCycles get_next_sequence_point();
|
||||
HalfCycles get_next_sequence_point() const;
|
||||
|
||||
/*!
|
||||
Returns the amount of time until the nominated line interrupt position is
|
||||
@@ -96,10 +121,9 @@ class TMS9918: public Base {
|
||||
/*!
|
||||
@returns @c true if the interrupt line is currently active; @c false otherwise.
|
||||
*/
|
||||
bool get_interrupt_line();
|
||||
bool get_interrupt_line() const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TMS9918_hpp */
|
||||
|
||||
1347
Components/9918/Implementation/9918.cpp
Normal file
1347
Components/9918/Implementation/9918.cpp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
112
Components/9918/Implementation/AccessEnums.hpp
Normal file
112
Components/9918/Implementation/AccessEnums.hpp
Normal file
@@ -0,0 +1,112 @@
|
||||
//
|
||||
// AccessEnums.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef AccessEnums_hpp
|
||||
#define AccessEnums_hpp
|
||||
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// The screen mode is a necessary predecessor to picking the line mode,
|
||||
// which is the thing latched per line.
|
||||
enum class ScreenMode {
|
||||
// Original TMS modes.
|
||||
Blank,
|
||||
Text,
|
||||
MultiColour,
|
||||
ColouredText,
|
||||
Graphics,
|
||||
|
||||
// 8-bit Sega modes.
|
||||
SMSMode4,
|
||||
|
||||
// New Yamaha V9938 modes.
|
||||
YamahaText80,
|
||||
YamahaGraphics3,
|
||||
YamahaGraphics4,
|
||||
YamahaGraphics5,
|
||||
YamahaGraphics6,
|
||||
YamahaGraphics7,
|
||||
|
||||
// Rebranded Yamaha V9938 modes.
|
||||
YamahaGraphics1 = ColouredText,
|
||||
YamahaGraphics2 = Graphics,
|
||||
};
|
||||
|
||||
constexpr int pixels_per_byte(ScreenMode mode) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case ScreenMode::Blank: return 1;
|
||||
case ScreenMode::Text: return 6;
|
||||
case ScreenMode::MultiColour: return 2;
|
||||
case ScreenMode::ColouredText: return 8;
|
||||
case ScreenMode::Graphics: return 8;
|
||||
case ScreenMode::SMSMode4: return 2;
|
||||
case ScreenMode::YamahaText80: return 6;
|
||||
case ScreenMode::YamahaGraphics3: return 8;
|
||||
case ScreenMode::YamahaGraphics4: return 2;
|
||||
case ScreenMode::YamahaGraphics5: return 4;
|
||||
case ScreenMode::YamahaGraphics6: return 2;
|
||||
case ScreenMode::YamahaGraphics7: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int width(ScreenMode mode) {
|
||||
switch(mode) {
|
||||
default:
|
||||
case ScreenMode::Blank: return 0;
|
||||
case ScreenMode::Text: return 240;
|
||||
case ScreenMode::MultiColour: return 256;
|
||||
case ScreenMode::ColouredText: return 256;
|
||||
case ScreenMode::Graphics: return 256;
|
||||
case ScreenMode::SMSMode4: return 256;
|
||||
case ScreenMode::YamahaText80: return 480;
|
||||
case ScreenMode::YamahaGraphics3: return 256;
|
||||
case ScreenMode::YamahaGraphics4: return 256;
|
||||
case ScreenMode::YamahaGraphics5: return 512;
|
||||
case ScreenMode::YamahaGraphics6: return 512;
|
||||
case ScreenMode::YamahaGraphics7: return 256;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr bool interleaves_banks(ScreenMode mode) {
|
||||
return mode == ScreenMode::YamahaGraphics6 || mode == ScreenMode::YamahaGraphics7;
|
||||
}
|
||||
|
||||
|
||||
enum class FetchMode {
|
||||
Text,
|
||||
Character,
|
||||
Refresh,
|
||||
SMS,
|
||||
Yamaha,
|
||||
};
|
||||
|
||||
enum class MemoryAccess {
|
||||
Read, Write, None
|
||||
};
|
||||
|
||||
enum class VerticalState {
|
||||
/// Describes any line on which pixels do not appear and no fetching occurs, including
|
||||
/// the border, blanking and sync.
|
||||
Blank,
|
||||
/// A line on which pixels do not appear but fetching occurs.
|
||||
Prefetch,
|
||||
/// A line on which pixels appear and fetching occurs.
|
||||
Pixels,
|
||||
};
|
||||
|
||||
enum class SpriteMode {
|
||||
Mode1,
|
||||
Mode2,
|
||||
MasterSystem,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* AccessEnums_hpp */
|
||||
197
Components/9918/Implementation/ClockConverter.hpp
Normal file
197
Components/9918/Implementation/ClockConverter.hpp
Normal file
@@ -0,0 +1,197 @@
|
||||
//
|
||||
// ClockConverter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ClockConverter_hpp
|
||||
#define ClockConverter_hpp
|
||||
|
||||
#include "../9918.hpp"
|
||||
#include "PersonalityTraits.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
enum class Clock {
|
||||
Internal,
|
||||
TMSPixel,
|
||||
TMSMemoryWindow,
|
||||
CRT
|
||||
};
|
||||
|
||||
template <Personality personality, Clock clk> constexpr int clock_rate() {
|
||||
static_assert(
|
||||
is_classic_vdp(personality) ||
|
||||
is_yamaha_vdp(personality) ||
|
||||
(personality == Personality::MDVDP)
|
||||
);
|
||||
|
||||
switch(clk) {
|
||||
case Clock::TMSPixel: return 342;
|
||||
case Clock::TMSMemoryWindow: return 171;
|
||||
case Clock::CRT: return 1368;
|
||||
case Clock::Internal:
|
||||
if constexpr (is_classic_vdp(personality)) {
|
||||
return 342;
|
||||
} else if constexpr (is_yamaha_vdp(personality)) {
|
||||
return 1368;
|
||||
} else if constexpr (personality == Personality::MDVDP) {
|
||||
return 3420;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <Personality personality, Clock clock> constexpr int to_internal(int length) {
|
||||
return length * clock_rate<personality, Clock::Internal>() / clock_rate<personality, clock>();
|
||||
}
|
||||
|
||||
template <Personality personality, Clock clock> constexpr int from_internal(int length) {
|
||||
return length * clock_rate<personality, clock>() / clock_rate<personality, Clock::Internal>();
|
||||
}
|
||||
|
||||
/// Provides default timing measurements that duplicate the layout of a TMS9928's line,
|
||||
/// scaled to the clock rate specified.
|
||||
template <Personality personality> struct StandardTiming {
|
||||
/// The total number of internal cycles per line of output.
|
||||
constexpr static int CyclesPerLine = clock_rate<personality, Clock::Internal>();
|
||||
|
||||
/// The number of internal cycles that must elapse between a request to read or write and
|
||||
/// it becoming a candidate for action.
|
||||
constexpr static int VRAMAccessDelay = 6;
|
||||
};
|
||||
|
||||
/// Provides concrete, specific timing for the nominated personality.
|
||||
template <Personality personality> struct Timing: public StandardTiming<personality> {};
|
||||
|
||||
/*!
|
||||
Provides a [potentially-]stateful conversion between the external and internal clocks.
|
||||
Unlike the other clock conversions, this one may be non-integral, requiring that
|
||||
an error term be tracked.
|
||||
*/
|
||||
template <Personality personality> class ClockConverter {
|
||||
public:
|
||||
/*!
|
||||
Given that another @c source external **half-cycles** has occurred,
|
||||
indicates how many complete internal **cycles** have additionally elapsed
|
||||
since the last call to @c to_internal.
|
||||
|
||||
E.g. for the TMS, @c source will count 456 ticks per line, and the internal clock
|
||||
runs at 342 ticks per line, so the proper conversion is to multiply by 3/4.
|
||||
*/
|
||||
int to_internal(int source) {
|
||||
switch(personality) {
|
||||
// Default behaviour is to apply a multiplication by 3/4;
|
||||
// this is correct for the TMS and Sega VDPs other than the Mega Drive.
|
||||
default: {
|
||||
const int result = source * 3 + cycles_error_;
|
||||
cycles_error_ = result & 3;
|
||||
return result >> 2;
|
||||
}
|
||||
|
||||
// The two Yamaha chips have an internal clock that is four times
|
||||
// as fast as the TMS, therefore a stateless translation is possible.
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return source * 3;
|
||||
|
||||
// The Mega Drive runs at 3420 master clocks per line, which is then
|
||||
// divided by 4 or 5 depending on other state. That's 7 times the
|
||||
// rate provided to the CPU; given that the input is in half-cycles
|
||||
// the proper multiplier is therefore 3.5.
|
||||
case Personality::MDVDP: {
|
||||
const int result = source * 7 + cycles_error_;
|
||||
cycles_error_ = result & 1;
|
||||
return result >> 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Provides the number of external cycles that need to begin from now in order to
|
||||
get at least @c internal_cycles into the future.
|
||||
*/
|
||||
HalfCycles half_cycles_before_internal_cycles(int internal_cycles) const {
|
||||
// Logic here correlates with multipliers as per @c to_internal.
|
||||
switch(personality) {
|
||||
default:
|
||||
// Relative to the external clock multiplied by 3, it will definitely take this
|
||||
// many cycles to complete a further (internal_cycles - 1) after the current one.
|
||||
internal_cycles = (internal_cycles - 1) << 2;
|
||||
|
||||
// It will also be necessary to complete the current one.
|
||||
internal_cycles += 4 - cycles_error_;
|
||||
|
||||
// Round up to get the first external cycle after
|
||||
// the number of internal_cycles has elapsed.
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
|
||||
case Personality::V9938:
|
||||
case Personality::V9958:
|
||||
return HalfCycles((internal_cycles + 2) / 3);
|
||||
|
||||
case Personality::MDVDP:
|
||||
internal_cycles = (internal_cycles - 1) << 1;
|
||||
internal_cycles += 2 - cycles_error_;
|
||||
return HalfCycles((internal_cycles + 6) / 7);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Holds current residue in conversion from the external to
|
||||
// internal clock.
|
||||
int cycles_error_ = 0;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
template <Personality personality, typename Enable = void> struct LineLayout;
|
||||
|
||||
// Line layout is:
|
||||
//
|
||||
// [0, EndOfSync] sync
|
||||
// (EndOfSync, StartOfColourBurst] blank
|
||||
// (StartOfColourBurst, EndOfColourBurst] colour burst
|
||||
// (EndOfColourBurst, EndOfLeftErase] blank
|
||||
// (EndOfLeftErase, EndOfLeftBorder] border colour
|
||||
// (EndOfLeftBorder, EndOfPixels] pixel content
|
||||
// (EndOfPixels, EndOfRightBorder] border colour
|
||||
// [EndOfRightBorder, <end of line>] blank
|
||||
//
|
||||
// ... with minor caveats:
|
||||
// * horizontal adjust on the Yamaha VDPs is applied to EndOfLeftBorder and EndOfPixels;
|
||||
// * the Sega VDPs may programatically extend the left border; and
|
||||
// * text mode on all VDPs adjusts border width.
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_classic_vdp(personality)>> {
|
||||
constexpr static int EndOfSync = 26;
|
||||
constexpr static int StartOfColourBurst = 29;
|
||||
constexpr static int EndOfColourBurst = 43;
|
||||
constexpr static int EndOfLeftErase = 50;
|
||||
constexpr static int EndOfLeftBorder = 63;
|
||||
constexpr static int EndOfPixels = 319;
|
||||
constexpr static int EndOfRightBorder = 334;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 69;
|
||||
constexpr static int TextModeEndOfPixels = 309;
|
||||
};
|
||||
|
||||
template <Personality personality> struct LineLayout<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||
constexpr static int EndOfSync = 100;
|
||||
constexpr static int StartOfColourBurst = 113;
|
||||
constexpr static int EndOfColourBurst = 167;
|
||||
constexpr static int EndOfLeftErase = 202;
|
||||
constexpr static int EndOfLeftBorder = 258;
|
||||
constexpr static int EndOfPixels = 1282;
|
||||
constexpr static int EndOfRightBorder = 1341;
|
||||
|
||||
constexpr static int TextModeEndOfLeftBorder = 294;
|
||||
constexpr static int TextModeEndOfPixels = 1254;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ClockConverter_hpp */
|
||||
568
Components/9918/Implementation/Draw.hpp
Normal file
568
Components/9918/Implementation/Draw.hpp
Normal file
@@ -0,0 +1,568 @@
|
||||
//
|
||||
// Draw.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Draw_hpp
|
||||
#define Draw_hpp
|
||||
|
||||
// MARK: - Sprites, as generalised.
|
||||
|
||||
template <Personality personality>
|
||||
template <SpriteMode mode, bool double_width>
|
||||
void Base<personality>::draw_sprites(uint8_t y, int start, int end, const std::array<uint32_t, 16> &palette, int *colour_buffer) {
|
||||
if(!draw_line_buffer_->sprites) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &buffer = *draw_line_buffer_->sprites;
|
||||
if(!buffer.active_sprite_slot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int shift_advance = sprites_magnified_ ? 1 : 2;
|
||||
|
||||
// If this is the start of the line clip any part of any sprites that is off to the left.
|
||||
if(!start) {
|
||||
for(int index = 0; index < buffer.active_sprite_slot; ++index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.x < 0) sprite.shift_position -= shift_advance * sprite.x;
|
||||
}
|
||||
}
|
||||
|
||||
int sprite_buffer[256];
|
||||
int sprite_collision = 0;
|
||||
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
|
||||
|
||||
if constexpr (mode == SpriteMode::MasterSystem) {
|
||||
// Draw all sprites into the sprite buffer.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= 0; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.shift_position >= 16) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int pixel_start = std::max(start, sprite.x);
|
||||
|
||||
// TODO: it feels like the work below should be simplifiable;
|
||||
// the double shift in particular, and hopefully the variable shift.
|
||||
for(int c = pixel_start; c < end && sprite.shift_position < 16; ++c) {
|
||||
const int shift = (sprite.shift_position >> 1);
|
||||
const int sprite_colour =
|
||||
(((sprite.image[3] << shift) & 0x80) >> 4) |
|
||||
(((sprite.image[2] << shift) & 0x80) >> 5) |
|
||||
(((sprite.image[1] << shift) & 0x80) >> 6) |
|
||||
(((sprite.image[0] << shift) & 0x80) >> 7);
|
||||
|
||||
if(sprite_colour) {
|
||||
sprite_collision |= sprite_buffer[c];
|
||||
sprite_buffer[c] = sprite_colour | 0x10;
|
||||
}
|
||||
|
||||
sprite.shift_position += shift_advance;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the sprite buffer onto the colour buffer, wherever the tile map doesn't have
|
||||
// priority (or is transparent).
|
||||
for(int c = start; c < end; ++c) {
|
||||
if(
|
||||
sprite_buffer[c] &&
|
||||
(!(colour_buffer[c]&0x20) || !(colour_buffer[c]&0xf))
|
||||
) colour_buffer[c] = sprite_buffer[c];
|
||||
}
|
||||
|
||||
if(sprite_collision) {
|
||||
status_ |= StatusSpriteCollision;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (SpriteBuffer::test_is_filling) {
|
||||
assert(!buffer.is_filling);
|
||||
}
|
||||
|
||||
constexpr uint32_t sprite_colour_selection_masks[2] = {0x00000000, 0xffffffff};
|
||||
constexpr int colour_masks[16] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
|
||||
const int sprite_width = sprites_16x16_ ? 16 : 8;
|
||||
const int shifter_target = sprite_width << 1;
|
||||
const int pixel_width = sprites_magnified_ ? sprite_width << 1 : sprite_width;
|
||||
int min_sprite = 0;
|
||||
|
||||
//
|
||||
// Approach taken for Mode 2 sprites:
|
||||
//
|
||||
// (1) precompute full sprite images, at up to 32 pixels wide;
|
||||
// (2) for each sprite that is marked as CC, walk backwards until the
|
||||
// first sprite that is not marked CC, ORing it into the precomputed
|
||||
// image at each step;
|
||||
// (3) subsequently, just draw each sprite image independently.
|
||||
//
|
||||
if constexpr (mode == SpriteMode::Mode2) {
|
||||
// Determine the lowest visible sprite; exit early if that leaves no sprites visible.
|
||||
for(; min_sprite < buffer.active_sprite_slot; min_sprite++) {
|
||||
auto &sprite = buffer.active_sprites[min_sprite];
|
||||
if(sprite.opaque()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(min_sprite == buffer.active_sprite_slot) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!start) {
|
||||
// Pre-rasterise the sprites one-by-one.
|
||||
if(sprites_magnified_) {
|
||||
for(int index = min_sprite; index < buffer.active_sprite_slot; index++) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
for(int c = 0; c < 32; c+= 2) {
|
||||
const int shift = (c >> 1) ^ 7;
|
||||
const int bit = 1 & (sprite.image[shift >> 3] >> (shift & 7));
|
||||
|
||||
Storage<personality>::sprite_cache_[index][c] =
|
||||
Storage<personality>::sprite_cache_[index][c + 1] =
|
||||
(sprite.image[2] & 0xf & sprite_colour_selection_masks[bit]) |
|
||||
uint8_t((bit << StatusSpriteCollisionShift) & sprite.collision_bit());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(int index = min_sprite; index < buffer.active_sprite_slot; index++) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
for(int c = 0; c < 16; c++) {
|
||||
const int shift = c ^ 7;
|
||||
const int bit = 1 & (sprite.image[shift >> 3] >> (shift & 7));
|
||||
|
||||
Storage<personality>::sprite_cache_[index][c] =
|
||||
(sprite.image[2] & 0xf & sprite_colour_selection_masks[bit]) |
|
||||
uint8_t((bit << StatusSpriteCollisionShift) & sprite.collision_bit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Go backwards compositing any sprites that are set as OR masks onto their parents.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite + 1; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.opaque()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Sprite may affect all previous up to and cindlugin the next one that is opaque.
|
||||
for(int previous_index = index - 1; previous_index >= min_sprite; --previous_index) {
|
||||
// Determine region of overlap (if any).
|
||||
auto &previous = buffer.active_sprites[previous_index];
|
||||
const int origin = sprite.x - previous.x;
|
||||
const int x1 = std::max(0, -origin);
|
||||
const int x2 = std::min(pixel_width - origin, pixel_width);
|
||||
|
||||
// Composite sprites.
|
||||
for(int x = x1; x < x2; x++) {
|
||||
Storage<personality>::sprite_cache_[previous_index][x + origin]
|
||||
|= Storage<personality>::sprite_cache_[index][x];
|
||||
}
|
||||
|
||||
// If a previous opaque sprite has been found, stop.
|
||||
if(previous.opaque()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw.
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
const int x1 = std::max(0, start - sprite.x);
|
||||
const int x2 = std::min(end - sprite.x, pixel_width);
|
||||
|
||||
for(int x = x1; x < x2; x++) {
|
||||
const uint8_t colour = Storage<personality>::sprite_cache_[index][x];
|
||||
|
||||
// Plot colour, if visible.
|
||||
if(colour) {
|
||||
pixel_origin_[sprite.x + x] = palette[colour & 0xf];
|
||||
}
|
||||
|
||||
// TODO: is collision location recorded in mode 1?
|
||||
|
||||
// Check for a new collision.
|
||||
if(!(status_ & StatusSpriteCollision)) {
|
||||
sprite_collision |= sprite_buffer[sprite.x + x];
|
||||
sprite_buffer[sprite.x + x] |= colour;
|
||||
status_ |= sprite_collision & StatusSpriteCollision;
|
||||
|
||||
if(status_ & StatusSpriteCollision) {
|
||||
Storage<personality>::collision_location_[0] = uint16_t(x);
|
||||
Storage<personality>::collision_location_[1] = uint16_t(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if constexpr (mode == SpriteMode::Mode1) {
|
||||
for(int index = buffer.active_sprite_slot - 1; index >= min_sprite; --index) {
|
||||
auto &sprite = buffer.active_sprites[index];
|
||||
if(sprite.shift_position >= shifter_target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int pixel_start = std::max(start, sprite.x);
|
||||
for(int c = pixel_start; c < end && sprite.shift_position < shifter_target; ++c) {
|
||||
const int shift = (sprite.shift_position >> 1) ^ 7;
|
||||
int sprite_colour = (sprite.image[shift >> 3] >> (shift & 7)) & 1;
|
||||
|
||||
// A colision is detected regardless of sprite colour ...
|
||||
sprite_collision |= sprite_buffer[c] & sprite_colour;
|
||||
sprite_buffer[c] |= sprite_colour;
|
||||
|
||||
// ... but a sprite with the transparent colour won't actually be visible.
|
||||
sprite_colour &= colour_masks[sprite.image[2] & 0xf];
|
||||
|
||||
pixel_origin_[c] =
|
||||
(pixel_origin_[c] & sprite_colour_selection_masks[sprite_colour^1]) |
|
||||
(palette[sprite.image[2] & 0xf] & sprite_colour_selection_masks[sprite_colour]);
|
||||
|
||||
sprite.shift_position += shift_advance;
|
||||
}
|
||||
}
|
||||
|
||||
status_ |= sprite_collision << StatusSpriteCollisionShift;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Mode 2 logic, as I currently understand it, as a note for my future self:
|
||||
//
|
||||
// If a sprite is marked as 'CC' then it doesn't collide, but its colour value is
|
||||
// ORd with those of all lower-numbered sprites down to the next one that is visible on
|
||||
// that line and not marked CC.
|
||||
//
|
||||
// If no previous sprite meets that criteria, no pixels are displayed. But if one does
|
||||
// then pixels are displayed even where they don't overlap with the earlier sprites.
|
||||
//
|
||||
// ... so in terms of my loop above, I guess I need temporary storage to accumulate
|
||||
// an OR mask up until I hit a non-CC sprite, at which point I composite everything out?
|
||||
// I'm not immediately sure whether I can appropriately reuse sprite_buffer, but possibly?
|
||||
|
||||
// MARK: - TMS9918
|
||||
|
||||
template <Personality personality>
|
||||
template <SpriteMode sprite_mode>
|
||||
void Base<personality>::draw_tms_character(int start, int end) {
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
// Paint the background tiles.
|
||||
const int pixels_left = end - start;
|
||||
if(this->screen_mode_ == ScreenMode::MultiColour) {
|
||||
for(int c = start; c < end; ++c) {
|
||||
pixel_target_[c] = palette()[
|
||||
(line_buffer.tiles.patterns[c >> 3][0] >> (((c & 4)^4))) & 15
|
||||
];
|
||||
}
|
||||
} else {
|
||||
const int shift = start & 7;
|
||||
int byte_column = start >> 3;
|
||||
|
||||
int length = std::min(pixels_left, 8 - shift);
|
||||
|
||||
int pattern = Numeric::bit_reverse(line_buffer.tiles.patterns[byte_column][0]) >> shift;
|
||||
uint8_t colour = line_buffer.tiles.patterns[byte_column][1];
|
||||
uint32_t colours[2] = {
|
||||
palette()[(colour & 15) ? (colour & 15) : background_colour_],
|
||||
palette()[(colour >> 4) ? (colour >> 4) : background_colour_]
|
||||
};
|
||||
|
||||
int background_pixels_left = pixels_left;
|
||||
while(true) {
|
||||
background_pixels_left -= length;
|
||||
for(int c = 0; c < length; ++c) {
|
||||
pixel_target_[c] = colours[pattern&0x01];
|
||||
pattern >>= 1;
|
||||
}
|
||||
pixel_target_ += length;
|
||||
|
||||
if(!background_pixels_left) break;
|
||||
length = std::min(8, background_pixels_left);
|
||||
byte_column++;
|
||||
|
||||
pattern = Numeric::bit_reverse(line_buffer.tiles.patterns[byte_column][0]);
|
||||
colour = line_buffer.tiles.patterns[byte_column][1];
|
||||
colours[0] = palette()[(colour & 15) ? (colour & 15) : background_colour_];
|
||||
colours[1] = palette()[(colour >> 4) ? (colour >> 4) : background_colour_];
|
||||
}
|
||||
}
|
||||
|
||||
draw_sprites<sprite_mode, false>(0, start, end, palette()); // TODO: propagate a real 'y' into here.
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template <bool apply_blink>
|
||||
void Base<personality>::draw_tms_text(int start, int end) {
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
uint32_t colours[2][2] = {
|
||||
{palette()[background_colour_], palette()[text_colour_]},
|
||||
{0, 0}
|
||||
};
|
||||
if constexpr (apply_blink) {
|
||||
colours[1][0] = palette()[Storage<personality>::blink_background_colour_];
|
||||
colours[1][1] = palette()[Storage<personality>::blink_text_colour_];
|
||||
}
|
||||
|
||||
const int shift = start % 6;
|
||||
int byte_column = start / 6;
|
||||
int pattern = Numeric::bit_reverse(line_buffer.characters.shapes[byte_column]) >> shift;
|
||||
int pixels_left = end - start;
|
||||
int length = std::min(pixels_left, 6 - shift);
|
||||
int flag = 0;
|
||||
if constexpr (apply_blink) {
|
||||
flag = (line_buffer.characters.flags[byte_column >> 3] >> ((byte_column & 7) ^ 7)) & Storage<personality>::in_blink_;
|
||||
}
|
||||
while(true) {
|
||||
pixels_left -= length;
|
||||
for(int c = 0; c < length; ++c) {
|
||||
pixel_target_[c] = colours[flag][(pattern&0x01)];
|
||||
pattern >>= 1;
|
||||
}
|
||||
pixel_target_ += length;
|
||||
|
||||
if(!pixels_left) break;
|
||||
length = std::min(6, pixels_left);
|
||||
byte_column++;
|
||||
pattern = Numeric::bit_reverse(line_buffer.characters.shapes[byte_column]);
|
||||
if constexpr (apply_blink) {
|
||||
flag = (line_buffer.characters.flags[byte_column >> 3] >> ((byte_column & 7) ^ 7)) & Storage<personality>::in_blink_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Master System
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::draw_sms(int start, int end, uint32_t cram_dot) {
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
int colour_buffer[256];
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
/*
|
||||
Add extra border for any pixels that fall before the fine scroll.
|
||||
*/
|
||||
int tile_start = start, tile_end = end;
|
||||
int tile_offset = start;
|
||||
if(output_pointer_.row >= 16 || !Storage<personality>::horizontal_scroll_lock_) {
|
||||
for(int c = start; c < (line_buffer.latched_horizontal_scroll & 7); ++c) {
|
||||
colour_buffer[c] = 16 + background_colour_;
|
||||
++tile_offset;
|
||||
}
|
||||
|
||||
// Remove the border area from that to which tiles will be drawn.
|
||||
tile_start = std::max(start - (line_buffer.latched_horizontal_scroll & 7), 0);
|
||||
tile_end = std::max(end - (line_buffer.latched_horizontal_scroll & 7), 0);
|
||||
}
|
||||
|
||||
|
||||
uint32_t pattern;
|
||||
uint8_t *const pattern_index = reinterpret_cast<uint8_t *>(&pattern);
|
||||
|
||||
/*
|
||||
Add background tiles; these will fill the colour_buffer with values in which
|
||||
the low five bits are a palette index, and bit six is set if this tile has
|
||||
priority over sprites.
|
||||
*/
|
||||
if(tile_start < end) {
|
||||
const int shift = tile_start & 7;
|
||||
int byte_column = tile_start >> 3;
|
||||
int pixels_left = tile_end - tile_start;
|
||||
int length = std::min(pixels_left, 8 - shift);
|
||||
|
||||
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.tiles.patterns[byte_column]);
|
||||
if(line_buffer.tiles.flags[byte_column]&2)
|
||||
pattern >>= shift;
|
||||
else
|
||||
pattern <<= shift;
|
||||
|
||||
while(true) {
|
||||
const int palette_offset = (line_buffer.tiles.flags[byte_column]&0x18) << 1;
|
||||
if(line_buffer.tiles.flags[byte_column]&2) {
|
||||
for(int c = 0; c < length; ++c) {
|
||||
colour_buffer[tile_offset] =
|
||||
((pattern_index[3] & 0x01) << 3) |
|
||||
((pattern_index[2] & 0x01) << 2) |
|
||||
((pattern_index[1] & 0x01) << 1) |
|
||||
((pattern_index[0] & 0x01) << 0) |
|
||||
palette_offset;
|
||||
++tile_offset;
|
||||
pattern >>= 1;
|
||||
}
|
||||
} else {
|
||||
for(int c = 0; c < length; ++c) {
|
||||
colour_buffer[tile_offset] =
|
||||
((pattern_index[3] & 0x80) >> 4) |
|
||||
((pattern_index[2] & 0x80) >> 5) |
|
||||
((pattern_index[1] & 0x80) >> 6) |
|
||||
((pattern_index[0] & 0x80) >> 7) |
|
||||
palette_offset;
|
||||
++tile_offset;
|
||||
pattern <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
pixels_left -= length;
|
||||
if(!pixels_left) break;
|
||||
|
||||
length = std::min(8, pixels_left);
|
||||
byte_column++;
|
||||
pattern = *reinterpret_cast<const uint32_t *>(line_buffer.tiles.patterns[byte_column]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Apply sprites (if any).
|
||||
*/
|
||||
draw_sprites<SpriteMode::MasterSystem, false>(0, start, end, palette(), colour_buffer); // TODO provide good y, as per elsewhere.
|
||||
|
||||
// Map from the 32-colour buffer to real output pixels, applying the specific CRAM dot if any.
|
||||
pixel_target_[start] = Storage<personality>::colour_ram_[colour_buffer[start] & 0x1f] | cram_dot;
|
||||
for(int c = start+1; c < end; ++c) {
|
||||
pixel_target_[c] = Storage<personality>::colour_ram_[colour_buffer[c] & 0x1f];
|
||||
}
|
||||
|
||||
// If the VDP is set to hide the left column and this is the final call that'll come
|
||||
// this line, hide it.
|
||||
if(end == 256) {
|
||||
if(Storage<personality>::hide_left_column_) {
|
||||
pixel_origin_[0] = pixel_origin_[1] = pixel_origin_[2] = pixel_origin_[3] =
|
||||
pixel_origin_[4] = pixel_origin_[5] = pixel_origin_[6] = pixel_origin_[7] =
|
||||
Storage<personality>::colour_ram_[16 + background_colour_];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Yamaha
|
||||
|
||||
template <Personality personality>
|
||||
template <ScreenMode mode>
|
||||
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
const auto active_palette = palette();
|
||||
const int sprite_start = start >> 2;
|
||||
const int sprite_end = end >> 2;
|
||||
auto &line_buffer = *draw_line_buffer_;
|
||||
|
||||
// Observation justifying Duff's device below: it's acceptable to paint too many pixels — to paint
|
||||
// beyond `end` — provided that the overpainting is within normal bitmap bounds, because any
|
||||
// mispainted pixels will be replaced before becoming visible to the user.
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics4 || mode == ScreenMode::YamahaGraphics6) {
|
||||
start >>= (mode == ScreenMode::YamahaGraphics4) ? 2 : 1;
|
||||
end >>= (mode == ScreenMode::YamahaGraphics4) ? 2 : 1;
|
||||
|
||||
int column = start & ~1;
|
||||
const int offset = start & 1;
|
||||
start >>= 1;
|
||||
end = (end + 1) >> 1;
|
||||
|
||||
switch(offset) {
|
||||
case 0:
|
||||
do {
|
||||
pixel_target_[column+0] = active_palette[line_buffer.bitmap[start] >> 4];
|
||||
case 1: pixel_target_[column+1] = active_palette[line_buffer.bitmap[start] & 0xf];
|
||||
++start;
|
||||
column += 2;
|
||||
} while(start < end);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics5) {
|
||||
start >>= 1;
|
||||
end >>= 1;
|
||||
|
||||
int column = start & ~3;
|
||||
const int offset = start & 3;
|
||||
start >>= 2;
|
||||
end = (end + 3) >> 2;
|
||||
|
||||
switch(offset) {
|
||||
case 0:
|
||||
do {
|
||||
pixel_target_[column+0] = active_palette[line_buffer.bitmap[start] >> 6];
|
||||
case 1: pixel_target_[column+1] = active_palette[(line_buffer.bitmap[start] >> 4) & 3];
|
||||
case 2: pixel_target_[column+2] = active_palette[(line_buffer.bitmap[start] >> 2) & 3];
|
||||
case 3: pixel_target_[column+3] = active_palette[line_buffer.bitmap[start] & 3];
|
||||
++start;
|
||||
column += 4;
|
||||
} while(start < end);
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (mode == ScreenMode::YamahaGraphics7) {
|
||||
start >>= 2;
|
||||
end >>= 2;
|
||||
|
||||
while(start < end) {
|
||||
pixel_target_[start] =
|
||||
palette_pack(
|
||||
uint8_t((line_buffer.bitmap[start] & 0x1c) + ((line_buffer.bitmap[start] & 0x1c) << 3) + ((line_buffer.bitmap[start] & 0x1c) >> 3)),
|
||||
uint8_t((line_buffer.bitmap[start] & 0xe0) + ((line_buffer.bitmap[start] & 0xe0) >> 3) + ((line_buffer.bitmap[start] & 0xe0) >> 6)),
|
||||
uint8_t((line_buffer.bitmap[start] & 0x03) + ((line_buffer.bitmap[start] & 0x03) << 2) + ((line_buffer.bitmap[start] & 0x03) << 4) + ((line_buffer.bitmap[start] & 0x03) << 6))
|
||||
);
|
||||
++start;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<uint32_t, 16> graphics7_sprite_palette = {
|
||||
palette_pack(0b00000000, 0b00000000, 0b00000000), palette_pack(0b00000000, 0b00000000, 0b01001001),
|
||||
palette_pack(0b00000000, 0b01101101, 0b00000000), palette_pack(0b00000000, 0b01101101, 0b01001001),
|
||||
palette_pack(0b01101101, 0b00000000, 0b00000000), palette_pack(0b01101101, 0b00000000, 0b01001001),
|
||||
palette_pack(0b01101101, 0b01101101, 0b00000000), palette_pack(0b01101101, 0b01101101, 0b01001001),
|
||||
|
||||
palette_pack(0b10010010, 0b11111111, 0b01001001), palette_pack(0b00000000, 0b00000000, 0b11111111),
|
||||
palette_pack(0b00000000, 0b11111111, 0b00000000), palette_pack(0b00000000, 0b11111111, 0b11111111),
|
||||
palette_pack(0b11111111, 0b00000000, 0b00000000), palette_pack(0b11111111, 0b00000000, 0b11111111),
|
||||
palette_pack(0b11111111, 0b11111111, 0b00000000), palette_pack(0b11111111, 0b11111111, 0b11111111),
|
||||
};
|
||||
|
||||
// Possibly TODO: is the data-sheet trying to allege some sort of colour mixing for sprites in Mode 6?
|
||||
draw_sprites<
|
||||
SpriteMode::Mode2,
|
||||
mode == ScreenMode::YamahaGraphics5 || mode == ScreenMode::YamahaGraphics6
|
||||
>(y, sprite_start, sprite_end, mode == ScreenMode::YamahaGraphics7 ? graphics7_sprite_palette : palette());
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
void Base<personality>::draw_yamaha(uint8_t y, int start, int end) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
switch(draw_line_buffer_->screen_mode) {
|
||||
// Modes that are the same (or close enough) to those on the TMS.
|
||||
case ScreenMode::Text: draw_tms_text<false>(start >> 2, end >> 2); break;
|
||||
case ScreenMode::YamahaText80: draw_tms_text<true>(start >> 1, end >> 1); break;
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
case ScreenMode::Graphics: draw_tms_character(start >> 2, end >> 2); break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3:
|
||||
draw_tms_character<SpriteMode::Mode2>(start >> 2, end >> 2);
|
||||
break;
|
||||
|
||||
#define Dispatch(x) case ScreenMode::x: draw_yamaha<ScreenMode::x>(y, start, end); break;
|
||||
Dispatch(YamahaGraphics4);
|
||||
Dispatch(YamahaGraphics5);
|
||||
Dispatch(YamahaGraphics6);
|
||||
Dispatch(YamahaGraphics7);
|
||||
#undef Dispatch
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mega Drive
|
||||
|
||||
// TODO.
|
||||
|
||||
#endif /* Draw_hpp */
|
||||
805
Components/9918/Implementation/Fetch.hpp
Normal file
805
Components/9918/Implementation/Fetch.hpp
Normal file
@@ -0,0 +1,805 @@
|
||||
//
|
||||
// Fetch.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Fetch_hpp
|
||||
#define Fetch_hpp
|
||||
|
||||
/*
|
||||
Fetching routines follow below; they obey the following rules:
|
||||
|
||||
1) input is a start position and an end position; they should perform the proper
|
||||
operations for the period: start <= time < end.
|
||||
2) times are measured relative to a 172-cycles-per-line clock (so: they directly
|
||||
count access windows on the TMS and Master System).
|
||||
3) within each sequencer, time 0 is the access window that straddles the beginning of
|
||||
horizontal sync. Which, conveniently, is the place to which Grauw's timing diagrams
|
||||
are aligned.
|
||||
4) all of these functions are templated with a `use_end` parameter. That will be true if
|
||||
end is < 172, false otherwise. So functions can use it to eliminate should-exit-not checks,
|
||||
for the more usual path of execution.
|
||||
|
||||
[Historically:
|
||||
position 0 was the beginning of the access window immediately after the last pattern/data
|
||||
block fetch that would contribute to this line, in a normal 32-column mode. So:
|
||||
|
||||
* it's cycle 309 on Mattias' TMS diagram;
|
||||
* it's cycle 1238 on his V9938 diagram;
|
||||
* it's after the last background render block in Mask of Destiny's Master System timing diagram.
|
||||
|
||||
That division point was selected, albeit arbitrarily, because it puts all the tile
|
||||
fetches for a single line into the same [0, 171] period.
|
||||
|
||||
I'm moving away from this per the desire not to have V9938 output straddle two lines if horizontally-adjusted,
|
||||
amongst other concerns.]
|
||||
|
||||
Provided for the benefit of the methods below:
|
||||
|
||||
* the function external_slot(), which will perform any pending VRAM read/write.
|
||||
* the macros slot(n) and external_slot(n) which can be used to schedule those things inside a
|
||||
switch(start)-based implementation.
|
||||
|
||||
All functions should just spool data to intermediary storage. This is because for most VDPs there is
|
||||
a decoupling between fetch pattern and output pattern, and it's neater to keep the same division
|
||||
for the exceptions.
|
||||
*/
|
||||
|
||||
// MARK: - Address mask helpers.
|
||||
|
||||
/// @returns An instance of @c AddressT with all top bits set down to and including
|
||||
/// bit @c end and all others clear.
|
||||
///
|
||||
/// So e.g. if @c AddressT is @c uint16_t and this VDP has a 15-bit address space then
|
||||
/// @c top_bits<10> will be the address with bits 15 to 10 (inclusive) set and the rest clear.
|
||||
template <typename AddressT, int end> constexpr AddressT top_bits() {
|
||||
return AddressT(~0) - AddressT((1 << end) - 1);
|
||||
}
|
||||
|
||||
/// Modifies and returns @c source so that all bits above position @c n are set; the others are unmodified.
|
||||
template <int n, typename AddressT> constexpr AddressT bits(AddressT source = 0) {
|
||||
return AddressT(source | top_bits<AddressT, n>());
|
||||
}
|
||||
|
||||
// MARK: - 171-window Dispatcher.
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end, typename SequencerT> void Base<personality>::dispatch(SequencerT &fetcher, int start, int end) {
|
||||
#define index(n) \
|
||||
if(use_end && end == n) return; \
|
||||
[[fallthrough]]; \
|
||||
case n: fetcher.template fetch<n>();
|
||||
|
||||
switch(start) {
|
||||
default: assert(false);
|
||||
index(0); index(1); index(2); index(3); index(4); index(5); index(6); index(7); index(8); index(9);
|
||||
index(10); index(11); index(12); index(13); index(14); index(15); index(16); index(17); index(18); index(19);
|
||||
index(20); index(21); index(22); index(23); index(24); index(25); index(26); index(27); index(28); index(29);
|
||||
index(30); index(31); index(32); index(33); index(34); index(35); index(36); index(37); index(38); index(39);
|
||||
index(40); index(41); index(42); index(43); index(44); index(45); index(46); index(47); index(48); index(49);
|
||||
index(50); index(51); index(52); index(53); index(54); index(55); index(56); index(57); index(58); index(59);
|
||||
index(60); index(61); index(62); index(63); index(64); index(65); index(66); index(67); index(68); index(69);
|
||||
index(70); index(71); index(72); index(73); index(74); index(75); index(76); index(77); index(78); index(79);
|
||||
index(80); index(81); index(82); index(83); index(84); index(85); index(86); index(87); index(88); index(89);
|
||||
index(90); index(91); index(92); index(93); index(94); index(95); index(96); index(97); index(98); index(99);
|
||||
index(100); index(101); index(102); index(103); index(104); index(105); index(106); index(107); index(108); index(109);
|
||||
index(110); index(111); index(112); index(113); index(114); index(115); index(116); index(117); index(118); index(119);
|
||||
index(120); index(121); index(122); index(123); index(124); index(125); index(126); index(127); index(128); index(129);
|
||||
index(130); index(131); index(132); index(133); index(134); index(135); index(136); index(137); index(138); index(139);
|
||||
index(140); index(141); index(142); index(143); index(144); index(145); index(146); index(147); index(148); index(149);
|
||||
index(150); index(151); index(152); index(153); index(154); index(155); index(156); index(157); index(158); index(159);
|
||||
index(160); index(161); index(162); index(163); index(164); index(165); index(166); index(167); index(168); index(169);
|
||||
index(170);
|
||||
}
|
||||
|
||||
#undef index
|
||||
}
|
||||
|
||||
// MARK: - Fetchers.
|
||||
|
||||
template <Personality personality>
|
||||
struct TextFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
TextFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
row_base(base->pattern_name_address_ & bits<10>(AddressT((y >> 3) * 40))),
|
||||
row_offset(base->pattern_generator_table_address_ & bits<11>(AddressT(y & 7))) {}
|
||||
|
||||
void fetch_name(AddressT column, int slot = 0) {
|
||||
base->name_[slot] = base->ram_[row_base + column];
|
||||
}
|
||||
|
||||
void fetch_pattern(AddressT column, int slot = 0) {
|
||||
base->fetch_line_buffer_->characters.shapes[column] = base->ram_[row_offset + size_t(base->name_[slot] << 3)];
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const AddressT row_base;
|
||||
const AddressT row_offset;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct CharacterFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
CharacterFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
y(y),
|
||||
row_base(base->pattern_name_address_ & bits<10>(AddressT((y << 2)&~31)))
|
||||
{
|
||||
pattern_base = base->pattern_generator_table_address_;
|
||||
colour_base = base->colour_table_address_;
|
||||
colour_name_shift = 6;
|
||||
|
||||
const ScreenMode mode = base->fetch_line_buffer_->screen_mode;
|
||||
if(mode == ScreenMode::Graphics || mode == ScreenMode::YamahaGraphics3) {
|
||||
// If this is high resolution mode, allow the row number to affect the pattern and colour addresses.
|
||||
pattern_base &= bits<13>(AddressT(((y & 0xc0) << 5)));
|
||||
colour_base &= bits<13>(AddressT(((y & 0xc0) << 5)));
|
||||
|
||||
colour_base += AddressT(y & 7);
|
||||
colour_name_shift = 0;
|
||||
} else {
|
||||
colour_base &= bits<6, AddressT>();
|
||||
pattern_base &= bits<11, AddressT>();
|
||||
}
|
||||
|
||||
if(mode == ScreenMode::MultiColour) {
|
||||
pattern_base += AddressT((y >> 2) & 7);
|
||||
} else {
|
||||
pattern_base += AddressT(y & 7);
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_name(int column) {
|
||||
base->tile_offset_ = base->ram_[row_base + AddressT(column)];
|
||||
}
|
||||
|
||||
void fetch_pattern(int column) {
|
||||
base->fetch_line_buffer_->tiles.patterns[column][0] = base->ram_[pattern_base + AddressT(base->tile_offset_ << 3)];
|
||||
}
|
||||
|
||||
void fetch_colour(int column) {
|
||||
base->fetch_line_buffer_->tiles.patterns[column][1] = base->ram_[colour_base + AddressT((base->tile_offset_ << 3) >> colour_name_shift)];
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const uint8_t y;
|
||||
const AddressT row_base;
|
||||
AddressT pattern_base;
|
||||
AddressT colour_base;
|
||||
int colour_name_shift;
|
||||
};
|
||||
|
||||
constexpr SpriteMode sprite_mode(ScreenMode screen_mode) {
|
||||
switch(screen_mode) {
|
||||
default:
|
||||
return SpriteMode::Mode2;
|
||||
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
case ScreenMode::Graphics:
|
||||
return SpriteMode::Mode1;
|
||||
|
||||
case ScreenMode::SMSMode4:
|
||||
return SpriteMode::MasterSystem;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: should this be extended to include Master System sprites?
|
||||
template <Personality personality, SpriteMode mode>
|
||||
class SpriteFetcher {
|
||||
public:
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
|
||||
// The Yamaha VDP adds an additional table when in Sprite Mode 2, the sprite colour
|
||||
// table, which is intended to fill the 512 bytes before the programmer-located sprite
|
||||
// attribute table.
|
||||
//
|
||||
// It partially enforces this proximity by forcing bits 7 and 8 to 0 in the address of
|
||||
// the attribute table, and forcing them to 1 but masking out bit 9 for the colour table.
|
||||
//
|
||||
// AttributeAddressMask is used to enable or disable that behaviour.
|
||||
static constexpr AddressT AttributeAddressMask = (mode == SpriteMode::Mode2) ? AddressT(~0x180) : AddressT(~0x000);
|
||||
|
||||
SpriteFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
y(y) {}
|
||||
|
||||
void fetch_location(int slot) {
|
||||
fetch_xy(slot);
|
||||
|
||||
if constexpr (mode == SpriteMode::Mode2) {
|
||||
fetch_xy(slot + 1);
|
||||
|
||||
base->name_[0] = name(slot);
|
||||
base->name_[1] = name(slot + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_pattern(int slot) {
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
fetch_image(slot, name(slot));
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2:
|
||||
fetch_image(slot, base->name_[0]);
|
||||
fetch_image(slot + 1, base->name_[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void fetch_y(int sprite) {
|
||||
const AddressT address = base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT(sprite << 2));
|
||||
const uint8_t sprite_y = base->ram_[address];
|
||||
base->posit_sprite(sprite, sprite_y, y);
|
||||
}
|
||||
|
||||
private:
|
||||
void fetch_xy(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
buffer.active_sprites[slot].x =
|
||||
base->ram_[
|
||||
base->sprite_attribute_table_address_ & AttributeAddressMask & bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 1))
|
||||
];
|
||||
}
|
||||
|
||||
uint8_t name(int slot) {
|
||||
auto &buffer = *base->fetch_sprite_buffer_;
|
||||
const AddressT address =
|
||||
base->sprite_attribute_table_address_ &
|
||||
AttributeAddressMask &
|
||||
bits<7>(AddressT((buffer.active_sprites[slot].index << 2) | 2));
|
||||
const uint8_t name = base->ram_[address] & (base->sprites_16x16_ ? ~3 : ~0);
|
||||
return name;
|
||||
}
|
||||
|
||||
void fetch_image(int slot, uint8_t name) {
|
||||
uint8_t colour = 0;
|
||||
auto &sprite = base->fetch_sprite_buffer_->active_sprites[slot];
|
||||
switch(mode) {
|
||||
case SpriteMode::Mode1:
|
||||
// Fetch colour from the attribute table, per this sprite's slot.
|
||||
colour = base->ram_[
|
||||
base->sprite_attribute_table_address_ & bits<7>(AddressT((sprite.index << 2) | 3))
|
||||
];
|
||||
break;
|
||||
|
||||
case SpriteMode::Mode2: {
|
||||
// Fetch colour from the colour table, per this sprite's slot and row.
|
||||
const AddressT colour_table_address = (base->sprite_attribute_table_address_ | ~AttributeAddressMask) & AddressT(~0x200);
|
||||
colour = base->ram_[
|
||||
colour_table_address &
|
||||
bits<9>(
|
||||
AddressT(sprite.index << 4) |
|
||||
AddressT(sprite.row)
|
||||
)
|
||||
];
|
||||
} break;
|
||||
}
|
||||
sprite.image[2] = colour;
|
||||
sprite.x -= sprite.early_clock();
|
||||
|
||||
const AddressT graphic_location = base->sprite_generator_table_address_ & bits<11>(AddressT((name << 3) | sprite.row));
|
||||
sprite.image[0] = base->ram_[graphic_location];
|
||||
sprite.image[1] = base->ram_[graphic_location+16];
|
||||
|
||||
if constexpr (SpriteBuffer::test_is_filling) {
|
||||
if(slot == ((mode == SpriteMode::Mode2) ? 7 : 3)) {
|
||||
base->fetch_sprite_buffer_->is_filling = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const uint8_t y;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct SMSFetcher {
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
struct RowInfo {
|
||||
AddressT pattern_address_base;
|
||||
AddressT sub_row[2];
|
||||
};
|
||||
|
||||
SMSFetcher(Base<personality> *base, uint8_t y) :
|
||||
base(base),
|
||||
storage(static_cast<Storage<personality> *>(base)),
|
||||
y(y),
|
||||
horizontal_offset((y >= 16 || !storage->horizontal_scroll_lock_) ? (base->fetch_line_buffer_->latched_horizontal_scroll >> 3) : 0)
|
||||
{
|
||||
// Limit address bits in use if this is a SMS2 mode.
|
||||
const bool is_tall_mode = base->mode_timing_.pixel_lines != 192;
|
||||
const AddressT pattern_name_address = storage->pattern_name_address_ | (is_tall_mode ? 0x800 : 0);
|
||||
const AddressT pattern_name_offset = is_tall_mode ? 0x100 : 0;
|
||||
|
||||
// Determine row info for the screen both (i) if vertical scrolling is applied; and (ii) if it isn't.
|
||||
// The programmer can opt out of applying vertical scrolling to the right-hand portion of the display.
|
||||
const int scrolled_row = (y + storage->latched_vertical_scroll_) % (is_tall_mode ? 256 : 224);
|
||||
scrolled_row_info.pattern_address_base = (pattern_name_address & bits<11>(AddressT((scrolled_row & ~7) << 3))) - pattern_name_offset;
|
||||
scrolled_row_info.sub_row[0] = AddressT((scrolled_row & 7) << 2);
|
||||
scrolled_row_info.sub_row[1] = AddressT(28 ^ ((scrolled_row & 7) << 2));
|
||||
if(storage->vertical_scroll_lock_) {
|
||||
static_row_info.pattern_address_base = bits<11>(AddressT(pattern_name_address & ((y & ~7) << 3))) - pattern_name_offset;
|
||||
static_row_info.sub_row[0] = AddressT((y & 7) << 2);
|
||||
static_row_info.sub_row[1] = 28 ^ AddressT((y & 7) << 2);
|
||||
} else static_row_info = scrolled_row_info;
|
||||
}
|
||||
|
||||
void fetch_sprite(int sprite) {
|
||||
auto &sprite_buffer = *base->fetch_sprite_buffer_;
|
||||
sprite_buffer.active_sprites[sprite].x =
|
||||
base->ram_[
|
||||
storage->sprite_attribute_table_address_ & bits<7>((sprite_buffer.active_sprites[sprite].index << 1) | 0)
|
||||
] - (storage->shift_sprites_8px_left_ ? 8 : 0);
|
||||
const uint8_t name = base->ram_[
|
||||
storage->sprite_attribute_table_address_ & bits<7>((sprite_buffer.active_sprites[sprite].index << 1) | 1)
|
||||
] & (base->sprites_16x16_ ? ~1 : ~0);
|
||||
|
||||
const AddressT graphic_location =
|
||||
storage->sprite_generator_table_address_ &
|
||||
bits<13>(AddressT((name << 5) | (sprite_buffer.active_sprites[sprite].row << 2)));
|
||||
sprite_buffer.active_sprites[sprite].image[0] = base->ram_[graphic_location];
|
||||
sprite_buffer.active_sprites[sprite].image[1] = base->ram_[graphic_location+1];
|
||||
sprite_buffer.active_sprites[sprite].image[2] = base->ram_[graphic_location+2];
|
||||
sprite_buffer.active_sprites[sprite].image[3] = base->ram_[graphic_location+3];
|
||||
}
|
||||
|
||||
void fetch_tile_name(int column) {
|
||||
const RowInfo &row_info = column < 24 ? scrolled_row_info : static_row_info;
|
||||
const size_t scrolled_column = (column - horizontal_offset) & 0x1f;
|
||||
const size_t address = row_info.pattern_address_base + (scrolled_column << 1);
|
||||
auto &line_buffer = *base->fetch_line_buffer_;
|
||||
|
||||
line_buffer.tiles.flags[column] = base->ram_[address+1];
|
||||
base->tile_offset_ = AddressT(
|
||||
(((line_buffer.tiles.flags[column]&1) << 8) | base->ram_[address]) << 5
|
||||
) + row_info.sub_row[(line_buffer.tiles.flags[column]&4) >> 2];
|
||||
}
|
||||
|
||||
void fetch_tile_pattern(int column) {
|
||||
auto &line_buffer = *base->fetch_line_buffer_;
|
||||
line_buffer.tiles.patterns[column][0] = base->ram_[base->tile_offset_];
|
||||
line_buffer.tiles.patterns[column][1] = base->ram_[base->tile_offset_+1];
|
||||
line_buffer.tiles.patterns[column][2] = base->ram_[base->tile_offset_+2];
|
||||
line_buffer.tiles.patterns[column][3] = base->ram_[base->tile_offset_+3];
|
||||
}
|
||||
|
||||
void posit_sprite(int sprite) {
|
||||
base->posit_sprite(sprite, base->ram_[storage->sprite_attribute_table_address_ & bits<8>(AddressT(sprite))], y);
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
const Storage<personality> *const storage;
|
||||
const uint8_t y;
|
||||
const int horizontal_offset;
|
||||
RowInfo scrolled_row_info, static_row_info;
|
||||
};
|
||||
|
||||
// MARK: - TMS Sequencers.
|
||||
|
||||
template <Personality personality>
|
||||
struct RefreshSequencer {
|
||||
RefreshSequencer(Base<personality> *base) : base(base) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 26 || (cycle & 1) || cycle >= 154) {
|
||||
base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
}
|
||||
|
||||
Base<personality> *const base;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct TextSequencer {
|
||||
template <typename... Args> TextSequencer(Args&&... args) : fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
// The first 30 and the final 4 slots are external.
|
||||
if constexpr (cycle < 30 || cycle >= 150) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
return;
|
||||
} else {
|
||||
// For the 120 slots in between follow a three-step pattern of:
|
||||
constexpr int offset = cycle - 30;
|
||||
constexpr auto column = AddressT(offset / 3);
|
||||
switch(offset % 3) {
|
||||
case 0: fetcher.fetch_name(column); break; // (1) fetch tile name.
|
||||
case 1: fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle)); break; // (2) external slot.
|
||||
case 2: fetcher.fetch_pattern(column); break; // (3) fetch tile pattern.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
TextFetcher<personality> fetcher;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
struct CharacterSequencer {
|
||||
template <typename... Args> CharacterSequencer(Args&&... args) :
|
||||
character_fetcher(std::forward<Args>(args)...),
|
||||
sprite_fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 5) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 5) {
|
||||
// Fetch: n1, c2, pat2a, pat2b, y3, x3, n3, c3, pat3a, pat3b.
|
||||
sprite_fetcher.fetch_pattern(2);
|
||||
sprite_fetcher.fetch_location(3);
|
||||
sprite_fetcher.fetch_pattern(3);
|
||||
}
|
||||
|
||||
if(cycle > 14 && cycle < 19) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
// Fetch 8 new sprite Y coordinates, to begin selecting sprites for next line.
|
||||
if(cycle == 19) {
|
||||
sprite_fetcher.fetch_y(0); sprite_fetcher.fetch_y(1); sprite_fetcher.fetch_y(2); sprite_fetcher.fetch_y(3);
|
||||
sprite_fetcher.fetch_y(4); sprite_fetcher.fetch_y(5); sprite_fetcher.fetch_y(6); sprite_fetcher.fetch_y(7);
|
||||
}
|
||||
|
||||
// Body of line: tiles themselves, plus some additional potential sprites.
|
||||
if(cycle >= 27 && cycle < 155) {
|
||||
constexpr int offset = cycle - 27;
|
||||
constexpr int block = offset >> 2;
|
||||
constexpr int sub_block = offset & 3;
|
||||
switch(sub_block) {
|
||||
case 0: character_fetcher.fetch_name(block); break;
|
||||
case 1:
|
||||
if(!(block & 3)) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
} else {
|
||||
constexpr int sprite = 8 + ((block >> 2) * 3) + ((block & 3) - 1);
|
||||
sprite_fetcher.fetch_y(sprite);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
character_fetcher.fetch_pattern(block);
|
||||
character_fetcher.fetch_colour(block);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if(cycle >= 155 && cycle < 157) {
|
||||
character_fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 157) {
|
||||
// Fetch: y0, x0, n0, c0, pat0a, pat0b, y1, x1, n1, c1, pat1a, pat1b, y2, x2.
|
||||
sprite_fetcher.fetch_location(0);
|
||||
sprite_fetcher.fetch_pattern(0);
|
||||
sprite_fetcher.fetch_location(1);
|
||||
sprite_fetcher.fetch_pattern(1);
|
||||
sprite_fetcher.fetch_location(2);
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
CharacterFetcher<personality> character_fetcher;
|
||||
SpriteFetcher<personality, SpriteMode::Mode1> sprite_fetcher;
|
||||
};
|
||||
|
||||
// MARK: - TMS fetch routines.
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_refresh(uint8_t, int start, int end) {
|
||||
RefreshSequencer sequencer(this);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_text(uint8_t y, int start, int end) {
|
||||
TextSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_tms_character(uint8_t y, int start, int end) {
|
||||
CharacterSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
|
||||
// MARK: - Master System
|
||||
|
||||
template <Personality personality>
|
||||
struct SMSSequencer {
|
||||
template <typename... Args> SMSSequencer(Args&&... args) : fetcher(std::forward<Args>(args)...) {}
|
||||
|
||||
// Cf. https://www.smspower.org/forums/16485-GenesisMode4VRAMTiming with this implementation pegging
|
||||
// window 0 to HSYNC low.
|
||||
template <int cycle> void fetch() {
|
||||
if(cycle < 3) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 3) {
|
||||
fetcher.fetch_sprite(4);
|
||||
fetcher.fetch_sprite(5);
|
||||
fetcher.fetch_sprite(6);
|
||||
fetcher.fetch_sprite(7);
|
||||
}
|
||||
|
||||
if(cycle == 15 || cycle == 16) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 17) {
|
||||
fetcher.posit_sprite(0); fetcher.posit_sprite(1); fetcher.posit_sprite(2); fetcher.posit_sprite(3);
|
||||
fetcher.posit_sprite(4); fetcher.posit_sprite(5); fetcher.posit_sprite(6); fetcher.posit_sprite(7);
|
||||
fetcher.posit_sprite(8); fetcher.posit_sprite(9); fetcher.posit_sprite(10); fetcher.posit_sprite(11);
|
||||
fetcher.posit_sprite(12); fetcher.posit_sprite(13); fetcher.posit_sprite(14); fetcher.posit_sprite(15);
|
||||
}
|
||||
|
||||
if(cycle >= 25 && cycle < 153) {
|
||||
constexpr int offset = cycle - 25;
|
||||
constexpr int block = offset >> 2;
|
||||
constexpr int sub_block = offset & 3;
|
||||
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
|
||||
case 0: fetcher.fetch_tile_name(block); break;
|
||||
case 1:
|
||||
if(!(block & 3)) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
} else {
|
||||
constexpr int sprite = (8 + ((block >> 2) * 3) + ((block & 3) - 1)) << 1;
|
||||
fetcher.posit_sprite(sprite);
|
||||
fetcher.posit_sprite(sprite+1);
|
||||
}
|
||||
break;
|
||||
case 2: fetcher.fetch_tile_pattern(block); break;
|
||||
}
|
||||
}
|
||||
|
||||
if(cycle >= 153 && cycle < 157) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
|
||||
if(cycle == 157) {
|
||||
fetcher.fetch_sprite(0);
|
||||
fetcher.fetch_sprite(1);
|
||||
fetcher.fetch_sprite(2);
|
||||
fetcher.fetch_sprite(3);
|
||||
}
|
||||
|
||||
if(cycle >= 169) {
|
||||
fetcher.base->do_external_slot(to_internal<personality, Clock::TMSMemoryWindow>(cycle));
|
||||
}
|
||||
}
|
||||
|
||||
using AddressT = typename Base<personality>::AddressT;
|
||||
SMSFetcher<personality> fetcher;
|
||||
};
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_sms(uint8_t y, int start, int end) {
|
||||
if constexpr (is_sega_vdp(personality)) {
|
||||
SMSSequencer<personality> sequencer(this, y);
|
||||
dispatch<use_end>(sequencer, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Yamaha
|
||||
|
||||
template <Personality personality>
|
||||
template<ScreenMode mode> void Base<personality>::fetch_yamaha(uint8_t y, int end) {
|
||||
CharacterFetcher character_fetcher(this, y);
|
||||
TextFetcher text_fetcher(this, y);
|
||||
SpriteFetcher<personality, sprite_mode(mode)> sprite_fetcher(this, y);
|
||||
|
||||
using Type = typename Storage<personality>::Event::Type;
|
||||
while(Storage<personality>::next_event_->offset < end) {
|
||||
switch(Storage<personality>::next_event_->type) {
|
||||
case Type::External:
|
||||
do_external_slot(Storage<personality>::next_event_->offset);
|
||||
break;
|
||||
|
||||
case Type::Name:
|
||||
switch(mode) {
|
||||
case ScreenMode::Text: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 1);
|
||||
|
||||
text_fetcher.fetch_name(column, 0);
|
||||
text_fetcher.fetch_name(column + 1, 1);
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 2);
|
||||
const auto start = pattern_name_address_ & bits<12>(AddressT((y >> 3) * 80));
|
||||
|
||||
name_[0] = ram_[start + column + 0];
|
||||
name_[1] = ram_[start + column + 1];
|
||||
name_[2] = ram_[start + column + 2];
|
||||
name_[3] = ram_[start + column + 3];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_name(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::Colour:
|
||||
switch(mode) {
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id);
|
||||
const auto address = colour_table_address_ & bits<9>(AddressT((y >> 3) * 10));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
line_buffer.characters.flags[column] = ram_[address + column];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_colour(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::Pattern:
|
||||
switch(mode) {
|
||||
case ScreenMode::Text: {
|
||||
const auto column = AddressT(Storage<personality>::next_event_->id << 1);
|
||||
|
||||
text_fetcher.fetch_pattern(column, 0);
|
||||
text_fetcher.fetch_pattern(column + 1, 1);
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaText80: {
|
||||
const auto column = Storage<personality>::next_event_->id << 2;
|
||||
const auto start = pattern_generator_table_address_ & bits<11>(AddressT(y & 7));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
line_buffer.characters.shapes[column + 0] = ram_[start + AddressT(name_[0] << 3)];
|
||||
line_buffer.characters.shapes[column + 1] = ram_[start + AddressT(name_[1] << 3)];
|
||||
line_buffer.characters.shapes[column + 2] = ram_[start + AddressT(name_[2] << 3)];
|
||||
line_buffer.characters.shapes[column + 3] = ram_[start + AddressT(name_[3] << 3)];
|
||||
} break;
|
||||
|
||||
case ScreenMode::Graphics:
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::ColouredText:
|
||||
character_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3:
|
||||
// As per comment elsewhere; my _guess_ is that G3 is slotted as if it were
|
||||
// a bitmap mode, with the three bytes that describe each column fitting into
|
||||
// the relevant windows.
|
||||
character_fetcher.fetch_name(Storage<personality>::next_event_->id);
|
||||
character_fetcher.fetch_colour(Storage<personality>::next_event_->id);
|
||||
character_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics4:
|
||||
case ScreenMode::YamahaGraphics5: {
|
||||
const int column = Storage<personality>::next_event_->id << 2;
|
||||
const auto start = bits<15>((y << 7) | column);
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & AddressT(start + 0)];
|
||||
line_buffer.bitmap[column + 1] = ram_[pattern_name_address_ & AddressT(start + 1)];
|
||||
line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & AddressT(start + 2)];
|
||||
line_buffer.bitmap[column + 3] = ram_[pattern_name_address_ & AddressT(start + 3)];
|
||||
} break;
|
||||
|
||||
case ScreenMode::YamahaGraphics6:
|
||||
case ScreenMode::YamahaGraphics7: {
|
||||
const uint8_t *const ram2 = &ram_[65536];
|
||||
const int column = Storage<personality>::next_event_->id << 3;
|
||||
const auto start = bits<15>((y << 7) | (column >> 1));
|
||||
auto &line_buffer = *fetch_line_buffer_;
|
||||
|
||||
// Fetch from alternate banks.
|
||||
line_buffer.bitmap[column + 0] = ram_[pattern_name_address_ & AddressT(start + 0) & 0xffff];
|
||||
line_buffer.bitmap[column + 1] = ram2[pattern_name_address_ & AddressT(start + 0) & 0xffff];
|
||||
line_buffer.bitmap[column + 2] = ram_[pattern_name_address_ & AddressT(start + 1) & 0xffff];
|
||||
line_buffer.bitmap[column + 3] = ram2[pattern_name_address_ & AddressT(start + 1) & 0xffff];
|
||||
line_buffer.bitmap[column + 4] = ram_[pattern_name_address_ & AddressT(start + 2) & 0xffff];
|
||||
line_buffer.bitmap[column + 5] = ram2[pattern_name_address_ & AddressT(start + 2) & 0xffff];
|
||||
line_buffer.bitmap[column + 6] = ram_[pattern_name_address_ & AddressT(start + 3) & 0xffff];
|
||||
line_buffer.bitmap[column + 7] = ram2[pattern_name_address_ & AddressT(start + 3) & 0xffff];
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpriteY:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_y(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpriteLocation:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_location(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case Type::SpritePattern:
|
||||
switch(mode) {
|
||||
case ScreenMode::Blank:
|
||||
case ScreenMode::Text:
|
||||
case ScreenMode::YamahaText80:
|
||||
// Ensure the compiler can discard character_fetcher in these modes.
|
||||
break;
|
||||
|
||||
default:
|
||||
sprite_fetcher.fetch_pattern(Storage<personality>::next_event_->id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
++Storage<personality>::next_event_;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <Personality personality>
|
||||
template<bool use_end> void Base<personality>::fetch_yamaha(uint8_t y, int, int end) {
|
||||
if constexpr (is_yamaha_vdp(personality)) {
|
||||
// Dispatch according to [supported] screen mode.
|
||||
#define Dispatch(mode) case mode: fetch_yamaha<mode>(y, end); break;
|
||||
switch(fetch_line_buffer_->screen_mode) {
|
||||
default: break;
|
||||
Dispatch(ScreenMode::Blank);
|
||||
Dispatch(ScreenMode::Text);
|
||||
Dispatch(ScreenMode::MultiColour);
|
||||
Dispatch(ScreenMode::ColouredText);
|
||||
Dispatch(ScreenMode::Graphics);
|
||||
Dispatch(ScreenMode::YamahaText80);
|
||||
Dispatch(ScreenMode::YamahaGraphics3);
|
||||
Dispatch(ScreenMode::YamahaGraphics4);
|
||||
Dispatch(ScreenMode::YamahaGraphics5);
|
||||
Dispatch(ScreenMode::YamahaGraphics6);
|
||||
Dispatch(ScreenMode::YamahaGraphics7);
|
||||
}
|
||||
#undef Dispatch
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mega Drive
|
||||
|
||||
// TODO.
|
||||
|
||||
#endif /* Fetch_hpp */
|
||||
132
Components/9918/Implementation/LineBuffer.hpp
Normal file
132
Components/9918/Implementation/LineBuffer.hpp
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// LineBuffer.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/02/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef LineBuffer_hpp
|
||||
#define LineBuffer_hpp
|
||||
|
||||
#include "AccessEnums.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// Temporary buffers collect a representation of each line prior to pixel serialisation.
|
||||
|
||||
struct SpriteBuffer {
|
||||
// An active sprite is one that has been selected for composition onto
|
||||
// _this_ line.
|
||||
struct ActiveSprite {
|
||||
int index = 0; // The original in-table index of this sprite.
|
||||
int row = 0; // The row of the sprite that should be drawn.
|
||||
int x = 0; // The sprite's x position on screen.
|
||||
|
||||
uint8_t image[4]; // Up to four bytes of image information.
|
||||
//
|
||||
// In practice:
|
||||
//
|
||||
// Master System mode: the four bytes of this 8x8 sprite;
|
||||
// TMS and Yamaha: [0] = the left half of this sprite; [1] = the right side (if 16x16 sprites enabled); [2] = colour, early-clock bit, etc.
|
||||
int shift_position = 0; // An offset representing how much of the image information has already been drawn.
|
||||
|
||||
// Yamaha helpers.
|
||||
bool opaque() const {
|
||||
return !(image[2] & 0x40);
|
||||
}
|
||||
|
||||
/// @returns @c 0x20 if this sprite should generate collisions; @c 0x00 otherwise.
|
||||
int collision_bit() const {
|
||||
return ((image[2] & 0x20) | ((image[2] & 0x40) >> 1)) ^ 0x20;
|
||||
}
|
||||
|
||||
// Yamaha and TMS helpers.
|
||||
int early_clock() const {
|
||||
return (image[2] & 0x80) >> 2;
|
||||
}
|
||||
} active_sprites[8];
|
||||
|
||||
int active_sprite_slot = 0; // A pointer to the slot into which a new active sprite will be deposited, if required.
|
||||
bool sprites_stopped = false; // A special TMS feature is that a sentinel value can be used to prevent any further sprites
|
||||
// being evaluated for display. This flag determines whether the sentinel has yet been reached.
|
||||
uint8_t sprite_terminator = 0;
|
||||
|
||||
#ifndef NDEBUG
|
||||
static constexpr bool test_is_filling = true;
|
||||
#else
|
||||
static constexpr bool test_is_filling = false;
|
||||
#endif
|
||||
bool is_filling = false;
|
||||
|
||||
void reset_sprite_collection();
|
||||
};
|
||||
|
||||
struct LineBuffer {
|
||||
LineBuffer() {}
|
||||
|
||||
// The fetch mode describes the proper timing diagram for this line;
|
||||
// screen mode captures proper output mode.
|
||||
FetchMode fetch_mode = FetchMode::Text;
|
||||
ScreenMode screen_mode = ScreenMode::Text;
|
||||
VerticalState vertical_state = VerticalState::Blank;
|
||||
SpriteBuffer *sprites = nullptr;
|
||||
|
||||
// Holds the horizontal scroll position to apply to this line;
|
||||
// of those VDPs currently implemented, affects the Master System only.
|
||||
uint8_t latched_horizontal_scroll = 0;
|
||||
|
||||
// The names array holds pattern names, as an offset into memory, and
|
||||
// potentially flags also.
|
||||
union {
|
||||
// This struct captures maximal potential detail across the TMS9918
|
||||
// and Sega VDP for tiled modes (plus multicolour).
|
||||
struct {
|
||||
uint8_t flags[32]{};
|
||||
|
||||
// The patterns array holds tile patterns, corresponding 1:1 with names.
|
||||
// Four bytes per pattern is the maximum required by any
|
||||
// currently-implemented VDP.
|
||||
uint8_t patterns[32][4]{};
|
||||
} tiles;
|
||||
|
||||
// The Yamaha and TMS both have text modes, with the former going up to
|
||||
// 80 columns plus 10 bytes of colour-esque flags.
|
||||
struct {
|
||||
uint8_t shapes[80];
|
||||
uint8_t flags[10];
|
||||
} characters;
|
||||
|
||||
// The Yamaha VDP also has a variety of bitmap modes,
|
||||
// the widest of which is 512px @ 4bpp.
|
||||
uint8_t bitmap[256];
|
||||
};
|
||||
|
||||
/*
|
||||
Horizontal layout (on a 342-cycle clock):
|
||||
|
||||
15 cycles right border
|
||||
58 cycles blanking & sync
|
||||
13 cycles left border
|
||||
|
||||
... i.e. to cycle 86, then:
|
||||
|
||||
border up to first_pixel_output_column;
|
||||
pixels up to next_border_column;
|
||||
border up to the end.
|
||||
|
||||
e.g. standard 256-pixel modes will want to set
|
||||
first_pixel_output_column = 86, next_border_column = 342.
|
||||
*/
|
||||
int first_pixel_output_column = 94;
|
||||
int next_border_column = 334;
|
||||
int pixel_count = 256;
|
||||
};
|
||||
|
||||
struct LineBufferPointer {
|
||||
int row = 0, column = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* LineBuffer_hpp */
|
||||
50
Components/9918/Implementation/PersonalityTraits.hpp
Normal file
50
Components/9918/Implementation/PersonalityTraits.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// PersonalityTraits.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 06/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef PersonalityTraits_hpp
|
||||
#define PersonalityTraits_hpp
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// Genus determinants for the various personalityes.
|
||||
constexpr bool is_sega_vdp(Personality p) {
|
||||
return p >= Personality::SMSVDP;
|
||||
}
|
||||
|
||||
constexpr bool is_yamaha_vdp(Personality p) {
|
||||
return p == Personality::V9938 || p == Personality::V9958;
|
||||
}
|
||||
|
||||
// i.e. one with the original internal timings.
|
||||
constexpr bool is_classic_vdp(Personality p) {
|
||||
return
|
||||
p == Personality::TMS9918A ||
|
||||
p == Personality::SMSVDP ||
|
||||
p == Personality::SMS2VDP ||
|
||||
p == Personality::GGVDP;
|
||||
}
|
||||
|
||||
constexpr size_t memory_size(Personality p) {
|
||||
switch(p) {
|
||||
case TI::TMS::TMS9918A:
|
||||
case TI::TMS::SMSVDP:
|
||||
case TI::TMS::SMS2VDP:
|
||||
case TI::TMS::GGVDP: return 16 * 1024;
|
||||
case TI::TMS::MDVDP: return 64 * 1024;
|
||||
case TI::TMS::V9938: return 128 * 1024;
|
||||
case TI::TMS::V9958: return 192 * 1024;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t memory_mask(Personality p) {
|
||||
return memory_size(p) - 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* PersonalityTraits_hpp */
|
||||
489
Components/9918/Implementation/Storage.hpp
Normal file
489
Components/9918/Implementation/Storage.hpp
Normal file
@@ -0,0 +1,489 @@
|
||||
//
|
||||
//
|
||||
// Storage.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/02/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Storage_h
|
||||
#define Storage_h
|
||||
|
||||
#include "LineBuffer.hpp"
|
||||
#include "YamahaCommands.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
/// A container for personality-specific storage; see specific instances below.
|
||||
template <Personality personality, typename Enable = void> struct Storage {
|
||||
};
|
||||
|
||||
template <> struct Storage<Personality::TMS9918A> {
|
||||
using AddressT = uint16_t;
|
||||
|
||||
void begin_line(ScreenMode, bool) {}
|
||||
};
|
||||
|
||||
// Yamaha-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_yamaha_vdp(personality)>> {
|
||||
using AddressT = uint32_t;
|
||||
|
||||
std::array<uint8_t, 65536> expansion_ram_;
|
||||
|
||||
int selected_status_ = 0;
|
||||
|
||||
int indirect_register_ = 0;
|
||||
bool increment_indirect_register_ = false;
|
||||
|
||||
int adjustment_[2]{};
|
||||
|
||||
std::array<uint32_t, 16> palette_{};
|
||||
std::array<uint32_t, 16> background_palette_{};
|
||||
bool solid_background_ = true;
|
||||
|
||||
uint8_t new_colour_ = 0;
|
||||
uint8_t palette_entry_ = 0;
|
||||
bool palette_write_phase_ = false;
|
||||
|
||||
uint8_t mode_ = 0;
|
||||
|
||||
uint8_t vertical_offset_ = 0;
|
||||
uint8_t sprite_cache_[8][32]{};
|
||||
|
||||
/// Describes an _observable_ memory access event. i.e. anything that it is safe
|
||||
/// (and convenient) to treat as atomic in between external slots.
|
||||
struct Event {
|
||||
/// Offset of the _beginning_ of the event. Not completely arbitrarily: this is when
|
||||
/// external data must be ready by in order to take part in those slots.
|
||||
uint16_t offset = 1368;
|
||||
enum class Type: uint8_t {
|
||||
/// A slot for reading or writing data on behalf of the CPU or the command engine.
|
||||
External,
|
||||
|
||||
//
|
||||
// Sprites.
|
||||
//
|
||||
SpriteY,
|
||||
SpriteLocation,
|
||||
SpritePattern,
|
||||
|
||||
//
|
||||
// Backgrounds.
|
||||
//
|
||||
Name,
|
||||
Colour,
|
||||
Pattern,
|
||||
} type = Type::External;
|
||||
uint8_t id = 0;
|
||||
|
||||
constexpr Event(Type type, uint8_t id = 0) noexcept :
|
||||
type(type),
|
||||
id(id) {}
|
||||
|
||||
constexpr Event() noexcept {}
|
||||
};
|
||||
|
||||
// State that tracks fetching position within a line.
|
||||
const Event *next_event_ = nullptr;
|
||||
|
||||
// Text blink colours.
|
||||
uint8_t blink_text_colour_ = 0;
|
||||
uint8_t blink_background_colour_ = 0;
|
||||
|
||||
// Blink state (which is also affects even/odd page display in applicable modes).
|
||||
int in_blink_ = 1;
|
||||
uint8_t blink_periods_ = 0;
|
||||
uint8_t blink_counter_ = 0;
|
||||
|
||||
// Sprite collection state.
|
||||
bool sprites_enabled_ = true;
|
||||
|
||||
// Additional status.
|
||||
uint8_t colour_status_ = 0;
|
||||
uint16_t colour_location_ = 0;
|
||||
uint16_t collision_location_[2]{};
|
||||
|
||||
/// Resets line-ephemeral state for a new line.
|
||||
void begin_line(ScreenMode mode, bool is_refresh) {
|
||||
if(is_refresh) {
|
||||
next_event_ = refresh_events.data();
|
||||
return;
|
||||
}
|
||||
|
||||
switch(mode) {
|
||||
case ScreenMode::YamahaText80:
|
||||
case ScreenMode::Text:
|
||||
next_event_ = text_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::MultiColour:
|
||||
case ScreenMode::YamahaGraphics1:
|
||||
case ScreenMode::YamahaGraphics2:
|
||||
next_event_ = character_events.data();
|
||||
break;
|
||||
|
||||
case ScreenMode::YamahaGraphics3: // TODO: verify; my guess is that G3 is timed like a bitmap mode
|
||||
// in order to fit the pattern for sprite mode 2. Just a guess.
|
||||
default:
|
||||
next_event_ = sprites_enabled_ ? sprites_events.data() : no_sprites_events.data();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Command engine state.
|
||||
CommandContext command_context_;
|
||||
ModeDescription mode_description_;
|
||||
std::unique_ptr<Command> command_ = nullptr;
|
||||
|
||||
enum class CommandStep {
|
||||
None,
|
||||
|
||||
CopySourcePixelToStatus,
|
||||
|
||||
ReadSourcePixel,
|
||||
ReadDestinationPixel,
|
||||
WritePixel,
|
||||
|
||||
ReadSourceByte,
|
||||
WriteByte,
|
||||
};
|
||||
CommandStep next_command_step_ = CommandStep::None;
|
||||
int minimum_command_column_ = 0;
|
||||
uint8_t command_latch_ = 0;
|
||||
|
||||
void update_command_step(int current_column) {
|
||||
if(!command_) {
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
if(command_->done()) {
|
||||
command_ = nullptr;
|
||||
next_command_step_ = CommandStep::None;
|
||||
return;
|
||||
}
|
||||
|
||||
minimum_command_column_ = current_column + command_->cycles;
|
||||
switch(command_->access) {
|
||||
case Command::AccessType::ReadPoint:
|
||||
next_command_step_ = CommandStep::CopySourcePixelToStatus;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyPoint:
|
||||
next_command_step_ = CommandStep::ReadSourcePixel;
|
||||
break;
|
||||
case Command::AccessType::PlotPoint:
|
||||
next_command_step_ = CommandStep::ReadDestinationPixel;
|
||||
break;
|
||||
|
||||
case Command::AccessType::WaitForColourReceipt:
|
||||
// i.e. nothing to do until a colour is received.
|
||||
next_command_step_ = CommandStep::None;
|
||||
break;
|
||||
|
||||
case Command::AccessType::CopyByte:
|
||||
next_command_step_ = CommandStep::ReadSourceByte;
|
||||
break;
|
||||
case Command::AccessType::WriteByte:
|
||||
next_command_step_ = CommandStep::WriteByte;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Storage() noexcept {
|
||||
// Perform sanity checks on the event lists.
|
||||
#ifndef NDEBUG
|
||||
const Event *lists[] = { no_sprites_events.data(), sprites_events.data(), text_events.data(), character_events.data(), refresh_events.data(), nullptr };
|
||||
const Event **list = lists;
|
||||
while(*list) {
|
||||
const Event *cursor = *list;
|
||||
++list;
|
||||
|
||||
while(cursor[1].offset != 1368) {
|
||||
assert(cursor[1].offset > cursor[0].offset);
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Seed to _something_ meaningful.
|
||||
//
|
||||
// TODO: this is a workaround [/hack], in effect, for the main TMS' habit of starting
|
||||
// in a randomised position, which means that start-of-line isn't announced.
|
||||
//
|
||||
// Do I really want that behaviour?
|
||||
next_event_ = refresh_events.data();
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename GeneratorT> static constexpr size_t events_size() {
|
||||
size_t size = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
const auto event_type = GeneratorT::event(c);
|
||||
size += event_type.has_value();
|
||||
}
|
||||
return size + 1;
|
||||
}
|
||||
|
||||
template <typename GeneratorT, size_t size = events_size<GeneratorT>()>
|
||||
static constexpr std::array<Event, size> events() {
|
||||
std::array<Event, size> result{};
|
||||
size_t index = 0;
|
||||
for(int c = 0; c < 1368; c++) {
|
||||
const auto event = GeneratorT::event(c);
|
||||
if(!event) {
|
||||
continue;
|
||||
}
|
||||
result[index] = *event;
|
||||
result[index].offset = uint16_t(c);
|
||||
++index;
|
||||
}
|
||||
result[index] = Event();
|
||||
return result;
|
||||
}
|
||||
|
||||
struct StandardGenerators {
|
||||
static constexpr std::optional<Event> external_every_eight(int index) {
|
||||
if(index & 7) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
};
|
||||
|
||||
struct RefreshGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// From 0 to 126: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index < 126) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 0);
|
||||
}
|
||||
|
||||
// From 164 to 1234: eight-cycle windows, the first 15 of each 16 being
|
||||
// CPU/CMD and the final being refresh.
|
||||
if(grauw_index >= 164 && grauw_index < 1234) {
|
||||
const int offset = grauw_index - 164;
|
||||
if(offset & 7) return std::nullopt;
|
||||
if(((offset >> 3) & 15) == 15) return std::nullopt;
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
// From 1268 to 1330: CPU/CMD slots at every cycle divisible by 8.
|
||||
if(grauw_index >= 1268 && grauw_index < 1330) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1268);
|
||||
}
|
||||
|
||||
// A CPU/CMD at 1334.
|
||||
if(grauw_index == 1334) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
|
||||
// From 1344 to 1366: CPU/CMD slots every cycle divisible by 8.
|
||||
if(grauw_index >= 1344 && grauw_index < 1366) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1344);
|
||||
}
|
||||
|
||||
// Otherwise: nothing.
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
static constexpr auto refresh_events = events<RefreshGenerator>();
|
||||
|
||||
template <bool include_sprites> struct BitmapGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
if(!include_sprites) {
|
||||
// Various standard zones of one-every-eight external slots.
|
||||
if(grauw_index < 124) {
|
||||
return StandardGenerators::external_every_eight(grauw_index + 2);
|
||||
}
|
||||
if(grauw_index > 1266) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1266);
|
||||
}
|
||||
} else {
|
||||
// This records collection points for all data for selected sprites.
|
||||
// There's only four of them (each site covering two sprites),
|
||||
// so it's clearer just to be explicit.
|
||||
//
|
||||
// There's also a corresponding number of extra external slots to spell out.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1238: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1302: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 2: return Event(Event::Type::SpriteLocation, 4);
|
||||
case 66: return Event(Event::Type::SpriteLocation, 6);
|
||||
case 1270: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1338: return Event(Event::Type::SpritePattern, 2);
|
||||
case 34: return Event(Event::Type::SpritePattern, 4);
|
||||
case 98: return Event(Event::Type::SpritePattern, 6);
|
||||
case 1264: case 1330: case 28: case 92:
|
||||
return Event::Type::External;
|
||||
}
|
||||
}
|
||||
|
||||
if(grauw_index >= 162 && grauw_index < 176) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 162);
|
||||
}
|
||||
|
||||
// Everywhere else the pattern is:
|
||||
//
|
||||
// external or sprite y, external, data block
|
||||
//
|
||||
// Subject to caveats:
|
||||
//
|
||||
// 1) the first data block is just a dummy fetch with no side effects,
|
||||
// so this emulator declines to record it; and
|
||||
// 2) every fourth block, the second external is actually a refresh.
|
||||
//
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
|
||||
switch(sub_block) {
|
||||
default: return std::nullopt;
|
||||
case 0:
|
||||
if(include_sprites) {
|
||||
// Don't include the sprite post-amble (i.e. a spurious read with no side effects).
|
||||
if(block < 32) {
|
||||
return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
}
|
||||
} else {
|
||||
return Event::Type::External;
|
||||
}
|
||||
case 6:
|
||||
if((block & 3) != 3) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
break;
|
||||
case 12:
|
||||
if(block) {
|
||||
return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
static constexpr auto no_sprites_events = events<BitmapGenerator<false>>();
|
||||
static constexpr auto sprites_events = events<BitmapGenerator<true>>();
|
||||
|
||||
struct TextGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// Capture various one-in-eight zones.
|
||||
if(grauw_index < 72) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 2);
|
||||
}
|
||||
if(grauw_index >= 166 && grauw_index < 228) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
if(grauw_index >= 1206 && grauw_index < 1332) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1206);
|
||||
}
|
||||
if(grauw_index == 1336) {
|
||||
return Event::Type::External;
|
||||
}
|
||||
if(grauw_index >= 1346) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 1346);
|
||||
}
|
||||
|
||||
// Elsewhere...
|
||||
if(grauw_index >= 246) {
|
||||
const int offset = grauw_index - 246;
|
||||
const int block = offset / 48;
|
||||
const int sub_block = offset % 48;
|
||||
switch(sub_block) {
|
||||
default: break;
|
||||
case 0: return Event(Event::Type::Name, uint8_t(block));
|
||||
case 18: return (block & 1) ? Event::Type::External : Event(Event::Type::Colour, uint8_t(block >> 1));
|
||||
case 24: return Event(Event::Type::Pattern, uint8_t(block));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
static constexpr auto text_events = events<TextGenerator>();
|
||||
|
||||
struct CharacterGenerator {
|
||||
static constexpr std::optional<Event> event(int grauw_index) {
|
||||
// Grab sprite events.
|
||||
switch(grauw_index) {
|
||||
default: break;
|
||||
case 1242: return Event(Event::Type::SpriteLocation, 0);
|
||||
case 1306: return Event(Event::Type::SpriteLocation, 1);
|
||||
case 6: return Event(Event::Type::SpriteLocation, 2);
|
||||
case 70: return Event(Event::Type::SpriteLocation, 3);
|
||||
case 1274: return Event(Event::Type::SpritePattern, 0);
|
||||
case 1342: return Event(Event::Type::SpritePattern, 1);
|
||||
case 38: return Event(Event::Type::SpritePattern, 2);
|
||||
case 102: return Event(Event::Type::SpritePattern, 3);
|
||||
case 1268: case 1334: case 32: case 96: return Event::Type::External;
|
||||
}
|
||||
|
||||
if(grauw_index >= 166 && grauw_index < 180) {
|
||||
return StandardGenerators::external_every_eight(grauw_index - 166);
|
||||
}
|
||||
|
||||
if(grauw_index >= 182 && grauw_index < 1238) {
|
||||
const int offset = grauw_index - 182;
|
||||
const int block = offset / 32;
|
||||
const int sub_block = offset & 31;
|
||||
switch(sub_block) {
|
||||
case 0: if(block > 0) return Event(Event::Type::Name, uint8_t(block - 1));
|
||||
case 6: if((sub_block & 3) != 3) return Event::Type::External;
|
||||
case 12: if(block < 32) return Event(Event::Type::SpriteY, uint8_t(block));
|
||||
case 18: if(block > 0) return Event(Event::Type::Pattern, uint8_t(block - 1));
|
||||
case 24: if(block > 0) return Event(Event::Type::Colour, uint8_t(block - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
static constexpr auto character_events = events<CharacterGenerator>();
|
||||
};
|
||||
|
||||
// Master System-specific storage.
|
||||
template <Personality personality> struct Storage<personality, std::enable_if_t<is_sega_vdp(personality)>> {
|
||||
using AddressT = uint16_t;
|
||||
|
||||
// The SMS VDP has a programmer-set colour palette, with a dedicated patch of RAM. But the RAM is only exactly
|
||||
// fast enough for the pixel clock. So when the programmer writes to it, that causes a one-pixel glitch; there
|
||||
// isn't the bandwidth for the read both write to occur simultaneously. The following buffer therefore keeps
|
||||
// track of pending collisions, for visual reproduction.
|
||||
struct CRAMDot {
|
||||
LineBufferPointer location;
|
||||
uint32_t value;
|
||||
};
|
||||
std::vector<CRAMDot> upcoming_cram_dots_;
|
||||
|
||||
// The Master System's additional colour RAM.
|
||||
uint32_t colour_ram_[32];
|
||||
bool cram_is_selected_ = false;
|
||||
|
||||
// Programmer-set flags.
|
||||
bool vertical_scroll_lock_ = false;
|
||||
bool horizontal_scroll_lock_ = false;
|
||||
bool hide_left_column_ = false;
|
||||
bool shift_sprites_8px_left_ = false;
|
||||
bool mode4_enable_ = false;
|
||||
uint8_t horizontal_scroll_ = 0;
|
||||
uint8_t vertical_scroll_ = 0;
|
||||
|
||||
// Holds the vertical scroll position for this frame; this is latched
|
||||
// once and cannot dynamically be changed until the next frame.
|
||||
uint8_t latched_vertical_scroll_ = 0;
|
||||
|
||||
// Various resource addresses with VDP-version-specific modifications
|
||||
// built in.
|
||||
AddressT pattern_name_address_;
|
||||
AddressT sprite_attribute_table_address_;
|
||||
AddressT sprite_generator_table_address_;
|
||||
|
||||
void begin_line(ScreenMode, bool) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Storage_h */
|
||||
382
Components/9918/Implementation/YamahaCommands.hpp
Normal file
382
Components/9918/Implementation/YamahaCommands.hpp
Normal file
@@ -0,0 +1,382 @@
|
||||
//
|
||||
// YamahaCommands.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef YamahaCommands_hpp
|
||||
#define YamahaCommands_hpp
|
||||
|
||||
#include "AccessEnums.hpp"
|
||||
|
||||
namespace TI::TMS {
|
||||
|
||||
// MARK: - Generics.
|
||||
|
||||
struct Vector {
|
||||
int v[2]{};
|
||||
|
||||
template <int offset, bool high> void set(uint8_t value) {
|
||||
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
||||
constexpr int shift = high ? 8 : 0;
|
||||
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
|
||||
}
|
||||
|
||||
template <int offset> void add(int amount) {
|
||||
v[offset] += amount;
|
||||
|
||||
if constexpr (offset == 1) {
|
||||
v[offset] &= 0x3ff;
|
||||
} else {
|
||||
v[offset] &= 0x1ff;
|
||||
}
|
||||
}
|
||||
|
||||
Vector & operator += (const Vector &rhs) {
|
||||
add<0>(rhs.v[0]);
|
||||
add<1>(rhs.v[1]);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct Colour {
|
||||
void set(uint8_t value) {
|
||||
colour = value;
|
||||
colour4bpp = uint8_t((value & 0xf) | (value << 4));
|
||||
colour2bpp = uint8_t((colour4bpp & 0x33) | ((colour4bpp & 0x33) << 2));
|
||||
}
|
||||
|
||||
void reset() {
|
||||
colour = 0x00;
|
||||
colour4bpp = 0xff;
|
||||
}
|
||||
|
||||
bool has_value() const {
|
||||
return (colour & 0xf) == (colour4bpp & 0xf);
|
||||
}
|
||||
|
||||
/// Colour as written by the CPU.
|
||||
uint8_t colour = 0x00;
|
||||
/// The low four bits of the CPU-written colour, repeated twice.
|
||||
uint8_t colour4bpp = 0xff;
|
||||
/// The low two bits of the CPU-written colour, repeated four times.
|
||||
uint8_t colour2bpp = 0xff;
|
||||
};
|
||||
|
||||
struct CommandContext {
|
||||
Vector source;
|
||||
Vector destination;
|
||||
Vector size;
|
||||
|
||||
uint8_t arguments = 0;
|
||||
Colour colour;
|
||||
Colour latched_colour;
|
||||
|
||||
enum class LogicalOperation {
|
||||
Copy = 0b0000,
|
||||
And = 0b0001,
|
||||
Or = 0b0010,
|
||||
Xor = 0b0011,
|
||||
Not = 0b0100,
|
||||
};
|
||||
LogicalOperation pixel_operation;
|
||||
bool test_source;
|
||||
};
|
||||
|
||||
struct ModeDescription {
|
||||
int width = 256;
|
||||
int pixels_per_byte = 4;
|
||||
bool rotate_address = false;
|
||||
};
|
||||
|
||||
struct Command {
|
||||
// In net:
|
||||
//
|
||||
// This command is blocked until @c access has been performed, reading
|
||||
// from or writing to @c value. It should not be performed until at least
|
||||
// @c cycles have passed.
|
||||
enum class AccessType {
|
||||
/// Plots a single pixel of the current contextual colour at @c destination,
|
||||
/// which occurs as a read, then a 24-cycle gap, then a write.
|
||||
PlotPoint,
|
||||
|
||||
/// Blocks until the next CPU write to the colour register.
|
||||
WaitForColourReceipt,
|
||||
|
||||
/// Writes an entire byte to the address containing the current @c destination.
|
||||
WriteByte,
|
||||
|
||||
/// Copies a single pixel from @c source location to @c destination,
|
||||
/// being a read, a 32-cycle gap, then a PlotPoint.
|
||||
CopyPoint,
|
||||
|
||||
/// Copies a complete byte from @c source location to @c destination,
|
||||
/// being a read, a 24-cycle gap, then a write.
|
||||
CopyByte,
|
||||
|
||||
/// Copies a single pixel from @c source to the colour status register.
|
||||
ReadPoint,
|
||||
|
||||
// ReadByte,
|
||||
// WaitForColourSend,
|
||||
};
|
||||
AccessType access = AccessType::PlotPoint;
|
||||
int cycles = 0;
|
||||
bool is_cpu_transfer = false;
|
||||
bool y_only = false;
|
||||
|
||||
/// Current command parameters.
|
||||
CommandContext &context;
|
||||
ModeDescription &mode_description;
|
||||
Command(CommandContext &context, ModeDescription &mode_description) : context(context), mode_description(mode_description) {}
|
||||
virtual ~Command() {}
|
||||
|
||||
/// @returns @c true if all output from this command is done; @c false otherwise.
|
||||
virtual bool done() = 0;
|
||||
|
||||
/// Repopulates the fields above with the next action to take, being provided with the
|
||||
/// number of pixels per byte in the current screen mode.
|
||||
virtual void advance() = 0;
|
||||
|
||||
protected:
|
||||
template <int axis, bool include_source> void advance_axis(int offset = 1) {
|
||||
context.destination.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
||||
if constexpr (include_source) {
|
||||
context.source.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace Commands {
|
||||
|
||||
// MARK: - Line drawing.
|
||||
|
||||
/// Implements the LINE command, which is plain-old Bresenham.
|
||||
///
|
||||
/// Per Grauw timing is:
|
||||
///
|
||||
/// * 88 cycles between every pixel plot;
|
||||
/// * plus an additional 32 cycles if a step along the minor axis is taken.
|
||||
struct Line: public Command {
|
||||
public:
|
||||
Line(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
// context.destination = start position;
|
||||
// context.size.v[0] = long side dots;
|
||||
// context.size.v[1] = short side dots;
|
||||
// context.arguments => direction
|
||||
|
||||
position_ = context.size.v[1];
|
||||
numerator_ = position_ << 1;
|
||||
denominator_ = context.size.v[0] << 1;
|
||||
|
||||
cycles = 32;
|
||||
access = AccessType::PlotPoint;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[0];
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
--context.size.v[0];
|
||||
cycles = 88;
|
||||
|
||||
// b0: 1 => long direction is y;
|
||||
// 0 => long direction is x.
|
||||
//
|
||||
// b2: 1 => x direction is left;
|
||||
// 0 => x direction is right.
|
||||
//
|
||||
// b3: 1 => y direction is up;
|
||||
// 0 => y direction is down.
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<1, false>();
|
||||
} else {
|
||||
advance_axis<0, false>();
|
||||
}
|
||||
|
||||
position_ -= numerator_;
|
||||
if(position_ < 0) {
|
||||
position_ += denominator_;
|
||||
cycles += 32;
|
||||
|
||||
if(context.arguments & 0x1) {
|
||||
advance_axis<0, false>();
|
||||
} else {
|
||||
advance_axis<1, false>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int position_, numerator_, denominator_, duration_;
|
||||
};
|
||||
|
||||
// MARK: - Single pixel manipulation.
|
||||
|
||||
/// Implements the PSET command, which plots a single pixel and POINT, which reads one.
|
||||
///
|
||||
/// No timings are documented, so this'll output or input as quickly as possible.
|
||||
template <bool is_read> struct Point: public Command {
|
||||
public:
|
||||
Point(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
cycles = 0; // TODO.
|
||||
access = is_read ? AccessType::ReadPoint : AccessType::PlotPoint;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return done_;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
done_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool done_ = false;
|
||||
};
|
||||
|
||||
// MARK: - Rectangular base.
|
||||
|
||||
/// Useful base class for anything that does logical work in a rectangle.
|
||||
template <bool logical, bool include_source> struct Rectangle: public Command {
|
||||
public:
|
||||
Rectangle(CommandContext &context, ModeDescription &mode_description) : Command(context, mode_description) {
|
||||
if constexpr (include_source) {
|
||||
start_x_[0] = context.source.v[0];
|
||||
}
|
||||
start_x_[1] = context.destination.v[0];
|
||||
width_ = context.size.v[0];
|
||||
|
||||
if(!width_) {
|
||||
// Width = 0 => maximal width for this mode.
|
||||
// (aside: it's still unclear to me whether commands are
|
||||
// automatically clipped to the display; I think so but
|
||||
// don't want to spend any time on it until I'm certain)
|
||||
// context.size.v[0] = width_ = mode_description.width;
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances the current destination and, if @c include_source is @c true also the source;
|
||||
/// @returns @c true if a new row was started; @c false otherwise.
|
||||
bool advance_pixel() {
|
||||
if constexpr (logical) {
|
||||
advance_axis<0, include_source>();
|
||||
--context.size.v[0];
|
||||
|
||||
if(context.size.v[0]) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
advance_axis<0, include_source>(mode_description.pixels_per_byte);
|
||||
context.size.v[0] -= mode_description.pixels_per_byte;
|
||||
|
||||
if(context.size.v[0] & ~(mode_description.pixels_per_byte - 1)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
context.size.v[0] = width_;
|
||||
if constexpr (include_source) {
|
||||
context.source.v[0] = start_x_[0];
|
||||
}
|
||||
context.destination.v[0] = start_x_[1];
|
||||
|
||||
advance_axis<1, include_source>();
|
||||
--context.size.v[1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool done() final {
|
||||
return !context.size.v[1] || !width_;
|
||||
}
|
||||
|
||||
private:
|
||||
int start_x_[2]{}, width_ = 0;
|
||||
};
|
||||
|
||||
// MARK: - Rectangular moves to/from CPU.
|
||||
|
||||
template <bool logical> struct MoveFromCPU: public Rectangle<logical, false> {
|
||||
MoveFromCPU(CommandContext &context, ModeDescription &mode_description) : Rectangle<logical, false>(context, mode_description) {
|
||||
Command::is_cpu_transfer = true;
|
||||
|
||||
// This command is started with the first colour ready to transfer.
|
||||
Command::cycles = 32;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
switch(Command::access) {
|
||||
default: break;
|
||||
|
||||
case Command::AccessType::WaitForColourReceipt:
|
||||
Command::cycles = 32;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
break;
|
||||
|
||||
case Command::AccessType::WriteByte:
|
||||
case Command::AccessType::PlotPoint:
|
||||
Command::cycles = 0;
|
||||
Command::access = Command::AccessType::WaitForColourReceipt;
|
||||
if(Rectangle<logical, false>::advance_pixel()) {
|
||||
Command::cycles = 64;
|
||||
// TODO: I'm not sure this will be honoured per the outer wrapping.
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - Rectangular moves within VRAM.
|
||||
|
||||
enum class MoveType {
|
||||
Logical,
|
||||
HighSpeed,
|
||||
YOnly,
|
||||
};
|
||||
|
||||
template <MoveType type> struct Move: public Rectangle<type == MoveType::Logical, true> {
|
||||
static constexpr bool is_logical = type == MoveType::Logical;
|
||||
static constexpr bool is_y_only = type == MoveType::YOnly;
|
||||
using RectangleBase = Rectangle<is_logical, true>;
|
||||
|
||||
Move(CommandContext &context, ModeDescription &mode_description) : RectangleBase(context, mode_description) {
|
||||
Command::access = is_logical ? Command::AccessType::CopyPoint : Command::AccessType::CopyByte;
|
||||
Command::cycles = is_y_only ? 0 : 64;
|
||||
Command::y_only = is_y_only;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
Command::cycles = is_y_only ? 40 : 64;
|
||||
if(RectangleBase::advance_pixel()) {
|
||||
Command::cycles += is_y_only ? 0 : 64;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// MARK: - Rectangular fills.
|
||||
|
||||
template <bool logical> struct Fill: public Rectangle<logical, false> {
|
||||
using RectangleBase = Rectangle<logical, false>;
|
||||
|
||||
Fill(CommandContext &context, ModeDescription &mode_description) : RectangleBase(context, mode_description) {
|
||||
Command::cycles = logical ? 64 : 56;
|
||||
Command::access = logical ? Command::AccessType::PlotPoint : Command::AccessType::WriteByte;
|
||||
}
|
||||
|
||||
void advance() final {
|
||||
Command::cycles = logical ? 72 : 48;
|
||||
if(RectangleBase::advance_pixel()) {
|
||||
Command::cycles += logical ? 64 : 56;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* YamahaCommands_hpp */
|
||||
@@ -16,7 +16,7 @@
|
||||
using namespace GI::AY38910;
|
||||
|
||||
template <bool is_stereo>
|
||||
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {
|
||||
AY38910<is_stereo>::AY38910(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {
|
||||
// Don't use the low bit of the envelope position if this is an AY.
|
||||
envelope_position_mask_ |= personality == Personality::AY38910;
|
||||
|
||||
@@ -252,7 +252,7 @@ template <bool is_stereo> void AY38910<is_stereo>::set_register_value(uint8_t va
|
||||
// If this is a register that affects audio output, enqueue a mutation onto the
|
||||
// audio generation thread.
|
||||
if(selected_register_ < 14) {
|
||||
task_queue_.defer([this, selected_register = selected_register_, value] () {
|
||||
task_queue_.enqueue([this, selected_register = selected_register_, value] () {
|
||||
// Perform any register-specific mutation to output generation.
|
||||
uint8_t masked_value = value;
|
||||
switch(selected_register) {
|
||||
|
||||
@@ -71,7 +71,7 @@ enum class Personality {
|
||||
template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
/// Creates a new AY38910.
|
||||
AY38910(Personality, Concurrency::DeferringAsyncTaskQueue &);
|
||||
AY38910(Personality, Concurrency::AsyncTaskQueue<false> &);
|
||||
|
||||
/// Sets the value the AY would read from its data lines if it were not outputting.
|
||||
void set_data_input(uint8_t r);
|
||||
@@ -114,7 +114,7 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
|
||||
static constexpr bool get_is_stereo() { return is_stereo; }
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue_;
|
||||
|
||||
int selected_register_ = 0;
|
||||
uint8_t registers_[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#ifndef Apple_RealTimeClock_hpp
|
||||
#define Apple_RealTimeClock_hpp
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Apple {
|
||||
namespace Clock {
|
||||
|
||||
@@ -21,32 +23,36 @@ namespace Clock {
|
||||
*/
|
||||
class ClockStorage {
|
||||
public:
|
||||
ClockStorage() {
|
||||
// TODO: this should persist, if possible, rather than
|
||||
// being default initialised.
|
||||
constexpr uint8_t default_data[] = {
|
||||
0xa8, 0x00, 0x00, 0x00,
|
||||
0xcc, 0x0a, 0xcc, 0x0a,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x63, 0x00,
|
||||
0x03, 0x88, 0x00, 0x4c
|
||||
};
|
||||
memcpy(data_, default_data, sizeof(default_data));
|
||||
memset(&data_[sizeof(default_data)], 0xff, sizeof(data_) - sizeof(default_data));
|
||||
}
|
||||
ClockStorage() {}
|
||||
|
||||
/*!
|
||||
Advances the clock by 1 second.
|
||||
|
||||
The caller should also signal an interrupt.
|
||||
The caller should also signal an interrupt if applicable.
|
||||
*/
|
||||
void update() {
|
||||
for(int c = 0; c < 4; ++c) {
|
||||
for(size_t c = 0; c < 4; ++c) {
|
||||
++seconds_[c];
|
||||
if(seconds_[c]) break;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the current [P/B]RAM contents.
|
||||
*/
|
||||
template <typename CollectionT> void set_data(const CollectionT &collection) {
|
||||
set_data(collection.begin(), collection.end());
|
||||
}
|
||||
|
||||
template <typename IteratorT> void set_data(IteratorT begin, const IteratorT end) {
|
||||
size_t c = 0;
|
||||
while(begin != end && c < 256) {
|
||||
data_[c] = *begin;
|
||||
++begin;
|
||||
++c;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr uint16_t NoResult = 0x100;
|
||||
static constexpr uint16_t DidComplete = 0x101;
|
||||
@@ -92,7 +98,7 @@ class ClockStorage {
|
||||
case 0x30:
|
||||
// Either a register access or an extended instruction.
|
||||
if(command & 0x08) {
|
||||
address_ = (command & 0x7) << 5;
|
||||
address_ = unsigned((command & 0x7) << 5);
|
||||
phase_ = (command & 0x80) ? Phase::SecondAddressByteRead : Phase::SecondAddressByteWrite;
|
||||
return NoResult;
|
||||
} else {
|
||||
@@ -162,10 +168,10 @@ class ClockStorage {
|
||||
|
||||
|
||||
private:
|
||||
uint8_t data_[256];
|
||||
uint8_t seconds_[4];
|
||||
uint8_t write_protect_;
|
||||
int address_;
|
||||
std::array<uint8_t, 256> data_{0xff};
|
||||
std::array<uint8_t, 4> seconds_{};
|
||||
uint8_t write_protect_ = 0;
|
||||
unsigned int address_ = 0;
|
||||
|
||||
static constexpr int SecondsBuffer = 0x100;
|
||||
static constexpr int RegisterTest = 0x200;
|
||||
@@ -257,7 +263,10 @@ class ParallelClock: public ClockStorage {
|
||||
// A no-op for now.
|
||||
} else {
|
||||
// Write to the RTC. Which in this implementation also sets up a future read.
|
||||
data_ = uint8_t(perform(data_));
|
||||
const auto result = perform(data_);
|
||||
if(result < 0x100) {
|
||||
data_ = uint8_t(result);
|
||||
}
|
||||
}
|
||||
|
||||
// MAGIC! The transaction took 0 seconds.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using namespace Audio;
|
||||
|
||||
Audio::Toggle::Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
Audio::Toggle::Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void Toggle::get_samples(std::size_t number_of_samples, std::int16_t *target) {
|
||||
@@ -28,7 +28,7 @@ void Toggle::skip_samples(std::size_t) {}
|
||||
void Toggle::set_output(bool enabled) {
|
||||
if(is_enabled_ == enabled) return;
|
||||
is_enabled_ = enabled;
|
||||
audio_queue_.defer([this, enabled] {
|
||||
audio_queue_.enqueue([this, enabled] {
|
||||
level_ = enabled ? volume_ : 0;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Audio {
|
||||
*/
|
||||
class Toggle: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Toggle(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
Toggle(Concurrency::AsyncTaskQueue<false> &audio_queue);
|
||||
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
void set_sample_volume_range(std::int16_t range);
|
||||
@@ -31,7 +31,7 @@ class Toggle: public Outputs::Speaker::SampleSource {
|
||||
private:
|
||||
// Accessed on the calling thread.
|
||||
bool is_enabled_ = false;
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
Concurrency::AsyncTaskQueue<false> &audio_queue_;
|
||||
|
||||
// Accessed on the audio thread.
|
||||
int16_t level_ = 0, volume_ = 0;
|
||||
|
||||
@@ -22,7 +22,10 @@ namespace {
|
||||
DiskII::DiskII(int clock_rate) :
|
||||
clock_rate_(clock_rate),
|
||||
inputs_(input_command),
|
||||
drives_{{clock_rate, 300, 1}, {clock_rate, 300, 1}}
|
||||
drives_{
|
||||
Storage::Disk::Drive{clock_rate, 300, 1},
|
||||
Storage::Disk::Drive{clock_rate, 300, 1}
|
||||
}
|
||||
{
|
||||
drives_[0].set_clocking_hint_observer(this);
|
||||
drives_[1].set_clocking_hint_observer(this);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
using namespace Konami;
|
||||
|
||||
SCC::SCC(Concurrency::DeferringAsyncTaskQueue &task_queue) :
|
||||
SCC::SCC(Concurrency::AsyncTaskQueue<false> &task_queue) :
|
||||
task_queue_(task_queue) {}
|
||||
|
||||
bool SCC::is_zero_level() const {
|
||||
@@ -55,7 +55,7 @@ void SCC::write(uint16_t address, uint8_t value) {
|
||||
address &= 0xff;
|
||||
if(address < 0x80) ram_[address] = value;
|
||||
|
||||
task_queue_.defer([this, address, value] {
|
||||
task_queue_.enqueue([this, address, value] {
|
||||
// Check for a write into waveform memory.
|
||||
if(address < 0x80) {
|
||||
waves_[address >> 5].samples[address & 0x1f] = value;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Konami {
|
||||
class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
/// Creates a new SCC.
|
||||
SCC(Concurrency::DeferringAsyncTaskQueue &task_queue);
|
||||
SCC(Concurrency::AsyncTaskQueue<false> &task_queue);
|
||||
|
||||
/// As per ::SampleSource; provides a broadphase test for silence.
|
||||
bool is_zero_level() const;
|
||||
@@ -41,7 +41,7 @@ class SCC: public ::Outputs::Speaker::SampleSource {
|
||||
uint8_t read(uint16_t address);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue_;
|
||||
|
||||
// State from here on down is accessed ony from the audio thread.
|
||||
int master_divider_ = 0;
|
||||
|
||||
@@ -26,9 +26,9 @@ template <typename Child> class OPLBase: public ::Outputs::Speaker::SampleSource
|
||||
}
|
||||
|
||||
protected:
|
||||
OPLBase(Concurrency::DeferringAsyncTaskQueue &task_queue) : task_queue_(task_queue) {}
|
||||
OPLBase(Concurrency::AsyncTaskQueue<false> &task_queue) : task_queue_(task_queue) {}
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue_;
|
||||
|
||||
private:
|
||||
uint8_t selected_register_ = 0;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
using namespace Yamaha::OPL;
|
||||
|
||||
OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider, bool is_vrc7):
|
||||
OPLL::OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider, bool is_vrc7):
|
||||
OPLBase(task_queue), audio_divider_(audio_divider), is_vrc7_(is_vrc7) {
|
||||
// Due to the way that sound mixing works on the OPLL, the audio divider may not
|
||||
// be larger than 4.
|
||||
@@ -74,7 +74,7 @@ OPLL::OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider,
|
||||
void OPLL::write_register(uint8_t address, uint8_t value) {
|
||||
// The OPLL doesn't have timers or other non-audio functions, so all writes
|
||||
// go to the audio queue.
|
||||
task_queue_.defer([this, address, value] {
|
||||
task_queue_.enqueue([this, address, value] {
|
||||
// The first 8 locations are used to define the custom instrument, and have
|
||||
// exactly the same format as the patch set arrays at the head of this file.
|
||||
if(address < 8) {
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace OPL {
|
||||
class OPLL: public OPLBase<OPLL> {
|
||||
public:
|
||||
/// Creates a new OPLL or VRC7.
|
||||
OPLL(Concurrency::DeferringAsyncTaskQueue &task_queue, int audio_divider = 1, bool is_vrc7 = false);
|
||||
OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);
|
||||
|
||||
/// As per ::SampleSource; provides audio output.
|
||||
void get_samples(std::size_t number_of_samples, std::int16_t *target);
|
||||
|
||||
323
Components/RP5C01/RP5C01.cpp
Normal file
323
Components/RP5C01/RP5C01.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
//
|
||||
// RP5C01.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "RP5C01.hpp"
|
||||
|
||||
#include "../../Numeric/NumericCoder.hpp"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
using namespace Ricoh::RP5C01;
|
||||
|
||||
RP5C01::RP5C01(HalfCycles clock_rate) : clock_rate_(clock_rate) {
|
||||
// Seed internal clock.
|
||||
std::time_t now = std::time(NULL);
|
||||
std::tm *time_date = std::localtime(&now);
|
||||
|
||||
seconds_ =
|
||||
time_date->tm_sec +
|
||||
time_date->tm_min * 60 +
|
||||
time_date->tm_hour * 60 * 60;
|
||||
|
||||
day_of_the_week_ = time_date->tm_wday;
|
||||
day_ = time_date->tm_mday;
|
||||
month_ = time_date->tm_mon;
|
||||
year_ = (time_date->tm_year + 20) % 100; // This is probably MSX specific; rethink if/when other machines use this chip.
|
||||
leap_year_ = time_date->tm_year % 4;
|
||||
}
|
||||
|
||||
void RP5C01::run_for(HalfCycles cycles) {
|
||||
sub_seconds_ += cycles;
|
||||
|
||||
// Guess: this happens so rarely (i.e. once a second, ordinarily) that
|
||||
// it's not worth worrying about the branch prediction consequences.
|
||||
//
|
||||
// ... and ditto all the conditionals below, which will be very rarely reached.
|
||||
if(sub_seconds_ < clock_rate_) {
|
||||
return;
|
||||
}
|
||||
const auto elapsed_seconds = int(sub_seconds_.as_integral() / clock_rate_.as_integral());
|
||||
sub_seconds_ %= clock_rate_;
|
||||
|
||||
// Update time within day.
|
||||
seconds_ += elapsed_seconds;
|
||||
|
||||
constexpr int day_length = 60 * 60 * 24;
|
||||
if(seconds_ < day_length) {
|
||||
return;
|
||||
}
|
||||
const int elapsed_days = seconds_ / day_length;
|
||||
seconds_ %= day_length;
|
||||
|
||||
// Day of the week doesn't aggregate upwards.
|
||||
day_of_the_week_ = (day_of_the_week_ + elapsed_days) % 7;
|
||||
|
||||
// Assumed for now: day and month run from 0.
|
||||
// A leap year count of 0 implies a leap year.
|
||||
// TODO: verify.
|
||||
day_ += elapsed_days;
|
||||
while(true) {
|
||||
int month_length = 1;
|
||||
switch(month_) {
|
||||
default:
|
||||
case 0: month_length = 31; break;
|
||||
case 1: month_length = 28 + !leap_year_; break;
|
||||
case 2: month_length = 31; break;
|
||||
case 3: month_length = 30; break;
|
||||
case 4: month_length = 31; break;
|
||||
case 5: month_length = 30; break;
|
||||
case 6: month_length = 31; break;
|
||||
case 7: month_length = 31; break;
|
||||
case 8: month_length = 30; break;
|
||||
case 9: month_length = 31; break;
|
||||
case 10: month_length = 30; break;
|
||||
case 11: month_length = 31; break;
|
||||
}
|
||||
|
||||
if(day_ < month_length) {
|
||||
return;
|
||||
}
|
||||
|
||||
day_ -= month_length;
|
||||
++month_;
|
||||
|
||||
if(month_ == 12) {
|
||||
month_ = 0;
|
||||
year_ = (year_ + 1) % 100;
|
||||
leap_year_ = (leap_year_ + 1) & 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int Reg(int mode, int address) {
|
||||
return address | mode << 4;
|
||||
}
|
||||
|
||||
constexpr int PM = 1 << 4;
|
||||
|
||||
constexpr int twenty_four_to_twelve(int hours) {
|
||||
switch(hours) {
|
||||
default: return (hours % 12) + (hours > 12 ? PM : 0);
|
||||
case 0: return 12;
|
||||
case 12: return 12 | PM;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int twelve_to_twenty_four(int hours) {
|
||||
hours = (hours & 0xf) + (hours & PM ? 12 : 0);
|
||||
switch(hours) {
|
||||
default: break;
|
||||
case 24: return 12;
|
||||
case 12: return 0;
|
||||
}
|
||||
return hours;
|
||||
}
|
||||
|
||||
using SecondEncoder = Numeric::NumericCoder<
|
||||
10, 6, // Seconds.
|
||||
10, 6, // Minutes.
|
||||
24 // Hours
|
||||
>;
|
||||
using TwoDigitEncoder = Numeric::NumericCoder<10, 10>;
|
||||
|
||||
}
|
||||
|
||||
/// Performs a write of @c value to @c address.
|
||||
void RP5C01::write(int address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
value &= 0xf;
|
||||
|
||||
// Handle potential RAM accesses.
|
||||
if(address < 0xd && mode_ >= 2) {
|
||||
address += mode_ == 3 ? 13 : 0;
|
||||
ram_[size_t(address)] = value & 0xf;
|
||||
return;
|
||||
}
|
||||
|
||||
switch(Reg(mode_, address)) {
|
||||
default: break;
|
||||
|
||||
// Seconds.
|
||||
case Reg(0, 0x00): SecondEncoder::encode<0>(seconds_, value); break;
|
||||
case Reg(0, 0x01): SecondEncoder::encode<1>(seconds_, value); break;
|
||||
|
||||
// Minutes.
|
||||
case Reg(0, 0x02): SecondEncoder::encode<2>(seconds_, value); break;
|
||||
case Reg(0, 0x03): SecondEncoder::encode<3>(seconds_, value); break;
|
||||
|
||||
// Hours.
|
||||
case Reg(0, 0x04):
|
||||
case Reg(0, 0x05): {
|
||||
int hours = SecondEncoder::decode<4>(seconds_);
|
||||
if(!twentyfour_hour_clock_) {
|
||||
hours = twenty_four_to_twelve(hours);
|
||||
}
|
||||
if(address == 0x4) {
|
||||
TwoDigitEncoder::encode<0>(hours, value);
|
||||
} else {
|
||||
TwoDigitEncoder::encode<1>(hours, value & 3);
|
||||
}
|
||||
if(!twentyfour_hour_clock_) {
|
||||
hours = twelve_to_twenty_four(hours);
|
||||
}
|
||||
SecondEncoder::encode<4>(seconds_, hours);
|
||||
} break;
|
||||
|
||||
// Day of the week.
|
||||
case Reg(0, 0x06): day_of_the_week_ = value % 7; break;
|
||||
|
||||
// Day.
|
||||
case Reg(0, 0x07): TwoDigitEncoder::encode<0>(day_, value); break;
|
||||
case Reg(0, 0x08): TwoDigitEncoder::encode<1>(day_, value & 3); break;
|
||||
|
||||
// Month.
|
||||
case Reg(0, 0x09): TwoDigitEncoder::encode<0>(month_, (value - 1)); break;
|
||||
case Reg(0, 0x0a): TwoDigitEncoder::encode<1>(month_, (value - 1) & 1); break;
|
||||
|
||||
// Year.
|
||||
case Reg(0, 0x0b): TwoDigitEncoder::encode<0>(year_, value); break;
|
||||
case Reg(0, 0x0c): TwoDigitEncoder::encode<1>(year_, value); break;
|
||||
|
||||
// TODO: alarm minutes.
|
||||
case Reg(1, 0x02):
|
||||
case Reg(1, 0x03): break;
|
||||
|
||||
// TODO: alarm hours.
|
||||
case Reg(1, 0x04):
|
||||
case Reg(1, 0x05): break;
|
||||
|
||||
// TODO: alarm day-of-the-week.
|
||||
case Reg(1, 0x06): break;
|
||||
|
||||
// TODO: alarm day.
|
||||
case Reg(1, 0x07):
|
||||
case Reg(1, 0x08): break;
|
||||
|
||||
// 24/12-hour clock.
|
||||
case Reg(1, 0x0a):
|
||||
twentyfour_hour_clock_ = value & 1;
|
||||
break;
|
||||
|
||||
// Lead-year counter.
|
||||
case Reg(1, 0x0b):
|
||||
leap_year_ = value & 3;
|
||||
break;
|
||||
|
||||
//
|
||||
// Registers D–F don't depend on the mode.
|
||||
//
|
||||
|
||||
case Reg(0, 0xd): case Reg(1, 0xd): case Reg(2, 0xd): case Reg(3, 0xd):
|
||||
timer_enabled_ = value & 0x8;
|
||||
alarm_enabled_ = value & 0x4;
|
||||
mode_ = value & 0x3;
|
||||
break;
|
||||
case Reg(0, 0xe): case Reg(1, 0xe): case Reg(2, 0xe): case Reg(3, 0xe):
|
||||
// Test register; unclear what is supposed to happen.
|
||||
break;
|
||||
case Reg(0, 0xf): case Reg(1, 0xf): case Reg(2, 0xf): case Reg(3, 0xf):
|
||||
one_hz_on_ = !(value & 0x8);
|
||||
sixteen_hz_on_ = !(value & 0x4);
|
||||
// TODO: b0 = alarm reset; b1 = timer reset.
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
uint8_t RP5C01::read(int address) {
|
||||
address &= 0xf;
|
||||
|
||||
if(address < 0xd && mode_ >= 2) {
|
||||
address += mode_ == 3 ? 13 : 0;
|
||||
return 0xf0 | ram_[size_t(address)];
|
||||
}
|
||||
|
||||
int value = 0xf;
|
||||
switch(Reg(mode_, address)) {
|
||||
// Second.
|
||||
case Reg(0, 0x00): value = SecondEncoder::decode<0>(seconds_); break;
|
||||
case Reg(0, 0x01): value = SecondEncoder::decode<1>(seconds_); break;
|
||||
|
||||
// Minute.
|
||||
case Reg(0, 0x02): value = SecondEncoder::decode<2>(seconds_); break;
|
||||
case Reg(0, 0x03): value = SecondEncoder::decode<3>(seconds_); break;
|
||||
|
||||
// Hour.
|
||||
case Reg(0, 0x04):
|
||||
case Reg(0, 0x05): {
|
||||
int hours = SecondEncoder::decode<4>(seconds_);
|
||||
if(!twentyfour_hour_clock_) {
|
||||
hours = twenty_four_to_twelve(hours);
|
||||
}
|
||||
if(address == 0x4) {
|
||||
value = TwoDigitEncoder::decode<0>(hours);
|
||||
} else {
|
||||
value = TwoDigitEncoder::decode<1>(hours);
|
||||
}
|
||||
} break;
|
||||
|
||||
// Day-of-the-week.
|
||||
case Reg(0, 0x06): value = day_of_the_week_; break;
|
||||
|
||||
// Day.
|
||||
case Reg(0, 0x07): value = TwoDigitEncoder::decode<0>(day_); break;
|
||||
case Reg(0, 0x08): value = TwoDigitEncoder::decode<1>(day_); break;
|
||||
|
||||
// Month.
|
||||
case Reg(0, 0x09): value = TwoDigitEncoder::decode<0>(month_ + 1); break;
|
||||
case Reg(0, 0x0a): value = TwoDigitEncoder::decode<1>(month_ + 1); break;
|
||||
|
||||
// Year.
|
||||
case Reg(0, 0x0b): value = TwoDigitEncoder::decode<0>(year_); break;
|
||||
case Reg(0, 0x0c): value = TwoDigitEncoder::decode<1>(year_); break;
|
||||
|
||||
// TODO: alarm minutes.
|
||||
case Reg(1, 0x02):
|
||||
case Reg(1, 0x03): break;
|
||||
|
||||
// TODO: alarm hours.
|
||||
case Reg(1, 0x04):
|
||||
case Reg(1, 0x05): break;
|
||||
|
||||
// TODO: alarm day-of-the-week.
|
||||
case Reg(1, 0x06): break;
|
||||
|
||||
// TODO: alarm day.
|
||||
case Reg(1, 0x07):
|
||||
case Reg(1, 0x08): break;
|
||||
|
||||
// 12/24-hour clock.
|
||||
case Reg(1, 0x0a): value = twentyfour_hour_clock_; break;
|
||||
|
||||
// Leap year.
|
||||
case Reg(1, 0x0b): value = leap_year_; break;
|
||||
|
||||
//
|
||||
// Registers D–F don't depend on the mode.
|
||||
//
|
||||
|
||||
case Reg(0, 0xd): case Reg(1, 0xd): case Reg(2, 0xd): case Reg(3, 0xd):
|
||||
value =
|
||||
(timer_enabled_ ? 0x8 : 0x0) |
|
||||
(alarm_enabled_ ? 0x4 : 0x0) |
|
||||
mode_;
|
||||
break;
|
||||
case Reg(0, 0xe): case Reg(1, 0xe): case Reg(2, 0xe): case Reg(3, 0xe):
|
||||
// Test register; unclear what is supposed to happen.
|
||||
break;
|
||||
case Reg(0, 0xf): case Reg(1, 0xf): case Reg(2, 0xf): case Reg(3, 0xf):
|
||||
value =
|
||||
(one_hz_on_ ? 0x0 : 0x8) |
|
||||
(sixteen_hz_on_ ? 0x0 : 0x4);
|
||||
break;
|
||||
}
|
||||
|
||||
return uint8_t(0xf0 | value);
|
||||
}
|
||||
63
Components/RP5C01/RP5C01.hpp
Normal file
63
Components/RP5C01/RP5C01.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
//
|
||||
// RP5C01.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/01/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef RP5C01_hpp
|
||||
#define RP5C01_hpp
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Ricoh {
|
||||
namespace RP5C01 {
|
||||
|
||||
class RP5C01 {
|
||||
public:
|
||||
RP5C01(HalfCycles clock_rate);
|
||||
|
||||
/// @returns the result of a read from @c address.
|
||||
uint8_t read(int address);
|
||||
|
||||
/// Performs a write of @c value to @c address.
|
||||
void write(int address, uint8_t value);
|
||||
|
||||
/// Advances time.
|
||||
void run_for(HalfCycles);
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 26> ram_;
|
||||
|
||||
HalfCycles sub_seconds_;
|
||||
const HalfCycles clock_rate_;
|
||||
|
||||
// Contains the seconds, minutes and hours fields.
|
||||
int seconds_ = 0;
|
||||
|
||||
// Calendar entries.
|
||||
int day_of_the_week_ = 0;
|
||||
int day_ = 0;
|
||||
int month_ = 0;
|
||||
int year_ = 0;
|
||||
int leap_year_ = 0;
|
||||
|
||||
// Other flags.
|
||||
bool timer_enabled_ = false;
|
||||
bool alarm_enabled_ = false;
|
||||
int mode_ = 0;
|
||||
bool one_hz_on_ = false;
|
||||
bool sixteen_hz_on_ = false;
|
||||
bool twentyfour_hour_clock_ = true;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#endif /* RP5C01_hpp */
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
using namespace TI;
|
||||
|
||||
SN76489::SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider) : task_queue_(task_queue) {
|
||||
SN76489::SN76489(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue, int additional_divider) : task_queue_(task_queue) {
|
||||
set_sample_volume_range(0);
|
||||
|
||||
switch(personality) {
|
||||
@@ -49,7 +49,7 @@ void SN76489::set_sample_volume_range(std::int16_t range) {
|
||||
}
|
||||
|
||||
void SN76489::write(uint8_t value) {
|
||||
task_queue_.defer([value, this] () {
|
||||
task_queue_.enqueue([value, this] () {
|
||||
if(value & 0x80) {
|
||||
active_register_ = value;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
|
||||
};
|
||||
|
||||
/// Creates a new SN76489.
|
||||
SN76489(Personality personality, Concurrency::DeferringAsyncTaskQueue &task_queue, int additional_divider = 1);
|
||||
SN76489(Personality personality, Concurrency::AsyncTaskQueue<false> &task_queue, int additional_divider = 1);
|
||||
|
||||
/// Writes a new value to the SN76489.
|
||||
void write(uint8_t value);
|
||||
@@ -41,7 +41,7 @@ class SN76489: public Outputs::Speaker::SampleSource {
|
||||
void evaluate_output_volume();
|
||||
int volumes_[16];
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &task_queue_;
|
||||
Concurrency::AsyncTaskQueue<false> &task_queue_;
|
||||
|
||||
struct ToneChannel {
|
||||
// Programmatically-set state; updated by the processor.
|
||||
|
||||
@@ -8,13 +8,18 @@
|
||||
|
||||
#include "Line.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
using namespace Serial;
|
||||
|
||||
void Line::set_writer_clock_rate(HalfCycles clock_rate) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::set_writer_clock_rate(HalfCycles clock_rate) {
|
||||
clock_rate_ = clock_rate;
|
||||
}
|
||||
|
||||
void Line::advance_writer(HalfCycles cycles) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::advance_writer(HalfCycles cycles) {
|
||||
if(cycles == HalfCycles(0)) return;
|
||||
|
||||
const auto integral_cycles = cycles.as_integral();
|
||||
@@ -25,7 +30,9 @@ void Line::advance_writer(HalfCycles cycles) {
|
||||
transmission_extra_ -= integral_cycles;
|
||||
if(transmission_extra_ <= 0) {
|
||||
transmission_extra_ = 0;
|
||||
update_delegate(level_);
|
||||
if constexpr (!include_clock) {
|
||||
update_delegate(level_);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -38,12 +45,17 @@ void Line::advance_writer(HalfCycles cycles) {
|
||||
auto iterator = events_.begin() + 1;
|
||||
while(iterator != events_.end() && iterator->type != Event::Delay) {
|
||||
level_ = iterator->type == Event::SetHigh;
|
||||
if constexpr(include_clock) {
|
||||
update_delegate(level_);
|
||||
}
|
||||
++iterator;
|
||||
}
|
||||
events_.erase(events_.begin(), iterator);
|
||||
|
||||
if(old_level != level_) {
|
||||
update_delegate(old_level);
|
||||
if constexpr (!include_clock) {
|
||||
if(old_level != level_) {
|
||||
update_delegate(old_level);
|
||||
}
|
||||
}
|
||||
|
||||
// Book enough extra time for the read delegate to be posted
|
||||
@@ -60,7 +72,8 @@ void Line::advance_writer(HalfCycles cycles) {
|
||||
}
|
||||
}
|
||||
|
||||
void Line::write(bool level) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::write(bool level) {
|
||||
if(!events_.empty()) {
|
||||
events_.emplace_back();
|
||||
events_.back().type = level ? Event::SetHigh : Event::SetLow;
|
||||
@@ -70,7 +83,8 @@ void Line::write(bool level) {
|
||||
}
|
||||
}
|
||||
|
||||
void Line::write(HalfCycles cycles, int count, int levels) {
|
||||
template <bool include_clock>
|
||||
template <bool lsb_first, typename IntT> void Line<include_clock>::write_internal(HalfCycles cycles, int count, IntT levels) {
|
||||
remaining_delays_ += count * cycles.as_integral();
|
||||
|
||||
auto event = events_.size();
|
||||
@@ -78,63 +92,122 @@ void Line::write(HalfCycles cycles, int count, int levels) {
|
||||
while(count--) {
|
||||
events_[event].type = Event::Delay;
|
||||
events_[event].delay = int(cycles.as_integral());
|
||||
events_[event+1].type = (levels&1) ? Event::SetHigh : Event::SetLow;
|
||||
levels >>= 1;
|
||||
IntT bit;
|
||||
if constexpr (lsb_first) {
|
||||
bit = levels & 1;
|
||||
levels >>= 1;
|
||||
} else {
|
||||
constexpr auto top_bit = IntT(0x80) << ((sizeof(IntT) - 1) * 8);
|
||||
bit = levels & top_bit;
|
||||
levels <<= 1;
|
||||
}
|
||||
|
||||
events_[event+1].type = bit ? Event::SetHigh : Event::SetLow;
|
||||
event += 2;
|
||||
}
|
||||
}
|
||||
|
||||
void Line::reset_writing() {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::write(HalfCycles cycles, int count, int levels) {
|
||||
write_internal<true, int>(cycles, count, levels);
|
||||
}
|
||||
|
||||
template <bool include_clock>
|
||||
template <bool lsb_first, typename IntT> void Line<include_clock>::write(HalfCycles cycles, IntT value) {
|
||||
write_internal<lsb_first, IntT>(cycles, 8 * sizeof(IntT), value);
|
||||
}
|
||||
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::reset_writing() {
|
||||
remaining_delays_ = 0;
|
||||
events_.clear();
|
||||
}
|
||||
|
||||
bool Line::read() const {
|
||||
template <bool include_clock>
|
||||
bool Line<include_clock>::read() const {
|
||||
return level_;
|
||||
}
|
||||
|
||||
void Line::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length) {
|
||||
read_delegate_ = delegate;
|
||||
read_delegate_bit_length_ = bit_length;
|
||||
read_delegate_bit_length_.simplify();
|
||||
write_cycles_since_delegate_call_ = 0;
|
||||
if constexpr (!include_clock) {
|
||||
assert(bit_length > Storage::Time(0));
|
||||
read_delegate_bit_length_ = bit_length;
|
||||
read_delegate_bit_length_.simplify();
|
||||
write_cycles_since_delegate_call_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Line::update_delegate(bool level) {
|
||||
template <bool include_clock>
|
||||
void Line<include_clock>::update_delegate(bool level) {
|
||||
// Exit early if there's no delegate, or if the delegate is waiting for
|
||||
// zero and this isn't zero.
|
||||
if(!read_delegate_) return;
|
||||
|
||||
const int cycles_to_forward = write_cycles_since_delegate_call_;
|
||||
write_cycles_since_delegate_call_ = 0;
|
||||
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
|
||||
if constexpr (!include_clock) {
|
||||
const int cycles_to_forward = write_cycles_since_delegate_call_;
|
||||
write_cycles_since_delegate_call_ = 0;
|
||||
if(level && read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) return;
|
||||
|
||||
// Deal with a transition out of waiting-for-zero mode by seeding time left
|
||||
// in bit at half a bit.
|
||||
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
|
||||
time_left_in_bit_ = read_delegate_bit_length_;
|
||||
time_left_in_bit_.clock_rate <<= 1;
|
||||
read_delegate_phase_ = ReadDelegatePhase::Serialising;
|
||||
}
|
||||
|
||||
// Forward as many bits as occur.
|
||||
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
|
||||
const int bit = level ? 1 : 0;
|
||||
int bits = 0;
|
||||
while(time_left >= time_left_in_bit_) {
|
||||
++bits;
|
||||
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
|
||||
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
|
||||
if(bit) return;
|
||||
// Deal with a transition out of waiting-for-zero mode by seeding time left
|
||||
// in bit at half a bit.
|
||||
if(read_delegate_phase_ == ReadDelegatePhase::WaitingForZero) {
|
||||
time_left_in_bit_ = read_delegate_bit_length_;
|
||||
time_left_in_bit_.clock_rate <<= 1;
|
||||
read_delegate_phase_ = ReadDelegatePhase::Serialising;
|
||||
}
|
||||
|
||||
time_left -= time_left_in_bit_;
|
||||
time_left_in_bit_ = read_delegate_bit_length_;
|
||||
// Forward as many bits as occur.
|
||||
Storage::Time time_left(cycles_to_forward, int(clock_rate_.as_integral()));
|
||||
const int bit = level ? 1 : 0;
|
||||
int bits = 0;
|
||||
while(time_left >= time_left_in_bit_) {
|
||||
++bits;
|
||||
if(!read_delegate_->serial_line_did_produce_bit(this, bit)) {
|
||||
read_delegate_phase_ = ReadDelegatePhase::WaitingForZero;
|
||||
if(bit) return;
|
||||
}
|
||||
|
||||
time_left -= time_left_in_bit_;
|
||||
time_left_in_bit_ = read_delegate_bit_length_;
|
||||
}
|
||||
time_left_in_bit_ -= time_left;
|
||||
} else {
|
||||
read_delegate_->serial_line_did_produce_bit(this, level);
|
||||
}
|
||||
time_left_in_bit_ -= time_left;
|
||||
}
|
||||
|
||||
Cycles::IntType Line::minimum_write_cycles_for_read_delegate_bit() {
|
||||
template <bool include_clock>
|
||||
Cycles::IntType Line<include_clock>::minimum_write_cycles_for_read_delegate_bit() {
|
||||
if(!read_delegate_) return 0;
|
||||
return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).get<int>();
|
||||
return 1 + (read_delegate_bit_length_ * unsigned(clock_rate_.as_integral())).template get<int>();
|
||||
}
|
||||
|
||||
//
|
||||
// Explicitly instantiate the meaningful instances of templates above;
|
||||
// this class uses templates primarily to keep the interface compact and
|
||||
// to take advantage of constexpr functionality selection, not so as
|
||||
// to be generic.
|
||||
//
|
||||
|
||||
template class Serial::Line<true>;
|
||||
template class Serial::Line<false>;
|
||||
|
||||
template void Line<true>::write<true, uint8_t>(HalfCycles, uint8_t);
|
||||
template void Line<true>::write<false, uint8_t>(HalfCycles, uint8_t);
|
||||
template void Line<true>::write<true, uint16_t>(HalfCycles, uint16_t);
|
||||
template void Line<true>::write<false, uint16_t>(HalfCycles, uint16_t);
|
||||
template void Line<true>::write<true, uint32_t>(HalfCycles, uint32_t);
|
||||
template void Line<true>::write<false, uint32_t>(HalfCycles, uint32_t);
|
||||
template void Line<true>::write<true, uint64_t>(HalfCycles, uint64_t);
|
||||
template void Line<true>::write<false, uint64_t>(HalfCycles, uint64_t);
|
||||
|
||||
template void Line<false>::write<true, uint8_t>(HalfCycles, uint8_t);
|
||||
template void Line<false>::write<false, uint8_t>(HalfCycles, uint8_t);
|
||||
template void Line<false>::write<true, uint16_t>(HalfCycles, uint16_t);
|
||||
template void Line<false>::write<false, uint16_t>(HalfCycles, uint16_t);
|
||||
template void Line<false>::write<true, uint32_t>(HalfCycles, uint32_t);
|
||||
template void Line<false>::write<false, uint32_t>(HalfCycles, uint32_t);
|
||||
template void Line<false>::write<true, uint64_t>(HalfCycles, uint64_t);
|
||||
template void Line<false>::write<false, uint64_t>(HalfCycles, uint64_t);
|
||||
|
||||
@@ -17,25 +17,42 @@
|
||||
namespace Serial {
|
||||
|
||||
/*!
|
||||
@c Line connects a single reader and a single writer, allowing timestamped events to be
|
||||
published and consumed, potentially with a clock conversion in between. It allows line
|
||||
levels to be written and read in larger collections.
|
||||
Models one of two connections, either:
|
||||
|
||||
It is assumed that the owner of the reader and writer will ensure that the reader will never
|
||||
get ahead of the writer. If the writer posts events behind the reader they will simply be
|
||||
given instanteous effect.
|
||||
(i) a plain single-line serial; or
|
||||
(ii) a two-line data + clock.
|
||||
|
||||
In both cases connects a single reader to a single writer.
|
||||
|
||||
When operating as a single-line serial connection:
|
||||
|
||||
Provides a mechanism for the writer to enqueue levels arbitrarily far
|
||||
ahead of the current time, which are played back only as the
|
||||
write queue advances. Permits the reader and writer to work at
|
||||
different clock rates, and provides a virtual delegate protocol with
|
||||
start bit detection.
|
||||
|
||||
Can alternatively be used by reader and/or writer only in immediate
|
||||
mode, getting or setting the current level now, without the actor on
|
||||
the other end having to have made the same decision.
|
||||
|
||||
When operating as a two-line connection:
|
||||
|
||||
Implies a clock over enqueued data and provides the reader with
|
||||
all enqueued bits at appropriate times.
|
||||
*/
|
||||
class Line {
|
||||
template <bool include_clock> class Line {
|
||||
public:
|
||||
void set_writer_clock_rate(HalfCycles clock_rate);
|
||||
|
||||
/// Advances the read position by @c cycles relative to the writer's
|
||||
/// clock rate.
|
||||
void advance_writer(HalfCycles cycles);
|
||||
|
||||
/// Sets the line to @c level.
|
||||
/// Sets the line to @c level instantaneously.
|
||||
void write(bool level);
|
||||
|
||||
/// @returns The instantaneous level of this line.
|
||||
bool read() const;
|
||||
|
||||
/// Sets the denominator for the between levels for any data enqueued
|
||||
/// via an @c write.
|
||||
void set_writer_clock_rate(HalfCycles clock_rate);
|
||||
|
||||
/// Enqueues @c count level changes, the first occurring immediately
|
||||
/// after the final event currently posted and each subsequent event
|
||||
/// occurring @c cycles after the previous. An additional gap of @c cycles
|
||||
@@ -44,6 +61,10 @@ class Line {
|
||||
/// relative to the writer's clock rate.
|
||||
void write(HalfCycles cycles, int count, int levels);
|
||||
|
||||
/// Enqueus every bit from @c value as per the rules of write(HalfCycles, int, int),
|
||||
/// either in LSB or MSB order as per the @c lsb_first template flag.
|
||||
template <bool lsb_first, typename IntT> void write(HalfCycles cycles, IntT value);
|
||||
|
||||
/// @returns the number of cycles until currently enqueued write data is exhausted.
|
||||
forceinline HalfCycles write_data_time_remaining() const {
|
||||
return HalfCycles(remaining_delays_);
|
||||
@@ -55,25 +76,36 @@ class Line {
|
||||
return HalfCycles(remaining_delays_ + transmission_extra_);
|
||||
}
|
||||
|
||||
/// Advances the read position by @c cycles relative to the writer's
|
||||
/// clock rate.
|
||||
void advance_writer(HalfCycles cycles);
|
||||
|
||||
/// Eliminates all future write states, leaving the output at whatever it is now.
|
||||
void reset_writing();
|
||||
|
||||
/// @returns The instantaneous level of this line.
|
||||
bool read() const;
|
||||
|
||||
struct ReadDelegate {
|
||||
virtual bool serial_line_did_produce_bit(Line *line, int bit) = 0;
|
||||
};
|
||||
/*!
|
||||
Sets a read delegate, which will receive samples of the output level every
|
||||
@c bit_lengths of a second apart subject to a state machine:
|
||||
Sets a read delegate.
|
||||
|
||||
* initially no bits will be delivered;
|
||||
* when a zero level is first detected, the line will wait half a bit's length, then start
|
||||
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
|
||||
* as soon as the delegate returns @c false, the line will return to the initial state.
|
||||
Single line serial connection:
|
||||
|
||||
The delegate will receive samples of the output level every
|
||||
@c bit_lengths of a second apart subject to a state machine:
|
||||
|
||||
* initially no bits will be delivered;
|
||||
* when a zero level is first detected, the line will wait half a bit's length, then start
|
||||
sampling at single-bit intervals, passing each bit to the delegate while it returns @c true;
|
||||
* as soon as the delegate returns @c false, the line will return to the initial state.
|
||||
|
||||
Two-line clock + data connection:
|
||||
|
||||
The delegate will receive every bit that has been enqueued, spaced as nominated
|
||||
by the writer. @c bit_length is ignored, as is the return result of
|
||||
@c ReadDelegate::serial_line_did_produce_bit.
|
||||
*/
|
||||
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length);
|
||||
void set_read_delegate(ReadDelegate *delegate, Storage::Time bit_length = Storage::Time());
|
||||
|
||||
private:
|
||||
struct Event {
|
||||
@@ -98,6 +130,9 @@ class Line {
|
||||
|
||||
void update_delegate(bool level);
|
||||
HalfCycles::IntType minimum_write_cycles_for_read_delegate_bit();
|
||||
|
||||
template <bool lsb_first, typename IntT> void
|
||||
write_internal(HalfCycles, int, IntT);
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
//
|
||||
// AsyncTaskQueue.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/10/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "AsyncTaskQueue.hpp"
|
||||
|
||||
using namespace Concurrency;
|
||||
|
||||
AsyncTaskQueue::AsyncTaskQueue()
|
||||
#ifndef USE_GCD
|
||||
: should_destruct_(false)
|
||||
#endif
|
||||
{
|
||||
#ifdef USE_GCD
|
||||
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
|
||||
#else
|
||||
thread_ = std::make_unique<std::thread>([this]() {
|
||||
while(!should_destruct_) {
|
||||
std::function<void(void)> next_function;
|
||||
|
||||
// Take lock, check for a new task
|
||||
std::unique_lock lock(queue_mutex_);
|
||||
if(!pending_tasks_.empty()) {
|
||||
next_function = pending_tasks_.front();
|
||||
pending_tasks_.pop_front();
|
||||
}
|
||||
|
||||
if(next_function) {
|
||||
// If there is a task, release lock and perform it
|
||||
lock.unlock();
|
||||
next_function();
|
||||
} else {
|
||||
// If there isn't a task, atomically block on the processing condition and release the lock
|
||||
// until there's something pending (and then release it again via scope)
|
||||
processing_condition_.wait(lock);
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef USE_GCD
|
||||
flush();
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
serial_dispatch_queue_ = nullptr;
|
||||
#else
|
||||
should_destruct_ = true;
|
||||
enqueue([](){});
|
||||
thread_->join();
|
||||
thread_.reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
#ifdef USE_GCD
|
||||
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||
#else
|
||||
std::lock_guard lock(queue_mutex_);
|
||||
pending_tasks_.push_back(function);
|
||||
processing_condition_.notify_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::flush() {
|
||||
#ifdef USE_GCD
|
||||
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||
#else
|
||||
auto flush_mutex = std::make_shared<std::mutex>();
|
||||
auto flush_condition = std::make_shared<std::condition_variable>();
|
||||
std::unique_lock lock(*flush_mutex);
|
||||
enqueue([=] () {
|
||||
std::unique_lock inner_lock(*flush_mutex);
|
||||
flush_condition->notify_all();
|
||||
});
|
||||
flush_condition->wait(lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
DeferringAsyncTaskQueue::~DeferringAsyncTaskQueue() {
|
||||
perform();
|
||||
flush();
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::defer(std::function<void(void)> function) {
|
||||
if(!deferred_tasks_) {
|
||||
deferred_tasks_ = std::make_shared<std::list<std::function<void(void)>>>();
|
||||
}
|
||||
deferred_tasks_->push_back(function);
|
||||
}
|
||||
|
||||
void DeferringAsyncTaskQueue::perform() {
|
||||
if(!deferred_tasks_) return;
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks = deferred_tasks_;
|
||||
deferred_tasks_.reset();
|
||||
enqueue([deferred_tasks] {
|
||||
for(const auto &function : *deferred_tasks) {
|
||||
function();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -12,87 +12,182 @@
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
#include <dispatch/dispatch.h>
|
||||
#define USE_GCD
|
||||
#endif
|
||||
#include "../ClockReceiver/TimeTypes.hpp"
|
||||
|
||||
namespace Concurrency {
|
||||
|
||||
/*!
|
||||
An async task queue allows a caller to enqueue void(void) functions. Those functions are guaranteed
|
||||
to be performed serially and asynchronously from the caller. A caller may also request to flush,
|
||||
causing it to block until all previously-enqueued functions are complete.
|
||||
*/
|
||||
class AsyncTaskQueue {
|
||||
public:
|
||||
AsyncTaskQueue();
|
||||
virtual ~AsyncTaskQueue();
|
||||
/// An implementation detail; provides the time-centric part of a TaskQueue with a real Performer.
|
||||
template <typename Performer> struct TaskQueueStorage {
|
||||
template <typename... Args> TaskQueueStorage(Args&&... args) :
|
||||
performer(std::forward<Args>(args)...),
|
||||
last_fired_(Time::nanos_now()) {}
|
||||
|
||||
/*!
|
||||
Adds @c function to the queue.
|
||||
Performer performer;
|
||||
|
||||
@discussion Functions will be performed serially and asynchronously. This method is safe to
|
||||
call from multiple threads.
|
||||
@parameter function The function to enqueue.
|
||||
*/
|
||||
void enqueue(std::function<void(void)> function);
|
||||
|
||||
/*!
|
||||
Blocks the caller until all previously-enqueud functions have completed.
|
||||
*/
|
||||
void flush();
|
||||
protected:
|
||||
void update() {
|
||||
auto time_now = Time::nanos_now();
|
||||
performer.perform(time_now - last_fired_);
|
||||
last_fired_ = time_now;
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef USE_GCD
|
||||
dispatch_queue_t serial_dispatch_queue_;
|
||||
#else
|
||||
std::unique_ptr<std::thread> thread_;
|
||||
Time::Nanos last_fired_;
|
||||
};
|
||||
|
||||
std::mutex queue_mutex_;
|
||||
std::list<std::function<void(void)>> pending_tasks_;
|
||||
std::condition_variable processing_condition_;
|
||||
std::atomic_bool should_destruct_;
|
||||
#endif
|
||||
/// An implementation detail; provides a no-op implementation of time advances for TaskQueues without a Performer.
|
||||
template <> struct TaskQueueStorage<void> {
|
||||
TaskQueueStorage() {}
|
||||
|
||||
protected:
|
||||
void update() {}
|
||||
};
|
||||
|
||||
/*!
|
||||
A deferring async task queue is one that accepts a list of functions to be performed but defers
|
||||
any action until told to perform. It performs them by enquing a single asynchronous task that will
|
||||
perform the deferred tasks in order.
|
||||
A task queue allows a caller to enqueue @c void(void) functions. Those functions are guaranteed
|
||||
to be performed serially and asynchronously from the caller.
|
||||
|
||||
It therefore offers similar semantics to an asynchronous task queue, but allows for management of
|
||||
synchronisation costs, since neither defer nor perform make any effort to be thread safe.
|
||||
If @c perform_automatically is true, functions will be performed as soon as is possible,
|
||||
at the cost of thread synchronisation.
|
||||
|
||||
If @c perform_automatically is false, functions will be queued up but not dispatched
|
||||
until a call to perform().
|
||||
|
||||
If a @c Performer type is supplied then a public member, @c performer will be constructed
|
||||
with the arguments supplied to TaskQueue's constructor. That instance will receive calls of the
|
||||
form @c .perform(nanos) before every batch of new actions, indicating how much time has
|
||||
passed since the previous @c perform.
|
||||
|
||||
@note Even if @c perform_automatically is true, actions may be batched, when a long-running
|
||||
action occupies the asynchronous thread for long enough. So it is not true that @c perform will be
|
||||
called once per action.
|
||||
*/
|
||||
class DeferringAsyncTaskQueue: public AsyncTaskQueue {
|
||||
template <bool perform_automatically, bool start_immediately = true, typename Performer = void> class AsyncTaskQueue: public TaskQueueStorage<Performer> {
|
||||
public:
|
||||
~DeferringAsyncTaskQueue();
|
||||
template <typename... Args> AsyncTaskQueue(Args&&... args) :
|
||||
TaskQueueStorage<Performer>(std::forward<Args>(args)...) {
|
||||
if constexpr (start_immediately) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Adds a function to the deferral list.
|
||||
/// Enqueus @c post_action to be performed asynchronously at some point
|
||||
/// in the future. If @c perform_automatically is @c true then the action
|
||||
/// will be performed as soon as possible. Otherwise it will sit unsheculed until
|
||||
/// a call to @c perform().
|
||||
///
|
||||
/// Actions may be elided.
|
||||
///
|
||||
/// If this TaskQueue has a @c Performer then the action will be performed
|
||||
/// on the same thread as the performer, after the performer has been updated
|
||||
/// to 'now'.
|
||||
void enqueue(const std::function<void(void)> &post_action) {
|
||||
std::lock_guard guard(condition_mutex_);
|
||||
actions_.push_back(post_action);
|
||||
|
||||
This is not thread safe; it should be serialised with other calls to itself and to perform.
|
||||
*/
|
||||
void defer(std::function<void(void)> function);
|
||||
if constexpr (perform_automatically) {
|
||||
condition_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Enqueues a function that will perform all currently deferred functions, in the
|
||||
order that they were deferred.
|
||||
/// Causes any enqueued actions that are not yet scheduled to be scheduled.
|
||||
void perform() {
|
||||
if(actions_.empty()) {
|
||||
return;
|
||||
}
|
||||
condition_.notify_all();
|
||||
}
|
||||
|
||||
This is not thread safe; it should be serialised with other calls to itself and to defer.
|
||||
*/
|
||||
void perform();
|
||||
/// Permanently stops this task queue, blocking until that has happened.
|
||||
/// All pending actions will be performed first.
|
||||
///
|
||||
/// The queue cannot be restarted; this is a destructive action.
|
||||
void stop() {
|
||||
if(thread_.joinable()) {
|
||||
should_quit_ = true;
|
||||
enqueue([] {});
|
||||
if constexpr (!perform_automatically) {
|
||||
perform();
|
||||
}
|
||||
thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the queue if it has never been started before.
|
||||
///
|
||||
/// This is not guaranteed safely to restart a stopped queue.
|
||||
void start() {
|
||||
thread_ = std::move(std::thread{
|
||||
[this] {
|
||||
ActionVector actions;
|
||||
|
||||
// Continue until told to quit.
|
||||
while(!should_quit_) {
|
||||
// Wait for new actions to be signalled, and grab them.
|
||||
std::unique_lock lock(condition_mutex_);
|
||||
while(actions_.empty()) {
|
||||
condition_.wait(lock);
|
||||
}
|
||||
std::swap(actions, actions_);
|
||||
lock.unlock();
|
||||
|
||||
// Update to now (which is possibly a no-op).
|
||||
TaskQueueStorage<Performer>::update();
|
||||
|
||||
// Perform the actions and destroy them.
|
||||
for(const auto &action: actions) {
|
||||
action();
|
||||
}
|
||||
actions.clear();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Schedules any remaining unscheduled work, then blocks synchronously
|
||||
/// until all scheduled work has been performed.
|
||||
void flush() {
|
||||
std::mutex flush_mutex;
|
||||
std::condition_variable flush_condition;
|
||||
bool has_run = false;
|
||||
std::unique_lock lock(flush_mutex);
|
||||
|
||||
enqueue([&flush_mutex, &flush_condition, &has_run] () {
|
||||
std::unique_lock inner_lock(flush_mutex);
|
||||
has_run = true;
|
||||
flush_condition.notify_all();
|
||||
});
|
||||
|
||||
if constexpr (!perform_automatically) {
|
||||
perform();
|
||||
}
|
||||
|
||||
flush_condition.wait(lock, [&has_run] { return has_run; });
|
||||
}
|
||||
|
||||
~AsyncTaskQueue() {
|
||||
stop();
|
||||
}
|
||||
|
||||
private:
|
||||
// TODO: this is a shared_ptr because of the issues capturing moveables in C++11;
|
||||
// switch to a unique_ptr if/when adapting to C++14
|
||||
std::shared_ptr<std::list<std::function<void(void)>>> deferred_tasks_;
|
||||
// The list of actions waiting be performed. These will be elided,
|
||||
// increasing their latency, if the emulation thread falls behind.
|
||||
using ActionVector = std::vector<std::function<void(void)>>;
|
||||
ActionVector actions_;
|
||||
|
||||
// Necessary synchronisation parts.
|
||||
std::atomic<bool> should_quit_ = false;
|
||||
std::mutex condition_mutex_;
|
||||
std::condition_variable condition_;
|
||||
|
||||
// Ensure the thread isn't constructed until after the mutex
|
||||
// and condition variable.
|
||||
std::thread thread_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Concurrency_hpp */
|
||||
#endif /* AsyncTaskQueue_hpp */
|
||||
|
||||
@@ -36,7 +36,10 @@ class Joystick {
|
||||
// Fire buttons.
|
||||
Fire,
|
||||
// Other labelled keys.
|
||||
Key
|
||||
Key,
|
||||
|
||||
// The maximum value this enum can contain.
|
||||
Max = Key
|
||||
};
|
||||
const Type type;
|
||||
|
||||
|
||||
245
InstructionSets/6809/OperationMapper.hpp
Normal file
245
InstructionSets/6809/OperationMapper.hpp
Normal file
@@ -0,0 +1,245 @@
|
||||
//
|
||||
// OperationMapper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/03/2023.
|
||||
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M6809_OperationMapper_hpp
|
||||
#define InstructionSets_M6809_OperationMapper_hpp
|
||||
|
||||
// Cf. https://techheap.packetizer.com/processors/6809/6809Instructions.html
|
||||
//
|
||||
// Subject to corrections:
|
||||
//
|
||||
// * CWAI and the pushes and pulls at 0x3x are immediate, not inherent.
|
||||
namespace InstructionSet {
|
||||
namespace M6809 {
|
||||
|
||||
enum class AddressingMode {
|
||||
Illegal,
|
||||
|
||||
Inherent,
|
||||
Immediate,
|
||||
Direct,
|
||||
Relative, // TODO: is it worth breaking this into 8- and 16-bit versions?
|
||||
Variant,
|
||||
Indexed,
|
||||
Extended,
|
||||
};
|
||||
|
||||
enum class Operation {
|
||||
None,
|
||||
|
||||
SUBB, CMPB, SBCB, ADDD, ANDB, BITB, LDB, STB,
|
||||
EORB, ADCB, ORB, ADDB, LDD, STD, LDU, STU,
|
||||
SUBA, CMPA, SBCA, SUBD, ANDA, BITA, LDA, STA,
|
||||
EORA, ADCA, ORA, ADDA, CMPX, JSR, LDX, STX,
|
||||
BSR,
|
||||
|
||||
NEG, COM, LSR, ROR, ASR,
|
||||
LSL, ROL, DEC, INC, TST, JMP, CLR,
|
||||
NEGA, COMA, LSRA, RORA, ASRA,
|
||||
LSLA, ROLA, DECA, INCA, TSTA, CLRA,
|
||||
NEGB, COMB, LSRB, RORB, ASRB,
|
||||
LSLB, ROLB, DECB, INCB, TSTB, CLRB,
|
||||
|
||||
LEAX, LEAY, LEAS, LEAU,
|
||||
PSHS, PULS, PSHU, PULU,
|
||||
RTS, ABX, RTI,
|
||||
CWAI, MUL, RESET, SWI,
|
||||
|
||||
BRA, BRN, BHI, BLS, BCC, BCS, BNE, BEQ,
|
||||
BVC, BVS, BPL, BMI, BGE, BLT, BGT, BLE,
|
||||
|
||||
Page1, Page2, NOP, SYNC, LBRA, LBSR,
|
||||
DAA, ORCC, ANDCC, SEX, EXG, TFR,
|
||||
|
||||
LBRN, LBHI, LBLS, LBCC, LBCS, LBNE, LBEQ,
|
||||
LBVC, LBVS, LBPL, LBMI, LBGE, LBLT, LBGT, LBLE,
|
||||
|
||||
SWI2, CMPD, CMPY, LDY, STY, LDS, STS,
|
||||
|
||||
SWI3, CMPU, CMPS,
|
||||
};
|
||||
|
||||
enum class Page {
|
||||
Page0, Page1, Page2,
|
||||
};
|
||||
|
||||
/*!
|
||||
Calls @c scheduler.schedule<Operation,AddressingMode> to describe the instruction
|
||||
defined by opcode @c i on page @c page.
|
||||
*/
|
||||
template <Page page> struct OperationMapper {
|
||||
template <int i, typename SchedulerT> void dispatch(SchedulerT &scheduler);
|
||||
};
|
||||
|
||||
template <>
|
||||
template <int i, typename SchedulerT> void OperationMapper<Page::Page0>::dispatch(SchedulerT &s) {
|
||||
using AM = AddressingMode;
|
||||
using O = Operation;
|
||||
|
||||
constexpr auto upper = (i >> 4) & 0xf;
|
||||
constexpr auto lower = (i >> 0) & 0xf;
|
||||
|
||||
constexpr AddressingMode modes[] = {
|
||||
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
|
||||
};
|
||||
constexpr AddressingMode mode = modes[(i >> 4) & 3];
|
||||
|
||||
switch(upper) {
|
||||
default: break;
|
||||
|
||||
case 0x1: {
|
||||
constexpr Operation operations[] = {
|
||||
O::Page1, O::Page2, O::NOP, O::SYNC, O::None, O::None, O::LBRA, O::LBSR,
|
||||
O::None, O::DAA, O::ORCC, O::None, O::ANDCC, O::SEX, O::EXG, O::TFR,
|
||||
};
|
||||
constexpr AddressingMode modes[] = {
|
||||
AM::Variant, AM::Variant, AM::Inherent, AM::Inherent,
|
||||
AM::Illegal, AM::Illegal, AM::Relative, AM::Relative,
|
||||
AM::Illegal, AM::Inherent, AM::Immediate, AM::Illegal,
|
||||
AM::Immediate, AM::Inherent, AM::Inherent, AM::Inherent,
|
||||
};
|
||||
s.template schedule<operations[lower], modes[lower]>();
|
||||
} break;
|
||||
case 0x2: {
|
||||
constexpr Operation operations[] = {
|
||||
O::BRA, O::BRN, O::BHI, O::BLS, O::BCC, O::BCS, O::BNE, O::BEQ,
|
||||
O::BVC, O::BVS, O::BPL, O::BMI, O::BGE, O::BLT, O::BGT, O::BLE,
|
||||
};
|
||||
s.template schedule<operations[lower], AM::Relative>();
|
||||
} break;
|
||||
case 0x3: {
|
||||
constexpr Operation operations[] = {
|
||||
O::LEAX, O::LEAY, O::LEAS, O::LEAU, O::PSHS, O::PULS, O::PSHU, O::PULU,
|
||||
O::None, O::RTS, O::ABX, O::RTI, O::CWAI, O::MUL, O::RESET, O::SWI,
|
||||
};
|
||||
constexpr auto op = operations[lower];
|
||||
switch(lower) {
|
||||
case 0x0: case 0x1: case 0x2: case 0x3:
|
||||
s.template schedule<op, AM::Indexed>();
|
||||
break;
|
||||
case 0x4: case 0x5: case 0x6: case 0x7: case 0xc:
|
||||
s.template schedule<op, AM::Immediate>();
|
||||
break;
|
||||
case 0x8:
|
||||
s.template schedule<op, AM::Illegal>();
|
||||
break;
|
||||
default:
|
||||
s.template schedule<op, AM::Inherent>();
|
||||
break;
|
||||
}
|
||||
} break;
|
||||
case 0x4: {
|
||||
constexpr Operation operations[] = {
|
||||
O::NEGA, O::None, O::None, O::COMA, O::LSRA, O::None, O::RORA, O::ASRA,
|
||||
O::LSLA, O::ROLA, O::DECA, O::None, O::INCA, O::TSTA, O::None, O::CLRA,
|
||||
};
|
||||
constexpr auto op = operations[lower];
|
||||
s.template schedule<op, op == O::None ? AM::Illegal : AM::Inherent>();
|
||||
} break;
|
||||
case 0x5: {
|
||||
constexpr Operation operations[] = {
|
||||
O::NEGB, O::None, O::None, O::COMB, O::LSRB, O::None, O::RORB, O::ASRB,
|
||||
O::LSLB, O::ROLB, O::DECB, O::None, O::INCB, O::TSTB, O::None, O::CLRB,
|
||||
};
|
||||
constexpr auto op = operations[lower];
|
||||
s.template schedule<op, op == O::None ? AM::Illegal : AM::Inherent>();
|
||||
} break;
|
||||
case 0x0: case 0x6: case 0x7: {
|
||||
constexpr Operation operations[] = {
|
||||
O::NEG, O::None, O::None, O::COM, O::LSR, O::None, O::ROR, O::ASR,
|
||||
O::LSL, O::ROL, O::DEC, O::None, O::INC, O::TST, O::JMP, O::CLR,
|
||||
};
|
||||
constexpr auto op = operations[lower];
|
||||
s.template schedule<op, op == O::None ? AM::Illegal : upper == 0 ? AM::Direct : mode>();
|
||||
} break;
|
||||
case 0x8: case 0x9: case 0xa: case 0xb: {
|
||||
constexpr Operation operations[] = {
|
||||
O::SUBA, O::CMPA, O::SBCA, O::SUBD, O::ANDA, O::BITA, O::LDA, O::STA,
|
||||
O::EORA, O::ADCA, O::ORA, O::ADDA, O::CMPX, O::JSR, O::LDX, O::STX,
|
||||
};
|
||||
if(i == 0x8d) s.template schedule<O::BSR, AM::Relative>();
|
||||
else s.template schedule<operations[lower], mode>();
|
||||
} break;
|
||||
case 0xc: case 0xd: case 0xe: case 0xf: {
|
||||
constexpr Operation operations[] = {
|
||||
O::SUBB, O::CMPB, O::SBCB, O::ADDD, O::ANDB, O::BITB, O::LDB, O::STB,
|
||||
O::EORB, O::ADCB, O::ORB, O::ADDB, O::LDD, O::STD, O::LDU, O::STU,
|
||||
};
|
||||
s.template schedule<operations[lower], mode>();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
template <int i, typename SchedulerT> void OperationMapper<Page::Page1>::dispatch(SchedulerT &s) {
|
||||
using AM = AddressingMode;
|
||||
using O = Operation;
|
||||
|
||||
constexpr AddressingMode modes[] = {
|
||||
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
|
||||
};
|
||||
constexpr auto mode = modes[(i >> 4) & 3];
|
||||
|
||||
if constexpr (i >= 0x21 && i < 0x30) {
|
||||
constexpr Operation operations[] = {
|
||||
O::LBRN, O::LBHI, O::LBLS, O::LBCC, O::LBCS, O::LBNE, O::LBEQ,
|
||||
O::LBVC, O::LBVS, O::LBPL, O::LBMI, O::LBGE, O::LBLT, O::LBGT, O::LBLE,
|
||||
};
|
||||
s.template schedule<operations[i - 0x21], AM::Relative>();
|
||||
} else switch(i) {
|
||||
default: s.template schedule<O::None, AM::Illegal>(); break;
|
||||
case 0x3f: s.template schedule<O::SWI2, AM::Inherent>(); break;
|
||||
|
||||
case 0x83: case 0x93: case 0xa3: case 0xb3:
|
||||
s.template schedule<O::CMPD, mode>();
|
||||
break;
|
||||
case 0x8c: case 0x9c: case 0xac: case 0xbc:
|
||||
s.template schedule<O::CMPY, mode>();
|
||||
break;
|
||||
case 0x8e: case 0x9e: case 0xae: case 0xbe:
|
||||
s.template schedule<O::LDY, mode>();
|
||||
break;
|
||||
case 0x9f: case 0xaf: case 0xbf:
|
||||
s.template schedule<O::STY, mode>();
|
||||
break;
|
||||
case 0xce: case 0xde: case 0xee: case 0xfe:
|
||||
s.template schedule<O::LDS, mode>();
|
||||
break;
|
||||
case 0xdf: case 0xef: case 0xff:
|
||||
s.template schedule<O::STS, mode>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
template <int i, typename SchedulerT> void OperationMapper<Page::Page2>::dispatch(SchedulerT &s) {
|
||||
using AM = AddressingMode;
|
||||
using O = Operation;
|
||||
|
||||
constexpr AddressingMode modes[] = {
|
||||
AM::Immediate, AM::Direct, AM::Indexed, AM::Extended
|
||||
};
|
||||
constexpr auto mode = modes[(i >> 4) & 3];
|
||||
|
||||
switch(i) {
|
||||
default: s.template schedule<O::None, AM::Illegal>(); break;
|
||||
case 0x3f: s.template schedule<O::SWI3, AM::Inherent>(); break;
|
||||
|
||||
case 0x83: case 0x93: case 0xa3: case 0xb3:
|
||||
s.template schedule<O::CMPU, mode>();
|
||||
break;
|
||||
case 0x8c: case 0x9c: case 0xac: case 0xbc:
|
||||
s.template schedule<O::CMPS, mode>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M6809_OperationMapper_hpp */
|
||||
1943
InstructionSets/M68k/Decoder.cpp
Normal file
1943
InstructionSets/M68k/Decoder.cpp
Normal file
File diff suppressed because it is too large
Load Diff
121
InstructionSets/M68k/Decoder.hpp
Normal file
121
InstructionSets/M68k/Decoder.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Decoder.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Decoder_hpp
|
||||
#define InstructionSets_M68k_Decoder_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "../../Numeric/Sizes.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
/*!
|
||||
A stateless decoder that can map from instruction words to preinstructions
|
||||
(i.e. enough to know the operation and size, and either know the addressing mode
|
||||
and registers or else know how many further extension words are needed).
|
||||
|
||||
WARNING: at present this handles the original 68000 instruction set only. It
|
||||
requires a model only for the sake of not baking in assumptions about MOVE SR, etc,
|
||||
and supporting extended addressing modes in some cases.
|
||||
|
||||
But it does not yet decode any operations which were not present on the 68000.
|
||||
*/
|
||||
template <Model model> class Predecoder {
|
||||
public:
|
||||
Preinstruction decode(uint16_t instruction);
|
||||
|
||||
private:
|
||||
// Page by page decoders; each gets a bit ad hoc so
|
||||
// it is neater to separate them.
|
||||
Preinstruction decode0(uint16_t instruction);
|
||||
Preinstruction decode1(uint16_t instruction);
|
||||
Preinstruction decode2(uint16_t instruction);
|
||||
Preinstruction decode3(uint16_t instruction);
|
||||
Preinstruction decode4(uint16_t instruction);
|
||||
Preinstruction decode5(uint16_t instruction);
|
||||
Preinstruction decode6(uint16_t instruction);
|
||||
Preinstruction decode7(uint16_t instruction);
|
||||
Preinstruction decode8(uint16_t instruction);
|
||||
Preinstruction decode9(uint16_t instruction);
|
||||
Preinstruction decodeA(uint16_t instruction);
|
||||
Preinstruction decodeB(uint16_t instruction);
|
||||
Preinstruction decodeC(uint16_t instruction);
|
||||
Preinstruction decodeD(uint16_t instruction);
|
||||
Preinstruction decodeE(uint16_t instruction);
|
||||
Preinstruction decodeF(uint16_t instruction);
|
||||
|
||||
// Yuckiness here: 67 is a count of the number of things contained below in
|
||||
// ExtendedOperation; this acts to ensure ExtendedOperation is the minimum
|
||||
// integer size large enough to hold all actual operations plus the ephemeral
|
||||
// ones used here. Intention is to support table-based decoding, which will mean
|
||||
// making those integers less ephemeral, hence the desire to pick a minimum size.
|
||||
using OpT = typename MinIntTypeValue<
|
||||
uint64_t(OperationMax<model>::value) + 67
|
||||
>::type;
|
||||
static constexpr auto OpMax = OpT(OperationMax<model>::value);
|
||||
|
||||
// Specific instruction decoders.
|
||||
template <OpT operation, bool validate = true> Preinstruction decode(uint16_t instruction);
|
||||
template <OpT operation, bool validate> Preinstruction validated(
|
||||
AddressingMode op1_mode = AddressingMode::None, int op1_reg = 0,
|
||||
AddressingMode op2_mode = AddressingMode::None, int op2_reg = 0,
|
||||
Condition condition = Condition::True,
|
||||
int further_extension_words = 0
|
||||
);
|
||||
template <OpT operation> uint32_t invalid_operands();
|
||||
|
||||
// Extended operation list; collapses into a single byte enough information to
|
||||
// know both the type of operation and how to decode the operands. Most of the
|
||||
// time that's knowable from the Operation alone, hence the rather awkward
|
||||
// extension of @c Operation.
|
||||
enum ExtendedOperation: OpT {
|
||||
MOVEPtoRl = OpMax + 1, MOVEPtoRw,
|
||||
MOVEPtoMl, MOVEPtoMw,
|
||||
|
||||
MOVEQ,
|
||||
|
||||
ADDQb, ADDQw, ADDQl,
|
||||
ADDQAw, ADDQAl,
|
||||
SUBQb, SUBQw, SUBQl,
|
||||
SUBQAw, SUBQAl,
|
||||
|
||||
ADDIb, ADDIw, ADDIl,
|
||||
ORIb, ORIw, ORIl,
|
||||
SUBIb, SUBIw, SUBIl,
|
||||
ANDIb, ANDIw, ANDIl,
|
||||
EORIb, EORIw, EORIl,
|
||||
CMPIb, CMPIw, CMPIl,
|
||||
|
||||
BTSTI, BCHGI, BCLRI, BSETI,
|
||||
|
||||
CMPMb, CMPMw, CMPMl,
|
||||
|
||||
ADDtoMb, ADDtoMw, ADDtoMl,
|
||||
ADDtoRb, ADDtoRw, ADDtoRl,
|
||||
|
||||
SUBtoMb, SUBtoMw, SUBtoMl,
|
||||
SUBtoRb, SUBtoRw, SUBtoRl,
|
||||
|
||||
ANDtoMb, ANDtoMw, ANDtoMl,
|
||||
ANDtoRb, ANDtoRw, ANDtoRl,
|
||||
|
||||
ORtoMb, ORtoMw, ORtoMl,
|
||||
ORtoRb, ORtoRw, ORtoRl,
|
||||
|
||||
EXGRtoR, EXGAtoA, EXGRtoA,
|
||||
};
|
||||
|
||||
static constexpr Operation operation(OpT op);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_Decoder_hpp */
|
||||
50
InstructionSets/M68k/ExceptionVectors.hpp
Normal file
50
InstructionSets/M68k/ExceptionVectors.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// ExceptionVectors.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_ExceptionVectors_hpp
|
||||
#define InstructionSets_M68k_ExceptionVectors_hpp
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
enum Exception {
|
||||
InitialStackPointer = 0,
|
||||
InitialProgramCounter = 1,
|
||||
AccessFault = 2,
|
||||
AddressError = 3,
|
||||
IllegalInstruction = 4,
|
||||
IntegerDivideByZero = 5,
|
||||
CHK = 6,
|
||||
TRAPV = 7,
|
||||
PrivilegeViolation = 8,
|
||||
Trace = 9,
|
||||
Line1010 = 10,
|
||||
Line1111 = 11,
|
||||
CoprocessorProtocolViolation = 13,
|
||||
FormatError = 14,
|
||||
UninitialisedInterrupt = 15,
|
||||
SpuriousInterrupt = 24,
|
||||
InterruptAutovectorBase = 25, // This is the vector for interrupt level _1_.
|
||||
TrapBase = 32,
|
||||
FPBranchOrSetOnUnorderedCondition = 48,
|
||||
FPInexactResult = 49,
|
||||
FPDivideByZero = 50,
|
||||
FPUnderflow = 51,
|
||||
FPOperandError = 52,
|
||||
FPOverflow = 53,
|
||||
FPSignallingNAN = 54,
|
||||
FPUnimplementedDataType = 55,
|
||||
MMUConfigurationError = 56,
|
||||
MMUIllegalOperationError = 57,
|
||||
MMUAccessLevelViolationError = 58,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_ExceptionVectors_hpp */
|
||||
171
InstructionSets/M68k/Executor.hpp
Normal file
171
InstructionSets/M68k/Executor.hpp
Normal file
@@ -0,0 +1,171 @@
|
||||
//
|
||||
// Executor.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Executor_hpp
|
||||
#define InstructionSets_M68k_Executor_hpp
|
||||
|
||||
#include "Decoder.hpp"
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "Perform.hpp"
|
||||
#include "RegisterSet.hpp"
|
||||
#include "Status.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
/// Maps the 68k function codes such that bits 0, 1 and 2 represent
|
||||
/// FC0, FC1 and FC2 respectively.
|
||||
enum class FunctionCode {
|
||||
UserData = 0b001,
|
||||
UserProgram = 0b010,
|
||||
SupervisorData = 0b101,
|
||||
SupervisorProgram = 0b110,
|
||||
InterruptAcknowledge = 0b111,
|
||||
};
|
||||
|
||||
/// The Executor is templated on a class that implements bus handling as defined below;
|
||||
/// the bus handler is responsible for all reads and writes, and will also receive resets and
|
||||
/// interrupt acknowledgements.
|
||||
///
|
||||
/// The executor will provide 32-bit addresses and act as if it had a 32-bit data bus, even
|
||||
/// if interpretting the original 68000 instruction set.
|
||||
struct BusHandler {
|
||||
/// Write @c value of type/size @c IntT to @c address with the processor signalling
|
||||
/// a FunctionCode of @c function. @c IntT will be one of @c uint8_t, @c uint16_t
|
||||
/// or @c uint32_t.
|
||||
template <typename IntT> void write(uint32_t address, IntT value, FunctionCode function);
|
||||
|
||||
/// Read and return a value of type/size @c IntT from @c address with the processor signalling
|
||||
/// a FunctionCode of @c function. @c IntT will be one of @c uint8_t, @c uint16_t
|
||||
/// or @c uint32_t.
|
||||
template <typename IntT> IntT read(uint32_t address, FunctionCode function);
|
||||
|
||||
/// React to the processor programmatically strobing its RESET output.
|
||||
void reset();
|
||||
|
||||
/// Respond to an interrupt acknowledgement at @c interrupt_level from the processor.
|
||||
/// Should return @c -1 in order to trigger autovectoring, or the appropriate exception vector
|
||||
/// number otherwise.
|
||||
///
|
||||
/// It is undefined behaviour to return a number greater than 255.
|
||||
int acknowlege_interrupt(int interrupt_level);
|
||||
};
|
||||
|
||||
/// Ties together the decoder, sequencer and performer to provide an executor for 680x0 instruction streams.
|
||||
/// As is standard for these executors, no bus- or cache-level fidelity to any real 680x0 is attempted. This is
|
||||
/// simply an executor of 680x0 code.
|
||||
template <Model model, typename BusHandler> class Executor {
|
||||
public:
|
||||
Executor(BusHandler &);
|
||||
|
||||
/// Reset the processor, back to a state as if just externally reset.
|
||||
void reset();
|
||||
|
||||
/// Executes the number of instructions specified;
|
||||
/// other events — such as initial reset or branching
|
||||
/// to exceptions — may be zero costed, and interrupts
|
||||
/// will not necessarily take effect immediately when signalled.
|
||||
void run_for_instructions(int);
|
||||
|
||||
/// Call this at any time to interrupt processing with a bus error;
|
||||
/// the function code and address must be provided. Internally
|
||||
/// this will raise a C++ exception, and therefore doesn't return.
|
||||
[[noreturn]] void signal_bus_error(FunctionCode, uint32_t address);
|
||||
|
||||
/// Sets the current input interrupt level.
|
||||
void set_interrupt_level(int);
|
||||
|
||||
// State for the executor is just the register set.
|
||||
RegisterSet get_state();
|
||||
void set_state(const RegisterSet &);
|
||||
|
||||
private:
|
||||
class State: public NullFlowController {
|
||||
public:
|
||||
State(BusHandler &handler) : bus_handler_(handler) {}
|
||||
|
||||
void run(int &);
|
||||
bool stopped = false;
|
||||
|
||||
void read(DataSize size, uint32_t address, CPU::SlicedInt32 &value);
|
||||
void write(DataSize size, uint32_t address, CPU::SlicedInt32 value);
|
||||
template <typename IntT> IntT read(uint32_t address, bool is_from_pc = false);
|
||||
template <typename IntT> void write(uint32_t address, IntT value);
|
||||
|
||||
template <typename IntT> IntT read_pc();
|
||||
|
||||
// Processor state.
|
||||
Status status;
|
||||
CPU::SlicedInt32 program_counter;
|
||||
CPU::SlicedInt32 registers[16]; // D0–D7 followed by A0–A7.
|
||||
CPU::SlicedInt32 stack_pointers[2];
|
||||
uint32_t instruction_address;
|
||||
uint16_t instruction_opcode;
|
||||
|
||||
// Things that are ephemerally duplicative of Status.
|
||||
int active_stack_pointer = 0;
|
||||
Status::FlagT should_trace = 0;
|
||||
|
||||
// Bus state.
|
||||
int interrupt_input = 0;
|
||||
|
||||
// A lookup table to ensure that A7 is adjusted by 2 rather than 1 in
|
||||
// postincrement and predecrement mode.
|
||||
static constexpr uint32_t byte_increments[] = {
|
||||
1, 1, 1, 1, 1, 1, 1, 2
|
||||
};
|
||||
|
||||
// Flow control; Cf. Perform.hpp.
|
||||
template <bool use_current_instruction_pc = true> void raise_exception(int);
|
||||
|
||||
void did_update_status();
|
||||
|
||||
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
|
||||
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
|
||||
void bsr(uint32_t offset);
|
||||
void jmp(uint32_t);
|
||||
void jsr(uint32_t offset);
|
||||
void rtr();
|
||||
void rts();
|
||||
void rte();
|
||||
void stop();
|
||||
void reset();
|
||||
|
||||
void link(Preinstruction instruction, uint32_t offset);
|
||||
void unlink(uint32_t &address);
|
||||
void pea(uint32_t address);
|
||||
|
||||
void move_to_usp(uint32_t address);
|
||||
void move_from_usp(uint32_t &address);
|
||||
|
||||
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
|
||||
void tas(Preinstruction instruction, uint32_t address);
|
||||
|
||||
private:
|
||||
BusHandler &bus_handler_;
|
||||
Predecoder<model> decoder_;
|
||||
|
||||
struct EffectiveAddress {
|
||||
CPU::SlicedInt32 value;
|
||||
bool requires_fetch;
|
||||
};
|
||||
EffectiveAddress calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index);
|
||||
uint32_t index_8bitdisplacement(uint32_t);
|
||||
} state_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/ExecutorImplementation.hpp"
|
||||
|
||||
#endif /* InstructionSets_M68k_Executor_hpp */
|
||||
755
InstructionSets/M68k/Implementation/ExecutorImplementation.hpp
Normal file
755
InstructionSets/M68k/Implementation/ExecutorImplementation.hpp
Normal file
@@ -0,0 +1,755 @@
|
||||
//
|
||||
//
|
||||
// ExecutorImplementation.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_ExecutorImplementation_hpp
|
||||
#define InstructionSets_M68k_ExecutorImplementation_hpp
|
||||
|
||||
#include "../Perform.hpp"
|
||||
#include "../ExceptionVectors.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
#define An(x) state_.registers[8 + x]
|
||||
#define Dn(x) state_.registers[x]
|
||||
#define sp An(7)
|
||||
|
||||
#define AccessException(code, address, vector) \
|
||||
uint64_t(((vector) << 8) | uint64_t(code) | ((address) << 16))
|
||||
|
||||
// MARK: - Executor itself.
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
Executor<model, BusHandler>::Executor(BusHandler &handler) : state_(handler) {
|
||||
reset();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::reset() {
|
||||
// Establish: supervisor state, all interrupts blocked.
|
||||
state_.status.set_status(0b0010'0011'1000'0000);
|
||||
state_.did_update_status();
|
||||
|
||||
// Clear the STOPped state, if currently active.
|
||||
state_.stopped = false;
|
||||
|
||||
// Seed stack pointer and program counter.
|
||||
sp.l = state_.template read<uint32_t>(0) & 0xffff'fffe;
|
||||
state_.program_counter.l = state_.template read<uint32_t>(4);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::signal_bus_error(FunctionCode code, uint32_t address) {
|
||||
throw AccessException(code, address, Exception::AccessFault);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::set_interrupt_level(int level) {
|
||||
state_.interrupt_input_ = level;
|
||||
state_.stopped &= !state_.status.would_accept_interrupt(level);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::run_for_instructions(int count) {
|
||||
if(state_.stopped) return;
|
||||
|
||||
while(count > 0) {
|
||||
try {
|
||||
state_.run(count);
|
||||
} catch (uint64_t exception) {
|
||||
// Potiental source of an exception #1: STOP. Check for that first.
|
||||
if(state_.stopped) return;
|
||||
|
||||
// Unpack the exception; this is the converse of the AccessException macro.
|
||||
const int vector_address = (exception >> 6) & 0xfc;
|
||||
const uint16_t code = uint16_t(exception & 0xff);
|
||||
const uint32_t faulting_address = uint32_t(exception >> 16);
|
||||
|
||||
// Grab the status to store, then switch into supervisor mode.
|
||||
const uint16_t status = state_.status.status();
|
||||
state_.status.is_supervisor = true;
|
||||
state_.status.trace_flag = 0;
|
||||
state_.did_update_status();
|
||||
|
||||
// Ensure no tracing occurs into the exception.
|
||||
state_.should_trace = 0;
|
||||
|
||||
// Push status and the program counter at instruction start.
|
||||
state_.template write<uint16_t>(sp.l - 14, code);
|
||||
state_.template write<uint32_t>(sp.l - 12, faulting_address);
|
||||
state_.template write<uint16_t>(sp.l - 8, state_.instruction_opcode);
|
||||
state_.template write<uint16_t>(sp.l - 6, status);
|
||||
state_.template write<uint16_t>(sp.l - 4, state_.instruction_address);
|
||||
sp.l -= 14;
|
||||
|
||||
// Fetch the new program counter; reset on a double fault.
|
||||
try {
|
||||
state_.program_counter.l = state_.template read<uint32_t>(vector_address);
|
||||
} catch (uint64_t) {
|
||||
// TODO: I think this is incorrect, but need to verify consistency
|
||||
// across different 680x0s.
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
RegisterSet Executor<model, BusHandler>::get_state() {
|
||||
RegisterSet result;
|
||||
|
||||
for(int c = 0; c < 8; c++) {
|
||||
result.data[c] = Dn(c).l;
|
||||
}
|
||||
for(int c = 0; c < 7; c++) {
|
||||
result.address[c] = An(c).l;
|
||||
}
|
||||
result.status = state_.status.status();
|
||||
result.program_counter = state_.program_counter.l;
|
||||
|
||||
state_.stack_pointers[state_.active_stack_pointer] = sp;
|
||||
result.user_stack_pointer = state_.stack_pointers[0].l;
|
||||
result.supervisor_stack_pointer = state_.stack_pointers[1].l;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::set_state(const RegisterSet &state) {
|
||||
for(int c = 0; c < 8; c++) {
|
||||
Dn(c).l = state.data[c];
|
||||
}
|
||||
for(int c = 0; c < 7; c++) {
|
||||
An(c).l = state.address[c];
|
||||
}
|
||||
state_.status.set_status(state.status);
|
||||
state_.did_update_status();
|
||||
state_.program_counter.l = state.program_counter;
|
||||
|
||||
state_.stack_pointers[0].l = state.user_stack_pointer;
|
||||
state_.stack_pointers[1].l = state.supervisor_stack_pointer;
|
||||
sp = state_.stack_pointers[state_.active_stack_pointer];
|
||||
}
|
||||
|
||||
#undef Dn
|
||||
#undef An
|
||||
|
||||
// MARK: - State.
|
||||
|
||||
#define An(x) registers[8 + x]
|
||||
#define Dn(x) registers[x]
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
IntT Executor<model, BusHandler>::State::read(uint32_t address, bool is_from_pc) {
|
||||
const auto code = FunctionCode((active_stack_pointer << 2) | 1 << int(is_from_pc));
|
||||
if(model == Model::M68000 && sizeof(IntT) > 1 && address & 1) {
|
||||
throw AccessException(code, address, Exception::AddressError | (int(is_from_pc) << 3) | (1 << 4));
|
||||
}
|
||||
|
||||
return bus_handler_.template read<IntT>(address, code);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
void Executor<model, BusHandler>::State::write(uint32_t address, IntT value) {
|
||||
const auto code = FunctionCode((active_stack_pointer << 2) | 1);
|
||||
if(model == Model::M68000 && sizeof(IntT) > 1 && address & 1) {
|
||||
throw AccessException(code, address, Exception::AddressError);
|
||||
}
|
||||
|
||||
bus_handler_.template write<IntT>(address, value, code);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::read(DataSize size, uint32_t address, CPU::SlicedInt32 &value) {
|
||||
switch(size) {
|
||||
case DataSize::Byte: value.b = read<uint8_t>(address); break;
|
||||
case DataSize::Word: value.w = read<uint16_t>(address); break;
|
||||
case DataSize::LongWord: value.l = read<uint32_t>(address); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::write(DataSize size, uint32_t address, CPU::SlicedInt32 value) {
|
||||
switch(size) {
|
||||
case DataSize::Byte: write<uint8_t>(address, value.b); break;
|
||||
case DataSize::Word: write<uint16_t>(address, value.w); break;
|
||||
case DataSize::LongWord: write<uint32_t>(address, value.l); break;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT> IntT Executor<model, BusHandler>::State::read_pc() {
|
||||
const IntT result = read<IntT>(program_counter.l, true);
|
||||
|
||||
if constexpr (sizeof(IntT) == 4) {
|
||||
program_counter.l += 4;
|
||||
} else {
|
||||
program_counter.l += 2;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// For all of below, cf PRM 2-2 (PDF p43)
|
||||
template <Model model, typename BusHandler>
|
||||
uint32_t Executor<model, BusHandler>::State::index_8bitdisplacement(uint32_t base) {
|
||||
// Determine whether full extension addressing modes are supported.
|
||||
constexpr bool supports_full_extensions = model >= Model::M68020;
|
||||
|
||||
// Get the [first] extension word.
|
||||
const auto extension = read_pc<uint16_t>();
|
||||
|
||||
// The 68000, 68080 and 68010 do not support the scale field, and are limited
|
||||
// to brief extension words.
|
||||
const int scale = supports_full_extensions ? (extension >> 9) & 3 : 0;
|
||||
|
||||
// Decode brief instruction word fields.
|
||||
const auto offset = int8_t(extension);
|
||||
const int register_index = (extension >> 12) & 15;
|
||||
|
||||
// Calculate the displacement; which on the 68020+ is better known as the index.
|
||||
const uint32_t raw_index = registers[register_index].l;
|
||||
uint32_t index = ((extension & 0x800) ? raw_index : int16_t(raw_index)) << scale;
|
||||
|
||||
// Use a brief extension word if instructed to, or if that's this processor's limit.
|
||||
if(!supports_full_extensions || !(extension & 0x100)) {
|
||||
return base + offset + index;
|
||||
}
|
||||
|
||||
//
|
||||
// Determine a long extension.
|
||||
//
|
||||
|
||||
// Apply suppressions.
|
||||
const bool suppress_base = extension & 0x80; // i.e. don't use whatever the first instruction word indicated.
|
||||
const bool suppress_index = extension & 0x40; // i.e. don't use whatever register_index points to.
|
||||
if(suppress_base) base = 0;
|
||||
if(suppress_index) index = 0;
|
||||
|
||||
// Fetch base displacement.
|
||||
uint32_t base_displacement = 0;
|
||||
switch((extension >> 4) & 3) {
|
||||
default: break;
|
||||
case 2: base_displacement = read_pc<uint16_t>(); break;
|
||||
case 3: base_displacement = read_pc<uint32_t>(); break;
|
||||
}
|
||||
|
||||
// Don't do a further indirection if there's no outer displacement.
|
||||
if(!(extension & 7)) {
|
||||
return index + base + base_displacement;
|
||||
}
|
||||
|
||||
// Fetch outer displacement.
|
||||
uint32_t outer_displacement = 0;
|
||||
switch(extension & 3) {
|
||||
default: break;
|
||||
case 2: outer_displacement = read_pc<uint16_t>(); break;
|
||||
case 3: outer_displacement = read_pc<uint32_t>(); break;
|
||||
}
|
||||
|
||||
// Apply outer displacement; either the index is before the indirection
|
||||
// or after it.
|
||||
if(extension & 4) {
|
||||
return read<uint32_t>(base + base_displacement) + index + outer_displacement;
|
||||
} else {
|
||||
return read<uint32_t>(base + base_displacement + index) + outer_displacement;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
typename Executor<model, BusHandler>::State::EffectiveAddress
|
||||
Executor<model, BusHandler>::State::calculate_effective_address(Preinstruction instruction, uint16_t opcode, int index) {
|
||||
EffectiveAddress ea;
|
||||
|
||||
switch(instruction.mode(index)) {
|
||||
case AddressingMode::None:
|
||||
// Permit an uninitialised effective address to be returned;
|
||||
// this value shouldn't be used.
|
||||
break;
|
||||
|
||||
//
|
||||
// Operands that don't have effective addresses, which are returned as values.
|
||||
//
|
||||
case AddressingMode::DataRegisterDirect:
|
||||
case AddressingMode::AddressRegisterDirect:
|
||||
ea.value = registers[instruction.lreg(index)];
|
||||
ea.requires_fetch = false;
|
||||
break;
|
||||
case AddressingMode::Quick:
|
||||
ea.value.l = quick(opcode, instruction.operation);
|
||||
ea.requires_fetch = false;
|
||||
break;
|
||||
case AddressingMode::ImmediateData:
|
||||
switch(instruction.operand_size()) {
|
||||
case DataSize::Byte:
|
||||
ea.value.l = read_pc<uint16_t>() & 0xff;
|
||||
break;
|
||||
case DataSize::Word:
|
||||
ea.value.l = read_pc<uint16_t>();
|
||||
break;
|
||||
case DataSize::LongWord:
|
||||
ea.value.l = read_pc<uint32_t>();
|
||||
break;
|
||||
}
|
||||
ea.requires_fetch = false;
|
||||
break;
|
||||
case AddressingMode::ExtensionWord:
|
||||
ea.value.l = read_pc<uint16_t>();
|
||||
ea.requires_fetch = false;
|
||||
break;
|
||||
|
||||
//
|
||||
// Absolute addresses.
|
||||
//
|
||||
case AddressingMode::AbsoluteShort:
|
||||
ea.value.l = int16_t(read_pc<uint16_t>());
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
case AddressingMode::AbsoluteLong:
|
||||
ea.value.l = read_pc<uint32_t>();
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
|
||||
//
|
||||
// Address register indirects.
|
||||
//
|
||||
case AddressingMode::AddressRegisterIndirect:
|
||||
ea.value = An(instruction.reg(index));
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
case AddressingMode::AddressRegisterIndirectWithPostincrement: {
|
||||
const auto reg = instruction.reg(index);
|
||||
|
||||
ea.value = An(reg);
|
||||
ea.requires_fetch = true;
|
||||
|
||||
switch(instruction.operand_size()) {
|
||||
case DataSize::Byte: An(reg).l += byte_increments[reg]; break;
|
||||
case DataSize::Word: An(reg).l += 2; break;
|
||||
case DataSize::LongWord: An(reg).l += 4; break;
|
||||
}
|
||||
} break;
|
||||
case AddressingMode::AddressRegisterIndirectWithPredecrement: {
|
||||
const auto reg = instruction.reg(index);
|
||||
|
||||
switch(instruction.operand_size()) {
|
||||
case DataSize::Byte: An(reg).l -= byte_increments[reg]; break;
|
||||
case DataSize::Word: An(reg).l -= 2; break;
|
||||
case DataSize::LongWord: An(reg).l -= 4; break;
|
||||
}
|
||||
|
||||
ea.value = An(reg);
|
||||
ea.requires_fetch = true;
|
||||
} break;
|
||||
case AddressingMode::AddressRegisterIndirectWithDisplacement:
|
||||
ea.value.l = An(instruction.reg(index)).l + int16_t(read_pc<uint16_t>());
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement:
|
||||
ea.value.l = index_8bitdisplacement(An(instruction.reg(index)).l);
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
|
||||
//
|
||||
// PC-relative addresses.
|
||||
//
|
||||
case AddressingMode::ProgramCounterIndirectWithDisplacement:
|
||||
ea.value.l = program_counter.l;
|
||||
ea.value.l += int16_t(read_pc<uint16_t>());
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement:
|
||||
ea.value.l = index_8bitdisplacement(program_counter.l);
|
||||
ea.requires_fetch = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return ea;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::run(int &count) {
|
||||
while(count--) {
|
||||
// Check for a new interrupt.
|
||||
if(status.would_accept_interrupt(interrupt_input)) {
|
||||
const int vector = bus_handler_.acknowlege_interrupt(interrupt_input);
|
||||
if(vector >= 0) {
|
||||
raise_exception<false>(vector);
|
||||
} else {
|
||||
raise_exception<false>(Exception::InterruptAutovectorBase - 1 + interrupt_input);
|
||||
}
|
||||
status.interrupt_level = interrupt_input;
|
||||
}
|
||||
|
||||
// Capture the trace bit, indicating whether to trace
|
||||
// after this instruction.
|
||||
//
|
||||
// If an exception occurs, this value will be cleared, but
|
||||
// it'll persist across mere status register changes for
|
||||
// one instruction's duration.
|
||||
should_trace = status.trace_flag;
|
||||
|
||||
// Read the next instruction.
|
||||
instruction_address = program_counter.l;
|
||||
instruction_opcode = read_pc<uint16_t>();
|
||||
const Preinstruction instruction = decoder_.decode(instruction_opcode);
|
||||
|
||||
if(instruction.requires_supervisor() && !status.is_supervisor) {
|
||||
raise_exception(Exception::PrivilegeViolation);
|
||||
continue;
|
||||
}
|
||||
if(instruction.operation == Operation::Undefined) {
|
||||
switch(instruction_opcode & 0xf000) {
|
||||
default:
|
||||
raise_exception(Exception::IllegalInstruction);
|
||||
continue;
|
||||
case 0xa000:
|
||||
raise_exception(Exception::Line1010);
|
||||
continue;
|
||||
case 0xf000:
|
||||
raise_exception(Exception::Line1111);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary storage.
|
||||
CPU::SlicedInt32 operand_[2];
|
||||
EffectiveAddress effective_address_[2];
|
||||
|
||||
// Calculate effective addresses; copy 'addresses' into the
|
||||
// operands by default both: (i) because they might be values,
|
||||
// rather than addresses; and (ii) then they'll be there for use
|
||||
// by LEA and PEA.
|
||||
effective_address_[0] = calculate_effective_address(instruction, instruction_opcode, 0);
|
||||
effective_address_[1] = calculate_effective_address(instruction, instruction_opcode, 1);
|
||||
operand_[0] = effective_address_[0].value;
|
||||
operand_[1] = effective_address_[1].value;
|
||||
|
||||
// Obtain the appropriate sequence.
|
||||
const auto flags = operand_flags<model>(instruction.operation);
|
||||
|
||||
#define fetch_operand(n) \
|
||||
if(effective_address_[n].requires_fetch) { \
|
||||
read(instruction.operand_size(), effective_address_[n].value.l, operand_[n]); \
|
||||
}
|
||||
|
||||
if(flags & FetchOp1) { fetch_operand(0); }
|
||||
if(flags & FetchOp2) { fetch_operand(1); }
|
||||
|
||||
#undef fetch_operand
|
||||
|
||||
perform<model>(instruction, operand_[0], operand_[1], status, *this);
|
||||
|
||||
#define store_operand(n) \
|
||||
if(!effective_address_[n].requires_fetch) { \
|
||||
registers[instruction.lreg(n)] = operand_[n]; \
|
||||
} else { \
|
||||
write(instruction.operand_size(), effective_address_[n].value.l, operand_[n]); \
|
||||
}
|
||||
|
||||
if(flags & StoreOp1) { store_operand(0); }
|
||||
if(flags & StoreOp2) { store_operand(1); }
|
||||
|
||||
#undef store_operand
|
||||
|
||||
// If the trace bit was set, trigger the trace exception.
|
||||
if(should_trace) {
|
||||
raise_exception<false>(Exception::Trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Flow Control.
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <bool use_current_instruction_pc>
|
||||
void Executor<model, BusHandler>::State::raise_exception(int index) {
|
||||
const uint32_t address = index << 2;
|
||||
|
||||
// Grab the status to store, then switch into supervisor mode
|
||||
// and disable tracing.
|
||||
const uint16_t previous_status = status.status();
|
||||
status.is_supervisor = true;
|
||||
status.trace_flag = 0;
|
||||
did_update_status();
|
||||
|
||||
// Push status and the program counter at instruction start.
|
||||
write<uint32_t>(sp.l - 4, use_current_instruction_pc ? instruction_address : program_counter.l);
|
||||
write<uint16_t>(sp.l - 6, previous_status);
|
||||
sp.l -= 6;
|
||||
|
||||
// Ensure no tracing occurs into the exception.
|
||||
should_trace = 0;
|
||||
|
||||
// Fetch the new program counter.
|
||||
program_counter.l = read<uint32_t>(address);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::did_update_status() {
|
||||
// Shuffle the stack pointers.
|
||||
stack_pointers[active_stack_pointer] = sp;
|
||||
sp = stack_pointers[int(status.is_supervisor)];
|
||||
active_stack_pointer = int(status.is_supervisor);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::stop() {
|
||||
stopped = true;
|
||||
|
||||
// Raise an exception to exit the run loop; it doesn't matter
|
||||
// what value is used as long as it is a uint64_t, so 0 will do.
|
||||
throw uint64_t();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::reset() {
|
||||
bus_handler_.reset();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::jmp(uint32_t address) {
|
||||
program_counter.l = address;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT> void Executor<model, BusHandler>::State::complete_bcc(bool branch, IntT offset) {
|
||||
if(branch) {
|
||||
program_counter.l = instruction_address + offset + 2;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::complete_dbcc(bool matched_condition, bool overflowed, int16_t offset) {
|
||||
if(!matched_condition && !overflowed) {
|
||||
program_counter.l = instruction_address + offset + 2;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::bsr(uint32_t offset) {
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, program_counter.l);
|
||||
program_counter.l = instruction_address + offset + 2;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::jsr(uint32_t address) {
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, program_counter.l);
|
||||
program_counter.l = address;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::link(Preinstruction instruction, uint32_t offset) {
|
||||
const auto reg = 8 + instruction.reg<0>();
|
||||
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, Dn(reg).l);
|
||||
Dn(reg) = sp;
|
||||
sp.l += offset;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::unlink(uint32_t &address) {
|
||||
sp.l = address;
|
||||
address = read<uint32_t>(sp.l);
|
||||
sp.l += 4;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::pea(uint32_t address) {
|
||||
sp.l -= 4;
|
||||
write<uint32_t>(sp.l, address);
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::rtr() {
|
||||
status.set_ccr(read<uint16_t>(sp.l));
|
||||
sp.l += 2;
|
||||
rts();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::rte() {
|
||||
status.set_status(read<uint16_t>(sp.l));
|
||||
sp.l += 2;
|
||||
rts();
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::rts() {
|
||||
program_counter.l = read<uint32_t>(sp.l);
|
||||
sp.l += 4;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::tas(Preinstruction instruction, uint32_t address) {
|
||||
uint8_t value;
|
||||
if(instruction.mode<0>() != AddressingMode::DataRegisterDirect) {
|
||||
value = read<uint8_t>(address);
|
||||
write<uint8_t>(address, value | 0x80);
|
||||
} else {
|
||||
value = uint8_t(address);
|
||||
Dn(instruction.reg<0>()).b = uint8_t(address | 0x80);
|
||||
}
|
||||
|
||||
status.overflow_flag = status.carry_flag = 0;
|
||||
status.zero_result = value;
|
||||
status.negative_flag = value & 0x80;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::move_to_usp(uint32_t address) {
|
||||
stack_pointers[0].l = address;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
void Executor<model, BusHandler>::State::move_from_usp(uint32_t &address) {
|
||||
address = stack_pointers[0].l;
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
void Executor<model, BusHandler>::State::movep(Preinstruction instruction, uint32_t source, uint32_t dest) {
|
||||
if(instruction.mode<0>() == AddressingMode::DataRegisterDirect) {
|
||||
// Move register to memory.
|
||||
const uint32_t reg = source;
|
||||
uint32_t address = dest;
|
||||
|
||||
if constexpr (sizeof(IntT) == 4) {
|
||||
write<uint8_t>(address, uint8_t(reg >> 24));
|
||||
address += 2;
|
||||
|
||||
write<uint8_t>(address, uint8_t(reg >> 16));
|
||||
address += 2;
|
||||
}
|
||||
|
||||
write<uint8_t>(address, uint8_t(reg >> 8));
|
||||
address += 2;
|
||||
|
||||
write<uint8_t>(address, uint8_t(reg));
|
||||
} else {
|
||||
// Move memory to register.
|
||||
uint32_t ® = Dn(instruction.reg<1>()).l;
|
||||
uint32_t address = source;
|
||||
|
||||
if constexpr (sizeof(IntT) == 4) {
|
||||
reg = read<uint8_t>(address) << 24;
|
||||
address += 2;
|
||||
|
||||
reg |= read<uint8_t>(address) << 16;
|
||||
address += 2;
|
||||
} else {
|
||||
reg &= 0xffff0000;
|
||||
}
|
||||
|
||||
reg |= read<uint8_t>(address) << 8;
|
||||
address += 2;
|
||||
|
||||
reg |= read<uint8_t>(address);
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
void Executor<model, BusHandler>::State::movem_toM(Preinstruction instruction, uint32_t source, uint32_t dest) {
|
||||
// Move registers to memory. This is the only permitted use of the predecrement mode,
|
||||
// which reverses output order.
|
||||
|
||||
if(instruction.mode<1>() == AddressingMode::AddressRegisterIndirectWithPredecrement) {
|
||||
// The structure of the code in the mainline part of the executor is such
|
||||
// that the address register will already have been predecremented before
|
||||
// reaching here, and it'll have been by two bytes per the operand size
|
||||
// rather than according to the instruction size. That's not wanted, so undo it.
|
||||
//
|
||||
// TODO: with the caveat that the 68020+ have different behaviour:
|
||||
//
|
||||
// "For the MC68020, MC68030, MC68040, and CPU32, if the addressing register is also
|
||||
// moved to memory, the value written is the initial register value decremented by the
|
||||
// size of the operation. The MC68000 and MC68010 write the initial register value
|
||||
// (not decremented)."
|
||||
An(instruction.reg<1>()).l += 2;
|
||||
|
||||
uint32_t address = An(instruction.reg<1>()).l;
|
||||
int index = 15;
|
||||
|
||||
while(source) {
|
||||
if(source & 1) {
|
||||
address -= sizeof(IntT);
|
||||
write<IntT>(address, IntT(registers[index].l));
|
||||
}
|
||||
--index;
|
||||
source >>= 1;
|
||||
}
|
||||
|
||||
An(instruction.reg<1>()).l = address;
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
while(source) {
|
||||
if(source & 1) {
|
||||
write<IntT>(dest, IntT(registers[index].l));
|
||||
dest += sizeof(IntT);
|
||||
}
|
||||
++index;
|
||||
source >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <Model model, typename BusHandler>
|
||||
template <typename IntT>
|
||||
void Executor<model, BusHandler>::State::movem_toR(Preinstruction instruction, uint32_t source, uint32_t dest) {
|
||||
// Move memory to registers.
|
||||
//
|
||||
// A 68000 convention has been broken here; the instruction form is:
|
||||
// MOVEM <ea>, #
|
||||
// ... but the instruction is encoded as [MOVEM] [#] [ea].
|
||||
//
|
||||
// This project's decoder decodes as #, <ea>.
|
||||
int index = 0;
|
||||
while(source) {
|
||||
if(source & 1) {
|
||||
if constexpr (sizeof(IntT) == 2) {
|
||||
registers[index].l = int16_t(read<uint16_t>(dest));
|
||||
} else {
|
||||
registers[index].l = read<uint32_t>(dest);
|
||||
}
|
||||
dest += sizeof(IntT);
|
||||
}
|
||||
++index;
|
||||
source >>= 1;
|
||||
}
|
||||
|
||||
if(instruction.mode<1>() == AddressingMode::AddressRegisterIndirectWithPostincrement) {
|
||||
// "If the effective address is specified by the postincrement mode ...
|
||||
// [i]f the addressing register is also loaded from memory, the memory value is
|
||||
// ignored and the register is written with the postincremented effective address."
|
||||
|
||||
An(instruction.reg<1>()).l = dest;
|
||||
}
|
||||
}
|
||||
|
||||
#undef sp
|
||||
#undef Dn
|
||||
#undef An
|
||||
#undef AccessException
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_ExecutorImplementation_hpp */
|
||||
174
InstructionSets/M68k/Implementation/InstructionOperandFlags.hpp
Normal file
174
InstructionSets/M68k/Implementation/InstructionOperandFlags.hpp
Normal file
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// InstructionOperandFlags.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_68k_InstructionOperandFlags_hpp
|
||||
#define InstructionSets_68k_InstructionOperandFlags_hpp
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
template <Model model, Operation t_operation> constexpr uint8_t operand_flags(Operation r_operation) {
|
||||
switch((t_operation != Operation::Undefined) ? t_operation : r_operation) {
|
||||
default:
|
||||
assert(false);
|
||||
|
||||
//
|
||||
// No operands are fetched or stored.
|
||||
//
|
||||
// (which means that source and destination, if they exist,
|
||||
// should be supplied as their effective addresses)
|
||||
//
|
||||
case Operation::PEA:
|
||||
case Operation::JMP: case Operation::JSR:
|
||||
case Operation::MOVEPw: case Operation::MOVEPl:
|
||||
case Operation::TAS:
|
||||
case Operation::RTR: case Operation::RTS: case Operation::RTE:
|
||||
case Operation::RTM:
|
||||
case Operation::RTD:
|
||||
case Operation::TRAP: case Operation::RESET: case Operation::NOP:
|
||||
case Operation::STOP: case Operation::TRAPV: case Operation::BKPT:
|
||||
case Operation::TRAPcc:
|
||||
case Operation::CASb: case Operation::CASw: case Operation::CASl:
|
||||
case Operation::CAS2w: case Operation::CAS2l:
|
||||
return 0;
|
||||
|
||||
//
|
||||
// Operand fetch/store status isn't certain just from the operation; this means
|
||||
// that further content from an extension word will be required.
|
||||
//
|
||||
case Operation::MOVESb: case Operation::MOVESw: case Operation::MOVESl:
|
||||
return 0;
|
||||
|
||||
//
|
||||
// Single-operand read.
|
||||
//
|
||||
case Operation::MOVEtoSR: case Operation::MOVEtoCCR: case Operation::MOVEtoUSP:
|
||||
case Operation::ORItoSR: case Operation::ORItoCCR:
|
||||
case Operation::ANDItoSR: case Operation::ANDItoCCR:
|
||||
case Operation::EORItoSR: case Operation::EORItoCCR:
|
||||
case Operation::Bccb: case Operation::Bccw: case Operation::Bccl:
|
||||
case Operation::BSRb: case Operation::BSRw: case Operation::BSRl:
|
||||
case Operation::TSTb: case Operation::TSTw: case Operation::TSTl:
|
||||
case Operation::MOVEMtoMw: case Operation::MOVEMtoMl:
|
||||
case Operation::MOVEMtoRw: case Operation::MOVEMtoRl:
|
||||
case Operation::MOVEtoC:
|
||||
case Operation::CALLM:
|
||||
case Operation::CHKorCMP2b: case Operation::CHKorCMP2w: case Operation::CHKorCMP2l:
|
||||
return FetchOp1;
|
||||
|
||||
//
|
||||
// Single-operand write.
|
||||
//
|
||||
case Operation::MOVEfromUSP:
|
||||
case Operation::MOVEfromCCR:
|
||||
case Operation::MOVEfromC:
|
||||
return StoreOp1;
|
||||
|
||||
//
|
||||
// Single-operand read-modify-write.
|
||||
//
|
||||
case Operation::NBCD:
|
||||
case Operation::NOTb: case Operation::NOTw: case Operation::NOTl:
|
||||
case Operation::NEGb: case Operation::NEGw: case Operation::NEGl:
|
||||
case Operation::NEGXb: case Operation::NEGXw: case Operation::NEGXl:
|
||||
case Operation::EXTbtow: case Operation::EXTwtol: case Operation::EXTbtol:
|
||||
case Operation::SWAP:
|
||||
case Operation::UNLINK:
|
||||
case Operation::ASLm: case Operation::ASRm:
|
||||
case Operation::LSLm: case Operation::LSRm:
|
||||
case Operation::ROLm: case Operation::RORm:
|
||||
case Operation::ROXLm: case Operation::ROXRm:
|
||||
case Operation::Scc:
|
||||
return FetchOp1 | StoreOp1;
|
||||
|
||||
//
|
||||
// CLR and MOVE SR, which are model-dependent.
|
||||
//
|
||||
case Operation::MOVEfromSR:
|
||||
case Operation::CLRb: case Operation::CLRw: case Operation::CLRl:
|
||||
if constexpr (model == Model::M68000) {
|
||||
return FetchOp1 | StoreOp1;
|
||||
} else {
|
||||
return StoreOp1;
|
||||
}
|
||||
|
||||
//
|
||||
// Two-operand; read both.
|
||||
//
|
||||
case Operation::CMPb: case Operation::CMPw: case Operation::CMPl:
|
||||
case Operation::CMPAw: case Operation::CMPAl:
|
||||
case Operation::CHKw: case Operation::CHKl:
|
||||
case Operation::BTST:
|
||||
case Operation::LINKw: case Operation::LINKl:
|
||||
case Operation::BFTST: case Operation::BFFFO:
|
||||
case Operation::BFEXTU: case Operation::BFEXTS:
|
||||
case Operation::DIVSorDIVUl:
|
||||
case Operation::MULSorMULUl:
|
||||
return FetchOp1 | FetchOp2;
|
||||
|
||||
//
|
||||
// Two-operand; read source, write dest.
|
||||
//
|
||||
case Operation::MOVEb: case Operation::MOVEw: case Operation::MOVEl:
|
||||
case Operation::MOVEAw: case Operation::MOVEAl:
|
||||
case Operation::PACK: case Operation::UNPK:
|
||||
return FetchOp1 | StoreOp2;
|
||||
|
||||
//
|
||||
// Two-operand; read both, write dest.
|
||||
//
|
||||
case Operation::ABCD: case Operation::SBCD:
|
||||
case Operation::ADDb: case Operation::ADDw: case Operation::ADDl:
|
||||
case Operation::ADDAw: case Operation::ADDAl:
|
||||
case Operation::ADDXb: case Operation::ADDXw: case Operation::ADDXl:
|
||||
case Operation::SUBb: case Operation::SUBw: case Operation::SUBl:
|
||||
case Operation::SUBAw: case Operation::SUBAl:
|
||||
case Operation::SUBXb: case Operation::SUBXw: case Operation::SUBXl:
|
||||
case Operation::ORb: case Operation::ORw: case Operation::ORl:
|
||||
case Operation::ANDb: case Operation::ANDw: case Operation::ANDl:
|
||||
case Operation::EORb: case Operation::EORw: case Operation::EORl:
|
||||
case Operation::DIVUw: case Operation::DIVSw:
|
||||
case Operation::MULUw: case Operation::MULSw:
|
||||
case Operation::ASLb: case Operation::ASLw: case Operation::ASLl:
|
||||
case Operation::ASRb: case Operation::ASRw: case Operation::ASRl:
|
||||
case Operation::LSLb: case Operation::LSLw: case Operation::LSLl:
|
||||
case Operation::LSRb: case Operation::LSRw: case Operation::LSRl:
|
||||
case Operation::ROLb: case Operation::ROLw: case Operation::ROLl:
|
||||
case Operation::RORb: case Operation::RORw: case Operation::RORl:
|
||||
case Operation::ROXLb: case Operation::ROXLw: case Operation::ROXLl:
|
||||
case Operation::ROXRb: case Operation::ROXRw: case Operation::ROXRl:
|
||||
case Operation::BCHG:
|
||||
case Operation::BCLR: case Operation::BSET:
|
||||
case Operation::BFCHG: case Operation::BFCLR: case Operation::BFSET:
|
||||
case Operation::BFINS:
|
||||
return FetchOp1 | FetchOp2 | StoreOp2;
|
||||
|
||||
//
|
||||
// Two-operand; read both, write source.
|
||||
//
|
||||
case Operation::DBcc:
|
||||
return FetchOp1 | FetchOp2 | StoreOp1;
|
||||
|
||||
//
|
||||
// Two-operand; read both, write both.
|
||||
//
|
||||
case Operation::EXG:
|
||||
return FetchOp1 | FetchOp2 | StoreOp1 | StoreOp2;
|
||||
|
||||
//
|
||||
// Two-operand; just write destination.
|
||||
//
|
||||
case Operation::LEA:
|
||||
return StoreOp2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_68k_InstructionOperandFlags_hpp */
|
||||
134
InstructionSets/M68k/Implementation/InstructionOperandSize.hpp
Normal file
134
InstructionSets/M68k/Implementation/InstructionOperandSize.hpp
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// InstructionOperandSize.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_68k_InstructionOperandSize_hpp
|
||||
#define InstructionSets_68k_InstructionOperandSize_hpp
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
template <Operation t_operation>
|
||||
constexpr DataSize operand_size(Operation r_operation) {
|
||||
switch((t_operation == Operation::Undefined) ? r_operation : t_operation) {
|
||||
// These are given a value arbitrarily, to
|
||||
// complete the switch statement.
|
||||
case Operation::Undefined:
|
||||
case Operation::NOP:
|
||||
case Operation::STOP:
|
||||
case Operation::RESET:
|
||||
case Operation::RTE: case Operation::RTR:
|
||||
case Operation::RTD:
|
||||
case Operation::TRAP:
|
||||
case Operation::TRAPV:
|
||||
case Operation::TRAPcc:
|
||||
case Operation::BKPT:
|
||||
|
||||
case Operation::ABCD: case Operation::SBCD:
|
||||
case Operation::NBCD:
|
||||
case Operation::ADDb: case Operation::ADDXb:
|
||||
case Operation::SUBb: case Operation::SUBXb:
|
||||
case Operation::MOVEb:
|
||||
case Operation::MOVESb:
|
||||
case Operation::MOVEfromCCR:
|
||||
case Operation::ORItoCCR:
|
||||
case Operation::ANDItoCCR:
|
||||
case Operation::EORItoCCR:
|
||||
case Operation::BTST: case Operation::BCLR:
|
||||
case Operation::BCHG: case Operation::BSET:
|
||||
case Operation::CMPb: case Operation::TSTb:
|
||||
case Operation::Bccb: case Operation::BSRb:
|
||||
case Operation::CLRb:
|
||||
case Operation::Scc:
|
||||
case Operation::NEGXb: case Operation::NEGb:
|
||||
case Operation::ASLb: case Operation::ASRb:
|
||||
case Operation::LSLb: case Operation::LSRb:
|
||||
case Operation::ROLb: case Operation::RORb:
|
||||
case Operation::ROXLb: case Operation::ROXRb:
|
||||
case Operation::ANDb: case Operation::EORb:
|
||||
case Operation::NOTb: case Operation::ORb:
|
||||
case Operation::TAS:
|
||||
return DataSize::Byte;
|
||||
|
||||
case Operation::ADDw: case Operation::ADDAw:
|
||||
case Operation::ADDXw: case Operation::SUBw:
|
||||
case Operation::SUBAw: case Operation::SUBXw:
|
||||
case Operation::MOVEw: case Operation::MOVEAw:
|
||||
case Operation::MOVESw:
|
||||
case Operation::ORItoSR:
|
||||
case Operation::ANDItoSR:
|
||||
case Operation::EORItoSR:
|
||||
case Operation::MOVEtoSR:
|
||||
case Operation::MOVEfromSR:
|
||||
case Operation::MOVEtoCCR: // TODO: is this true?
|
||||
case Operation::CMPw: case Operation::CMPAw:
|
||||
case Operation::TSTw:
|
||||
case Operation::DBcc:
|
||||
case Operation::Bccw: case Operation::BSRw:
|
||||
case Operation::CLRw:
|
||||
case Operation::NEGXw: case Operation::NEGw:
|
||||
case Operation::ASLw: case Operation::ASLm:
|
||||
case Operation::ASRw: case Operation::ASRm:
|
||||
case Operation::LSLw: case Operation::LSLm:
|
||||
case Operation::LSRw: case Operation::LSRm:
|
||||
case Operation::ROLw: case Operation::ROLm:
|
||||
case Operation::RORw: case Operation::RORm:
|
||||
case Operation::ROXLw: case Operation::ROXLm:
|
||||
case Operation::ROXRw: case Operation::ROXRm:
|
||||
case Operation::MOVEMtoRw:
|
||||
case Operation::MOVEMtoRl:
|
||||
case Operation::MOVEMtoMw:
|
||||
case Operation::MOVEMtoMl:
|
||||
case Operation::MOVEPw:
|
||||
case Operation::ANDw: case Operation::EORw:
|
||||
case Operation::NOTw: case Operation::ORw:
|
||||
case Operation::DIVUw: case Operation::DIVSw:
|
||||
case Operation::MULUw: case Operation::MULSw:
|
||||
case Operation::EXTbtow:
|
||||
case Operation::LINKw:
|
||||
case Operation::CHKw:
|
||||
return DataSize::Word;
|
||||
|
||||
case Operation::ADDl: case Operation::ADDAl:
|
||||
case Operation::ADDXl: case Operation::SUBl:
|
||||
case Operation::SUBAl: case Operation::SUBXl:
|
||||
case Operation::MOVEl: case Operation::MOVEAl:
|
||||
case Operation::MOVESl:
|
||||
case Operation::LEA: case Operation::PEA:
|
||||
case Operation::EXG: case Operation::SWAP:
|
||||
case Operation::MOVEtoUSP:
|
||||
case Operation::MOVEfromUSP:
|
||||
case Operation::MOVEtoC:
|
||||
case Operation::MOVEfromC:
|
||||
case Operation::CMPl: case Operation::CMPAl:
|
||||
case Operation::TSTl:
|
||||
case Operation::JMP: case Operation::JSR:
|
||||
case Operation::RTS:
|
||||
case Operation::Bccl: case Operation::BSRl:
|
||||
case Operation::CLRl:
|
||||
case Operation::NEGXl: case Operation::NEGl:
|
||||
case Operation::ASLl: case Operation::ASRl:
|
||||
case Operation::LSLl: case Operation::LSRl:
|
||||
case Operation::ROLl: case Operation::RORl:
|
||||
case Operation::ROXLl: case Operation::ROXRl:
|
||||
case Operation::MOVEPl:
|
||||
case Operation::ANDl: case Operation::EORl:
|
||||
case Operation::NOTl: case Operation::ORl:
|
||||
case Operation::EXTwtol:
|
||||
case Operation::UNLINK:
|
||||
return DataSize::LongWord;
|
||||
|
||||
default:
|
||||
// 68020 TODO.
|
||||
return DataSize::Byte;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_68k_InstructionOperandSize_hpp */
|
||||
1036
InstructionSets/M68k/Implementation/PerformImplementation.hpp
Normal file
1036
InstructionSets/M68k/Implementation/PerformImplementation.hpp
Normal file
File diff suppressed because it is too large
Load Diff
323
InstructionSets/M68k/Instruction.cpp
Normal file
323
InstructionSets/M68k/Instruction.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
//
|
||||
// Instruction.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace InstructionSet::M68k;
|
||||
|
||||
std::string Preinstruction::operand_description(int index, int opcode) const {
|
||||
switch(mode(index)) {
|
||||
default: assert(false);
|
||||
|
||||
case AddressingMode::None:
|
||||
return "";
|
||||
|
||||
case AddressingMode::DataRegisterDirect:
|
||||
return std::string("D") + std::to_string(reg(index));
|
||||
|
||||
case AddressingMode::AddressRegisterDirect:
|
||||
return std::string("A") + std::to_string(reg(index));
|
||||
case AddressingMode::AddressRegisterIndirect:
|
||||
return std::string("(A") + std::to_string(reg(index)) + ")";
|
||||
case AddressingMode::AddressRegisterIndirectWithPostincrement:
|
||||
return std::string("(A") + std::to_string(reg(index)) + ")+";
|
||||
case AddressingMode::AddressRegisterIndirectWithPredecrement:
|
||||
return std::string("-(A") + std::to_string(reg(index)) + ")";
|
||||
case AddressingMode::AddressRegisterIndirectWithDisplacement:
|
||||
return std::string("(d16, A") + std::to_string(reg(index)) + ")";
|
||||
case AddressingMode::AddressRegisterIndirectWithIndex8bitDisplacement:
|
||||
return std::string("(d8, A") + std::to_string(reg(index)) + ", Xn)";
|
||||
|
||||
case AddressingMode::ProgramCounterIndirectWithDisplacement:
|
||||
return "(d16, PC)";
|
||||
case AddressingMode::ProgramCounterIndirectWithIndex8bitDisplacement:
|
||||
return "(d8, PC, Xn)";
|
||||
|
||||
case AddressingMode::AbsoluteShort:
|
||||
return "(xxx).w";
|
||||
case AddressingMode::AbsoluteLong:
|
||||
return "(xxx).l";
|
||||
|
||||
case AddressingMode::ExtensionWord:
|
||||
case AddressingMode::ImmediateData:
|
||||
return "#";
|
||||
|
||||
case AddressingMode::Quick:
|
||||
if(opcode == -1) {
|
||||
return "Q";
|
||||
}
|
||||
return std::to_string(int(quick(uint16_t(opcode), operation)));
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const char *_to_string(Operation operation, bool is_quick) {
|
||||
switch(operation) {
|
||||
case Operation::Undefined: return "None";
|
||||
case Operation::NOP: return "NOP";
|
||||
case Operation::ABCD: return "ABCD";
|
||||
case Operation::SBCD: return "SBCD";
|
||||
case Operation::NBCD: return "NBCD";
|
||||
|
||||
case Operation::ADDb: return "ADD.b";
|
||||
case Operation::ADDw: return "ADD.w";
|
||||
case Operation::ADDl: return "ADD.l";
|
||||
|
||||
case Operation::ADDAw: return is_quick ? "ADD.w" : "ADDA.w";
|
||||
case Operation::ADDAl: return is_quick ? "ADD.l" : "ADDA.l";
|
||||
|
||||
case Operation::ADDXb: return "ADDX.b";
|
||||
case Operation::ADDXw: return "ADDX.w";
|
||||
case Operation::ADDXl: return "ADDX.l";
|
||||
|
||||
case Operation::SUBb: return "SUB.b";
|
||||
case Operation::SUBw: return "SUB.w";
|
||||
case Operation::SUBl: return "SUB.l";
|
||||
|
||||
case Operation::SUBAw: return is_quick ? "SUB.w" : "SUBA.w";
|
||||
case Operation::SUBAl: return is_quick ? "SUB.l" : "SUBA.l";
|
||||
|
||||
case Operation::SUBXb: return "SUBX.b";
|
||||
case Operation::SUBXw: return "SUBX.w";
|
||||
case Operation::SUBXl: return "SUBX.l";
|
||||
|
||||
case Operation::MOVEb: return "MOVE.b";
|
||||
case Operation::MOVEw: return "MOVE.w";
|
||||
case Operation::MOVEl: return is_quick ? "MOVE.q" : "MOVE.l";
|
||||
|
||||
case Operation::MOVEAw: return "MOVEA.w";
|
||||
case Operation::MOVEAl: return "MOVEA.l";
|
||||
|
||||
case Operation::MOVESb: return "MOVES.b";
|
||||
case Operation::MOVESw: return "MOVES.w";
|
||||
case Operation::MOVESl: return "MOVES.l";
|
||||
|
||||
case Operation::LEA: return "LEA";
|
||||
case Operation::PEA: return "PEA";
|
||||
|
||||
case Operation::MOVEtoSR: return "MOVEtoSR";
|
||||
case Operation::MOVEfromSR: return "MOVEfromSR";
|
||||
case Operation::MOVEtoCCR: return "MOVEtoCCR";
|
||||
case Operation::MOVEfromCCR: return "MOVEfromCCR";
|
||||
case Operation::MOVEtoUSP: return "MOVEtoUSP";
|
||||
case Operation::MOVEfromUSP: return "MOVEfromUSP";
|
||||
case Operation::MOVEtoC: return "MOVEtoC";
|
||||
case Operation::MOVEfromC: return "MOVEfromC";
|
||||
|
||||
case Operation::ORItoSR: return "ORItoSR";
|
||||
case Operation::ORItoCCR: return "ORItoCCR";
|
||||
case Operation::ANDItoSR: return "ANDItoSR";
|
||||
case Operation::ANDItoCCR: return "ANDItoCCR";
|
||||
case Operation::EORItoSR: return "EORItoSR";
|
||||
case Operation::EORItoCCR: return "EORItoCCR";
|
||||
|
||||
case Operation::BTST: return "BTST";
|
||||
case Operation::BCLR: return "BCLR";
|
||||
case Operation::BCHG: return "BCHG";
|
||||
case Operation::BSET: return "BSET";
|
||||
|
||||
case Operation::CMPb: return "CMP.b";
|
||||
case Operation::CMPw: return "CMP.w";
|
||||
case Operation::CMPl: return "CMP.l";
|
||||
|
||||
case Operation::CMPAw: return "CMPA.w";
|
||||
case Operation::CMPAl: return "CMPA.l";
|
||||
|
||||
case Operation::TSTb: return "TST.b";
|
||||
case Operation::TSTw: return "TST.w";
|
||||
case Operation::TSTl: return "TST.l";
|
||||
|
||||
case Operation::JMP: return "JMP";
|
||||
case Operation::JSR: return "JSR";
|
||||
case Operation::RTS: return "RTS";
|
||||
case Operation::RTD: return "RTD";
|
||||
case Operation::RTM: return "RTM";
|
||||
|
||||
case Operation::DBcc: return "DBcc";
|
||||
case Operation::Scc: return "Scc";
|
||||
case Operation::TRAPcc: return "TRAPcc";
|
||||
|
||||
case Operation::Bccb:
|
||||
case Operation::Bccl:
|
||||
case Operation::Bccw: return "Bcc";
|
||||
|
||||
case Operation::BSRb:
|
||||
case Operation::BSRl:
|
||||
case Operation::BSRw: return "BSR";
|
||||
|
||||
case Operation::CASb: return "CAS.b";
|
||||
case Operation::CASw: return "CAS.w";
|
||||
case Operation::CASl: return "CAS.l";
|
||||
|
||||
case Operation::CAS2w: return "CAS2.w";
|
||||
case Operation::CAS2l: return "CAS2.l";
|
||||
|
||||
case Operation::CLRb: return "CLR.b";
|
||||
case Operation::CLRw: return "CLR.w";
|
||||
case Operation::CLRl: return "CLR.l";
|
||||
|
||||
case Operation::NEGXb: return "NEGX.b";
|
||||
case Operation::NEGXw: return "NEGX.w";
|
||||
case Operation::NEGXl: return "NEGX.l";
|
||||
|
||||
case Operation::NEGb: return "NEG.b";
|
||||
case Operation::NEGw: return "NEG.w";
|
||||
case Operation::NEGl: return "NEG.l";
|
||||
|
||||
case Operation::ASLb: return "ASL.b";
|
||||
case Operation::ASLw: return "ASL.w";
|
||||
case Operation::ASLl: return "ASL.l";
|
||||
case Operation::ASLm: return "ASL.w";
|
||||
|
||||
case Operation::ASRb: return "ASR.b";
|
||||
case Operation::ASRw: return "ASR.w";
|
||||
case Operation::ASRl: return "ASR.l";
|
||||
case Operation::ASRm: return "ASR.w";
|
||||
|
||||
case Operation::LSLb: return "LSL.b";
|
||||
case Operation::LSLw: return "LSL.w";
|
||||
case Operation::LSLl: return "LSL.l";
|
||||
case Operation::LSLm: return "LSL.w";
|
||||
|
||||
case Operation::LSRb: return "LSR.b";
|
||||
case Operation::LSRw: return "LSR.w";
|
||||
case Operation::LSRl: return "LSR.l";
|
||||
case Operation::LSRm: return "LSR.w";
|
||||
|
||||
case Operation::ROLb: return "ROL.b";
|
||||
case Operation::ROLw: return "ROL.w";
|
||||
case Operation::ROLl: return "ROL.l";
|
||||
case Operation::ROLm: return "ROL.w";
|
||||
|
||||
case Operation::RORb: return "ROR.b";
|
||||
case Operation::RORw: return "ROR.w";
|
||||
case Operation::RORl: return "ROR.l";
|
||||
case Operation::RORm: return "ROR.w";
|
||||
|
||||
case Operation::ROXLb: return "ROXL.b";
|
||||
case Operation::ROXLw: return "ROXL.w";
|
||||
case Operation::ROXLl: return "ROXL.l";
|
||||
case Operation::ROXLm: return "ROXL.w";
|
||||
|
||||
case Operation::ROXRb: return "ROXR.b";
|
||||
case Operation::ROXRw: return "ROXR.w";
|
||||
case Operation::ROXRl: return "ROXR.l";
|
||||
case Operation::ROXRm: return "ROXR.w";
|
||||
|
||||
case Operation::MOVEMtoMl: return "MOVEM.l";
|
||||
case Operation::MOVEMtoMw: return "MOVEM.w";
|
||||
case Operation::MOVEMtoRl: return "MOVEM.l";
|
||||
case Operation::MOVEMtoRw: return "MOVEM.w";
|
||||
|
||||
case Operation::MOVEPl: return "MOVEP.l";
|
||||
case Operation::MOVEPw: return "MOVEP.w";
|
||||
|
||||
case Operation::ANDb: return "AND.b";
|
||||
case Operation::ANDw: return "AND.w";
|
||||
case Operation::ANDl: return "AND.l";
|
||||
|
||||
case Operation::EORb: return "EOR.b";
|
||||
case Operation::EORw: return "EOR.w";
|
||||
case Operation::EORl: return "EOR.l";
|
||||
|
||||
case Operation::NOTb: return "NOT.b";
|
||||
case Operation::NOTw: return "NOT.w";
|
||||
case Operation::NOTl: return "NOT.l";
|
||||
|
||||
case Operation::ORb: return "OR.b";
|
||||
case Operation::ORw: return "OR.w";
|
||||
case Operation::ORl: return "OR.l";
|
||||
|
||||
case Operation::MULUw: return "MULU";
|
||||
case Operation::MULSw: return "MULS";
|
||||
case Operation::MULSorMULUl: return "[MULS/MULU]{L}.l";
|
||||
|
||||
case Operation::DIVUw: return "DIVU";
|
||||
case Operation::DIVSw: return "DIVS";
|
||||
case Operation::DIVSorDIVUl: return "[DIVS/DIVU]{L}.l";
|
||||
|
||||
case Operation::RTE: return "RTE";
|
||||
case Operation::RTR: return "RTR";
|
||||
|
||||
case Operation::TRAP: return "TRAP";
|
||||
case Operation::TRAPV: return "TRAPV";
|
||||
|
||||
case Operation::CHKw: return "CHK";
|
||||
case Operation::CHKl: return "CHK.l";
|
||||
|
||||
case Operation::CHKorCMP2b: return "[CHK/CMP]2.b";
|
||||
case Operation::CHKorCMP2w: return "[CHK/CMP]2.w";
|
||||
case Operation::CHKorCMP2l: return "[CHK/CMP]2.l";
|
||||
|
||||
case Operation::EXG: return "EXG";
|
||||
case Operation::SWAP: return "SWAP";
|
||||
|
||||
case Operation::TAS: return "TAS";
|
||||
|
||||
case Operation::EXTbtow: return "EXT.w";
|
||||
case Operation::EXTwtol: return "EXT.l";
|
||||
case Operation::EXTbtol: return "EXTB.l";
|
||||
|
||||
case Operation::LINKw: return "LINK";
|
||||
case Operation::LINKl: return "LINK.l";
|
||||
case Operation::UNLINK: return "UNLINK";
|
||||
|
||||
case Operation::STOP: return "STOP";
|
||||
case Operation::RESET: return "RESET";
|
||||
|
||||
case Operation::BKPT: return "BKPT";
|
||||
|
||||
case Operation::BFCHG: return "BFCHG";
|
||||
case Operation::BFCLR: return "BFCLR";
|
||||
case Operation::BFEXTS: return "BFEXTS";
|
||||
case Operation::BFEXTU: return "BFEXTU";
|
||||
case Operation::BFFFO: return "BFFFO";
|
||||
case Operation::BFINS: return "BFINS";
|
||||
case Operation::BFSET: return "BFSET";
|
||||
case Operation::BFTST: return "BFTST";
|
||||
|
||||
case Operation::PACK: return "PACK";
|
||||
case Operation::UNPK: return "UNPK";
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const char *InstructionSet::M68k::to_string(Operation operation) {
|
||||
return _to_string(operation, false);
|
||||
}
|
||||
|
||||
std::string Preinstruction::to_string(int opcode) const {
|
||||
if(operation == Operation::Undefined) return "None";
|
||||
|
||||
const char *const instruction = _to_string(operation, mode<0>() == AddressingMode::Quick);
|
||||
const bool flip_operands = (operation == Operation::MOVEMtoRl) || (operation == Operation::MOVEMtoRw);
|
||||
|
||||
const std::string operand1 = operand_description(0 ^ int(flip_operands), opcode);
|
||||
const std::string operand2 = operand_description(1 ^ int(flip_operands), opcode);
|
||||
|
||||
std::string result = instruction;
|
||||
if(!operand1.empty()) result += std::string(" ") + operand1;
|
||||
if(!operand2.empty()) result += std::string(", ") + operand2;
|
||||
|
||||
const int extension_words = additional_extension_words();
|
||||
if(extension_words) result += std::string(" [+") + std::to_string(extension_words) + "]";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const char *Preinstruction::operation_string() const {
|
||||
return _to_string(operation, mode<0>() == AddressingMode::Quick);
|
||||
}
|
||||
503
InstructionSets/M68k/Instruction.hpp
Normal file
503
InstructionSets/M68k/Instruction.hpp
Normal file
@@ -0,0 +1,503 @@
|
||||
//
|
||||
// Instruction.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_68k_Instruction_hpp
|
||||
#define InstructionSets_68k_Instruction_hpp
|
||||
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
enum class Operation: uint8_t {
|
||||
Undefined,
|
||||
|
||||
//
|
||||
// 68000 operations.
|
||||
//
|
||||
|
||||
NOP,
|
||||
|
||||
ABCD, SBCD, NBCD,
|
||||
|
||||
ADDb, ADDw, ADDl,
|
||||
ADDAw, ADDAl,
|
||||
ADDXb, ADDXw, ADDXl,
|
||||
|
||||
SUBb, SUBw, SUBl,
|
||||
SUBAw, SUBAl,
|
||||
SUBXb, SUBXw, SUBXl,
|
||||
|
||||
MOVEb, MOVEw, MOVEl,
|
||||
MOVEAw, MOVEAl,
|
||||
LEA, PEA,
|
||||
|
||||
MOVEtoSR, MOVEfromSR,
|
||||
MOVEtoCCR,
|
||||
MOVEtoUSP, MOVEfromUSP,
|
||||
|
||||
ORItoSR, ORItoCCR,
|
||||
ANDItoSR, ANDItoCCR,
|
||||
EORItoSR, EORItoCCR,
|
||||
|
||||
BTST, BCLR,
|
||||
BCHG, BSET,
|
||||
|
||||
CMPb, CMPw, CMPl,
|
||||
CMPAw, CMPAl,
|
||||
TSTb, TSTw, TSTl,
|
||||
|
||||
JMP,
|
||||
JSR, RTS,
|
||||
DBcc,
|
||||
Scc,
|
||||
|
||||
Bccb, Bccw,
|
||||
BSRb, BSRw,
|
||||
|
||||
CLRb, CLRw, CLRl,
|
||||
NEGXb, NEGXw, NEGXl,
|
||||
NEGb, NEGw, NEGl,
|
||||
|
||||
ASLb, ASLw, ASLl, ASLm,
|
||||
ASRb, ASRw, ASRl, ASRm,
|
||||
LSLb, LSLw, LSLl, LSLm,
|
||||
LSRb, LSRw, LSRl, LSRm,
|
||||
ROLb, ROLw, ROLl, ROLm,
|
||||
RORb, RORw, RORl, RORm,
|
||||
ROXLb, ROXLw, ROXLl, ROXLm,
|
||||
ROXRb, ROXRw, ROXRl, ROXRm,
|
||||
|
||||
MOVEMtoRl, MOVEMtoRw,
|
||||
MOVEMtoMl, MOVEMtoMw,
|
||||
|
||||
MOVEPl, MOVEPw,
|
||||
|
||||
ANDb, ANDw, ANDl,
|
||||
EORb, EORw, EORl,
|
||||
NOTb, NOTw, NOTl,
|
||||
ORb, ORw, ORl,
|
||||
|
||||
MULUw, MULSw,
|
||||
DIVUw, DIVSw,
|
||||
|
||||
RTE, RTR,
|
||||
|
||||
TRAP, TRAPV,
|
||||
CHKw,
|
||||
|
||||
EXG, SWAP,
|
||||
|
||||
TAS,
|
||||
|
||||
EXTbtow, EXTwtol,
|
||||
|
||||
LINKw, UNLINK,
|
||||
|
||||
STOP, RESET,
|
||||
|
||||
//
|
||||
// 68010 additions.
|
||||
//
|
||||
|
||||
MOVEfromCCR,
|
||||
MOVEtoC, MOVEfromC,
|
||||
MOVESb, MOVESw, MOVESl,
|
||||
BKPT, RTD,
|
||||
|
||||
//
|
||||
// 68020 additions.
|
||||
//
|
||||
|
||||
TRAPcc,
|
||||
|
||||
CALLM, RTM,
|
||||
|
||||
BFCHG, BFCLR,
|
||||
BFEXTS, BFEXTU,
|
||||
BFFFO, BFINS,
|
||||
BFSET, BFTST,
|
||||
|
||||
PACK, UNPK,
|
||||
|
||||
CASb, CASw, CASl,
|
||||
CAS2w, CAS2l,
|
||||
|
||||
// CHK2 and CMP2 are distinguished by their extension word;
|
||||
// since this code deals in Preinstructions, i.e. as much
|
||||
// as can be derived from the instruction word alone, in addition
|
||||
// to the full things, the following enums result.
|
||||
CHKorCMP2b, CHKorCMP2w, CHKorCMP2l,
|
||||
|
||||
// DIVS.l, DIVSL.l, DIVU.l and DIVUL.l are all distinguishable
|
||||
// only by the extension word.
|
||||
DIVSorDIVUl,
|
||||
|
||||
// MULS.l, MULSL.l, MULU.l and MULUL.l are all distinguishable
|
||||
// only by the extension word.
|
||||
MULSorMULUl,
|
||||
|
||||
Bccl, BSRl,
|
||||
LINKl, CHKl,
|
||||
|
||||
EXTbtol,
|
||||
|
||||
// Coprocessor instructions are omitted for now, until I can
|
||||
// determine by what mechanism the number of
|
||||
// "OPTIONAL COPROCESSOR-DEFINED EXTENSION WORDS" is determined.
|
||||
// cpBcc, cpDBcc, cpGEN,
|
||||
// cpScc, cpTRAPcc, cpRESTORE,
|
||||
// cpSAVE,
|
||||
|
||||
//
|
||||
// 68030 additions.
|
||||
//
|
||||
|
||||
PFLUSH, PFLUSHA,
|
||||
PLOADR, PLOADW,
|
||||
PMOVE, PMOVEFD,
|
||||
PTESTR, PTESTW,
|
||||
|
||||
//
|
||||
// 68040 additions.
|
||||
//
|
||||
|
||||
// TODO: the big addition of the 68040 is incorporation of the FPU; should I make decoding of those instructions
|
||||
// dependent upon a 68040 being selected, or should I offer a separate decoder in order to support systems with
|
||||
// a coprocessor?
|
||||
|
||||
//
|
||||
// Introspection.
|
||||
//
|
||||
Max68000 = RESET,
|
||||
Max68010 = RTD,
|
||||
Max68020 = EXTbtol,
|
||||
Max68030 = PTESTW,
|
||||
Max68040 = PTESTW,
|
||||
};
|
||||
|
||||
// Provide per-model max entries in Operation.
|
||||
template <Model> struct OperationMax {};
|
||||
template <> struct OperationMax<Model::M68000> {
|
||||
static constexpr Operation value = Operation::Max68000;
|
||||
};
|
||||
template <> struct OperationMax<Model::M68010> {
|
||||
static constexpr Operation value = Operation::Max68010;
|
||||
};
|
||||
template <> struct OperationMax<Model::M68020> {
|
||||
static constexpr Operation value = Operation::Max68020;
|
||||
};
|
||||
template <> struct OperationMax<Model::M68030> {
|
||||
static constexpr Operation value = Operation::Max68030;
|
||||
};
|
||||
template <> struct OperationMax<Model::M68040> {
|
||||
static constexpr Operation value = Operation::Max68040;
|
||||
};
|
||||
|
||||
const char *to_string(Operation op);
|
||||
|
||||
template <Model model>
|
||||
constexpr bool requires_supervisor(Operation op) {
|
||||
switch(op) {
|
||||
case Operation::MOVEfromSR:
|
||||
if constexpr (model == Model::M68000) {
|
||||
return false;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case Operation::ORItoSR: case Operation::ANDItoSR:
|
||||
case Operation::EORItoSR: case Operation::RTE:
|
||||
case Operation::RESET: case Operation::STOP:
|
||||
case Operation::MOVEtoUSP: case Operation::MOVEfromUSP:
|
||||
case Operation::MOVEtoC: case Operation::MOVEfromC:
|
||||
case Operation::MOVEtoSR:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum class DataSize {
|
||||
Byte = 0,
|
||||
Word = 1,
|
||||
LongWord = 2,
|
||||
};
|
||||
|
||||
/// Classifies operations by the size of their memory accesses, if any.
|
||||
///
|
||||
/// For any operations that don't fit the neat model of reading one or two operands,
|
||||
/// then writing zero or one, the size determines the data size of the operands only,
|
||||
/// not any other accesses.
|
||||
template <Operation t_operation = Operation::Undefined>
|
||||
constexpr DataSize operand_size(Operation operation = Operation::Undefined);
|
||||
|
||||
template <Operation t_op = Operation::Undefined>
|
||||
constexpr uint32_t quick(uint16_t instruction, Operation r_op = Operation::Undefined) {
|
||||
switch((t_op != Operation::Undefined) ? t_op : r_op) {
|
||||
case Operation::Bccb:
|
||||
case Operation::BSRb:
|
||||
case Operation::MOVEl: return uint32_t(int8_t(instruction));
|
||||
case Operation::TRAP: return uint32_t(instruction & 15);
|
||||
case Operation::BKPT: return uint32_t(instruction & 7);
|
||||
default: {
|
||||
uint32_t value = (instruction >> 9) & 7;
|
||||
value |= (value - 1)&8;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint8_t FetchOp1 = (1 << 0);
|
||||
static constexpr uint8_t FetchOp2 = (1 << 1);
|
||||
static constexpr uint8_t StoreOp1 = (1 << 2);
|
||||
static constexpr uint8_t StoreOp2 = (1 << 3);
|
||||
|
||||
/*!
|
||||
Provides a bitfield with a value in the range 0–15 indicating which of the provided operation's
|
||||
operands are accessed via standard fetch and store cycles; the bitfield is composted of
|
||||
[Fetch/Store]Op[1/2] as defined above.
|
||||
|
||||
Unusual bus sequences, such as TAS or MOVEM, are not described here.
|
||||
*/
|
||||
template <Model model, Operation t_operation = Operation::Undefined>
|
||||
constexpr uint8_t operand_flags(Operation r_operation = Operation::Undefined);
|
||||
|
||||
/// Lists the various condition codes used by the 680x0.
|
||||
enum class Condition {
|
||||
True = 0x00, False = 0x01,
|
||||
High = 0x02, LowOrSame = 0x03,
|
||||
CarryClear = 0x04, CarrySet = 0x05,
|
||||
NotEqual = 0x06, Equal = 0x07,
|
||||
OverflowClear = 0x08, OverflowSet = 0x09,
|
||||
Positive = 0x0a, Negative = 0x0b,
|
||||
GreaterThanOrEqual = 0x0c, LessThan = 0x0d,
|
||||
GreaterThan = 0x0e, LessThanOrEqual = 0x0f,
|
||||
};
|
||||
|
||||
/// Indicates the addressing mode applicable to an operand.
|
||||
///
|
||||
/// Implementation notes:
|
||||
///
|
||||
/// Those entries starting 0b00 or 0b01 are mapped as per the 68000's native encoding;
|
||||
/// those starting 0b00 are those which are indicated directly by a mode field and those starting
|
||||
/// 0b01 are those which are indicated by a register field given a mode of 0b111. The only minor
|
||||
/// exception is AddressRegisterDirect, which exists on a 68000 but isn't specifiable by a
|
||||
/// mode and register, it's contextual based on the instruction.
|
||||
///
|
||||
/// Those modes starting in 0b10 are the various extended addressing modes introduced as
|
||||
/// of the 68020, which can be detected only after interpreting an extension word. At the
|
||||
/// Preinstruction stage:
|
||||
///
|
||||
/// * AddressRegisterIndirectWithIndexBaseDisplacement, MemoryIndirectPostindexed
|
||||
/// and MemoryIndirectPreindexed will have been partially decoded as
|
||||
/// AddressRegisterIndirectWithIndex8bitDisplacement; and
|
||||
/// * ProgramCounterIndirectWithIndexBaseDisplacement,
|
||||
/// ProgramCounterMemoryIndirectPostindexed and
|
||||
/// ProgramCounterMemoryIndirectPreindexed will have been partially decoded
|
||||
/// as ProgramCounterIndirectWithIndex8bitDisplacement.
|
||||
enum class AddressingMode: uint8_t {
|
||||
/// No adddressing mode; this operand doesn't exist.
|
||||
None = 0b01'101,
|
||||
|
||||
/// Dn
|
||||
DataRegisterDirect = 0b00'000,
|
||||
|
||||
/// An
|
||||
AddressRegisterDirect = 0b00'001,
|
||||
/// (An)
|
||||
AddressRegisterIndirect = 0b00'010,
|
||||
/// (An)+
|
||||
AddressRegisterIndirectWithPostincrement = 0b00'011,
|
||||
/// -(An)
|
||||
AddressRegisterIndirectWithPredecrement = 0b00'100,
|
||||
/// (d16, An)
|
||||
AddressRegisterIndirectWithDisplacement = 0b00'101,
|
||||
/// (d8, An, Xn)
|
||||
AddressRegisterIndirectWithIndex8bitDisplacement = 0b00'110,
|
||||
/// (bd, An, Xn) [68020+]
|
||||
AddressRegisterIndirectWithIndexBaseDisplacement = 0b10'000,
|
||||
|
||||
/// ([bd, An, Xn], od) [68020+]
|
||||
MemoryIndirectPostindexed = 0b10'001,
|
||||
/// ([bd, An], Xn, od) [68020+]
|
||||
MemoryIndirectPreindexed = 0b10'010,
|
||||
|
||||
/// (d16, PC)
|
||||
ProgramCounterIndirectWithDisplacement = 0b01'010,
|
||||
/// (d8, PC, Xn)
|
||||
ProgramCounterIndirectWithIndex8bitDisplacement = 0b01'011,
|
||||
/// (bd, PC, Xn) [68020+]
|
||||
ProgramCounterIndirectWithIndexBaseDisplacement = 0b10'011,
|
||||
/// ([bd, PC, Xn], od) [68020+]
|
||||
ProgramCounterMemoryIndirectPostindexed = 0b10'100,
|
||||
/// ([bc, PC], Xn, od) [68020+]
|
||||
ProgramCounterMemoryIndirectPreindexed = 0b10'101,
|
||||
|
||||
/// (xxx).W
|
||||
AbsoluteShort = 0b01'000,
|
||||
/// (xxx).L
|
||||
AbsoluteLong = 0b01'001,
|
||||
|
||||
/// #
|
||||
ImmediateData = 0b01'100,
|
||||
|
||||
/// An additional word of data. Differs from ImmediateData by being
|
||||
/// a fixed size, rather than the @c operand_size of the operation.
|
||||
ExtensionWord = 0b01'111,
|
||||
|
||||
/// .q; value is embedded in the opcode.
|
||||
Quick = 0b01'110,
|
||||
};
|
||||
/// Guaranteed to be 1+[largest value used by AddressingMode].
|
||||
static constexpr int AddressingModeCount = 0b10'110;
|
||||
|
||||
/*!
|
||||
A preinstruction is as much of an instruction as can be decoded with
|
||||
only the first instruction word — i.e. an operation, and:
|
||||
|
||||
* on the 68000 and 68010, the complete addressing modes;
|
||||
* on subsequent, a decent proportion of the addressing mode. See
|
||||
the notes on @c AddressingMode for potential aliasing.
|
||||
*/
|
||||
class Preinstruction {
|
||||
public:
|
||||
Operation operation = Operation::Undefined;
|
||||
|
||||
// Instructions come with 0, 1 or 2 operands;
|
||||
// the getters below act to provide a list of operands
|
||||
// that is terminated by an AddressingMode::None.
|
||||
//
|
||||
// For two-operand instructions, argument 0 is a source
|
||||
// and argument 1 is a destination.
|
||||
//
|
||||
// For one-operand instructions, only argument 0 will
|
||||
// be provided, and will be a source and/or destination as
|
||||
// per the semantics of the operation.
|
||||
//
|
||||
// The versions templated on index do a range check;
|
||||
// if using the runtime versions then results for indices
|
||||
// other than 0 and 1 are undefined.
|
||||
|
||||
AddressingMode mode(int index) const {
|
||||
return AddressingMode(operands_[index] >> 3);
|
||||
}
|
||||
template <int index> AddressingMode mode() const {
|
||||
if constexpr (index > 1) {
|
||||
return AddressingMode::None;
|
||||
}
|
||||
return mode(index);
|
||||
}
|
||||
int reg(int index) const {
|
||||
return operands_[index] & 7;
|
||||
}
|
||||
template <int index> int reg() const {
|
||||
if constexpr (index > 1) {
|
||||
return 0;
|
||||
}
|
||||
return reg(index);
|
||||
}
|
||||
|
||||
/// @returns 0–7 to indicate data registers 0 to 7, or 8–15 to indicate address registers 0 to 7 respectively.
|
||||
/// Provides undefined results if the addressing mode is not either @c DataRegisterDirect or
|
||||
/// @c AddressRegisterDirect.
|
||||
int lreg(int index) const {
|
||||
return operands_[index] & 0xf;
|
||||
}
|
||||
|
||||
/// @returns @c true if this instruction requires supervisor privileges; @c false otherwise.
|
||||
bool requires_supervisor() const {
|
||||
return flags_ & Flags::IsSupervisor;
|
||||
}
|
||||
/// @returns @c true if this instruction will require further fetching than can be encoded in a
|
||||
/// @c Preinstruction. In practice this means it is one of a very small quantity of 68020+
|
||||
/// instructions; those that can rationalise extension words into one of the two operands will
|
||||
/// do so. Use the free function @c extension_words(instruction.operation) to
|
||||
/// look up the number of additional words required.
|
||||
///
|
||||
/// (specifically affected, at least: PACK, UNPK, CAS, CAS2)
|
||||
bool requires_further_extension() const {
|
||||
return flags_ & Flags::RequiresFurtherExtension;
|
||||
}
|
||||
/// @returns The number of additional extension words required, beyond those encoded as operands.
|
||||
int additional_extension_words() const {
|
||||
return flags_ & Flags::RequiresFurtherExtension ? (flags_ & Flags::ConditionMask) >> Flags::ConditionShift : 0;
|
||||
}
|
||||
/// @returns The @c DataSize used for operands of this instruction, i.e. byte, word or longword.
|
||||
DataSize operand_size() const {
|
||||
return DataSize((flags_ & Flags::SizeMask) >> Flags::SizeShift);
|
||||
}
|
||||
/// @returns The condition code evaluated by this instruction if applicable. If this instruction is not
|
||||
/// conditional, the result is undefined.
|
||||
Condition condition() const {
|
||||
return Condition((flags_ & Flags::ConditionMask) >> Flags::ConditionShift);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t operands_[2] = { uint8_t(AddressingMode::None), uint8_t(AddressingMode::None)};
|
||||
uint8_t flags_ = 0;
|
||||
|
||||
std::string operand_description(int index, int opcode) const;
|
||||
|
||||
public:
|
||||
Preinstruction(
|
||||
Operation operation,
|
||||
AddressingMode op1_mode, int op1_reg,
|
||||
AddressingMode op2_mode, int op2_reg,
|
||||
bool is_supervisor,
|
||||
int extension_words,
|
||||
DataSize size,
|
||||
Condition condition) : operation(operation)
|
||||
{
|
||||
operands_[0] = uint8_t((uint8_t(op1_mode) << 3) | op1_reg);
|
||||
operands_[1] = uint8_t((uint8_t(op2_mode) << 3) | op2_reg);
|
||||
flags_ = uint8_t(
|
||||
(is_supervisor ? Flags::IsSupervisor : 0x00) |
|
||||
(extension_words ? Flags::RequiresFurtherExtension : 0x00) |
|
||||
(int(condition) << Flags::ConditionShift) |
|
||||
(extension_words << Flags::ConditionShift) |
|
||||
(int(size) << Flags::SizeShift)
|
||||
);
|
||||
}
|
||||
|
||||
struct Flags {
|
||||
static constexpr uint8_t IsSupervisor = 0b1000'0000;
|
||||
static constexpr uint8_t RequiresFurtherExtension = 0b0100'0000;
|
||||
static constexpr uint8_t ConditionMask = 0b0011'1100;
|
||||
static constexpr uint8_t SizeMask = 0b0000'0011;
|
||||
|
||||
static constexpr int IsSupervisorShift = 7;
|
||||
static constexpr int RequiresFurtherExtensionShift = 6;
|
||||
static constexpr int ConditionShift = 2;
|
||||
static constexpr int SizeShift = 0;
|
||||
};
|
||||
|
||||
Preinstruction() {}
|
||||
|
||||
/// Produces a string description of this instruction; if @c opcode
|
||||
/// is supplied then any quick fields in this instruction will be decoded;
|
||||
/// otherwise they'll be printed as just 'Q'.
|
||||
std::string to_string(int opcode = -1) const;
|
||||
|
||||
/// Produces a slightly-more-idiomatic version of the operation name than
|
||||
/// a direct to_string(instruction.operation) would, given that this decoder
|
||||
/// sometimes aliases operations, disambiguating based on addressing mode
|
||||
/// (e.g. MOVEQ is MOVE.l with the Q addressing mode).
|
||||
const char *operation_string() const;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/InstructionOperandSize.hpp"
|
||||
#include "Implementation/InstructionOperandFlags.hpp"
|
||||
|
||||
#endif /* InstructionSets_68k_Instruction_hpp */
|
||||
26
InstructionSets/M68k/Model.hpp
Normal file
26
InstructionSets/M68k/Model.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// Model.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 15/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Model_hpp
|
||||
#define InstructionSets_M68k_Model_hpp
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
enum class Model {
|
||||
M68000,
|
||||
M68010,
|
||||
M68020,
|
||||
M68030,
|
||||
M68040,
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_Model_hpp */
|
||||
175
InstructionSets/M68k/Perform.hpp
Normal file
175
InstructionSets/M68k/Perform.hpp
Normal file
@@ -0,0 +1,175 @@
|
||||
//
|
||||
// Perform.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Perform_h
|
||||
#define InstructionSets_M68k_Perform_h
|
||||
|
||||
#include "Model.hpp"
|
||||
#include "Instruction.hpp"
|
||||
#include "Status.hpp"
|
||||
#include "../../Numeric/RegisterSizes.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
struct NullFlowController {
|
||||
//
|
||||
// Various operation-specific did-perform notfications; these all relate to operations
|
||||
// with variable timing on a 68000, providing the fields that contribute to that timing.
|
||||
//
|
||||
|
||||
/// Indicates that a @c MULU was performed, providing the @c source operand.
|
||||
template <typename IntT> void did_mulu(IntT) {}
|
||||
|
||||
/// Indicates that a @c MULS was performed, providing the @c source operand.
|
||||
template <typename IntT> void did_muls(IntT) {}
|
||||
|
||||
/// Indicates that a @c CHK was performed, along with whether the result @c was_under zero or @c was_over the source operand.
|
||||
void did_chk([[maybe_unused]] bool was_under, [[maybe_unused]] bool was_over) {}
|
||||
|
||||
/// Indicates an in-register shift or roll occurred, providing the number of bits shifted by
|
||||
/// and the type shifted.
|
||||
///
|
||||
/// @c IntT may be uint8_t, uint16_t or uint32_t.
|
||||
template <typename IntT> void did_shift([[maybe_unused]] int bit_count) {}
|
||||
|
||||
/// Indicates that a @c DIVU was performed, providing the @c dividend and @c divisor.
|
||||
/// If @c did_overflow is @c true then the divide ended in overflow.
|
||||
template <bool did_overflow> void did_divu([[maybe_unused]] uint32_t dividend, [[maybe_unused]] uint32_t divisor) {}
|
||||
|
||||
/// Indicates that a @c DIVS was performed, providing the @c dividend and @c divisor.
|
||||
/// If @c did_overflow is @c true then the divide ended in overflow.
|
||||
template <bool did_overflow> void did_divs([[maybe_unused]] int32_t dividend, [[maybe_unused]] int32_t divisor) {}
|
||||
|
||||
/// Indicates that a bit-manipulation operation (i.e. BTST, BCHG or BSET) was performed, affecting the bit at posiition @c bit_position.
|
||||
void did_bit_op([[maybe_unused]] int bit_position) {}
|
||||
|
||||
/// Indicates that an @c Scc was performed; if @c did_set_ff is true then the condition was true and FF
|
||||
/// written to the operand; otherwise 00 was written.
|
||||
void did_scc([[maybe_unused]] bool did_set_ff) {}
|
||||
|
||||
/// Provides a notification that the upper byte of the status register has been affected by the current instruction;
|
||||
/// this gives an opportunity to track the supervisor flag.
|
||||
void did_update_status() {}
|
||||
|
||||
//
|
||||
// Operations that don't fit the reductive load-modify-store pattern; these are requests from perform
|
||||
// that the flow controller do something (and, correspondingly, do not have empty implementations).
|
||||
//
|
||||
// All offsets are the native values as encoded in the corresponding operations.
|
||||
//
|
||||
|
||||
/// If @c matched_condition is @c true, apply the @c offset to the PC.
|
||||
template <typename IntT> void complete_bcc(bool matched_condition, IntT offset);
|
||||
|
||||
/// If both @c matched_condition and @c overflowed are @c false, apply @c offset to the PC.
|
||||
void complete_dbcc(bool matched_condition, bool overflowed, int16_t offset);
|
||||
|
||||
/// Push the program counter of the next instruction to the stack, and add @c offset to the PC.
|
||||
void bsr(uint32_t offset);
|
||||
|
||||
/// Push the program counter of the next instruction to the stack, and load @c offset to the PC.
|
||||
void jsr(uint32_t address);
|
||||
|
||||
/// Set the program counter to @c address.
|
||||
void jmp(uint32_t address);
|
||||
|
||||
/// Pop a word from the stack and use that to set the status condition codes. Then pop a new value for the PC.
|
||||
void rtr();
|
||||
|
||||
/// Pop a word from the stack and use that to set the entire status register. Then pop a new value for the PC.
|
||||
void rte();
|
||||
|
||||
/// Pop a new value for the PC from the stack.
|
||||
void rts();
|
||||
|
||||
/// Put the processor into the stopped state, waiting for interrupts.
|
||||
void stop();
|
||||
|
||||
/// Assert the reset output.
|
||||
void reset();
|
||||
|
||||
/// Perform LINK using the address register identified by @c instruction and the specified @c offset.
|
||||
void link(Preinstruction instruction, uint32_t offset);
|
||||
|
||||
/// Perform unlink, with @c address being the target address register.
|
||||
void unlink(uint32_t &address);
|
||||
|
||||
/// Push @c address to the stack.
|
||||
void pea(uint32_t address);
|
||||
|
||||
/// Replace the current user stack pointer with @c address.
|
||||
/// The processor is guranteed to be in supervisor mode.
|
||||
void move_to_usp(uint32_t address);
|
||||
|
||||
/// Put the value of the user stack pointer into @c address.
|
||||
/// The processor is guranteed to be in supervisor mode.
|
||||
void move_from_usp(uint32_t &address);
|
||||
|
||||
/// Perform an atomic TAS cycle; if @c instruction indicates that this is a TAS Dn then
|
||||
/// perform the TAS directly upon that register; otherwise perform it on the memory at
|
||||
/// @c address. If this is a TAS Dn then @c address will contain the initial value of
|
||||
/// the register.
|
||||
void tas(Preinstruction instruction, uint32_t address);
|
||||
|
||||
/// Use @c instruction to determine the direction of this MOVEP and perform it;
|
||||
/// @c source is the first operand provided to the MOVEP — either an address or register
|
||||
/// contents — and @c dest is the second.
|
||||
///
|
||||
/// @c IntT may be either uint16_t or uint32_t.
|
||||
template <typename IntT> void movep(Preinstruction instruction, uint32_t source, uint32_t dest);
|
||||
|
||||
/// Perform a MOVEM to memory, from registers. @c instruction will indicate the mask as the first operand,
|
||||
/// and the target address and addressing mode as the second; the mask and address are also supplied
|
||||
/// as @c mask and @c address. If the addressing mode is -(An) then the address register will have
|
||||
/// been decremented already.
|
||||
///
|
||||
/// The receiver is responsible for updating the address register if applicable.
|
||||
///
|
||||
/// @c IntT may be either uint16_t or uint32_t.
|
||||
template <typename IntT> void movem_toM(Preinstruction instruction, uint32_t mask, uint32_t address);
|
||||
|
||||
/// Perform a MOVEM to registers, from memory. @c instruction will indicate the mask as the first operand,
|
||||
/// and the target address and addressing mode as the second; the mask and address are also supplied
|
||||
/// as @c mask and @c address. If the addressing mode is (An)+ then the address register will have been
|
||||
/// incremented, but @c address will be its value before that occurred.
|
||||
///
|
||||
/// The receiver is responsible for updating the address register if applicable.
|
||||
///
|
||||
/// @c IntT may be either uint16_t or uint32_t.
|
||||
template <typename IntT> void movem_toR(Preinstruction instruction, uint32_t mask, uint32_t address);
|
||||
|
||||
/// Raises a short-form exception using @c vector. If @c use_current_instruction_pc is @c true,
|
||||
/// the program counter for the current instruction is included in the resulting stack frame. Otherwise the program
|
||||
/// counter for the next instruction is used.
|
||||
template <bool use_current_instruction_pc = true>
|
||||
void raise_exception([[maybe_unused]] int vector);
|
||||
};
|
||||
|
||||
/// Performs @c instruction using @c source and @c dest (one or both of which may be ignored as per
|
||||
/// the semantics of the operation).
|
||||
///
|
||||
/// Any change in processor status will be applied to @c status. If this operation does not fit the reductive model
|
||||
/// of being a read and possibly a modify and possibly a write of up to two operands then the @c flow_controller
|
||||
/// will be asked to fill in the gaps.
|
||||
///
|
||||
/// If the template parameter @c operation is not @c Operation::Undefined then that operation will be performed, ignoring
|
||||
/// whatever is specifed in @c instruction. This allows selection either at compile time or at run time; per Godbolt all modern
|
||||
/// compilers seem to be smart enough fully to optimise the compile-time case.
|
||||
template <
|
||||
Model model,
|
||||
typename FlowController,
|
||||
Operation operation = Operation::Undefined
|
||||
> void perform(Preinstruction instruction, CPU::RegisterPair32 &source, CPU::RegisterPair32 &dest, Status &status, FlowController &flow_controller);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#include "Implementation/PerformImplementation.hpp"
|
||||
|
||||
#endif /* InstructionSets_M68k_Perform_h */
|
||||
31
InstructionSets/M68k/RegisterSet.hpp
Normal file
31
InstructionSets/M68k/RegisterSet.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// RegisterSet.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/05/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_RegisterSet_h
|
||||
#define InstructionSets_M68k_RegisterSet_h
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
struct RegisterSet {
|
||||
uint32_t data[8], address[7];
|
||||
uint32_t user_stack_pointer;
|
||||
uint32_t supervisor_stack_pointer;
|
||||
uint16_t status;
|
||||
uint32_t program_counter;
|
||||
|
||||
/// @returns The active stack pointer, whichever it may be.
|
||||
uint32_t stack_pointer() const {
|
||||
return (status & 0x2000) ? supervisor_stack_pointer : user_stack_pointer;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_RegisterSet_h */
|
||||
165
InstructionSets/M68k/Status.hpp
Normal file
165
InstructionSets/M68k/Status.hpp
Normal file
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// Status.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 28/04/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_M68k_Status_h
|
||||
#define InstructionSets_M68k_Status_h
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace M68k {
|
||||
|
||||
namespace ConditionCode {
|
||||
|
||||
static constexpr uint16_t Carry = 1 << 0;
|
||||
static constexpr uint16_t Overflow = 1 << 1;
|
||||
static constexpr uint16_t Zero = 1 << 2;
|
||||
static constexpr uint16_t Negative = 1 << 3;
|
||||
static constexpr uint16_t Extend = 1 << 4;
|
||||
|
||||
static constexpr uint16_t AllConditions = Carry | Overflow | Zero | Negative | Extend;
|
||||
|
||||
static constexpr uint16_t Supervisor = 1 << 13;
|
||||
static constexpr uint16_t Trace = 1 << 15;
|
||||
|
||||
static constexpr uint16_t InterruptPriorityMask = 0b111 << 8;
|
||||
|
||||
}
|
||||
|
||||
|
||||
struct Status {
|
||||
/// Generally holds an unevaluated flag for potential later lazy evaluation; it'll be zero for one outcome,
|
||||
/// non-zero for the other, but no guarantees are made about the potential range of non-zero values.
|
||||
using FlagT = uint_fast32_t;
|
||||
|
||||
/* b15 */
|
||||
FlagT trace_flag = 0; // The trace flag is set if and only if this value is non-zero.
|
||||
|
||||
/* b13 */
|
||||
bool is_supervisor = false; // true => processor is in supervisor mode; false => it isn't.
|
||||
|
||||
/* b7–b9 */
|
||||
int interrupt_level = 0; // The direct integer value of the current interrupt level.
|
||||
// Values of 8 or greater have undefined meaning.
|
||||
|
||||
/* b0–b4 */
|
||||
FlagT zero_result = 0; // The zero flag is set if and only if this value is zero.
|
||||
FlagT carry_flag = 0; // The carry flag is set if and only if this value is non-zero.
|
||||
FlagT extend_flag = 0; // The extend flag is set if and only if this value is non-zero.
|
||||
FlagT overflow_flag = 0; // The overflow flag is set if and only if this value is non-zero.
|
||||
FlagT negative_flag = 0; // The negative flag is set if and only this value is non-zero.
|
||||
|
||||
/// Sets the negative flag per @c value
|
||||
template <typename IntT> void set_negative(IntT value) {
|
||||
constexpr auto top_bit = IntT(1 << ((sizeof(IntT) * 8) - 1));
|
||||
negative_flag = value & top_bit;
|
||||
}
|
||||
|
||||
/// Sets both the negative and zero flags according to @c value.
|
||||
template <typename IntT> void set_neg_zero(IntT value) {
|
||||
zero_result = value;
|
||||
set_negative(value);
|
||||
}
|
||||
|
||||
/// Gets the current condition codes.
|
||||
constexpr uint16_t ccr() const {
|
||||
return
|
||||
(carry_flag ? ConditionCode::Carry : 0) |
|
||||
(overflow_flag ? ConditionCode::Overflow : 0) |
|
||||
(!zero_result ? ConditionCode::Zero : 0) |
|
||||
(negative_flag ? ConditionCode::Negative : 0) |
|
||||
(extend_flag ? ConditionCode::Extend : 0);
|
||||
}
|
||||
|
||||
/// Sets the current condition codes.
|
||||
constexpr void set_ccr(uint16_t ccr) {
|
||||
carry_flag = ccr & ConditionCode::Carry;
|
||||
overflow_flag = ccr & ConditionCode::Overflow;
|
||||
zero_result = ~ccr & ConditionCode::Zero;
|
||||
negative_flag = ccr & ConditionCode::Negative;
|
||||
extend_flag = ccr & ConditionCode::Extend;
|
||||
}
|
||||
|
||||
/// Gets the current value of the status register.
|
||||
constexpr uint16_t status() const {
|
||||
return uint16_t(
|
||||
ccr() |
|
||||
(interrupt_level << 8) |
|
||||
(trace_flag ? ConditionCode::Trace : 0) |
|
||||
(is_supervisor ? ConditionCode::Supervisor : 0)
|
||||
);
|
||||
}
|
||||
|
||||
/// Sets the current value of the status register;
|
||||
/// @returns @c true if the processor finishes in supervisor mode; @c false otherwise.
|
||||
constexpr bool set_status(uint16_t status) {
|
||||
set_ccr(status);
|
||||
|
||||
interrupt_level = (status >> 8) & 7;
|
||||
trace_flag = status & ConditionCode::Trace;
|
||||
is_supervisor = status & ConditionCode::Supervisor;
|
||||
|
||||
return is_supervisor;
|
||||
}
|
||||
|
||||
/// Adjusts the status for exception processing — sets supervisor mode, disables trace,
|
||||
/// and if @c new_interrupt_level is greater than or equal to 0 sets that as the new
|
||||
/// interrupt level.
|
||||
///
|
||||
/// @returns The status prior to those changes.
|
||||
uint16_t begin_exception(int new_interrupt_level = -1) {
|
||||
const uint16_t initial_status = status();
|
||||
|
||||
if(new_interrupt_level >= 0) {
|
||||
interrupt_level = new_interrupt_level;
|
||||
}
|
||||
is_supervisor = true;
|
||||
trace_flag = 0;
|
||||
|
||||
return initial_status;
|
||||
}
|
||||
|
||||
/// Evaluates @c condition.
|
||||
constexpr bool evaluate_condition(Condition condition) const {
|
||||
switch(condition) {
|
||||
default:
|
||||
case Condition::True: return true;
|
||||
case Condition::False: return false;
|
||||
case Condition::High: return zero_result && !carry_flag;
|
||||
case Condition::LowOrSame: return !zero_result || carry_flag;
|
||||
case Condition::CarryClear: return !carry_flag;
|
||||
case Condition::CarrySet: return carry_flag;
|
||||
case Condition::NotEqual: return zero_result;
|
||||
case Condition::Equal: return !zero_result;
|
||||
case Condition::OverflowClear: return !overflow_flag;
|
||||
case Condition::OverflowSet: return overflow_flag;
|
||||
case Condition::Positive: return !negative_flag;
|
||||
case Condition::Negative: return negative_flag;
|
||||
case Condition::GreaterThanOrEqual:
|
||||
return (negative_flag && overflow_flag) || (!negative_flag && !overflow_flag);
|
||||
case Condition::LessThan:
|
||||
return (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
|
||||
case Condition::GreaterThan:
|
||||
return zero_result && ((negative_flag && overflow_flag) || (!negative_flag && !overflow_flag));
|
||||
case Condition::LessThanOrEqual:
|
||||
return !zero_result || (negative_flag && !overflow_flag) || (!negative_flag && overflow_flag);
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns @c true if an interrupt at level @c level should be accepted; @c false otherwise.
|
||||
constexpr bool would_accept_interrupt(int level) const {
|
||||
// TODO: is level seven really non-maskable? If so then what mechanism prevents
|
||||
// rapid stack overflow upon a level-seven interrupt?
|
||||
return level > interrupt_level;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_M68k_Status_h */
|
||||
@@ -10,9 +10,240 @@
|
||||
|
||||
using namespace InstructionSet::PowerPC;
|
||||
|
||||
Decoder::Decoder(Model model) : model_(model) {}
|
||||
namespace {
|
||||
|
||||
Instruction Decoder::decode(uint32_t opcode) {
|
||||
template <Model model, bool validate_reserved_bits, Operation operation> Instruction instruction(uint32_t opcode, bool is_supervisor = false) {
|
||||
// If validation isn't required, there's nothing to do here.
|
||||
if constexpr (!validate_reserved_bits) {
|
||||
return Instruction(operation, opcode, is_supervisor);
|
||||
}
|
||||
|
||||
// Otherwise, validation depends on operation
|
||||
// (and, in principle, processor model).
|
||||
switch(operation) {
|
||||
case Operation::absx: case Operation::clcs:
|
||||
case Operation::nabsx:
|
||||
case Operation::addmex: case Operation::addzex:
|
||||
case Operation::bcctrx: case Operation::bclrx:
|
||||
case Operation::cntlzdx: case Operation::cntlzwx:
|
||||
case Operation::extsbx: case Operation::extshx: case Operation::extswx:
|
||||
case Operation::fmulx: case Operation::fmulsx:
|
||||
case Operation::negx:
|
||||
case Operation::subfmex: case Operation::subfzex:
|
||||
if(opcode & 0b000000'00000'00000'11111'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::cmp: case Operation::cmpl:
|
||||
if(opcode & 0b000000'00010'00000'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::cmpi: case Operation::cmpli:
|
||||
if(opcode & 0b000000'00010'00000'00000'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::dcbf: case Operation::dcbi: case Operation::dcbst:
|
||||
case Operation::dcbt: case Operation::dcbtst: case Operation::dcbz:
|
||||
if(opcode & 0b000000'11111'00000'00000'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::crand: case Operation::crandc: case Operation::creqv:
|
||||
case Operation::crnand: case Operation::crnor: case Operation::cror:
|
||||
case Operation::crorc: case Operation::crxor:
|
||||
case Operation::eciwx: case Operation::ecowx:
|
||||
case Operation::lbzux: case Operation::lbzx:
|
||||
case Operation::ldarx:
|
||||
case Operation::ldux: case Operation::ldx:
|
||||
case Operation::lfdux: case Operation::lfdx:
|
||||
case Operation::lfsux: case Operation::lfsx:
|
||||
case Operation::lhaux: case Operation::lhax: case Operation::lhbrx:
|
||||
case Operation::lhzux: case Operation::lhzx:
|
||||
case Operation::lswi: case Operation::lswx:
|
||||
case Operation::lwarx: case Operation::lwaux: case Operation::lwax: case Operation::lwbrx:
|
||||
case Operation::lwzux: case Operation::lwzx:
|
||||
case Operation::mfspr: case Operation::mftb:
|
||||
case Operation::mtspr:
|
||||
case Operation::stbux: case Operation::stbx:
|
||||
case Operation::stdux: case Operation::stdx:
|
||||
case Operation::stfdux: case Operation::stfdx:
|
||||
case Operation::stfiwx:
|
||||
case Operation::stfsux: case Operation::stfsx:
|
||||
case Operation::sthbrx:
|
||||
case Operation::sthux: case Operation::sthx:
|
||||
case Operation::stswi: case Operation::stswx:
|
||||
case Operation::stwbrx:
|
||||
case Operation::stwux: case Operation::stwx:
|
||||
case Operation::td: case Operation::tw:
|
||||
if(opcode & 0b000000'00000'00000'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::fabsx: case Operation::fcfidx:
|
||||
case Operation::fctidx: case Operation::fctidzx:
|
||||
case Operation::fctiwx: case Operation::fctiwzx:
|
||||
case Operation::fmrx: case Operation::fnabsx:
|
||||
case Operation::fnegx: case Operation::frspx:
|
||||
if(opcode & 0b000000'00000'11111'00000'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::faddx: case Operation::faddsx:
|
||||
case Operation::fdivx: case Operation::fdivsx:
|
||||
case Operation::fsubx: case Operation::fsubsx:
|
||||
if(opcode & 0b000000'00000'00000'00000'1111100000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::fcmpo: case Operation::fcmpu:
|
||||
if(opcode & 0b000000'00011'00000'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::fresx: case Operation::frsqrtex:
|
||||
case Operation::fsqrtx: case Operation::fsqrtsx:
|
||||
if(opcode & 0b000000'00000'11111'00000'1111100000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::icbi:
|
||||
if(opcode & 0b000000'11111'00000'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::eieio:
|
||||
case Operation::isync:
|
||||
case Operation::rfi:
|
||||
case Operation::slbia:
|
||||
case Operation::sync:
|
||||
case Operation::tlbia:
|
||||
case Operation::tlbsync:
|
||||
if(opcode & 0b000000'11111'11111'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mcrf: case Operation::mcrfs:
|
||||
if(opcode & 0b000000'00011'00011'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mcrxr:
|
||||
if(opcode & 0b000000'00011'11111'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mfcr:
|
||||
case Operation::mfmsr:
|
||||
case Operation::mtmsr:
|
||||
if(opcode & 0b000000'00000'11111'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mffsx:
|
||||
case Operation::mtfsb0x:
|
||||
case Operation::mtfsb1x:
|
||||
if(opcode & 0b000000'00000'11111'11111'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtfsfx:
|
||||
if(opcode & 0b000000'10000'00001'00000'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtfsfix:
|
||||
if(opcode & 0b000000'00011'11111'00001'0000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtsr:
|
||||
if(opcode & 0b000000'00000'10000'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtsrin: case Operation::mfsrin:
|
||||
if(opcode & 0b000000'00000'11111'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mfsr:
|
||||
if(opcode & 0b000000'00000'10000'11111'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mtcrf:
|
||||
if(opcode & 0b000000'00000'10000'00001'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::mulhdx: case Operation::mulhdux:
|
||||
case Operation::mulhwx: case Operation::mulhwux:
|
||||
if(opcode & 0b000000'00000'00000'00000'1000000000'0) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::sc:
|
||||
if(opcode & 0b000000'11111'11111'11111'1111111110'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::slbie:
|
||||
case Operation::tlbie:
|
||||
if(opcode & 0b000000'11111'11111'00000'0000000000'1) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::stwcx_:
|
||||
if(!(opcode & 0b000000'00000'00000'00000'0000000000'1)) return Instruction(opcode);
|
||||
break;
|
||||
|
||||
case Operation::divx: case Operation::divsx:
|
||||
case Operation::dozx: case Operation::dozi:
|
||||
case Operation::lscbxx:
|
||||
case Operation::maskgx: case Operation::maskirx:
|
||||
case Operation::mulx:
|
||||
case Operation::rlmix: case Operation::rribx:
|
||||
case Operation::slex: case Operation::sleqx: case Operation::sliqx:
|
||||
case Operation::slliqx: case Operation::sllqx: case Operation::slqx:
|
||||
case Operation::sraiqx: case Operation::sraqx:
|
||||
case Operation::srex: case Operation::sreqx:
|
||||
case Operation::sriqx: case Operation::srliqx:
|
||||
case Operation::srlqx: case Operation::srqx:
|
||||
case Operation::sreax:
|
||||
case Operation::addx: case Operation::addcx: case Operation::addex:
|
||||
case Operation::addi: case Operation::addic: case Operation::addic_:
|
||||
case Operation::addis:
|
||||
case Operation::andx: case Operation::andcx:
|
||||
case Operation::andi_: case Operation::andis_:
|
||||
case Operation::bx: case Operation::bcx:
|
||||
case Operation::divdx: case Operation::divdux:
|
||||
case Operation::divwx: case Operation::divwux:
|
||||
case Operation::eqvx:
|
||||
case Operation::fmaddx: case Operation::fmaddsx:
|
||||
case Operation::fmsubx: case Operation::fmsubsx:
|
||||
case Operation::fnmaddx: case Operation::fnmaddsx:
|
||||
case Operation::fnmsubx: case Operation::fnmsubsx:
|
||||
case Operation::fselx:
|
||||
case Operation::lbz: case Operation::lbzu:
|
||||
case Operation::lfd: case Operation::lfdu:
|
||||
case Operation::lfs: case Operation::lfsu:
|
||||
case Operation::lha: case Operation::lhau:
|
||||
case Operation::lhz: case Operation::lhzu:
|
||||
case Operation::lmw: case Operation::lwa:
|
||||
case Operation::lwz: case Operation::lwzu:
|
||||
case Operation::mulldx: case Operation::mulli: case Operation::mullwx:
|
||||
case Operation::nandx: case Operation::norx:
|
||||
case Operation::orx: case Operation::orcx:
|
||||
case Operation::ori: case Operation::oris:
|
||||
case Operation::rlwimix: case Operation::rlwinmx: case Operation::rlwnmx:
|
||||
case Operation::sldx: case Operation::slwx:
|
||||
case Operation::sradx: case Operation::sradix:
|
||||
case Operation::srawx: case Operation::srawix:
|
||||
case Operation::srdx: case Operation::srwx:
|
||||
case Operation::stb: case Operation::stbu:
|
||||
case Operation::std: case Operation::stdcx_: case Operation::stdu:
|
||||
case Operation::stfd: case Operation::stfdu:
|
||||
case Operation::stfs: case Operation::stfsu:
|
||||
case Operation::sth: case Operation::sthu:
|
||||
case Operation::stmw:
|
||||
case Operation::stw: case Operation::stwu:
|
||||
case Operation::subfx: case Operation::subfcx: case Operation::subfex:
|
||||
case Operation::subfic:
|
||||
case Operation::tdi: case Operation::twi:
|
||||
case Operation::xorx: case Operation::xori: case Operation::xoris:
|
||||
case Operation::ld: case Operation::ldu:
|
||||
case Operation::rldclx: case Operation::rldcrx:
|
||||
case Operation::rldicx: case Operation::rldiclx:
|
||||
case Operation::rldicrx: case Operation::rldimix:
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return Instruction(operation, opcode, is_supervisor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template <Model model, bool validate_reserved_bits>
|
||||
Instruction Decoder<model, validate_reserved_bits>::decode(uint32_t opcode) {
|
||||
// Quick bluffer's guide to PowerPC instruction encoding:
|
||||
//
|
||||
// There is a six-bit field at the very top of the instruction.
|
||||
@@ -32,16 +263,16 @@ Instruction Decoder::decode(uint32_t opcode) {
|
||||
// currently check the value of reserved bits. That may need to change
|
||||
// if/when I add support for extended instruction sets.
|
||||
|
||||
#define Bind(mask, operation) case mask: return Instruction(Operation::operation, opcode);
|
||||
#define BindSupervisor(mask, operation) case mask: return Instruction(Operation::operation, opcode, true);
|
||||
#define Bind(mask, operation) case mask: return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
|
||||
#define BindSupervisor(mask, operation) case mask: return instruction<model, validate_reserved_bits, Operation::operation>(opcode, true);
|
||||
#define BindConditional(condition, mask, operation) \
|
||||
case mask: \
|
||||
if(condition()) return Instruction(Operation::operation, opcode); \
|
||||
return Instruction(opcode);
|
||||
if(condition(model)) return instruction<model, validate_reserved_bits, Operation::operation>(opcode); \
|
||||
return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
|
||||
#define BindSupervisorConditional(condition, mask, operation) \
|
||||
case mask: \
|
||||
if(condition()) return Instruction(Operation::operation, opcode, true); \
|
||||
return Instruction(opcode);
|
||||
if(condition(model)) return instruction<model, validate_reserved_bits, Operation::operation>(opcode, true); \
|
||||
return instruction<model, validate_reserved_bits, Operation::operation>(opcode);
|
||||
|
||||
#define Six(x) (unsigned(x) << 26)
|
||||
#define SixTen(x, y) (Six(x) | ((y) << 1))
|
||||
@@ -64,7 +295,7 @@ Instruction Decoder::decode(uint32_t opcode) {
|
||||
case 0: case 1: case 2: case 3: case 4: case 5:
|
||||
case 8: case 9: case 10: case 11: case 12: case 13:
|
||||
case 16: case 17: case 18: case 19: case 20:
|
||||
return Instruction(Operation::bcx, opcode);
|
||||
return instruction<model, validate_reserved_bits, Operation::bcx>(opcode);
|
||||
|
||||
default: return Instruction(opcode);
|
||||
}
|
||||
@@ -111,7 +342,7 @@ Instruction Decoder::decode(uint32_t opcode) {
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0001010100), ldarx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0010010101), stdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0010110101), stdux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0011101001), mulld); BindConditional(is64bit, SixTen(0b011111, 0b1011101001), mulld);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0011101001), mulldx); BindConditional(is64bit, SixTen(0b011111, 0b1011101001), mulldx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0101010101), lwax);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0101110101), lwaux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1100111011), sradix); BindConditional(is64bit, SixTen(0b011111, 0b1100111010), sradix);
|
||||
@@ -120,7 +351,7 @@ Instruction Decoder::decode(uint32_t opcode) {
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0111101001), divdx); BindConditional(is64bit, SixTen(0b011111, 0b1111101001), divdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1000011011), srdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1100011010), sradx);
|
||||
BindConditional(is64bit, SixTen(0b111111, 0b1111011010), extsw);
|
||||
BindConditional(is64bit, SixTen(0b111111, 0b1111011010), extswx);
|
||||
|
||||
// Power instructions; these are all taken from the MPC601 manual rather than
|
||||
// the PowerPC Programmer's Reference Guide, hence the decimal encoding of the
|
||||
@@ -309,6 +540,8 @@ Instruction Decoder::decode(uint32_t opcode) {
|
||||
|
||||
BindConditional(is64bit, SixTen(0b111011, 0b10110), fsqrtsx);
|
||||
BindConditional(is64bit, SixTen(0b111011, 0b11000), fresx);
|
||||
BindConditional(is64bit, SixTen(0b011110, 0b01000), rldclx);
|
||||
BindConditional(is64bit, SixTen(0b011110, 0b01001), rldcrx);
|
||||
|
||||
// Optional...
|
||||
Bind(SixTen(0b111111, 0b10110), fsqrtx);
|
||||
@@ -316,25 +549,41 @@ Instruction Decoder::decode(uint32_t opcode) {
|
||||
Bind(SixTen(0b111111, 0b11010), frsqrtex);
|
||||
}
|
||||
|
||||
// rldicx, rldiclx, rldicrx, rldimix
|
||||
if(is64bit(model)) {
|
||||
switch(opcode & 0b111111'00000'00000'00000'000000'111'00) {
|
||||
default: break;
|
||||
case 0b011110'00000'00000'00000'000000'000'00: return instruction<model, validate_reserved_bits, Operation::rldiclx>(opcode);
|
||||
case 0b011110'00000'00000'00000'000000'001'00: return instruction<model, validate_reserved_bits, Operation::rldicrx>(opcode);
|
||||
case 0b011110'00000'00000'00000'000000'010'00: return instruction<model, validate_reserved_bits, Operation::rldicx>(opcode);
|
||||
case 0b011110'00000'00000'00000'000000'011'00: return instruction<model, validate_reserved_bits, Operation::rldimix>(opcode);
|
||||
}
|
||||
}
|
||||
|
||||
// stwcx. and stdcx.
|
||||
switch(opcode & 0b111111'00'00000000'000'111111111'1){
|
||||
case 0b011111'00'00000000'00000'0010010110'1: return Instruction(Operation::stwcx_, opcode);
|
||||
case 0b011111'00'00000000'00000'0011010110'1:
|
||||
if(is64bit()) return Instruction(Operation::stdcx_, opcode);
|
||||
switch(opcode & 0b111111'0000'0000'0000'0000'111111111'1) {
|
||||
default: break;
|
||||
case 0b011111'0000'0000'0000'0000'010010110'1: return instruction<model, validate_reserved_bits, Operation::stwcx_>(opcode);
|
||||
case 0b011111'0000'0000'0000'0000'011010110'1:
|
||||
if(is64bit(model)) return instruction<model, validate_reserved_bits, Operation::stdcx_>(opcode);
|
||||
return Instruction(opcode);
|
||||
}
|
||||
|
||||
// std and stdu
|
||||
switch(opcode & 0b111111'00'00000000'00000000'000000'11){
|
||||
case 0b111110'00'00000000'00000000'000000'00: return Instruction(Operation::std, opcode);
|
||||
case 0b111110'00'00000000'00000000'000000'01:
|
||||
if(is64bit()) return Instruction(Operation::stdu, opcode);
|
||||
return Instruction(opcode);
|
||||
// std, stdu, ld, ldu, lwa
|
||||
if(is64bit(model)) {
|
||||
switch(opcode & 0b111111'00'00000000'00000000'000000'11) {
|
||||
default: break;
|
||||
case 0b111010'00'00000000'00000000'000000'00: return instruction<model, validate_reserved_bits, Operation::ld>(opcode);
|
||||
case 0b111010'00'00000000'00000000'000000'01: return instruction<model, validate_reserved_bits, Operation::ldu>(opcode);
|
||||
case 0b111010'00'00000000'00000000'000000'10: return instruction<model, validate_reserved_bits, Operation::lwa>(opcode);
|
||||
case 0b111110'00'00000000'00000000'000000'00: return instruction<model, validate_reserved_bits, Operation::std>(opcode);
|
||||
case 0b111110'00'00000000'00000000'000000'01: return instruction<model, validate_reserved_bits, Operation::stdu>(opcode);
|
||||
}
|
||||
}
|
||||
|
||||
// sc
|
||||
if((opcode & 0b111111'00'00000000'00000000'000000'1'0) == 0b010001'00'00000000'00000000'000000'1'0) {
|
||||
return Instruction(Operation::sc, opcode);
|
||||
return instruction<model, validate_reserved_bits, Operation::sc>(opcode);
|
||||
}
|
||||
|
||||
#undef Six
|
||||
@@ -345,3 +594,11 @@ Instruction Decoder::decode(uint32_t opcode) {
|
||||
|
||||
return Instruction(opcode);
|
||||
}
|
||||
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC601, true>;
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC603, true>;
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC620, true>;
|
||||
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC601, false>;
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC603, false>;
|
||||
template struct InstructionSet::PowerPC::Decoder<InstructionSet::PowerPC::Model::MPC620, false>;
|
||||
|
||||
@@ -23,31 +23,31 @@ enum class Model {
|
||||
MPC620,
|
||||
};
|
||||
|
||||
constexpr bool is64bit(Model model) {
|
||||
return model == Model::MPC620;
|
||||
}
|
||||
|
||||
constexpr bool is32bit(Model model) {
|
||||
return !is64bit(model);
|
||||
}
|
||||
|
||||
constexpr bool is601(Model model) {
|
||||
return model == Model::MPC601;
|
||||
}
|
||||
|
||||
/*!
|
||||
Implements PowerPC instruction decoding.
|
||||
|
||||
This is an experimental implementation; it has not yet undergone significant testing.
|
||||
@c model Indicates the instruction set to decode.
|
||||
|
||||
@c validate_reserved_bits If set to @c true, check that all
|
||||
reserved bits are 0 or 1 as required and produce an invalid opcode if not.
|
||||
Otherwise does no inspection of reserved bits.
|
||||
|
||||
TODO: determine what specific models of PowerPC do re: reserved bits.
|
||||
*/
|
||||
struct Decoder {
|
||||
public:
|
||||
Decoder(Model model);
|
||||
|
||||
Instruction decode(uint32_t opcode);
|
||||
|
||||
private:
|
||||
Model model_;
|
||||
|
||||
bool is64bit() const {
|
||||
return model_ == Model::MPC620;
|
||||
}
|
||||
|
||||
bool is32bit() const {
|
||||
return !is64bit();
|
||||
}
|
||||
|
||||
bool is601() const {
|
||||
return model_ == Model::MPC601;
|
||||
}
|
||||
template <Model model, bool validate_reserved_bits = false> struct Decoder {
|
||||
Instruction decode(uint32_t opcode);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
320
InstructionSets/x86/DataPointerResolver.hpp
Normal file
320
InstructionSets/x86/DataPointerResolver.hpp
Normal file
@@ -0,0 +1,320 @@
|
||||
//
|
||||
// DataPointerResolver.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/02/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DataPointerResolver_hpp
|
||||
#define DataPointerResolver_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace x86 {
|
||||
|
||||
/// Unlike source, describes only registers, and breaks
|
||||
/// them down by conventional name — so AL, AH, AX and EAX are all
|
||||
/// listed separately and uniquely, rather than being eAX+size or
|
||||
/// eSPorAH with a size of 1.
|
||||
enum class Register: uint8_t {
|
||||
// 8-bit registers.
|
||||
AL, AH,
|
||||
CL, CH,
|
||||
DL, DH,
|
||||
BL, BH,
|
||||
|
||||
// 16-bit registers.
|
||||
AX, CX, DX, BX,
|
||||
SP, BP, SI, DI,
|
||||
ES, CS, SS, DS,
|
||||
FS, GS,
|
||||
|
||||
// 32-bit registers.
|
||||
EAX, ECX, EDX, EBX,
|
||||
ESP, EBP, ESI, EDI,
|
||||
|
||||
//
|
||||
None
|
||||
};
|
||||
|
||||
/// @returns @c true if @c r is the same size as @c DataT; @c false otherwise.
|
||||
/// @discussion Provided primarily to aid in asserts; if the decoder and resolver are both
|
||||
/// working then it shouldn't be necessary to test this in register files.
|
||||
template <typename DataT> constexpr bool is_sized(Register r) {
|
||||
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
|
||||
|
||||
if constexpr (sizeof(DataT) == 4) {
|
||||
return r >= Register::EAX && r < Register::None;
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 2) {
|
||||
return r >= Register::AX && r < Register::EAX;
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 1) {
|
||||
return r >= Register::AL && r < Register::AX;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// @returns the proper @c Register given @c source and data of size @c sizeof(DataT),
|
||||
/// or Register::None if no such register exists (e.g. asking for a 32-bit version of CS).
|
||||
template <typename DataT> constexpr Register register_for_source(Source source) {
|
||||
static_assert(sizeof(DataT) == 4 || sizeof(DataT) == 2 || sizeof(DataT) == 1);
|
||||
|
||||
if constexpr (sizeof(DataT) == 4) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::EAX;
|
||||
case Source::eCX: return Register::ECX;
|
||||
case Source::eDX: return Register::EDX;
|
||||
case Source::eBX: return Register::EBX;
|
||||
case Source::eSPorAH: return Register::ESP;
|
||||
case Source::eBPorCH: return Register::EBP;
|
||||
case Source::eSIorDH: return Register::ESI;
|
||||
case Source::eDIorBH: return Register::EDI;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 2) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::AX;
|
||||
case Source::eCX: return Register::CX;
|
||||
case Source::eDX: return Register::DX;
|
||||
case Source::eBX: return Register::BX;
|
||||
case Source::eSPorAH: return Register::SP;
|
||||
case Source::eBPorCH: return Register::BP;
|
||||
case Source::eSIorDH: return Register::SI;
|
||||
case Source::eDIorBH: return Register::DI;
|
||||
case Source::ES: return Register::ES;
|
||||
case Source::CS: return Register::CS;
|
||||
case Source::SS: return Register::SS;
|
||||
case Source::DS: return Register::DS;
|
||||
case Source::FS: return Register::FS;
|
||||
case Source::GS: return Register::GS;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (sizeof(DataT) == 1) {
|
||||
switch(source) {
|
||||
case Source::eAX: return Register::AL;
|
||||
case Source::eCX: return Register::CL;
|
||||
case Source::eDX: return Register::DL;
|
||||
case Source::eBX: return Register::BL;
|
||||
case Source::eSPorAH: return Register::AH;
|
||||
case Source::eBPorCH: return Register::CH;
|
||||
case Source::eSIorDH: return Register::DH;
|
||||
case Source::eDIorBH: return Register::BH;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
return Register::None;
|
||||
}
|
||||
|
||||
/// Reads from or writes to the source or target identified by a DataPointer, relying upon two user-supplied classes:
|
||||
///
|
||||
/// * a register bank; and
|
||||
/// * a memory pool.
|
||||
///
|
||||
/// The register bank should implement `template<typename DataT, Register> DataT read()` and `template<typename DataT, Register> void write(DataT)`.
|
||||
/// Those functions will be called only with registers and data types that are appropriate to the @c model.
|
||||
///
|
||||
/// The memory pool should implement `template<typename DataT> DataT read(Source segment, uint32_t address)` and
|
||||
/// `template<typename DataT> void write(Source segment, uint32_t address, DataT value)`.
|
||||
template <Model model, typename RegistersT, typename MemoryT> class DataPointerResolver {
|
||||
public:
|
||||
public:
|
||||
/// Reads the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
|
||||
template <typename DataT> static DataT read(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer);
|
||||
|
||||
/// Writes @c value to the data pointed to by @c pointer, referencing @c instruction, @c memory and @c registers as necessary.
|
||||
template <typename DataT> static void write(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT value);
|
||||
|
||||
/// Computes the effective address of @c pointer including any displacement applied by @c instruction.
|
||||
/// @c pointer must be of type Source::Indirect.
|
||||
template <bool obscured_indirectNoBase = true, bool has_base = true>
|
||||
static uint32_t effective_address(
|
||||
RegistersT ®isters,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer);
|
||||
|
||||
private:
|
||||
template <bool is_write, typename DataT> static void access(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT &value);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Implementation begins here.
|
||||
//
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <typename DataT> DataT DataPointerResolver<model, RegistersT, MemoryT>::read(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer) {
|
||||
DataT result;
|
||||
access<false>(registers, memory, instruction, pointer, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::write(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT value) {
|
||||
access<true>(registers, memory, instruction, pointer, value);
|
||||
}
|
||||
|
||||
#define rw(v, r, is_write) \
|
||||
case Source::r: \
|
||||
using VType = typename std::remove_reference<decltype(v)>::type; \
|
||||
if constexpr (is_write) { \
|
||||
registers.template write<VType, register_for_source<VType>(Source::r)>(v); \
|
||||
} else { \
|
||||
v = registers.template read<VType, register_for_source<VType>(Source::r)>(); \
|
||||
} \
|
||||
break;
|
||||
|
||||
#define ALLREGS(v, i) rw(v, eAX, i); rw(v, eCX, i); \
|
||||
rw(v, eDX, i); rw(v, eBX, i); \
|
||||
rw(v, eSPorAH, i); rw(v, eBPorCH, i); \
|
||||
rw(v, eSIorDH, i); rw(v, eDIorBH, i); \
|
||||
rw(v, ES, i); rw(v, CS, i); \
|
||||
rw(v, SS, i); rw(v, DS, i); \
|
||||
rw(v, FS, i); rw(v, GS, i);
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <bool obscured_indirectNoBase, bool has_base>
|
||||
uint32_t DataPointerResolver<model, RegistersT, MemoryT>::effective_address(
|
||||
RegistersT ®isters,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer) {
|
||||
using AddressT = typename Instruction<is_32bit(model)>::AddressT;
|
||||
AddressT base = 0, index = 0;
|
||||
|
||||
if constexpr (has_base) {
|
||||
switch(pointer.base<obscured_indirectNoBase>()) {
|
||||
default: break;
|
||||
ALLREGS(base, false);
|
||||
}
|
||||
}
|
||||
|
||||
switch(pointer.index()) {
|
||||
default: break;
|
||||
ALLREGS(index, false);
|
||||
}
|
||||
|
||||
uint32_t address = index;
|
||||
if constexpr (model >= Model::i80386) {
|
||||
address <<= pointer.scale();
|
||||
} else {
|
||||
assert(!pointer.scale());
|
||||
}
|
||||
|
||||
// Always compute address as 32-bit.
|
||||
// TODO: verify use of memory_mask around here.
|
||||
// Also I think possibly an exception is supposed to be generated
|
||||
// if the programmer is in 32-bit mode and has asked for 16-bit
|
||||
// address computation but generated e.g. a 17-bit result. Look into
|
||||
// that when working on execution. For now the goal is merely decoding
|
||||
// and this code exists both to verify the presence of all necessary
|
||||
// fields and to help to explore the best breakdown of storage
|
||||
// within Instruction.
|
||||
constexpr uint32_t memory_masks[] = {0x0000'ffff, 0xffff'ffff};
|
||||
const uint32_t memory_mask = memory_masks[int(instruction.address_size())];
|
||||
address = (address & memory_mask) + (base & memory_mask) + instruction.displacement();
|
||||
return address;
|
||||
}
|
||||
|
||||
template <Model model, typename RegistersT, typename MemoryT>
|
||||
template <bool is_write, typename DataT> void DataPointerResolver<model, RegistersT, MemoryT>::access(
|
||||
RegistersT ®isters,
|
||||
MemoryT &memory,
|
||||
const Instruction<is_32bit(model)> &instruction,
|
||||
DataPointer pointer,
|
||||
DataT &value) {
|
||||
const Source source = pointer.source<false>();
|
||||
|
||||
switch(source) {
|
||||
default:
|
||||
if constexpr (!is_write) {
|
||||
value = 0;
|
||||
}
|
||||
return;
|
||||
|
||||
ALLREGS(value, is_write);
|
||||
|
||||
case Source::DirectAddress:
|
||||
if constexpr(is_write) {
|
||||
memory.template write(instruction.data_segment(), instruction.displacement(), value);
|
||||
} else {
|
||||
value = memory.template read<DataT>(instruction.data_segment(), instruction.displacement());
|
||||
}
|
||||
break;
|
||||
case Source::Immediate:
|
||||
value = DataT(instruction.operand());
|
||||
break;
|
||||
|
||||
#define indirect(has_base) { \
|
||||
const auto address = effective_address<false, has_base> \
|
||||
(registers, instruction, pointer); \
|
||||
\
|
||||
if constexpr (is_write) { \
|
||||
memory.template write( \
|
||||
instruction.data_segment(), \
|
||||
address, \
|
||||
value \
|
||||
); \
|
||||
} else { \
|
||||
value = memory.template read<DataT>( \
|
||||
instruction.data_segment(), \
|
||||
address \
|
||||
); \
|
||||
} \
|
||||
}
|
||||
case Source::IndirectNoBase:
|
||||
indirect(false);
|
||||
break;
|
||||
|
||||
case Source::Indirect:
|
||||
indirect(true);
|
||||
break;
|
||||
#undef indirect
|
||||
|
||||
}
|
||||
}
|
||||
#undef ALLREGS
|
||||
#undef rw
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* DataPointerResolver_hpp */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
#define InstructionSets_x86_Decoder_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
@@ -17,38 +18,54 @@
|
||||
namespace InstructionSet {
|
||||
namespace x86 {
|
||||
|
||||
enum class Model {
|
||||
i8086,
|
||||
};
|
||||
|
||||
/*!
|
||||
Implements Intel x86 instruction decoding.
|
||||
|
||||
This is an experimental implementation; it has not yet undergone significant testing.
|
||||
*/
|
||||
class Decoder {
|
||||
template <Model model> class Decoder {
|
||||
public:
|
||||
Decoder(Model model);
|
||||
using InstructionT = Instruction<is_32bit(model)>;
|
||||
|
||||
/*!
|
||||
@returns an @c Instruction plus a size; a positive size to indicate successful decoding; a
|
||||
negative size specifies the [negatived] number of further bytes the caller should ideally
|
||||
collect before calling again. The caller is free to call with fewer, but may not get a decoded
|
||||
instruction in response, and the decoder may still not be able to complete decoding
|
||||
even if given that number of bytes.
|
||||
@returns an @c Instruction plus a size; a positive size indicates successful decoding of
|
||||
an instruction that was that many bytes long in total; a negative size specifies the [negatived]
|
||||
minimum number of further bytes the caller should ideally collect before calling again. The
|
||||
caller is free to call with fewer, but may not get a decoded instruction in response, and the
|
||||
decoder may still not be able to complete decoding even if given that number of bytes.
|
||||
|
||||
Successful decoding is defined to mean that all decoding steps are complete. The output
|
||||
may still be an illegal instruction (indicated by Operation::Invalid), if the byte sequence
|
||||
supplied cannot form a valid instruction.
|
||||
|
||||
@discussion although instructions also contain an indicator of their length, on chips prior
|
||||
to the 80286 there is no limit to instruction length and that could in theory overflow the available
|
||||
storage, which can describe instructions only up to 1kb in size.
|
||||
|
||||
The 80286 and 80386 have instruction length limits of 10 and 15 bytes respectively, so
|
||||
cannot overflow the field.
|
||||
*/
|
||||
std::pair<int, Instruction> decode(const uint8_t *source, size_t length);
|
||||
std::pair<int, InstructionT> decode(const uint8_t *source, size_t length);
|
||||
|
||||
/*!
|
||||
Enables or disables 32-bit protected mode. Meaningful only if the @c Model supports it.
|
||||
*/
|
||||
void set_32bit_protected_mode(bool);
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
/// Captures all prefixes and continues until an instruction byte is encountered.
|
||||
Instruction,
|
||||
/// Having encountered a 0x0f first instruction byte, waits for the next byte fully to determine the instruction.
|
||||
InstructionPageF,
|
||||
/// Receives a ModRegRM byte and either populates the source_ and dest_ fields appropriately
|
||||
/// or completes decoding of the instruction, as per the instruction format.
|
||||
ModRegRM,
|
||||
/// Awaits n 80386+-style scale-index-base byte ('SIB'), indicating the form of indirect addressing.
|
||||
ScaleIndexBase,
|
||||
/// Waits for sufficiently many bytes to pass for the required displacement and operand to be captured.
|
||||
/// Cf. displacement_size_ and operand_size_.
|
||||
AwaitingDisplacementOrOperand,
|
||||
DisplacementOrOperand,
|
||||
/// Forms and returns an Instruction, and resets parsing state.
|
||||
ReadyToPost
|
||||
} phase_ = Phase::Instruction;
|
||||
@@ -59,29 +76,27 @@ class Decoder {
|
||||
/// are packaged into an Instruction.
|
||||
enum class ModRegRMFormat: uint8_t {
|
||||
// Parse the ModRegRM for mode, register and register/memory fields
|
||||
// and populate the source_ and destination_ fields appropriate.
|
||||
// and populate the source_ and destination_ fields appropriately.
|
||||
MemReg_Reg,
|
||||
Reg_MemReg,
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// source_ and destination_ fields with the result. Use the 'register'
|
||||
// field to pick an operation from the TEST/NOT/NEG/MUL/IMUL/DIV/IDIV group.
|
||||
MemRegTEST_to_IDIV,
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// source_ and destination_ fields with the result. Use the 'register'
|
||||
// field to check for the POP operation.
|
||||
MemRegPOP,
|
||||
// source_ and destination_ fields with the single register/memory result.
|
||||
MemRegSingleOperand,
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// the destination_ field with the result and setting source_ to Immediate.
|
||||
// Use the 'register' field to check for the MOV operation.
|
||||
MemRegMOV,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// destination_ field with the result. Use the 'register' field
|
||||
// to pick an operation from the ROL/ROR/RCL/RCR/SAL/SHR/SAR group.
|
||||
MemRegROL_to_SAR,
|
||||
// source_ field with the result. Fills destination_ with a segment
|
||||
// register based on the reg field.
|
||||
Seg_MemReg,
|
||||
MemReg_Seg,
|
||||
|
||||
//
|
||||
// 'Group 1'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// destination_ field with the result. Use the 'register' field
|
||||
@@ -89,32 +104,76 @@ class Decoder {
|
||||
// waits for an operand equal to the operation size.
|
||||
MemRegADD_to_CMP,
|
||||
|
||||
// Acts exactly as MemRegADD_to_CMP but the operand is fixed in size
|
||||
// at a single byte, which is sign extended to the operation size.
|
||||
MemRegADD_to_CMP_SignExtend,
|
||||
|
||||
//
|
||||
// 'Group 2'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ field with the result. Fills destination_ with a segment
|
||||
// register based on the reg field.
|
||||
SegReg,
|
||||
// destination_ field with the result. Use the 'register' field
|
||||
// to pick an operation from the ROL/ROR/RCL/RCR/SAL/SHR/SAR group.
|
||||
MemRegROL_to_SAR,
|
||||
|
||||
//
|
||||
// 'Group 3'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// source_ and destination_ fields with the result. Use the 'register'
|
||||
// field to pick an operation from the TEST/NOT/NEG/MUL/IMUL/DIV/IDIV group.
|
||||
MemRegTEST_to_IDIV,
|
||||
|
||||
//
|
||||
// 'Group 4'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ and destination_ fields with the result. Uses the
|
||||
// 'register' field to pick INC or DEC.
|
||||
MemRegINC_DEC,
|
||||
|
||||
//
|
||||
// 'Group 5'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ and destination_ fields with the result. Uses the
|
||||
// 'register' field to pick from INC/DEC/CALL/JMP/PUSH, altering
|
||||
// the source to ::Immediate and setting an operand size if necessary.
|
||||
MemRegINC_to_PUSH,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ and destination_ fields with the result. Uses the
|
||||
// 'register' field to pick from ADD/ADC/SBB/SUB/CMP, altering
|
||||
// the source to ::Immediate and setting an appropriate operand size.
|
||||
MemRegADC_to_CMP,
|
||||
//
|
||||
// 'Group 6'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory field, populating both source_
|
||||
// and destination_ fields with the result. Uses the 'register' field
|
||||
// to pick from SLDT/STR/LLDT/LTR/VERR/VERW.
|
||||
MemRegSLDT_to_VERW,
|
||||
|
||||
//
|
||||
// 'Group 7'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory field, populating both source_
|
||||
// and destination_ fields with the result. Uses the 'register' field
|
||||
// to pick from SGDT/LGDT/SMSW/LMSW.
|
||||
MemRegSGDT_to_LMSW,
|
||||
|
||||
//
|
||||
// 'Group 8'
|
||||
//
|
||||
|
||||
// Parse for mode and register/memory field, populating destination,
|
||||
// and prepare to read a single byte as source.
|
||||
MemRegBT_to_BTC,
|
||||
} modregrm_format_ = ModRegRMFormat::MemReg_Reg;
|
||||
|
||||
// Ephemeral decoding state.
|
||||
Operation operation_ = Operation::Invalid;
|
||||
uint8_t instr_ = 0x00; // TODO: is this desired, versus loading more context into ModRegRMFormat?
|
||||
int consumed_ = 0, operand_bytes_ = 0;
|
||||
|
||||
// Source and destination locations.
|
||||
@@ -122,30 +181,49 @@ class Decoder {
|
||||
Source destination_ = Source::None;
|
||||
|
||||
// Immediate fields.
|
||||
int16_t displacement_ = 0;
|
||||
uint16_t operand_ = 0;
|
||||
int32_t displacement_ = 0;
|
||||
uint32_t operand_ = 0;
|
||||
uint64_t inward_data_ = 0;
|
||||
int next_inward_data_shift_ = 0;
|
||||
|
||||
// Indirection style.
|
||||
ScaleIndexBase sib_;
|
||||
|
||||
// Facts about the instruction.
|
||||
int displacement_size_ = 0; // i.e. size of in-stream displacement, if any.
|
||||
int operand_size_ = 0; // i.e. size of in-stream operand, if any.
|
||||
int operation_size_ = 0; // i.e. size of data manipulated by the operation.
|
||||
DataSize displacement_size_ = DataSize::None; // i.e. size of in-stream displacement, if any.
|
||||
DataSize operand_size_ = DataSize::None; // i.e. size of in-stream operand, if any.
|
||||
DataSize operation_size_ = DataSize::None; // i.e. size of data manipulated by the operation.
|
||||
|
||||
bool sign_extend_ = false; // If set then sign extend the operand up to the operation size;
|
||||
// otherwise it'll be zero-padded.
|
||||
|
||||
// Prefix capture fields.
|
||||
Repetition repetition_ = Repetition::None;
|
||||
bool lock_ = false;
|
||||
Source segment_override_ = Source::None;
|
||||
|
||||
// 32-bit/16-bit selection.
|
||||
AddressSize default_address_size_ = AddressSize::b16;
|
||||
DataSize default_data_size_ = DataSize::Word;
|
||||
AddressSize address_size_ = AddressSize::b16;
|
||||
DataSize data_size_ = DataSize::Word;
|
||||
|
||||
/// Resets size capture and all fields with default values.
|
||||
void reset_parsing() {
|
||||
consumed_ = operand_bytes_ = 0;
|
||||
displacement_size_ = operand_size_ = 0;
|
||||
displacement_size_ = operand_size_ = operation_size_ = DataSize::None;
|
||||
displacement_ = operand_ = 0;
|
||||
lock_ = false;
|
||||
address_size_ = default_address_size_;
|
||||
data_size_ = default_data_size_;
|
||||
segment_override_ = Source::None;
|
||||
repetition_ = Repetition::None;
|
||||
phase_ = Phase::Instruction;
|
||||
source_ = destination_ = Source::None;
|
||||
sib_ = ScaleIndexBase();
|
||||
next_inward_data_shift_ = 0;
|
||||
inward_data_ = 0;
|
||||
sign_extend_ = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
917
InstructionSets/x86/Documentation/80386 opcode map.html
Normal file
917
InstructionSets/x86/Documentation/80386 opcode map.html
Normal file
@@ -0,0 +1,917 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>80386 Opcode Map</title>
|
||||
<style>
|
||||
table, table th, table td {
|
||||
border: 1px solid;
|
||||
border-collapse: collapse;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.codetable, .codetable th, .codetable td {
|
||||
border: 0px;
|
||||
border-collapse: collapse;
|
||||
padding-right: 1em;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.optable th, .optable td {
|
||||
width: 5em;
|
||||
}
|
||||
.optable tr:nth-child(even) {
|
||||
border-top: 3px solid;
|
||||
}
|
||||
|
||||
.grouptable, .grouptable th, .grouptable td {
|
||||
border-bottom: 3px solid;
|
||||
}
|
||||
.grouptable th, .grouptable td {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
.skiprow {
|
||||
background-color: darkgray;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Codes for Addressing Method</h1>
|
||||
|
||||
<table class="codetable">
|
||||
<tr>
|
||||
<td>A</td>
|
||||
<td>Direct address; the instruction has no MODRM field; the address of the operand is encoded in the instruction; no base register, index register, or scaling factor can be applied; e.g., far JMP (EA).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C</td>
|
||||
<td>The reg field of the MODRM field selects a control register; e.g., MOV (0F20, 0F22).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>D</td>
|
||||
<td>The reg field of the MODRM field selects a debug register; e.g., MOV (0F21, 0F23).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E</td>
|
||||
<td>A MODRM field follows the opcode and specifies the operand. The operand is either a general register or a memory address. If it is a memory address, the address is computed from a segment register and any of the following values: a base register, an index register, a scaling factor, a displacement.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>F</td>
|
||||
<td>Flags register</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>G</td>
|
||||
<td>The reg field of the MODRM field selects a general register; e.g,. ADD (00).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>I</td>
|
||||
<td>Immediate data. The value of the operand is encoded in subsequent bytes of the instruction.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>J</td>
|
||||
<td>The instruction contains a relative offset to be added to the instruction-pointer register; e.g., JMP short, LOOP.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>M</td>
|
||||
<td>The MODRM field may refer only to memory; e.g., BOUND, LES, LDS, LSS, LFS, LGS.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>O</td>
|
||||
<td>The instruction has no MODRM field; the offset of the operand is coded as a word or dword (depending on address sie attribute) in the instruction. No base register, index register, or scaling factor can be applied; e.g., MOV (A0–A3).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>R</td>
|
||||
<td>The mod field of the MODRM field may refer only to a general register; e.g., MOV(0F20–0F24, 0F26).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>S</td>
|
||||
<td>The reg field of the MODRM field selects a segment register; e.g., MOV (8C, 8E).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>T</td>
|
||||
<td>The reg field of the MODRM field selects a test register; e.g., MOV (0F24, 0F26).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>X</td>
|
||||
<td>Memory addressed by DS:SI; e.g., MOVS, COMPS, OUTS, LODS, SCAS.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Y</td>
|
||||
<td>Memory addressed by ES:DI; e.g., MOVS, CMPS, INS, STOS.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1>Codes for Operand Type</h1>
|
||||
|
||||
<table class="codetable">
|
||||
<tr>
|
||||
<td>a</td>
|
||||
<td>Two one-word operands in memory or two dword operands in memory, depending on operand size attribute (used only by BOUND).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>b</td>
|
||||
<td>Byte (regardless of operand size attribute).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>c</td>
|
||||
<td>Byte or word, depending on operand size attribute.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>d</td>
|
||||
<td>Dword (regardless of operand size attribute).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>p</td>
|
||||
<td>32-bit or 48-bit pointer, depending on operand size attribute.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>s</td>
|
||||
<td>Six-byte pesudo-descriptor.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>v</td>
|
||||
<td>Word or dword, depending on operand size attribute.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>w</td>
|
||||
<td>Word (regardless of operand size attribute).</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1>Register Codes</h1>
|
||||
|
||||
When an operand is a specific register encoded in the opcode, the register is identifed by its name; e.g., AX, CL, or ESI. The name of the register indicates whether the register is 32, 16, or 8 bits wide. A register identifier of the form eXX is used when the width of the register depends on the operand size attribute. For example, eAX indicates that the AX register is used when the operand size attribute is 16, and the EAX register is used when the operand size attribute is 32.
|
||||
|
||||
<h1>One-byte 80386 Opcode Map</h1>
|
||||
<table class="optable">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>x0</th>
|
||||
<th>x1</th>
|
||||
<th>x2</th>
|
||||
<th>x3</th>
|
||||
<th>x4</th>
|
||||
<th>x5</th>
|
||||
<th>x6</th>
|
||||
<th>x7</th>
|
||||
<th>x8</th>
|
||||
<th>x9</th>
|
||||
<th>xA</th>
|
||||
<th>xB</th>
|
||||
<th>xC</th>
|
||||
<th>xD</th>
|
||||
<th>xE</th>
|
||||
<th>xF</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>0x</th>
|
||||
|
||||
<td colspan=6>ADD</td>
|
||||
<td rowspan=2>PUSH ES</td>
|
||||
<td rowspan=2>POP ES</td>
|
||||
<td colspan=6>OR</td>
|
||||
<td rowspan=2>PUSH CS</td>
|
||||
<td rowspan=2>2-byte escape codes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- ADD -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
|
||||
<!-- OR -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>1x</th>
|
||||
|
||||
<td colspan=6>ADC</td>
|
||||
<td rowspan=2>PUSH SS</td>
|
||||
<td rowspan=2>POP SS</td>
|
||||
<td colspan=6>SBB</td>
|
||||
<td rowspan=2>PUSH DS</td>
|
||||
<td rowspan=2>POP DS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- ADC -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
|
||||
<!-- SBB -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>2x</th>
|
||||
|
||||
<td colspan=6>AND</td>
|
||||
<td rowspan=2>SEG =ES</td>
|
||||
<td rowspan=2>POP ES</td>
|
||||
<td colspan=6>SUB</td>
|
||||
<td rowspan=2>SEG =CS</td>
|
||||
<td rowspan=2>DAS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- AND -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
|
||||
<!-- SUB -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>3x</th>
|
||||
|
||||
<td colspan=6>XOR</td>
|
||||
<td rowspan=2>SEG =SS</td>
|
||||
<td rowspan=2>AAA</td>
|
||||
<td colspan=6>CMP</td>
|
||||
<td rowspan=2>SEG =DS</td>
|
||||
<td rowspan=2>AAS</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- XOR -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
|
||||
<!-- CMP -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>4x</th>
|
||||
|
||||
<td colspan=8>INC general register</td>
|
||||
<td colspan=8>DEC general register</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- INC general register -->
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
|
||||
<!-- DEC general register -->
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>5x</th>
|
||||
|
||||
<td colspan=8>PUSH general register</td>
|
||||
<td colspan=8>POP general register</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- PUSH general register -->
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
|
||||
<!-- POP general register -->
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>6x</th>
|
||||
|
||||
<td rowspan=2>PUSHA</td>
|
||||
<td rowspan=2>POPA</td>
|
||||
<td rowspan=2>BOUND Gv, Ma</td>
|
||||
<td rowspan=2>ARPL Gv, Ma</td>
|
||||
<td rowspan=2>SEG =FS</td>
|
||||
<td rowspan=2>SEG =GS</td>
|
||||
<td rowspan=2>Operand Size</td>
|
||||
<td rowspan=2>Address Size</td>
|
||||
<td rowspan=2>PUSH Iv</td>
|
||||
<td rowspan=2>IMUL GvEvIv</td>
|
||||
<td rowspan=2>PUSH Ib</td>
|
||||
<td rowspan=2>IMUL GvEvIb</td>
|
||||
<td rowspan=2>INSB Yb, Dx</td>
|
||||
<td rowspan=2>INSW/D Yv, Dx</td>
|
||||
<td rowspan=2>OUTSB Dx, Xb</td>
|
||||
<td rowspan=2>OUTSW/D Dx, Xb</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>7x</th>
|
||||
|
||||
<td colspan=16>Short-displacement jump on condition (Jb)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Short-displacement jump on condition (Jb) -->
|
||||
<td>JO</td>
|
||||
<td>JNO</td>
|
||||
<td>JB</td>
|
||||
<td>JNB</td>
|
||||
<td>JZ</td>
|
||||
<td>JNZ</td>
|
||||
<td>JBE</td>
|
||||
<td>JNBE</td>
|
||||
<td>JS</td>
|
||||
<td>JNS</td>
|
||||
<td>JP</td>
|
||||
<td>JNP</td>
|
||||
<td>JL</td>
|
||||
<td>JNL</td>
|
||||
<td>JLE</td>
|
||||
<td>JNLE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>8x</th>
|
||||
|
||||
<td colspan=2>Immediate Grp1</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>Grp1 Ev, Ib</td>
|
||||
<td colspan=2>TEST</td>
|
||||
<td colspan=2>XCHG</td>
|
||||
<td colspan=4>MOV</td>
|
||||
<td rowspan=2>MOV Ew, Sw</td>
|
||||
<td rowspan=2>LEA Gv, M</td>
|
||||
<td rowspan=2>MOV Sw, Ew</td>
|
||||
<td rowspan=2>POP Ev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Immediate Grp1 -->
|
||||
<td>Eb, Ib</td>
|
||||
<td>Ev, Iv</td>
|
||||
|
||||
<!-- TEST -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
|
||||
<!-- XCHG -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
|
||||
<!-- MOV -->
|
||||
<td>Eb, Gb</td>
|
||||
<td>Ev, Gv</td>
|
||||
<td>Gb, Eb</td>
|
||||
<td>Gv, Ev</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>9x</th>
|
||||
|
||||
<td rowspan=2>NOP</td>
|
||||
<td colspan=7>XCHG word or double-word register with eAX</td>
|
||||
<td rowspan=2>CBW</td>
|
||||
<td rowspan=2>CWD</td>
|
||||
<td rowspan=2>CALL Ap</td>
|
||||
<td rowspan=2>WAIT</td>
|
||||
<td rowspan=2>PUSHF Fv</td>
|
||||
<td rowspan=2>POPF Fv</td>
|
||||
<td rowspan=2>SAHF</td>
|
||||
<td rowspan=2>LAHF</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- XCHG -->
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Ax</th>
|
||||
|
||||
<td colspan=4>MOV</td>
|
||||
<td rowspan=2>MOVSB Xb, Yv</td>
|
||||
<td rowspan=2>MOVSW/D Xv, Yv</td>
|
||||
<td rowspan=2>CMPSB Xb, Yb</td>
|
||||
<td rowspan=2>CMPSW/D Xv, Yv</td>
|
||||
<td colspan=2>TEST</td>
|
||||
<td rowspan=2>STOSB Yb, AL</td>
|
||||
<td rowspan=2>STOSW/D Yv, eAX</td>
|
||||
<td rowspan=2>LDSB AL, Xb</td>
|
||||
<td rowspan=2>LDSW/D eAX, Yv</td>
|
||||
<td rowspan=2>SCASB AL, Xb</td>
|
||||
<td rowspan=2>SCASW/D eAX, Xv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- MOV -->
|
||||
<td>AL, Ob</td>
|
||||
<td>eAX, Ov</td>
|
||||
<td>Ob, AL</td>
|
||||
<td>Ov, eAX</td>
|
||||
|
||||
<!-- TEST -->
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Iv</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Bx</th>
|
||||
|
||||
<td colspan=8>MOV immediate byte into byte register</td>
|
||||
<td colspan=8>MOV immediate word or double into word or double register</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AL</td>
|
||||
<td>CL</td>
|
||||
<td>DL</td>
|
||||
<td>BL</td>
|
||||
<td>AH</td>
|
||||
<td>CH</td>
|
||||
<td>DH</td>
|
||||
<td>BH</td>
|
||||
|
||||
<td>eAX</td>
|
||||
<td>eCX</td>
|
||||
<td>eDX</td>
|
||||
<td>eBX</td>
|
||||
<td>eSP</td>
|
||||
<td>eBP</td>
|
||||
<td>eSI</td>
|
||||
<td>eDI</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Cx</th>
|
||||
|
||||
<td colspan=2>Shift Grp2</td>
|
||||
<td colspan=2>RET near</td>
|
||||
<td rowspan=2>LES Gv, Mp</td>
|
||||
<td rowspan=2>LDS Gv, Mp</td>
|
||||
<td colspan=2>MOV</td>
|
||||
<td rowspan=2>ENTER</td>
|
||||
<td rowspan=2>LEAVE</td>
|
||||
<td colspan=2>RET far</td>
|
||||
<td rowspan=2>INT 3</td>
|
||||
<td rowspan=2>INT Ib</td>
|
||||
<td rowspan=2>INTO</td>
|
||||
<td rowspan=2>IRET</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Eb, Ib</td>
|
||||
<td>Ev, Iv</td>
|
||||
<td>Iw</td>
|
||||
<td></td>
|
||||
<td>Eb, Ib</td>
|
||||
<td>Ev, Iv</td>
|
||||
<td>Iw</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Dx</th>
|
||||
|
||||
<td colspan=4>Shift Grp2</td>
|
||||
<td rowspan=2>AAM</td>
|
||||
<td rowspan=2>AAD</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>XLAT</td>
|
||||
<td colspan=8 rowspan=2>ESC (Escape to coprocessor instruction set)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Eb, 1</td>
|
||||
<td>Ev, 1</td>
|
||||
<td>Eb, CL</td>
|
||||
<td>Ev, CL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Ex</th>
|
||||
|
||||
<td rowspan=2>LOOPNE Jb</td>
|
||||
<td rowspan=2>LOOPE Jb</td>
|
||||
<td rowspan=2>LOOP Jb</td>
|
||||
<td rowspan=2>JCXZ Jb</td>
|
||||
<td colspan=2>IN</td>
|
||||
<td colspan=2>OUT</td>
|
||||
<td rowspan=2>CALL Jv</td>
|
||||
<td colspan=3>JMP</td>
|
||||
<td colspan=2>IN</td>
|
||||
<td colspan=2>OUT</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- IN -->
|
||||
<td>AL, Ib</td>
|
||||
<td>eAX, Ib</td>
|
||||
|
||||
<!-- OUT -->
|
||||
<td>Ib, AL</td>
|
||||
<td>Ib, eAX</td>
|
||||
|
||||
<!-- JMP -->
|
||||
<td>Jv</td>
|
||||
<td>Ap</td>
|
||||
<td>Jb</td>
|
||||
|
||||
<!-- IN -->
|
||||
<td>AL, DX</td>
|
||||
<td>eAX, DX</td>
|
||||
|
||||
<!-- OUT -->
|
||||
<td>DX, AL</td>
|
||||
<td>DX, eAX</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Fx</th>
|
||||
|
||||
<td rowspan=2>LOCK</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>REPNE</td>
|
||||
<td rowspan=2>REP / REPE</td>
|
||||
<td rowspan=2>HLT</td>
|
||||
<td rowspan=2>CMC</td>
|
||||
<td colspan=2>Unary Grp3</td>
|
||||
<td rowspan=2>CLC</td>
|
||||
<td rowspan=2>STC</td>
|
||||
<td rowspan=2>CLI</td>
|
||||
<td rowspan=2>STI</td>
|
||||
<td rowspan=2>CLD</td>
|
||||
<td rowspan=2>STD</td>
|
||||
<td rowspan=2>INC/DEC Grp4</td>
|
||||
<td rowspan=2>Indirect Grp5</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Unary Grp3 -->
|
||||
<td>Eb</td>
|
||||
<td>Ev</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h1>Two-Byte 80386 Opcode Map (First byte is 0FH)</h1>
|
||||
<table class="optable">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>x0</th>
|
||||
<th>x1</th>
|
||||
<th>x2</th>
|
||||
<th>x3</th>
|
||||
<th>x4</th>
|
||||
<th>x5</th>
|
||||
<th>x6</th>
|
||||
<th>x7</th>
|
||||
<th>x8</th>
|
||||
<th>x9</th>
|
||||
<th>xA</th>
|
||||
<th>xB</th>
|
||||
<th>xC</th>
|
||||
<th>xD</th>
|
||||
<th>xE</th>
|
||||
<th>xF</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>0x</th>
|
||||
|
||||
<td rowspan=2>Grp6</td>
|
||||
<td rowspan=2>Grp7</td>
|
||||
<td rowspan=2>LAR Gv, Ew</td>
|
||||
<td rowspan=2>LSL Gv, Ew</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>CLTS</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>1x</th>
|
||||
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>2x</th>
|
||||
|
||||
<td rowspan=2>MOV Cd, Rd</td>
|
||||
<td rowspan=2>MOV Dd, Rd</td>
|
||||
<td rowspan=2>MOV Rd, Cd</td>
|
||||
<td rowspan=2>MOV Rd, Dd</td>
|
||||
<td rowspan=2>MOV Td, Rd</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>MOV Rd, Td</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr class="skiprow">
|
||||
<th rowspan=2 colspan=17>≈</th>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>8x</th>
|
||||
|
||||
<td colspan=16>Long-displacement jump on condition (Jv)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Long-displacement jump on condition (Jv) -->
|
||||
<td>JO</td>
|
||||
<td>JNO</td>
|
||||
<td>JB</td>
|
||||
<td>JNB</td>
|
||||
<td>JZ</td>
|
||||
<td>JNZ</td>
|
||||
<td>JBE</td>
|
||||
<td>JNBE</td>
|
||||
<td>JS</td>
|
||||
<td>JNS</td>
|
||||
<td>JP</td>
|
||||
<td>JNP</td>
|
||||
<td>JL</td>
|
||||
<td>JNL</td>
|
||||
<td>JLE</td>
|
||||
<td>JNLE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>9x</th>
|
||||
|
||||
<td colspan=16>Byte set on condition (Eb)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- Byte set on condition (Eb) -->
|
||||
<td>SETO</td>
|
||||
<td>SETNO</td>
|
||||
<td>SETB</td>
|
||||
<td>SETNB</td>
|
||||
<td>SETZ</td>
|
||||
<td>SETNZ</td>
|
||||
<td>SETBE</td>
|
||||
<td>SETNBE</td>
|
||||
<td>SETS</td>
|
||||
<td>SETNS</td>
|
||||
<td>SETP</td>
|
||||
<td>SETNP</td>
|
||||
<td>SETL</td>
|
||||
<td>SETNL</td>
|
||||
<td>SETLE</td>
|
||||
<td>SETNLE</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th rowspan=2>Ax</th>
|
||||
|
||||
<td rowspan=2>PUSH FS</td>
|
||||
<td rowspan=2>POP FS</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>BT Ev, Gv</td>
|
||||
<td rowspan=2>SHLD EvGvIb</td>
|
||||
<td rowspan=2>SHLD EvGvCL</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>PUSH GS</td>
|
||||
<td rowspan=2>POP GS</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>BTS Ev, Gv</td>
|
||||
<td rowspan=2>SHRD EvGvIb</td>
|
||||
<td rowspan=2>SHRD EvGvCL</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>IMUL Gv, Ev</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>Bx</th>
|
||||
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>LSS Mp</td>
|
||||
<td rowspan=2>BTR Ev, Gv</td>
|
||||
<td rowspan=2>LFS Mp</td>
|
||||
<td rowspan=2>LGS Mp</td>
|
||||
<td colspan=2>MOVZX</td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2>Grp8 Ev, Ib</td>
|
||||
<td rowspan=2>BTC Ev, Gv</td>
|
||||
<td rowspan=2>BSF Gv, Ev</td>
|
||||
<td rowspan=2>BSR Gv, Ev</td>
|
||||
<td colspan=2>MOVSX</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<!-- MOVZX -->
|
||||
<td>Gv, Eb</td>
|
||||
<td>Gv, Ew</td>
|
||||
|
||||
<!-- MOVSX -->
|
||||
<td>Gv, Eb</td>
|
||||
<td>Gv, Ew</td>
|
||||
</tr>
|
||||
<tr class="skiprow">
|
||||
<th rowspan=2 colspan=17>≈</th>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<th rowspan=2>Fx</th>
|
||||
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
<td rowspan=2></td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</table>
|
||||
<h1>Opcodes Determined by Bits 5, 4, 3 of MODRM Field</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td>mod</td>
|
||||
<td>nnn</td>
|
||||
<td>R/M</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br />
|
||||
<table class="grouptable">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>000</th>
|
||||
<th>001</th>
|
||||
<th>010</th>
|
||||
<th>011</th>
|
||||
<th>100</th>
|
||||
<th>101</th>
|
||||
<th>110</th>
|
||||
<th>111</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 1</th>
|
||||
|
||||
<td>ADD</td>
|
||||
<td>OR</td>
|
||||
<td>ADC</td>
|
||||
<td>SBB</td>
|
||||
<td>AND</td>
|
||||
<td>SUB</td>
|
||||
<td>XOR</td>
|
||||
<td>CMP</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 2</th>
|
||||
|
||||
<td>ROL</td>
|
||||
<td>ROR</td>
|
||||
<td>RCL</td>
|
||||
<td>RCR</td>
|
||||
<td>SHL</td>
|
||||
<td>SHR</td>
|
||||
<td></td>
|
||||
<td>SAR</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 3</th>
|
||||
|
||||
<td>TEST Ib/Iv</td>
|
||||
<td></td>
|
||||
<td>NOT</td>
|
||||
<td>NEG</td>
|
||||
<td>MUL AL/eAX</td>
|
||||
<td>IMUL AL/EAX</td>
|
||||
<td>DIV AL/eAX</td>
|
||||
<td>IDIV AL/eAX</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 4</th>
|
||||
|
||||
<td>INC Eb</td>
|
||||
<td>DEC Eb</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 5</th>
|
||||
|
||||
<td>INC Ev</td>
|
||||
<td>DEC Ev</td>
|
||||
<td>CALL Ev</td>
|
||||
<td>CALL Ep</td>
|
||||
<td>JMP Ev</td>
|
||||
<td>JMP Ep</td>
|
||||
<td>PUSH Ev</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 6</th>
|
||||
|
||||
<td>SLDT Ew</td>
|
||||
<td>STR Ew</td>
|
||||
<td>LLDT Ew</td>
|
||||
<td>LTR Ew</td>
|
||||
<td>VERR Ew</td>
|
||||
<td>VERW Ew</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 7</th>
|
||||
|
||||
<td>SGDT Ms</td>
|
||||
<td>SIDT Ms</td>
|
||||
<td>LGDT Ms</td>
|
||||
<td>LIDT Ms</td>
|
||||
<td>SMSW Ew</td>
|
||||
<td></td>
|
||||
<td>LMSW Ew</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Group 8</th>
|
||||
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>BT</td>
|
||||
<td>BTS</td>
|
||||
<td>BTR</td>
|
||||
<td>BTC</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,7 +9,9 @@
|
||||
#ifndef InstructionSets_x86_Instruction_h
|
||||
#define InstructionSets_x86_Instruction_h
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace x86 {
|
||||
@@ -23,6 +25,10 @@ namespace x86 {
|
||||
enum class Operation: uint8_t {
|
||||
Invalid,
|
||||
|
||||
//
|
||||
// 8086 instructions.
|
||||
//
|
||||
|
||||
/// ASCII adjust after addition; source will be AL and destination will be AX.
|
||||
AAA,
|
||||
/// ASCII adjust before division; destination will be AX and source will be a multiplier.
|
||||
@@ -36,9 +42,13 @@ enum class Operation: uint8_t {
|
||||
/// Decimal adjust after subtraction; source and destination will be AL.
|
||||
DAS,
|
||||
|
||||
/// Convert byte into word; source will be AL, destination will be AH.
|
||||
/// If data size is word, convert byte into word; source will be AL, destination will be AH.
|
||||
/// If data size is DWord, convert word to dword; AX will be expanded to fill EAX.
|
||||
/// In both cases, conversion will be by sign extension.
|
||||
CBW,
|
||||
/// Convert word to double word; source will be AX and destination will be DX.
|
||||
/// If data size is Word, converts word to double word; source will be AX and destination will be DX.
|
||||
/// If data size is DWord, converts double word to quad word (i.e. CDW); source will be EAX and destination will be EDX:EAX.
|
||||
/// In both cases, conversion will be by sign extension.
|
||||
CWD,
|
||||
|
||||
/// Escape, for a coprocessor; perform the bus cycles necessary to read the source and destination and perform a NOP.
|
||||
@@ -59,8 +69,8 @@ enum class Operation: uint8_t {
|
||||
SUB,
|
||||
/// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
MUL,
|
||||
/// Signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
IMUL,
|
||||
/// Single operand signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
IMUL_1,
|
||||
/// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
|
||||
DIV,
|
||||
/// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
|
||||
@@ -81,27 +91,27 @@ enum class Operation: uint8_t {
|
||||
JS, JNS, JP, JNP, JL, JNL, JLE, JNLE,
|
||||
|
||||
/// Far call; see the segment() and offset() fields.
|
||||
CALLF,
|
||||
/// Displacement call; followed by a 16-bit operand providing a call offset.
|
||||
CALLD,
|
||||
CALLfar,
|
||||
/// Relative call; see displacement().
|
||||
CALLrel,
|
||||
/// Near call.
|
||||
CALLN,
|
||||
CALLabs,
|
||||
/// Return from interrupt.
|
||||
IRET,
|
||||
/// Near return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
|
||||
RETF,
|
||||
RETfar,
|
||||
/// Far return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
|
||||
RETN,
|
||||
/// Near jump; if an operand is not ::None then it gives an absolute destination; otherwise see the displacement.
|
||||
JMPN,
|
||||
RETnear,
|
||||
/// Near jump with an absolute destination.
|
||||
JMPabs,
|
||||
/// Near jump with a relative destination.
|
||||
JMPrel,
|
||||
/// Far jump to the indicated segment and offset.
|
||||
JMPF,
|
||||
JMPfar,
|
||||
/// Relative jump performed only if CX = 0; see the displacement.
|
||||
JPCX,
|
||||
/// Generates a software interrupt of the level stated in the operand.
|
||||
INT,
|
||||
/// Generates a software interrupt of level 3.
|
||||
INT3,
|
||||
/// Generates a software interrupt of level 4 if overflow is set.
|
||||
INTO,
|
||||
|
||||
@@ -152,19 +162,19 @@ enum class Operation: uint8_t {
|
||||
PUSH,
|
||||
/// PUSH the flags register to the stack.
|
||||
PUSHF,
|
||||
/// Rotate the destination left through carry the number of bits indicated by source.
|
||||
/// Rotate the destination left through carry the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
RCL,
|
||||
/// Rotate the destination right through carry the number of bits indicated by source.
|
||||
/// Rotate the destination right through carry the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
RCR,
|
||||
/// Rotate the destination left the number of bits indicated by source.
|
||||
/// Rotate the destination left the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
ROL,
|
||||
/// Rotate the destination right the number of bits indicated by source.
|
||||
/// Rotate the destination right the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
ROR,
|
||||
/// Arithmetic shift left the destination by the number of bits indicated by source.
|
||||
/// Arithmetic shift left the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
SAL,
|
||||
/// Arithmetic shift right the destination by the number of bits indicated by source.
|
||||
/// Arithmetic shift right the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
SAR,
|
||||
/// Logical shift right the destination by the number of bits indicated by source.
|
||||
/// Logical shift right the destination by the number of bits indicated by source; if the source is a register then implicitly its size is 1.
|
||||
SHR,
|
||||
|
||||
/// Clear carry flag; no source or destination provided.
|
||||
@@ -192,110 +202,599 @@ enum class Operation: uint8_t {
|
||||
|
||||
/// Load AL with DS:[AL+BX].
|
||||
XLAT,
|
||||
|
||||
//
|
||||
// 80186 additions.
|
||||
//
|
||||
|
||||
/// Checks whether the signed value in the destination register is within the bounds
|
||||
/// stored at the location indicated by the source register, which will point to two
|
||||
/// 16- or 32-bit words, the first being a signed lower bound and the signed upper.
|
||||
/// Raises a bounds exception if not.
|
||||
BOUND,
|
||||
|
||||
|
||||
/// Create stack frame. See operand() for the nesting level and offset()
|
||||
/// for the dynamic storage size.
|
||||
ENTER,
|
||||
/// Procedure exit; copies BP to SP, then pops a new BP from the stack.
|
||||
LEAVE,
|
||||
|
||||
/// Inputs a byte, word or double word from the port specified by DX, writing it to
|
||||
/// ES:[e]DI and incrementing or decrementing [e]DI as per the
|
||||
/// current EFLAGS DF flag.
|
||||
INS,
|
||||
/// Outputs a byte, word or double word from ES:[e]DI to the port specified by DX,
|
||||
/// incrementing or decrementing [e]DI as per the current EFLAGS DF flag.]
|
||||
OUTS,
|
||||
|
||||
/// Pushes all general purpose registers to the stack, in the order:
|
||||
/// AX, CX, DX, BX, [original] SP, BP, SI, DI.
|
||||
PUSHA,
|
||||
/// Pops all general purpose registers from the stack, in the reverse of
|
||||
/// the PUSHA order, i.e. DI, SI, BP, [final] SP, BX, DX, CX, AX.
|
||||
POPA,
|
||||
|
||||
//
|
||||
// 80286 additions.
|
||||
//
|
||||
|
||||
// TODO: expand detail on all operations below.
|
||||
|
||||
/// Adjusts requested privilege level.
|
||||
ARPL,
|
||||
/// Clears the task-switched flag.
|
||||
CLTS,
|
||||
/// Loads access rights.
|
||||
LAR,
|
||||
|
||||
/// Loads the global descriptor table.
|
||||
LGDT,
|
||||
/// Loads the interrupt descriptor table.
|
||||
LIDT,
|
||||
/// Loads the local descriptor table.
|
||||
LLDT,
|
||||
/// Stores the global descriptor table.
|
||||
SGDT,
|
||||
/// Stores the interrupt descriptor table.
|
||||
SIDT,
|
||||
/// Stores the local descriptor table.
|
||||
SLDT,
|
||||
|
||||
/// Verifies a segment for reading.
|
||||
VERR,
|
||||
/// Verifies a segment for writing.
|
||||
VERW,
|
||||
|
||||
/// Loads the machine status word.
|
||||
LMSW,
|
||||
/// Stores the machine status word.
|
||||
SMSW,
|
||||
/// Loads a segment limit
|
||||
LSL,
|
||||
/// Loads the task register.
|
||||
LTR,
|
||||
/// Stores the task register.
|
||||
STR,
|
||||
|
||||
/// Three-operand form of IMUL; multiply the immediate by the source and write to the destination.
|
||||
IMUL_3,
|
||||
|
||||
/// Undocumented (but used); loads all registers, including internal ones.
|
||||
LOADALL,
|
||||
|
||||
//
|
||||
// 80386 additions.
|
||||
//
|
||||
|
||||
/// Loads a pointer to FS.
|
||||
LFS,
|
||||
/// Loads a pointer to GS.
|
||||
LGS,
|
||||
/// Loads a pointer to SS.
|
||||
LSS,
|
||||
|
||||
/// Shift left double.
|
||||
SHLDimm,
|
||||
SHLDCL,
|
||||
/// Shift right double.
|
||||
SHRDimm,
|
||||
SHRDCL,
|
||||
|
||||
/// Bit scan forwards.
|
||||
BSF,
|
||||
/// Bit scan reverse.
|
||||
BSR,
|
||||
/// Bit test.
|
||||
BT,
|
||||
/// Bit test and complement.
|
||||
BTC,
|
||||
/// Bit test and reset.
|
||||
BTR,
|
||||
/// Bit test and set.
|
||||
BTS,
|
||||
|
||||
/// Move from the source to the destination, extending the source with zeros.
|
||||
/// The instruction data size dictates the size of the source; the destination will
|
||||
/// be either 16- or 32-bit depending on the current processor operating mode.
|
||||
MOVZX,
|
||||
/// Move from the source to the destination, applying a sign extension.
|
||||
/// The instruction data size dictates the size of the source; the destination will
|
||||
/// be either 16- or 32-bit depending on the current processor operating mode.
|
||||
MOVSX,
|
||||
|
||||
/// Two-operand form of IMUL; multiply the source by the destination and write to the destination.
|
||||
IMUL_2,
|
||||
|
||||
// Various conditional sets; each sets the byte at the location given by the operand
|
||||
// to $ff if the condition is met; $00 otherwise.
|
||||
SETO, SETNO, SETB, SETNB, SETZ, SETNZ, SETBE, SETNBE,
|
||||
SETS, SETNS, SETP, SETNP, SETL, SETNL, SETLE, SETNLE,
|
||||
|
||||
// Various special-case moves (i.e. those where it is impractical to extend the
|
||||
// Source enum, so the requirement for special handling is loaded into the operation).
|
||||
// In all cases the Cx, Dx and Tx Source aliases can be used to reinterpret the relevant
|
||||
// source or destination.
|
||||
MOVtoCr, MOVfromCr,
|
||||
MOVtoDr, MOVfromDr,
|
||||
MOVtoTr, MOVfromTr,
|
||||
};
|
||||
|
||||
enum class Size: uint8_t {
|
||||
Implied = 0,
|
||||
Byte = 1,
|
||||
Word = 2,
|
||||
DWord = 4,
|
||||
enum class DataSize: uint8_t {
|
||||
Byte = 0,
|
||||
Word = 1,
|
||||
DWord = 2,
|
||||
None = 3,
|
||||
};
|
||||
|
||||
constexpr int byte_size(DataSize size) {
|
||||
return (1 << int(size)) & 7;
|
||||
}
|
||||
|
||||
constexpr int bit_size(DataSize size) {
|
||||
return (8 << int(size)) & 0x3f;
|
||||
}
|
||||
|
||||
enum class AddressSize: uint8_t {
|
||||
b16 = 0,
|
||||
b32 = 1,
|
||||
};
|
||||
|
||||
constexpr DataSize data_size(AddressSize size) {
|
||||
return DataSize(int(size) + 1);
|
||||
}
|
||||
|
||||
constexpr int byte_size(AddressSize size) {
|
||||
return 2 << int(size);
|
||||
}
|
||||
|
||||
constexpr int bit_size(AddressSize size) {
|
||||
return 16 << int(size);
|
||||
}
|
||||
|
||||
enum class Source: uint8_t {
|
||||
// These are in SIB order; this matters for packing later on.
|
||||
|
||||
/// AL, AX or EAX depending on size.
|
||||
eAX,
|
||||
/// CL, CX or ECX depending on size.
|
||||
eCX,
|
||||
/// DL, DX or EDX depending on size.
|
||||
eDX,
|
||||
/// BL, BX or BDX depending on size.
|
||||
eBX,
|
||||
/// AH if size is 1; SP or ESP otherwise.
|
||||
eSPorAH,
|
||||
/// CH if size is 1; BP or EBP otherwise.
|
||||
eBPorCH,
|
||||
/// DH if size is 1; SI or ESI otherwise.
|
||||
eSIorDH,
|
||||
/// BH if size is 1; DI or EDI otherwise.
|
||||
eDIorBH,
|
||||
|
||||
// Aliases for the dual-purpose enums.
|
||||
eSP = eSPorAH, AH = eSPorAH,
|
||||
eBP = eBPorCH, CH = eBPorCH,
|
||||
eSI = eSIorDH, DH = eSIorDH,
|
||||
eDI = eDIorBH, BH = eDIorBH,
|
||||
|
||||
// Aliases for control, test and debug registers.
|
||||
C0 = 0, C1 = 1, C2 = 2, C3 = 3, C4 = 4, C5 = 5, C6 = 6, C7 = 7,
|
||||
T0 = 0, T1 = 1, T2 = 2, T3 = 3, T4 = 4, T5 = 5, T6 = 6, T7 = 7,
|
||||
D0 = 0, D1 = 1, D2 = 2, D3 = 3, D4 = 4, D5 = 5, D6 = 6, D7 = 7,
|
||||
|
||||
// Selectors.
|
||||
ES, CS, SS, DS, FS, GS,
|
||||
|
||||
/// @c None can be treated as a source that produces 0 when encountered;
|
||||
/// it is semantically valid to receive it with that meaning in some contexts —
|
||||
/// e.g. to indicate no index in indirect addressing.
|
||||
/// It's listed here in order to allow an [optional] segment override to fit into three bits.
|
||||
None,
|
||||
CS, DS, ES, SS,
|
||||
|
||||
AL, AH, AX,
|
||||
BL, BH, BX,
|
||||
CL, CH, CX,
|
||||
DL, DH, DX,
|
||||
|
||||
SI, DI,
|
||||
BP, SP,
|
||||
|
||||
IndBXPlusSI,
|
||||
IndBXPlusDI,
|
||||
IndBPPlusSI,
|
||||
IndBPPlusDI,
|
||||
IndSI,
|
||||
IndDI,
|
||||
/// The address included within this instruction should be used as the source.
|
||||
DirectAddress,
|
||||
IndBP,
|
||||
IndBX,
|
||||
|
||||
Immediate
|
||||
/// The immediate value included within this instruction should be used as the source.
|
||||
Immediate,
|
||||
|
||||
/// The ScaleIndexBase associated with this source should be used.
|
||||
Indirect = 0b11000,
|
||||
// Elsewhere, as an implementation detail, the low three bits of an indirect source
|
||||
// are reused; (Indirect-1) is also used as a sentinel value but is not a valid member
|
||||
// of the enum and isn't exposed externally.
|
||||
|
||||
/// The ScaleIndexBase associated with this source should be used, but
|
||||
/// its base should be ignored (and is guaranteed to be zero if the default
|
||||
/// getter is used).
|
||||
IndirectNoBase = Indirect - 1,
|
||||
};
|
||||
|
||||
enum class Repetition: uint8_t {
|
||||
None, RepE, RepNE
|
||||
};
|
||||
|
||||
class Instruction {
|
||||
/// Provides a 32-bit-style scale, index and base; to produce the address this represents,
|
||||
/// calcluate base() + (index() << scale()).
|
||||
///
|
||||
/// This form of indirect addressing is used to describe both 16- and 32-bit indirect addresses,
|
||||
/// even though it is a superset of that supported prior to the 80386.
|
||||
///
|
||||
/// This class can represent only exactly what a SIB byte can — a scale of 0 to 3, a base
|
||||
/// that is any one of the eight general purpose registers, and an index that is one of the seven
|
||||
/// general purpose registers excluding eSP or is ::None.
|
||||
///
|
||||
/// It cannot natively describe a base of ::None.
|
||||
class ScaleIndexBase {
|
||||
public:
|
||||
Operation operation = Operation::Invalid;
|
||||
constexpr ScaleIndexBase() noexcept {}
|
||||
constexpr ScaleIndexBase(uint8_t sib) noexcept : sib_(sib) {}
|
||||
constexpr ScaleIndexBase(int scale, Source index, Source base) noexcept :
|
||||
sib_(uint8_t(
|
||||
scale << 6 |
|
||||
(int(index != Source::None ? index : Source::eSI) << 3) |
|
||||
int(base)
|
||||
)) {}
|
||||
constexpr ScaleIndexBase(Source index, Source base) noexcept : ScaleIndexBase(0, index, base) {}
|
||||
constexpr explicit ScaleIndexBase(Source base) noexcept : ScaleIndexBase(0, Source::None, base) {}
|
||||
|
||||
bool operator ==(const Instruction &rhs) const {
|
||||
/// @returns the power of two by which to multiply @c index() before adding it to @c base().
|
||||
constexpr int scale() const {
|
||||
return sib_ >> 6;
|
||||
}
|
||||
|
||||
/// @returns the @c index for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, None, eBP, eSI or eDI.
|
||||
constexpr Source index() const {
|
||||
constexpr Source sources[] = {
|
||||
Source::eAX, Source::eCX, Source::eDX, Source::eBX, Source::None, Source::eBP, Source::eSI, Source::eDI,
|
||||
};
|
||||
static_assert(sizeof(sources) == 8);
|
||||
return sources[(sib_ >> 3) & 0x7];
|
||||
}
|
||||
|
||||
/// @returns the @c base for this address; this is guaranteed to be one of eAX, eBX, eCX, eDX, eSP, eBP, eSI or eDI.
|
||||
constexpr Source base() const {
|
||||
return Source(sib_ & 0x7);
|
||||
}
|
||||
|
||||
constexpr uint8_t without_base() const {
|
||||
return sib_ & ~0x3;
|
||||
}
|
||||
|
||||
bool operator ==(const ScaleIndexBase &rhs) const {
|
||||
// Permit either exact equality or index and base being equal
|
||||
// but transposed with a scale of 1.
|
||||
return
|
||||
repetition_size_ == rhs.repetition_size_ &&
|
||||
sources_ == rhs.sources_ &&
|
||||
displacement_ == rhs.displacement_ &&
|
||||
operand_ == rhs.operand_;
|
||||
(sib_ == rhs.sib_) ||
|
||||
(
|
||||
!scale() && !rhs.scale() &&
|
||||
rhs.index() == base() &&
|
||||
rhs.base() == index()
|
||||
);
|
||||
}
|
||||
|
||||
operator uint8_t() const {
|
||||
return sib_;
|
||||
}
|
||||
|
||||
private:
|
||||
// b0, b1: a Repetition;
|
||||
// b2+: operation size.
|
||||
uint8_t repetition_size_ = 0;
|
||||
// Data is stored directly as an 80386 SIB byte.
|
||||
uint8_t sib_ = 0;
|
||||
};
|
||||
static_assert(sizeof(ScaleIndexBase) == 1);
|
||||
static_assert(alignof(ScaleIndexBase) == 1);
|
||||
|
||||
// b0–b5: source;
|
||||
// b6–b11: destination;
|
||||
// b12–b14: segment override;
|
||||
// b15: lock.
|
||||
uint16_t sources_ = 0;
|
||||
/// Provides the location of an operand's source or destination.
|
||||
///
|
||||
/// Callers should use .source() as a first point of entry. If it directly nominates a register
|
||||
/// then use the register contents directly. If it indicates ::DirectAddress or ::Immediate
|
||||
/// then ask the instruction for the address or immediate value that was provided in
|
||||
/// the instruction.
|
||||
///
|
||||
/// If .source() indicates ::Indirect then use base(), index() and scale() to construct an address.
|
||||
///
|
||||
/// In all cases, the applicable segment is indicated by the instruction.
|
||||
class DataPointer {
|
||||
public:
|
||||
/// Constricts a DataPointer referring to the given source; it shouldn't be ::Indirect.
|
||||
constexpr DataPointer(Source source) noexcept : source_(source) {}
|
||||
|
||||
// Unpackable fields.
|
||||
int16_t displacement_ = 0;
|
||||
uint16_t operand_ = 0; // ... or used to store a segment for far operations.
|
||||
/// Constricts a DataPointer with a source of ::Indirect and the specified sib.
|
||||
constexpr DataPointer(ScaleIndexBase sib) noexcept : sib_(sib) {}
|
||||
|
||||
/// Constructs a DataPointer with a source and SIB; use the source to indicate
|
||||
/// whether the base field of the SIB is effective.
|
||||
constexpr DataPointer(Source source, ScaleIndexBase sib) noexcept : source_(source), sib_(sib) {}
|
||||
|
||||
/// Constructs an indirect DataPointer referencing the given base, index and scale.
|
||||
/// Automatically maps Source::Indirect to Source::IndirectNoBase if base is Source::None.
|
||||
constexpr DataPointer(Source base, Source index, int scale) noexcept :
|
||||
source_(base != Source::None ? Source::Indirect : Source::IndirectNoBase),
|
||||
sib_(scale, index, base) {}
|
||||
|
||||
constexpr bool operator ==(const DataPointer &rhs) const {
|
||||
// Require a SIB match only if source_ is ::Indirect or ::IndirectNoBase.
|
||||
return
|
||||
source_ == rhs.source_ && (
|
||||
source_ < Source::IndirectNoBase ||
|
||||
(source_ == Source::Indirect && sib_ == rhs.sib_) ||
|
||||
(source_ == Source::IndirectNoBase && sib_.without_base() == rhs.sib_.without_base())
|
||||
);
|
||||
}
|
||||
|
||||
template <bool obscure_indirectNoBase = false> constexpr Source source() const {
|
||||
if constexpr (obscure_indirectNoBase) {
|
||||
return (source_ >= Source::IndirectNoBase) ? Source::Indirect : source_;
|
||||
}
|
||||
return source_;
|
||||
}
|
||||
|
||||
constexpr int scale() const {
|
||||
return sib_.scale();
|
||||
}
|
||||
|
||||
constexpr Source index() const {
|
||||
return sib_.index();
|
||||
}
|
||||
|
||||
template <bool obscure_indirectNoBase = false> constexpr Source base() const {
|
||||
if constexpr (obscure_indirectNoBase) {
|
||||
return (source_ <= Source::IndirectNoBase) ? Source::None : sib_.base();
|
||||
}
|
||||
return sib_.base();
|
||||
}
|
||||
|
||||
private:
|
||||
Source source_ = Source::Indirect;
|
||||
ScaleIndexBase sib_;
|
||||
};
|
||||
|
||||
template<bool is_32bit> class Instruction {
|
||||
public:
|
||||
Operation operation = Operation::Invalid;
|
||||
|
||||
bool operator ==(const Instruction<is_32bit> &rhs) const {
|
||||
if( operation != rhs.operation ||
|
||||
mem_exts_source_ != rhs.mem_exts_source_ ||
|
||||
source_data_dest_sib_ != rhs.source_data_dest_sib_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Have already established above that this and RHS have the
|
||||
// same extensions, if any.
|
||||
const int extension_count = has_length_extension() + has_displacement() + has_operand();
|
||||
for(int c = 0; c < extension_count; c++) {
|
||||
if(extensions_[c] != rhs.extensions_[c]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
using DisplacementT = typename std::conditional<is_32bit, int32_t, int16_t>::type;
|
||||
using ImmediateT = typename std::conditional<is_32bit, uint32_t, uint16_t>::type;
|
||||
using AddressT = ImmediateT;
|
||||
|
||||
private:
|
||||
// Packing and encoding of fields is admittedly somewhat convoluted; what this
|
||||
// achieves is that instructions will be sized:
|
||||
//
|
||||
// four bytes + up to three extension words
|
||||
// (two bytes for 16-bit instructions, four for 32)
|
||||
//
|
||||
// Two of the extension words are used to retain an operand and displacement
|
||||
// if the instruction has those. The other can store sizes greater than 15
|
||||
// bytes (for earlier processors), plus any repetition, segment override or
|
||||
// repetition prefixes.
|
||||
|
||||
// b7: address size;
|
||||
// b6: has displacement;
|
||||
// b5: has operand;
|
||||
// [b4, b0]: source.
|
||||
uint8_t mem_exts_source_ = 0;
|
||||
|
||||
bool has_displacement() const {
|
||||
return mem_exts_source_ & (1 << 6);
|
||||
}
|
||||
bool has_operand() const {
|
||||
return mem_exts_source_ & (1 << 5);
|
||||
}
|
||||
|
||||
// [b15, b14]: data size;
|
||||
// [b13, b10]: source length (0 => has length extension);
|
||||
// [b9, b5]: top five of SIB;
|
||||
// [b4, b0]: dest.
|
||||
uint16_t source_data_dest_sib_ = 1 << 10; // So that ::Invalid doesn't seem to have a length extension.
|
||||
|
||||
bool has_length_extension() const {
|
||||
return !((source_data_dest_sib_ >> 10) & 15);
|
||||
}
|
||||
|
||||
// {operand}, {displacement}, {length extension}.
|
||||
//
|
||||
// If length extension is present then:
|
||||
//
|
||||
// [b15, b6]: source length;
|
||||
// [b5, b4]: repetition;
|
||||
// [b3, b1]: segment override;
|
||||
// b0: lock.
|
||||
ImmediateT extensions_[3]{};
|
||||
|
||||
ImmediateT operand_extension() const {
|
||||
return extensions_[0];
|
||||
}
|
||||
ImmediateT displacement_extension() const {
|
||||
return extensions_[(mem_exts_source_ >> 5) & 1];
|
||||
}
|
||||
ImmediateT length_extension() const {
|
||||
return extensions_[((mem_exts_source_ >> 5) & 1) + ((mem_exts_source_ >> 6) & 1)];
|
||||
}
|
||||
|
||||
public:
|
||||
Source source() const { return Source(sources_ & 0x3f); }
|
||||
Source destination() const { return Source((sources_ >> 6) & 0x3f); }
|
||||
bool lock() const { return sources_ & 0x8000; }
|
||||
Source segment_override() const { return Source((sources_ >> 12) & 7); }
|
||||
/// @returns The number of bytes used for meaningful content within this class. A receiver must use at least @c sizeof(Instruction) bytes
|
||||
/// to store an @c Instruction but is permitted to reuse the trailing sizeof(Instruction) - packing_size() for any purpose it likes. Teleologically,
|
||||
/// this allows a denser packing of instructions into containers.
|
||||
size_t packing_size() const {
|
||||
return
|
||||
offsetof(Instruction<is_32bit>, extensions) +
|
||||
(has_displacement() + has_operand() + has_length_extension()) * sizeof(ImmediateT);
|
||||
|
||||
Repetition repetition() const { return Repetition(repetition_size_ & 3); }
|
||||
Size operation_size() const { return Size(repetition_size_ >> 2); }
|
||||
// To consider in the future: the length extension is always the last one,
|
||||
// and uses only 8 bits of content within 32-bit instructions, so it'd be
|
||||
// possible further to trim the packing size on little endian machines.
|
||||
//
|
||||
// ... but is that a speed improvement? How much space does it save, and
|
||||
// is it enough to undo the costs of unaligned data?
|
||||
}
|
||||
|
||||
uint16_t segment() const { return uint16_t(operand_); }
|
||||
uint16_t offset() const { return uint16_t(displacement_); }
|
||||
private:
|
||||
// A lookup table to help with stripping parts of the SIB that have been
|
||||
// hidden within the source/destination fields.
|
||||
static constexpr uint8_t sib_masks[] = {
|
||||
0x1f, 0x1f, 0x1f, 0x18
|
||||
};
|
||||
|
||||
int16_t displacement() const { return displacement_; }
|
||||
uint16_t operand() const { return operand_; }
|
||||
public:
|
||||
DataPointer source() const {
|
||||
return DataPointer(
|
||||
Source(mem_exts_source_ & sib_masks[(mem_exts_source_ >> 3) & 3]),
|
||||
((source_data_dest_sib_ >> 2) & 0xf8) | (mem_exts_source_ & 0x07)
|
||||
);
|
||||
}
|
||||
DataPointer destination() const {
|
||||
return DataPointer(
|
||||
Source(source_data_dest_sib_ & sib_masks[(source_data_dest_sib_ >> 3) & 3]),
|
||||
((source_data_dest_sib_ >> 2) & 0xf8) | (source_data_dest_sib_ & 0x07)
|
||||
);
|
||||
}
|
||||
bool lock() const {
|
||||
return has_length_extension() && length_extension()&1;
|
||||
}
|
||||
|
||||
Instruction() noexcept {}
|
||||
Instruction(
|
||||
AddressSize address_size() const {
|
||||
return AddressSize(mem_exts_source_ >> 7);
|
||||
}
|
||||
|
||||
/// @returns @c Source::DS if no segment override was found; the overridden segment otherwise.
|
||||
/// On x86 a segment override cannot modify the segment used as a destination in string instructions,
|
||||
/// or that used by stack instructions, but this function does not spend the time necessary to provide
|
||||
/// the correct default for those.
|
||||
Source data_segment() const {
|
||||
if(!has_length_extension()) return Source::DS;
|
||||
return Source(
|
||||
int(Source::ES) +
|
||||
((length_extension() >> 1) & 7)
|
||||
);
|
||||
}
|
||||
|
||||
Repetition repetition() const {
|
||||
if(!has_length_extension()) return Repetition::None;
|
||||
return Repetition((length_extension() >> 4) & 3);
|
||||
}
|
||||
DataSize operation_size() const {
|
||||
return DataSize(source_data_dest_sib_ >> 14);
|
||||
}
|
||||
|
||||
int length() const {
|
||||
const int short_length = (source_data_dest_sib_ >> 10) & 15;
|
||||
if(short_length) return short_length;
|
||||
return length_extension() >> 6;
|
||||
}
|
||||
|
||||
ImmediateT operand() const {
|
||||
const ImmediateT ops[] = {0, operand_extension()};
|
||||
return ops[has_operand()];
|
||||
}
|
||||
DisplacementT displacement() const {
|
||||
return DisplacementT(offset());
|
||||
}
|
||||
|
||||
uint16_t segment() const {
|
||||
return uint16_t(operand());
|
||||
}
|
||||
ImmediateT offset() const {
|
||||
const ImmediateT offsets[] = {0, displacement_extension()};
|
||||
return offsets[has_displacement()];
|
||||
}
|
||||
|
||||
constexpr Instruction() noexcept {}
|
||||
constexpr Instruction(Operation operation, int length) noexcept :
|
||||
Instruction(operation, Source::None, Source::None, ScaleIndexBase(), false, AddressSize::b16, Source::None, Repetition::None, DataSize::None, 0, 0, length) {}
|
||||
constexpr Instruction(
|
||||
Operation operation,
|
||||
Source source,
|
||||
Source destination,
|
||||
ScaleIndexBase sib,
|
||||
bool lock,
|
||||
AddressSize address_size,
|
||||
Source segment_override,
|
||||
Repetition repetition,
|
||||
Size operation_size,
|
||||
int16_t displacement,
|
||||
uint16_t operand) noexcept :
|
||||
DataSize data_size,
|
||||
DisplacementT displacement,
|
||||
ImmediateT operand,
|
||||
int length) noexcept :
|
||||
operation(operation),
|
||||
repetition_size_(uint8_t((int(operation_size) << 2) | int(repetition))),
|
||||
sources_(uint16_t(
|
||||
mem_exts_source_(uint8_t(
|
||||
(int(address_size) << 7) |
|
||||
(displacement ? 0x40 : 0x00) |
|
||||
(operand ? 0x20 : 0x00) |
|
||||
int(source) |
|
||||
(int(destination) << 6) |
|
||||
(int(segment_override) << 12) |
|
||||
(int(lock) << 15)
|
||||
(source == Source::Indirect ? (uint8_t(sib) & 7) : 0)
|
||||
)),
|
||||
displacement_(displacement),
|
||||
operand_(operand) {}
|
||||
source_data_dest_sib_(uint16_t(
|
||||
(int(data_size) << 14) |
|
||||
((
|
||||
(lock || (segment_override != Source::None) || (length > 15) || (repetition != Repetition::None))
|
||||
) ? 0 : (length << 10)) |
|
||||
((uint8_t(sib) & 0xf8) << 2) |
|
||||
int(destination) |
|
||||
(destination == Source::Indirect ? (uint8_t(sib) & 7) : 0)
|
||||
)) {
|
||||
|
||||
// Decisions on whether to include operand, displacement and/or size extension words
|
||||
// have implicitly been made in the int packing above; honour them here.
|
||||
int extension = 0;
|
||||
if(has_operand()) {
|
||||
extensions_[extension] = operand;
|
||||
++extension;
|
||||
}
|
||||
if(has_displacement()) {
|
||||
extensions_[extension] = ImmediateT(displacement);
|
||||
++extension;
|
||||
}
|
||||
if(has_length_extension()) {
|
||||
// As per the rule stated for segment(), this class provides ::DS for any instruction
|
||||
// that doesn't have a segment override.
|
||||
if(segment_override == Source::None) segment_override = Source::DS;
|
||||
extensions_[extension] = ImmediateT(
|
||||
(length << 6) | (int(repetition) << 4) | ((int(segment_override) & 7) << 1) | int(lock)
|
||||
);
|
||||
++extension;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Instruction) <= 8);
|
||||
static_assert(sizeof(Instruction<true>) <= 16);
|
||||
static_assert(sizeof(Instruction<false>) <= 10);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
27
InstructionSets/x86/Model.hpp
Normal file
27
InstructionSets/x86/Model.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Model.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 27/02/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Model_h
|
||||
#define Model_h
|
||||
|
||||
namespace InstructionSet {
|
||||
namespace x86 {
|
||||
|
||||
enum class Model {
|
||||
i8086,
|
||||
i80186,
|
||||
i80286,
|
||||
i80386,
|
||||
};
|
||||
|
||||
static constexpr bool is_32bit(Model model) { return model >= Model::i80386; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Model_h */
|
||||
261
Machines/Amiga/Amiga.cpp
Normal file
261
Machines/Amiga/Amiga.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
//
|
||||
// Amiga.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Amiga.hpp"
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../MachineTypes.hpp"
|
||||
|
||||
#include "../../Processors/68000Mk2/68000Mk2.hpp"
|
||||
|
||||
#include "../../Analyser/Static/Amiga/Target.hpp"
|
||||
|
||||
#include "../Utility/MemoryPacker.hpp"
|
||||
#include "../Utility/MemoryFuzzer.hpp"
|
||||
|
||||
//#define NDEBUG
|
||||
#define LOG_PREFIX "[Amiga] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include "Chipset.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "MemoryMap.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace {
|
||||
|
||||
// NTSC clock rate: 2*3.579545 = 7.15909Mhz.
|
||||
// PAL clock rate: 7.09379Mhz; 227 cycles/line.
|
||||
constexpr int PALClockRate = 7'093'790;
|
||||
//constexpr int NTSCClockRate = 7'159'090;
|
||||
|
||||
}
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class ConcreteMachine:
|
||||
public Activity::Source,
|
||||
public CPU::MC68000Mk2::BusHandler,
|
||||
public MachineTypes::AudioProducer,
|
||||
public MachineTypes::JoystickMachine,
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
public MachineTypes::MediaTarget,
|
||||
public MachineTypes::MouseMachine,
|
||||
public MachineTypes::ScanProducer,
|
||||
public MachineTypes::TimedMachine,
|
||||
public Machine {
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Amiga::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
mc68000_(*this),
|
||||
memory_(target.chip_ram, target.fast_ram),
|
||||
chipset_(memory_, PALClockRate)
|
||||
{
|
||||
// Temporary: use a hard-coded Kickstart selection.
|
||||
constexpr ROM::Name rom_name = ROM::Name::AmigaA500Kickstart13;
|
||||
ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
Memory::PackBigEndian16(roms.find(rom_name)->second, memory_.kickstart.data());
|
||||
|
||||
// For now, also hard-code assumption of PAL.
|
||||
// (Assumption is both here and in the video timing of the Chipset).
|
||||
set_clock_rate(PALClockRate);
|
||||
|
||||
// Insert supplied media.
|
||||
insert_media(target.media);
|
||||
}
|
||||
|
||||
// MARK: - MediaTarget.
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
return chipset_.insert(media.disks);
|
||||
}
|
||||
|
||||
// MARK: - MC68000::BusHandler.
|
||||
template <typename Microcycle> HalfCycles perform_bus_operation(const Microcycle &cycle, int) {
|
||||
|
||||
// Do a quick advance check for Chip RAM access; add a suitable delay if required.
|
||||
HalfCycles total_length;
|
||||
if(cycle.operation & Microcycle::NewAddress && *cycle.address < 0x20'0000) {
|
||||
total_length = chipset_.run_until_after_cpu_slot().duration;
|
||||
assert(total_length >= cycle.length);
|
||||
} else {
|
||||
total_length = cycle.length;
|
||||
chipset_.run_for(total_length);
|
||||
}
|
||||
mc68000_.set_interrupt_level(chipset_.get_interrupt_level());
|
||||
|
||||
// Check for assertion of reset.
|
||||
if(cycle.operation & Microcycle::Reset) {
|
||||
memory_.reset();
|
||||
LOG("Reset; PC is around " << PADHEX(8) << mc68000_.get_state().registers.program_counter);
|
||||
}
|
||||
|
||||
// Autovector interrupts.
|
||||
if(cycle.operation & Microcycle::InterruptAcknowledge) {
|
||||
mc68000_.set_is_peripheral_address(true);
|
||||
return total_length - cycle.length;
|
||||
}
|
||||
|
||||
// Do nothing if no address is exposed.
|
||||
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return total_length - cycle.length;
|
||||
|
||||
// Grab the target address to pick a memory source.
|
||||
const uint32_t address = cycle.host_endian_byte_address();
|
||||
|
||||
// Set VPA if this is [going to be] a CIA access.
|
||||
mc68000_.set_is_peripheral_address((address & 0xe0'0000) == 0xa0'0000);
|
||||
|
||||
if(!memory_.regions[address >> 18].read_write_mask) {
|
||||
if((cycle.operation & (Microcycle::SelectByte | Microcycle::SelectWord))) {
|
||||
// Check for various potential chip accesses.
|
||||
|
||||
// Per the manual:
|
||||
//
|
||||
// CIA A is: 101x xxxx xx01 rrrr xxxx xxx0 (i.e. loaded into high byte)
|
||||
// CIA B is: 101x xxxx xx10 rrrr xxxx xxx1 (i.e. loaded into low byte)
|
||||
//
|
||||
// but in order to map 0xbfexxx to CIA A and 0xbfdxxx to CIA B, I think
|
||||
// these might be listed the wrong way around.
|
||||
//
|
||||
// Additional assumption: the relevant CIA select lines are connected
|
||||
// directly to the chip enables.
|
||||
if((address & 0xe0'0000) == 0xa0'0000) {
|
||||
const int reg = address >> 8;
|
||||
const bool select_a = !(address & 0x1000);
|
||||
const bool select_b = !(address & 0x2000);
|
||||
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
uint16_t result = 0xffff;
|
||||
if(select_a) result &= 0xff00 | (chipset_.cia_a.read(reg) << 0);
|
||||
if(select_b) result &= 0x00ff | (chipset_.cia_b.read(reg) << 8);
|
||||
cycle.set_value16(result);
|
||||
} else {
|
||||
if(select_a) chipset_.cia_a.write(reg, cycle.value8_low());
|
||||
if(select_b) chipset_.cia_b.write(reg, cycle.value8_high());
|
||||
}
|
||||
|
||||
// LOG("CIA " << (((address >> 12) & 3)^3) << " " << (cycle.operation & Microcycle::Read ? "read " : "write ") << std::dec << (reg & 0xf) << " of " << PADHEX(4) << +cycle.value16());
|
||||
} else if(address >= 0xdf'f000 && address <= 0xdf'f1be) {
|
||||
chipset_.perform(cycle);
|
||||
} else if(address >= 0xe8'0000 && address < 0xe9'0000) {
|
||||
// This is the Autoconf space; right now the only
|
||||
// Autoconf device this emulator implements is fast RAM,
|
||||
// which if present is provided as part of the memory map.
|
||||
//
|
||||
// Relevant quote: "The Zorro II configuration space is the 64K memory block $00E8xxxx"
|
||||
memory_.perform(cycle);
|
||||
} else {
|
||||
// This'll do for open bus, for now.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.set_value16(0xffff);
|
||||
}
|
||||
|
||||
// Don't log for the region that is definitely just ROM this machine doesn't have.
|
||||
if(address < 0xf0'0000) {
|
||||
LOG("Unmapped " << (cycle.operation & Microcycle::Read ? "read from " : "write to ") << PADHEX(6) << ((*cycle.address)&0xffffff) << " of " << cycle.value16());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// A regular memory access.
|
||||
cycle.apply(
|
||||
&memory_.regions[address >> 18].contents[address],
|
||||
memory_.regions[address >> 18].read_write_mask
|
||||
);
|
||||
}
|
||||
|
||||
return total_length - cycle.length;
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000Mk2::Processor<ConcreteMachine, true, true> mc68000_;
|
||||
|
||||
// MARK: - Memory map.
|
||||
|
||||
MemoryMap memory_;
|
||||
|
||||
// MARK: - Chipset.
|
||||
|
||||
Chipset chipset_;
|
||||
|
||||
// MARK: - Activity Source
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
chipset_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::AudioProducer.
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return chipset_.get_speaker();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::ScanProducer.
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) final {
|
||||
chipset_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const final {
|
||||
return chipset_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::TimedMachine.
|
||||
|
||||
void run_for(const Cycles cycles) final {
|
||||
mc68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
void flush_output(int) final {
|
||||
chipset_.flush();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::MouseMachine.
|
||||
|
||||
Inputs::Mouse &get_mouse() final {
|
||||
return chipset_.get_mouse();;
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::JoystickMachine.
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() final {
|
||||
return chipset_.get_joysticks();
|
||||
}
|
||||
|
||||
// MARK: - Keyboard.
|
||||
|
||||
Amiga::KeyboardMapper keyboard_mapper_;
|
||||
KeyboardMapper *get_keyboard_mapper() {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) {
|
||||
chipset_.get_keyboard().set_key_state(key, is_pressed);
|
||||
}
|
||||
|
||||
void clear_all_keys() {
|
||||
chipset_.get_keyboard().clear_all_keys();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
Machine *Machine::Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Amiga::Target;
|
||||
const Target *const amiga_target = dynamic_cast<const Target *>(target);
|
||||
return new Amiga::ConcreteMachine(*amiga_target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
27
Machines/Amiga/Amiga.hpp
Normal file
27
Machines/Amiga/Amiga.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Amiga.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Amiga_hpp
|
||||
#define Amiga_hpp
|
||||
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
/// Creates and returns an Amiga.
|
||||
static Machine *Amiga(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Amiga_hpp */
|
||||
678
Machines/Amiga/Audio.cpp
Normal file
678
Machines/Amiga/Audio.cpp
Normal file
@@ -0,0 +1,678 @@
|
||||
//
|
||||
// Audio.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Audio.hpp"
|
||||
|
||||
#include "Flags.hpp"
|
||||
|
||||
#define LOG_PREFIX "[Audio] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <tuple>
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
Audio::Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate) :
|
||||
DMADevice<4>(chipset, ram, word_size) {
|
||||
|
||||
// Mark all buffers as available.
|
||||
for(auto &flag: buffer_available_) {
|
||||
flag.store(true, std::memory_order::memory_order_relaxed);
|
||||
}
|
||||
|
||||
speaker_.set_input_rate(output_rate);
|
||||
speaker_.set_high_frequency_cutoff(7000.0f);
|
||||
}
|
||||
|
||||
// MARK: - Exposed setters.
|
||||
|
||||
void Audio::set_length(int channel, uint16_t length) {
|
||||
assert(channel >= 0 && channel < 4);
|
||||
channels_[channel].length = length;
|
||||
}
|
||||
|
||||
void Audio::set_period(int channel, uint16_t period) {
|
||||
assert(channel >= 0 && channel < 4);
|
||||
channels_[channel].period = period;
|
||||
}
|
||||
|
||||
void Audio::set_volume(int channel, uint16_t volume) {
|
||||
assert(channel >= 0 && channel < 4);
|
||||
channels_[channel].volume = (volume & 0x40) ? 64 : (volume & 0x3f);
|
||||
}
|
||||
|
||||
template <bool is_external> void Audio::set_data(int channel, uint16_t data) {
|
||||
assert(channel >= 0 && channel < 4);
|
||||
channels_[channel].wants_data = false;
|
||||
channels_[channel].data = data;
|
||||
|
||||
// TODO: "the [PWM] counter is reset when ... AUDxDAT is written", but
|
||||
// does that just mean written by the CPU, or does it include DMA?
|
||||
// My guess is the former. But TODO.
|
||||
if constexpr (is_external) {
|
||||
channels_[channel].reset_output_phase();
|
||||
}
|
||||
}
|
||||
|
||||
template void Audio::set_data<false>(int, uint16_t);
|
||||
template void Audio::set_data<true>(int, uint16_t);
|
||||
|
||||
void Audio::set_channel_enables(uint16_t enables) {
|
||||
channels_[0].dma_enabled = enables & 1;
|
||||
channels_[1].dma_enabled = enables & 2;
|
||||
channels_[2].dma_enabled = enables & 4;
|
||||
channels_[3].dma_enabled = enables & 8;
|
||||
}
|
||||
|
||||
void Audio::set_modulation_flags(uint16_t flags) {
|
||||
channels_[3].attach_period = flags & 0x80;
|
||||
channels_[2].attach_period = flags & 0x40;
|
||||
channels_[1].attach_period = flags & 0x20;
|
||||
channels_[0].attach_period = flags & 0x10;
|
||||
|
||||
channels_[3].attach_volume = flags & 0x08;
|
||||
channels_[2].attach_volume = flags & 0x04;
|
||||
channels_[1].attach_volume = flags & 0x02;
|
||||
channels_[0].attach_volume = flags & 0x01;
|
||||
}
|
||||
|
||||
void Audio::set_interrupt_requests(uint16_t requests) {
|
||||
channels_[0].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel0);
|
||||
channels_[1].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel1);
|
||||
channels_[2].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel2);
|
||||
channels_[3].interrupt_pending = requests & uint16_t(InterruptFlag::AudioChannel3);
|
||||
}
|
||||
|
||||
// MARK: - DMA and mixing.
|
||||
|
||||
bool Audio::advance_dma(int channel) {
|
||||
if(!channels_[channel].wants_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(channels_[channel].should_reload_address) {
|
||||
channels_[channel].data_address = pointer_[size_t(channel)];
|
||||
channels_[channel].should_reload_address = false;
|
||||
}
|
||||
|
||||
set_data<false>(channel, ram_[channels_[channel].data_address & ram_mask_]);
|
||||
|
||||
if(channels_[channel].state != Channel::State::WaitingForDummyDMA) {
|
||||
++channels_[channel].data_address;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Audio::output() {
|
||||
constexpr InterruptFlag::FlagT interrupts[] = {
|
||||
InterruptFlag::AudioChannel0,
|
||||
InterruptFlag::AudioChannel1,
|
||||
InterruptFlag::AudioChannel2,
|
||||
InterruptFlag::AudioChannel3,
|
||||
};
|
||||
Channel *const modulands[] = {
|
||||
&channels_[1],
|
||||
&channels_[2],
|
||||
&channels_[3],
|
||||
nullptr,
|
||||
};
|
||||
|
||||
for(int c = 0; c < 4; c++) {
|
||||
if(channels_[c].output(modulands[c])) {
|
||||
posit_interrupt(interrupts[c]);
|
||||
}
|
||||
}
|
||||
|
||||
// Spin until the next buffer is available if just entering it for the first time.
|
||||
// Contention here should be essentially non-existent.
|
||||
if(!sample_pointer_) {
|
||||
while(!buffer_available_[buffer_pointer_].load(std::memory_order::memory_order_relaxed));
|
||||
}
|
||||
|
||||
// Left.
|
||||
static_assert(std::tuple_size<AudioBuffer>::value % 2 == 0);
|
||||
buffer_[buffer_pointer_][sample_pointer_] = int16_t(
|
||||
(
|
||||
channels_[1].output_level * channels_[1].output_enabled +
|
||||
channels_[2].output_level * channels_[2].output_enabled
|
||||
) << 7
|
||||
);
|
||||
|
||||
// Right.
|
||||
buffer_[buffer_pointer_][sample_pointer_ + 1] = int16_t(
|
||||
(
|
||||
channels_[0].output_level * channels_[0].output_enabled +
|
||||
channels_[3].output_level * channels_[3].output_enabled
|
||||
) << 7
|
||||
);
|
||||
sample_pointer_ += 2;
|
||||
|
||||
if(sample_pointer_ == buffer_[buffer_pointer_].size()) {
|
||||
const auto &buffer = buffer_[buffer_pointer_];
|
||||
auto &flag = buffer_available_[buffer_pointer_];
|
||||
|
||||
flag.store(false, std::memory_order::memory_order_release);
|
||||
queue_.enqueue([this, &buffer, &flag] {
|
||||
speaker_.push(buffer.data(), buffer.size() >> 1);
|
||||
flag.store(true, std::memory_order::memory_order_relaxed);
|
||||
});
|
||||
|
||||
buffer_pointer_ = (buffer_pointer_ + 1) % BufferCount;
|
||||
sample_pointer_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Per-channel logic.
|
||||
|
||||
/*
|
||||
Big spiel on the state machine:
|
||||
|
||||
Commodore's Hardware Rerefence Manual provides the audio subsystem's state
|
||||
machine, so I've just tried to reimplement it verbatim. It's depicted
|
||||
diagrammatically in the original source as a finite state automata, the
|
||||
below is my attempt to translate that into text.
|
||||
|
||||
|
||||
000 State::Disabled:
|
||||
|
||||
-> State::Disabled (000)
|
||||
if: N/A
|
||||
action: percntrld
|
||||
|
||||
-> State::PlayingHigh (010)
|
||||
if: AUDDAT, and not AUDxON, and not AUDxIP
|
||||
action: percntrld, AUDxIR, volcntrld, pbudld1
|
||||
|
||||
-> State::WaitingForDummyDMA (001)
|
||||
if: AUDxON
|
||||
action: percntrld, AUDxDR, lencntrld, dmasen*
|
||||
|
||||
|
||||
* NOTE: except for this case, dmasen is true only when
|
||||
LENFIN = 1. Also, AUDxDSR = (AUDxDR and dmasen).
|
||||
|
||||
|
||||
|
||||
001 State::WaitingForDummyDMA:
|
||||
|
||||
-> State::WaitingForDummyDMA (001)
|
||||
if: N/A
|
||||
action: None
|
||||
|
||||
-> State::Disabled (000)
|
||||
if: not AUDxON
|
||||
action: None
|
||||
|
||||
-> State::WaitingForDMA (101)
|
||||
if: AUDxON, and AUDxDAT
|
||||
action:
|
||||
1. AUDxIR
|
||||
2. if not lenfin, then lencount
|
||||
|
||||
|
||||
|
||||
101 State::WaitingForDMA:
|
||||
|
||||
-> State::WaitingForDMA (101)
|
||||
if: N/A
|
||||
action: None
|
||||
|
||||
-> State:Disabled (000)
|
||||
if: not AUDxON
|
||||
action: None
|
||||
|
||||
-> State::PlayingHigh (010)
|
||||
if: AUDxON, and AUDxDAT
|
||||
action:
|
||||
1. volcntrld, percntrld, pbufld1
|
||||
2. if napnav, then AUDxDR
|
||||
|
||||
|
||||
|
||||
010 State::PlayingHigh
|
||||
|
||||
-> State::PlayingHigh (010)
|
||||
if: N/A
|
||||
action: percount, and penhi
|
||||
|
||||
-> State::PlayingLow (011)
|
||||
if: perfin
|
||||
action:
|
||||
1. if AUDxAP, then pbufld2
|
||||
2. if AUDxAP and AUDxON, then AUDxDR
|
||||
3. percntrld
|
||||
4. if intreq2 and AUDxON and AUDxAP, then AUDxIR
|
||||
5. if AUDxAP and AUDxON, then AUDxIR
|
||||
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
|
||||
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
||||
8. if lenfin and AUDxON and AUDxDAT, then intreq2
|
||||
|
||||
[note that 6–8 are shared with the Low -> High transition]
|
||||
|
||||
|
||||
|
||||
011 State::PlayingLow
|
||||
|
||||
-> State::PlayingLow (011)
|
||||
if: N/A
|
||||
action: percount, and not penhi
|
||||
|
||||
-> State::Disabled (000)
|
||||
if: perfin and not (AUDxON or not AUDxIP)
|
||||
action: None
|
||||
|
||||
-> State::PlayingHigh (010)
|
||||
if: perfin and (AUDxON or not AUDxIP)
|
||||
action:
|
||||
1. pbufld1
|
||||
2. percntrld
|
||||
3. if napnav and AUDxON, then AUDxDR
|
||||
4. if napnav and AUDxON and intreq2, AUDxIR
|
||||
5. if napnav and not AUDxON, AUDxIR
|
||||
6. if lenfin and AUDxON and AUDxDAT, then lencntrld
|
||||
7. if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
||||
8. if lenfin and AUDxON and AUDxDAT, then intreq2
|
||||
|
||||
[note that 6-8 are shared with the High -> Low transition]
|
||||
|
||||
|
||||
|
||||
Definitions:
|
||||
|
||||
AUDxON DMA on "x" indicates channel number (signal from DMACON).
|
||||
|
||||
AUDxIP Audio interrupt pending (input to channel from interrupt circuitry).
|
||||
|
||||
AUDxIR Audio interrupt request (output from channel to interrupt circuitry).
|
||||
|
||||
intreq1 Interrupt request that combines with intreq2 to form AUDxIR.
|
||||
|
||||
intreq2 Prepare for interrupt request. Request comes out after the
|
||||
next 011->010 transition in normal operation.
|
||||
|
||||
AUDxDAT Audio data load signal. Loads 16 bits of data to audio channel.
|
||||
|
||||
AUDxDR Audio DMA request to Agnus for one word of data.
|
||||
|
||||
AUDxDSR Audio DMA request to Agnus to reset pointer to start of block.
|
||||
|
||||
dmasen Restart request enable.
|
||||
|
||||
percntrld Reload period counter from back-up latch typically written
|
||||
by processor with AUDxPER (can also be written by attach mode).
|
||||
|
||||
percount Count period counter down one latch.
|
||||
|
||||
perfin Period counter finished (value = 1).
|
||||
|
||||
lencntrld Reload length counter from back-up latch.
|
||||
|
||||
lencount Count length counter down one notch.
|
||||
|
||||
lenfin Length counter finished (value = 1).
|
||||
|
||||
volcntrld Reload volume counter from back-up latch.
|
||||
|
||||
pbufld1 Load output buffer from holding latch written to by AUDxDAT.
|
||||
|
||||
pbufld2 Like pbufld1, but only during 010->011 with attach period.
|
||||
|
||||
AUDxAV Attach volume. Send data to volume latch of next channel
|
||||
instead of to D->A converter.
|
||||
|
||||
AUDxAP Attach period. Send data to period latch of next channel
|
||||
instead of to the D->A converter.
|
||||
|
||||
penhi Enable the high 8 bits of data to go to the D->A converter.
|
||||
|
||||
napnav /AUDxAV * /AUDxAP + AUDxAV -- no attach stuff or else attach
|
||||
volume. Condition for normal DMA and interrupt requests.
|
||||
*/
|
||||
|
||||
//
|
||||
// Non-action fallback transition and setter, plus specialised begin_state declarations.
|
||||
//
|
||||
|
||||
template <Audio::Channel::State end> void Audio::Channel::begin_state(Channel *) {
|
||||
state = end;
|
||||
}
|
||||
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *);
|
||||
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *);
|
||||
|
||||
template <
|
||||
Audio::Channel::State begin,
|
||||
Audio::Channel::State end> bool Audio::Channel::transit(Channel *moduland) {
|
||||
begin_state<end>(moduland);
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::Disabled
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::Disabled,
|
||||
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
||||
begin_state<State::PlayingHigh>(moduland);
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
// [AUDxIR]: see return result.
|
||||
|
||||
// volcntrld
|
||||
volume_latch = volume;
|
||||
reset_output_phase();
|
||||
|
||||
// pbufld1
|
||||
data_latch = data;
|
||||
wants_data = true;
|
||||
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
||||
|
||||
// AUDxIR.
|
||||
return true;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::Disabled,
|
||||
Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
|
||||
begin_state<State::WaitingForDummyDMA>(moduland);
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
|
||||
// lencntrld
|
||||
length_counter = length;
|
||||
|
||||
// dmasen / AUDxDSR
|
||||
should_reload_address = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::Disabled>(Channel *moduland) {
|
||||
// if AUDDAT, and not AUDxON, and not AUDxIP.
|
||||
if(!wants_data && !dma_enabled && !interrupt_pending) {
|
||||
return transit<State::Disabled, State::PlayingHigh>(moduland);
|
||||
}
|
||||
|
||||
// if AUDxON.
|
||||
if(dma_enabled) {
|
||||
return transit<State::Disabled, State::WaitingForDummyDMA>(moduland);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::WaitingForDummyDMA
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::WaitingForDummyDMA,
|
||||
Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
|
||||
begin_state<State::WaitingForDMA>(moduland);
|
||||
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
|
||||
// if not lenfin, then lencount
|
||||
if(length != 1) {
|
||||
-- length_counter;
|
||||
}
|
||||
|
||||
// AUDxIR
|
||||
return true;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDummyDMA>(Channel *moduland) {
|
||||
// if not AUDxON
|
||||
if(!dma_enabled) {
|
||||
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
|
||||
}
|
||||
|
||||
// if AUDxON and AUDxDAT
|
||||
if(dma_enabled && !wants_data) {
|
||||
return transit<State::WaitingForDummyDMA, State::WaitingForDMA>(moduland);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::WaitingForDMA
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::WaitingForDMA,
|
||||
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
||||
begin_state<State::PlayingHigh>(moduland);
|
||||
|
||||
// volcntrld
|
||||
volume_latch = volume;
|
||||
reset_output_phase();
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
// pbufld1
|
||||
data_latch = data;
|
||||
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
||||
|
||||
// if napnav
|
||||
if(attach_volume || !(attach_volume || attach_period)) {
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::WaitingForDMA>(Channel *moduland) {
|
||||
// if: not AUDxON
|
||||
if(!dma_enabled) {
|
||||
return transit<State::WaitingForDummyDMA, State::Disabled>(moduland);
|
||||
}
|
||||
|
||||
// if: AUDxON, and AUDxDAT
|
||||
if(dma_enabled && !wants_data) {
|
||||
return transit<State::WaitingForDummyDMA, State::PlayingHigh>(moduland);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::PlayingHigh
|
||||
//
|
||||
|
||||
void Audio::Channel::decrement_length() {
|
||||
// if lenfin and AUDxON and AUDxDAT, then lencntrld
|
||||
// if (not lenfin) and AUDxON and AUDxDAT, then lencount
|
||||
// if lenfin and AUDxON and AUDxDAT, then intreq2
|
||||
if(dma_enabled && !wants_data) {
|
||||
-- length_counter;
|
||||
|
||||
if(!length_counter) {
|
||||
length_counter = length;
|
||||
will_request_interrupt = true;
|
||||
should_reload_address = true; // This feels logical to me; it's a bit
|
||||
// of a stab in the dark though.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::PlayingHigh,
|
||||
Audio::Channel::State::PlayingLow>(Channel *moduland) {
|
||||
begin_state<State::PlayingLow>(moduland);
|
||||
|
||||
bool wants_interrupt = false;
|
||||
|
||||
// if AUDxAP
|
||||
if(attach_period) {
|
||||
// pbufld2
|
||||
data_latch = data;
|
||||
if(moduland) moduland->period = data_latch;
|
||||
|
||||
// [if AUDxAP] and AUDxON
|
||||
if(dma_enabled) {
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
|
||||
// [if AUDxAP and AUDxON] and intreq2
|
||||
if(will_request_interrupt) {
|
||||
will_request_interrupt = false;
|
||||
|
||||
// AUDxIR
|
||||
wants_interrupt = true;
|
||||
}
|
||||
} else {
|
||||
// i.e. if AUDxAP and AUDxON, then AUDxIR
|
||||
wants_interrupt = true;
|
||||
}
|
||||
}
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
decrement_length();
|
||||
|
||||
return wants_interrupt;
|
||||
}
|
||||
|
||||
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingHigh>(Channel *) {
|
||||
state = Audio::Channel::State::PlayingHigh;
|
||||
|
||||
// penhi.
|
||||
output_level = int8_t(data_latch >> 8);
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
||||
// This is a reasonable guess as to the exit condition for this node;
|
||||
// Commodore doesn't document.
|
||||
if(period_counter == 1) {
|
||||
return transit<State::PlayingHigh, State::PlayingLow>(moduland);
|
||||
}
|
||||
|
||||
// percount.
|
||||
-- period_counter;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Audio::Channel::State::PlayingLow
|
||||
//
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::PlayingLow,
|
||||
Audio::Channel::State::Disabled>(Channel *moduland) {
|
||||
begin_state<State::Disabled>(moduland);
|
||||
|
||||
// Clear the slightly nebulous 'if intreq2 occurred' state.
|
||||
will_request_interrupt = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::transit<
|
||||
Audio::Channel::State::PlayingLow,
|
||||
Audio::Channel::State::PlayingHigh>(Channel *moduland) {
|
||||
begin_state<State::PlayingHigh>(moduland);
|
||||
|
||||
bool wants_interrupt = false;
|
||||
|
||||
// volcntrld
|
||||
volume_latch = volume;
|
||||
reset_output_phase(); // Is this correct?
|
||||
|
||||
// percntrld
|
||||
period_counter = period;
|
||||
|
||||
// pbufld1
|
||||
data_latch = data;
|
||||
if(moduland && attach_volume) moduland->volume = uint8_t(data_latch);
|
||||
|
||||
// if napnav
|
||||
if(attach_volume || !(attach_volume || attach_period)) {
|
||||
// [if napnav] and AUDxON
|
||||
if(dma_enabled) {
|
||||
// AUDxDR
|
||||
wants_data = true;
|
||||
|
||||
// [if napnav and AUDxON] and intreq2
|
||||
if(will_request_interrupt) {
|
||||
will_request_interrupt = false;
|
||||
wants_interrupt = true;
|
||||
}
|
||||
} else {
|
||||
// AUDxIR
|
||||
wants_interrupt = true;
|
||||
}
|
||||
}
|
||||
|
||||
decrement_length();
|
||||
|
||||
return wants_interrupt;
|
||||
}
|
||||
|
||||
template <> void Audio::Channel::begin_state<Audio::Channel::State::PlayingLow>(Channel *) {
|
||||
state = Audio::Channel::State::PlayingLow;
|
||||
|
||||
// Output low byte.
|
||||
output_level = int8_t(data_latch & 0xff);
|
||||
}
|
||||
|
||||
template <> bool Audio::Channel::output<Audio::Channel::State::PlayingLow>(Channel *moduland) {
|
||||
-- period_counter;
|
||||
|
||||
if(!period_counter) {
|
||||
const bool dma_or_no_interrupt = dma_enabled || !interrupt_pending;
|
||||
if(dma_or_no_interrupt) {
|
||||
return transit<State::PlayingLow, State::PlayingHigh>(moduland);
|
||||
} else {
|
||||
return transit<State::PlayingLow, State::Disabled>(moduland);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Dispatcher
|
||||
//
|
||||
|
||||
bool Audio::Channel::output(Channel *moduland) {
|
||||
// Update pulse-width modulation.
|
||||
output_phase = output_phase + 1;
|
||||
if(output_phase == 64) {
|
||||
reset_output_phase();
|
||||
} else {
|
||||
output_enabled &= output_phase != volume_latch;
|
||||
}
|
||||
|
||||
switch(state) {
|
||||
case State::Disabled: return output<State::Disabled>(moduland);
|
||||
case State::WaitingForDummyDMA: return output<State::WaitingForDummyDMA>(moduland);
|
||||
case State::WaitingForDMA: return output<State::WaitingForDMA>(moduland);
|
||||
case State::PlayingHigh: return output<State::PlayingHigh>(moduland);
|
||||
case State::PlayingLow: return output<State::PlayingLow>(moduland);
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
163
Machines/Amiga/Audio.hpp
Normal file
163
Machines/Amiga/Audio.hpp
Normal file
@@ -0,0 +1,163 @@
|
||||
//
|
||||
// Audio.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 09/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Audio_hpp
|
||||
#define Audio_hpp
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include "DMADevice.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Audio: public DMADevice<4> {
|
||||
public:
|
||||
Audio(Chipset &chipset, uint16_t *ram, size_t word_size, float output_rate);
|
||||
|
||||
/// Idiomatic call-in for DMA scheduling; indicates that this class may
|
||||
/// perform a DMA access for the stated channel now.
|
||||
bool advance_dma(int channel);
|
||||
|
||||
/// Advances output by one DMA window, which is implicitly two cycles
|
||||
/// at the output rate that was specified to the constructor.
|
||||
void output();
|
||||
|
||||
/// Sets the total number of words to fetch for the given channel.
|
||||
void set_length(int channel, uint16_t);
|
||||
|
||||
/// Sets the number of DMA windows between each 8-bit output,
|
||||
/// in the same time base as @c ticks_per_line.
|
||||
void set_period(int channel, uint16_t);
|
||||
|
||||
/// Sets the output volume for the given channel; if bit 6 is set
|
||||
/// then output is maximal; otherwise bits 0–5 select
|
||||
/// a volume of [0–63]/64, on a logarithmic scale.
|
||||
void set_volume(int channel, uint16_t);
|
||||
|
||||
/// Sets the next two samples of audio to output.
|
||||
template <bool is_external = true> void set_data(int channel, uint16_t);
|
||||
|
||||
/// Provides a copy of the DMA enable flags, for the purpose of
|
||||
/// determining which channels are enabled for DMA.
|
||||
void set_channel_enables(uint16_t);
|
||||
|
||||
/// Sets which channels, if any, modulate period or volume of
|
||||
/// their neighbours.
|
||||
void set_modulation_flags(uint16_t);
|
||||
|
||||
/// Sets which interrupt requests are currently active.
|
||||
void set_interrupt_requests(uint16_t);
|
||||
|
||||
/// Obtains the output source.
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Channel {
|
||||
// The data latch plus a count of unused samples
|
||||
// in the latch, which will always be 0, 1 or 2.
|
||||
uint16_t data = 0x0000;
|
||||
bool wants_data = false;
|
||||
uint16_t data_latch = 0x0000;
|
||||
|
||||
// The DMA address; unlike most of the Amiga Chipset,
|
||||
// the user posts a value to feed a pointer, rather
|
||||
// than having access to the pointer itself.
|
||||
bool should_reload_address = false;
|
||||
uint32_t data_address = 0x0000'0000;
|
||||
|
||||
// Number of words remaining in DMA data.
|
||||
uint16_t length = 0;
|
||||
uint16_t length_counter = 0;
|
||||
|
||||
// Number of ticks between each sample, plus the
|
||||
// current counter, which counts downward.
|
||||
uint16_t period = 0;
|
||||
uint16_t period_counter = 0;
|
||||
|
||||
// Modulation / attach flags.
|
||||
bool attach_period = false;
|
||||
bool attach_volume = false;
|
||||
|
||||
// Output volume, [0, 64].
|
||||
uint8_t volume = 0;
|
||||
uint8_t volume_latch = 0;
|
||||
|
||||
// Indicates whether DMA is enabled for this channel.
|
||||
bool dma_enabled = false;
|
||||
|
||||
// Records whether this audio interrupt is pending.
|
||||
bool interrupt_pending = false;
|
||||
bool will_request_interrupt = false;
|
||||
|
||||
// Replicates the Hardware Reference Manual state machine;
|
||||
// comments indicate which of the documented states each
|
||||
// label refers to.
|
||||
enum class State {
|
||||
Disabled, // 000
|
||||
WaitingForDummyDMA, // 001
|
||||
WaitingForDMA, // 101
|
||||
PlayingHigh, // 010
|
||||
PlayingLow, // 011
|
||||
} state = State::Disabled;
|
||||
|
||||
/// Dispatches to the appropriate templatised output for the current state.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
bool output(Channel *moduland);
|
||||
|
||||
/// Applies dynamic logic for @c state, mostly testing for potential state transitions.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
template <State state> bool output(Channel *moduland);
|
||||
|
||||
/// Transitions from @c begin to @c end, calling the appropriate @c begin_state
|
||||
/// and taking any steps specific to that particular transition.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
/// @returns @c true if an interrupt should be posted; @c false otherwise.
|
||||
template <State begin, State end> bool transit(Channel *moduland);
|
||||
|
||||
/// Begins @c state, performing all fixed logic that would otherwise have to be
|
||||
/// repeated endlessly in the relevant @c output.
|
||||
/// @param moduland The channel to modulate, if modulation is enabled.
|
||||
template <State state> void begin_state(Channel *moduland);
|
||||
|
||||
/// Provides the common length-decrementing logic used when transitioning
|
||||
/// between PlayingHigh and PlayingLow in either direction.
|
||||
void decrement_length();
|
||||
|
||||
// Output state.
|
||||
int8_t output_level = 0;
|
||||
uint8_t output_phase = 0;
|
||||
bool output_enabled = false;
|
||||
|
||||
void reset_output_phase() {
|
||||
output_phase = 0;
|
||||
output_enabled = (volume_latch > 0) && !attach_period && !attach_volume;
|
||||
}
|
||||
} channels_[4];
|
||||
|
||||
// Transient output state, and its destination.
|
||||
Outputs::Speaker::PushLowpass<true> speaker_;
|
||||
Concurrency::AsyncTaskQueue<true> queue_;
|
||||
|
||||
using AudioBuffer = std::array<int16_t, 4096>;
|
||||
static constexpr int BufferCount = 3;
|
||||
AudioBuffer buffer_[BufferCount];
|
||||
std::atomic<bool> buffer_available_[BufferCount];
|
||||
size_t buffer_pointer_ = 0, sample_pointer_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Audio_hpp */
|
||||
132
Machines/Amiga/Bitplanes.cpp
Normal file
132
Machines/Amiga/Bitplanes.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
//
|
||||
// Bitplanes.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Bitplanes.hpp"
|
||||
#include "Chipset.hpp"
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
namespace {
|
||||
|
||||
/// Expands @c source so that b7 is the least-significant bit of the most-significant byte of the result,
|
||||
/// b6 is the least-significant bit of the next most significant byte, etc. b0 stays in place.
|
||||
constexpr uint64_t expand_bitplane_byte(uint8_t source) {
|
||||
uint64_t result = source; // 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 abcd efgh
|
||||
result = (result | (result << 28)) & 0x0000'000f'0000'000f; // 0000 0000 0000 0000 0000 0000 0000 abcd 0000 0000 0000 0000 0000 0000 0000 efgh
|
||||
result = (result | (result << 14)) & 0x0003'0003'0003'0003; // 0000 0000 0000 00ab 0000 0000 0000 00cd 0000 0000 0000 00ef 0000 0000 0000 00gh
|
||||
result = (result | (result << 7)) & 0x0101'0101'0101'0101; // 0000 000a 0000 000b 0000 000c 0000 000d 0000 000e 0000 000f 0000 000g 0000 000h
|
||||
return result;
|
||||
}
|
||||
|
||||
// A very small selection of test cases.
|
||||
static_assert(expand_bitplane_byte(0xff) == 0x01'01'01'01'01'01'01'01);
|
||||
static_assert(expand_bitplane_byte(0x55) == 0x00'01'00'01'00'01'00'01);
|
||||
static_assert(expand_bitplane_byte(0xaa) == 0x01'00'01'00'01'00'01'00);
|
||||
static_assert(expand_bitplane_byte(0x00) == 0x00'00'00'00'00'00'00'00);
|
||||
|
||||
}
|
||||
|
||||
// MARK: - BitplaneShifter.
|
||||
|
||||
void BitplaneShifter::set(const BitplaneData &previous, const BitplaneData &next, int odd_delay, int even_delay) {
|
||||
const uint16_t planes[6] = {
|
||||
uint16_t(((previous[0] << 16) | next[0]) >> even_delay),
|
||||
uint16_t(((previous[1] << 16) | next[1]) >> odd_delay),
|
||||
uint16_t(((previous[2] << 16) | next[2]) >> even_delay),
|
||||
uint16_t(((previous[3] << 16) | next[3]) >> odd_delay),
|
||||
uint16_t(((previous[4] << 16) | next[4]) >> even_delay),
|
||||
uint16_t(((previous[5] << 16) | next[5]) >> odd_delay),
|
||||
};
|
||||
|
||||
// Swizzle bits into the form:
|
||||
//
|
||||
// [b5 b3 b1 b4 b2 b0]
|
||||
//
|
||||
// ... and assume a suitably adjusted palette is in use elsewhere.
|
||||
// This makes dual playfields very easy to separate.
|
||||
data_[0] =
|
||||
(expand_bitplane_byte(uint8_t(planes[0])) << 0) |
|
||||
(expand_bitplane_byte(uint8_t(planes[2])) << 1) |
|
||||
(expand_bitplane_byte(uint8_t(planes[4])) << 2) |
|
||||
(expand_bitplane_byte(uint8_t(planes[1])) << 3) |
|
||||
(expand_bitplane_byte(uint8_t(planes[3])) << 4) |
|
||||
(expand_bitplane_byte(uint8_t(planes[5])) << 5);
|
||||
|
||||
data_[1] =
|
||||
(expand_bitplane_byte(uint8_t(planes[0] >> 8)) << 0) |
|
||||
(expand_bitplane_byte(uint8_t(planes[2] >> 8)) << 1) |
|
||||
(expand_bitplane_byte(uint8_t(planes[4] >> 8)) << 2) |
|
||||
(expand_bitplane_byte(uint8_t(planes[1] >> 8)) << 3) |
|
||||
(expand_bitplane_byte(uint8_t(planes[3] >> 8)) << 4) |
|
||||
(expand_bitplane_byte(uint8_t(planes[5] >> 8)) << 5);
|
||||
}
|
||||
|
||||
// MARK: - Bitplanes.
|
||||
|
||||
bool Bitplanes::advance_dma(int cycle) {
|
||||
#define BIND_CYCLE(offset, plane) \
|
||||
case offset: \
|
||||
if(plane_count_ > plane) { \
|
||||
next[plane] = ram_[pointer_[plane] & ram_mask_]; \
|
||||
++pointer_[plane]; \
|
||||
if constexpr (!plane) { \
|
||||
chipset_.post_bitplanes(next); \
|
||||
} \
|
||||
return true; \
|
||||
} \
|
||||
return false;
|
||||
|
||||
if(is_high_res_) {
|
||||
switch(cycle&3) {
|
||||
default: return false;
|
||||
BIND_CYCLE(0, 3);
|
||||
BIND_CYCLE(1, 1);
|
||||
BIND_CYCLE(2, 2);
|
||||
BIND_CYCLE(3, 0);
|
||||
}
|
||||
} else {
|
||||
switch(cycle&7) {
|
||||
default: return false;
|
||||
/* Omitted: 0. */
|
||||
BIND_CYCLE(1, 3);
|
||||
BIND_CYCLE(2, 5);
|
||||
BIND_CYCLE(3, 1);
|
||||
/* Omitted: 4. */
|
||||
BIND_CYCLE(5, 2);
|
||||
BIND_CYCLE(6, 4);
|
||||
BIND_CYCLE(7, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
#undef BIND_CYCLE
|
||||
}
|
||||
|
||||
void Bitplanes::do_end_of_line() {
|
||||
// Apply modulos here. Posssibly correct?
|
||||
pointer_[0] += modulos_[1];
|
||||
pointer_[2] += modulos_[1];
|
||||
pointer_[4] += modulos_[1];
|
||||
|
||||
pointer_[1] += modulos_[0];
|
||||
pointer_[3] += modulos_[0];
|
||||
pointer_[5] += modulos_[0];
|
||||
}
|
||||
|
||||
void Bitplanes::set_control(uint16_t control) {
|
||||
is_high_res_ = control & 0x8000;
|
||||
plane_count_ = (control >> 12) & 7;
|
||||
|
||||
// TODO: who really has responsibility for clearing the other
|
||||
// bit plane fields?
|
||||
std::fill(next.begin() + plane_count_, next.end(), 0);
|
||||
if(plane_count_ == 7) {
|
||||
plane_count_ = 4;
|
||||
}
|
||||
}
|
||||
102
Machines/Amiga/Bitplanes.hpp
Normal file
102
Machines/Amiga/Bitplanes.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// Bitplanes.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Bitplanes_hpp
|
||||
#define Bitplanes_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "DMADevice.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
struct BitplaneData: public std::array<uint16_t, 6> {
|
||||
BitplaneData &operator <<= (int c) {
|
||||
(*this)[0] <<= c;
|
||||
(*this)[1] <<= c;
|
||||
(*this)[2] <<= c;
|
||||
(*this)[3] <<= c;
|
||||
(*this)[4] <<= c;
|
||||
(*this)[5] <<= c;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
std::fill(begin(), end(), 0);
|
||||
}
|
||||
};
|
||||
|
||||
class Bitplanes: public DMADevice<6, 2> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
bool advance_dma(int cycle);
|
||||
void do_end_of_line();
|
||||
void set_control(uint16_t);
|
||||
|
||||
private:
|
||||
bool is_high_res_ = false;
|
||||
int plane_count_ = 0;
|
||||
|
||||
BitplaneData next;
|
||||
};
|
||||
|
||||
template <typename SourceT> constexpr SourceT bitplane_swizzle(SourceT value) {
|
||||
return
|
||||
(value&0x21) |
|
||||
((value&0x02) << 2) |
|
||||
((value&0x04) >> 1) |
|
||||
((value&0x08) << 1) |
|
||||
((value&0x10) >> 2);
|
||||
}
|
||||
|
||||
class BitplaneShifter {
|
||||
public:
|
||||
/// Installs a new set of output pixels.
|
||||
void set(
|
||||
const BitplaneData &previous,
|
||||
const BitplaneData &next,
|
||||
int odd_delay,
|
||||
int even_delay);
|
||||
|
||||
/// Shifts either two pixels (in low-res mode) or four pixels (in high-res).
|
||||
void shift(bool high_res) {
|
||||
constexpr int shifts[] = {16, 32};
|
||||
|
||||
data_[1] = (data_[1] << shifts[high_res]) | (data_[0] >> (64 - shifts[high_res]));
|
||||
data_[0] <<= shifts[high_res];
|
||||
}
|
||||
|
||||
/// @returns The next four pixels to output; in low-resolution mode only two
|
||||
/// of them will be unique.
|
||||
///
|
||||
/// The value is arranges so that MSB = first pixel to output, LSB = last.
|
||||
///
|
||||
/// Each byte is swizzled to provide easier playfield separation, being in the form:
|
||||
/// b6, b7 = 0;
|
||||
/// b3–b5: planes 1, 3 and 5;
|
||||
/// b0–b2: planes 0, 2 and 4.
|
||||
uint32_t get(bool high_res) {
|
||||
if(high_res) {
|
||||
return uint32_t(data_[1] >> 32);
|
||||
} else {
|
||||
uint32_t result = uint16_t(data_[1] >> 48);
|
||||
result = ((result & 0xff00) << 8) | (result & 0x00ff);
|
||||
result |= result << 8;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint64_t, 2> data_{};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Bitplanes_hpp */
|
||||
582
Machines/Amiga/Blitter.cpp
Normal file
582
Machines/Amiga/Blitter.cpp
Normal file
@@ -0,0 +1,582 @@
|
||||
//
|
||||
// Blitter.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Blitter.hpp"
|
||||
|
||||
#include "Minterms.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
#define LOG_PREFIX "[Blitter] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
namespace {
|
||||
|
||||
/// @returns Either the final carry flag or the output nibble when using fill mode given that it either @c is_exclusive fill mode, or isn't;
|
||||
/// and the specified initial @c carry and input @c nibble.
|
||||
template <bool wants_carry> constexpr uint32_t fill_nibble(bool is_exclusive, uint8_t carry, uint8_t nibble) {
|
||||
uint8_t fill_output = 0;
|
||||
uint8_t bit = 0x01;
|
||||
while(bit < 0x10) {
|
||||
auto pre_toggle = nibble & bit, post_toggle = pre_toggle;
|
||||
if(!is_exclusive) {
|
||||
pre_toggle &= ~carry; // Accept bits that would transition to set immediately.
|
||||
post_toggle &= carry; // Accept bits that would transition to clear after the fact.
|
||||
} else {
|
||||
post_toggle = 0; // Just do the pre-toggle.
|
||||
}
|
||||
|
||||
carry ^= pre_toggle;
|
||||
fill_output |= carry;
|
||||
carry ^= post_toggle;
|
||||
|
||||
bit <<= 1;
|
||||
carry <<= 1;
|
||||
}
|
||||
|
||||
if constexpr (wants_carry) {
|
||||
return carry >> 4;
|
||||
} else {
|
||||
return fill_output;
|
||||
}
|
||||
}
|
||||
|
||||
// Lookup key for these tables is:
|
||||
//
|
||||
// b0–b3: input nibble
|
||||
// b4: carry
|
||||
// b5: is_exclusive
|
||||
//
|
||||
// i.e. it's in the range [0, 63].
|
||||
//
|
||||
// Tables below are indexed such that the higher-order bits select a table entry, lower-order bits select
|
||||
// a bit or nibble from within the indexed item.
|
||||
|
||||
constexpr uint32_t fill_carries[] = {
|
||||
(fill_nibble<true>(false, 0, 0x0) << 0x0) | (fill_nibble<true>(false, 0, 0x1) << 0x1) | (fill_nibble<true>(false, 0, 0x2) << 0x2) | (fill_nibble<true>(false, 0, 0x3) << 0x3) |
|
||||
(fill_nibble<true>(false, 0, 0x4) << 0x4) | (fill_nibble<true>(false, 0, 0x5) << 0x5) | (fill_nibble<true>(false, 0, 0x6) << 0x6) | (fill_nibble<true>(false, 0, 0x7) << 0x7) |
|
||||
(fill_nibble<true>(false, 0, 0x8) << 0x8) | (fill_nibble<true>(false, 0, 0x9) << 0x9) | (fill_nibble<true>(false, 0, 0xa) << 0xa) | (fill_nibble<true>(false, 0, 0xb) << 0xb) |
|
||||
(fill_nibble<true>(false, 0, 0xc) << 0xc) | (fill_nibble<true>(false, 0, 0xd) << 0xd) | (fill_nibble<true>(false, 0, 0xe) << 0xe) | (fill_nibble<true>(false, 0, 0xf) << 0xf) |
|
||||
|
||||
(fill_nibble<true>(false, 1, 0x0) << 0x10) | (fill_nibble<true>(false, 1, 0x1) << 0x11) | (fill_nibble<true>(false, 1, 0x2) << 0x12) | (fill_nibble<true>(false, 1, 0x3) << 0x13) |
|
||||
(fill_nibble<true>(false, 1, 0x4) << 0x14) | (fill_nibble<true>(false, 1, 0x5) << 0x15) | (fill_nibble<true>(false, 1, 0x6) << 0x16) | (fill_nibble<true>(false, 1, 0x7) << 0x17) |
|
||||
(fill_nibble<true>(false, 1, 0x8) << 0x18) | (fill_nibble<true>(false, 1, 0x9) << 0x19) | (fill_nibble<true>(false, 1, 0xa) << 0x1a) | (fill_nibble<true>(false, 1, 0xb) << 0x1b) |
|
||||
(fill_nibble<true>(false, 1, 0xc) << 0x1c) | (fill_nibble<true>(false, 1, 0xd) << 0x1d) | (fill_nibble<true>(false, 1, 0xe) << 0x1e) | (fill_nibble<true>(false, 1, 0xf) << 0x1f),
|
||||
|
||||
(fill_nibble<true>(true, 0, 0x0) << 0x0) | (fill_nibble<true>(true, 0, 0x1) << 0x1) | (fill_nibble<true>(true, 0, 0x2) << 0x2) | (fill_nibble<true>(true, 0, 0x3) << 0x3) |
|
||||
(fill_nibble<true>(true, 0, 0x4) << 0x4) | (fill_nibble<true>(true, 0, 0x5) << 0x5) | (fill_nibble<true>(true, 0, 0x6) << 0x6) | (fill_nibble<true>(true, 0, 0x7) << 0x7) |
|
||||
(fill_nibble<true>(true, 0, 0x8) << 0x8) | (fill_nibble<true>(true, 0, 0x9) << 0x9) | (fill_nibble<true>(true, 0, 0xa) << 0xa) | (fill_nibble<true>(true, 0, 0xb) << 0xb) |
|
||||
(fill_nibble<true>(true, 0, 0xc) << 0xc) | (fill_nibble<true>(true, 0, 0xd) << 0xd) | (fill_nibble<true>(true, 0, 0xe) << 0xe) | (fill_nibble<true>(true, 0, 0xf) << 0xf) |
|
||||
|
||||
(fill_nibble<true>(true, 1, 0x0) << 0x10) | (fill_nibble<true>(true, 1, 0x1) << 0x11) | (fill_nibble<true>(true, 1, 0x2) << 0x12) | (fill_nibble<true>(true, 1, 0x3) << 0x13) |
|
||||
(fill_nibble<true>(true, 1, 0x4) << 0x14) | (fill_nibble<true>(true, 1, 0x5) << 0x15) | (fill_nibble<true>(true, 1, 0x6) << 0x16) | (fill_nibble<true>(true, 1, 0x7) << 0x17) |
|
||||
(fill_nibble<true>(true, 1, 0x8) << 0x18) | (fill_nibble<true>(true, 1, 0x9) << 0x19) | (fill_nibble<true>(true, 1, 0xa) << 0x1a) | (fill_nibble<true>(true, 1, 0xb) << 0x1b) |
|
||||
(fill_nibble<true>(true, 1, 0xc) << 0x1c) | (fill_nibble<true>(true, 1, 0xd) << 0x1d) | (fill_nibble<true>(true, 1, 0xe) << 0x1e) | (fill_nibble<true>(true, 1, 0xf) << 0x1f),
|
||||
};
|
||||
|
||||
constexpr uint32_t fill_values[] = {
|
||||
(fill_nibble<false>(false, 0, 0x0) << 0) | (fill_nibble<false>(false, 0, 0x1) << 4) | (fill_nibble<false>(false, 0, 0x2) << 8) | (fill_nibble<false>(false, 0, 0x3) << 12) |
|
||||
(fill_nibble<false>(false, 0, 0x4) << 16) | (fill_nibble<false>(false, 0, 0x5) << 20) | (fill_nibble<false>(false, 0, 0x6) << 24) | (fill_nibble<false>(false, 0, 0x7) << 28),
|
||||
|
||||
(fill_nibble<false>(false, 0, 0x8) << 0) | (fill_nibble<false>(false, 0, 0x9) << 4) | (fill_nibble<false>(false, 0, 0xa) << 8) | (fill_nibble<false>(false, 0, 0xb) << 12) |
|
||||
(fill_nibble<false>(false, 0, 0xc) << 16) | (fill_nibble<false>(false, 0, 0xd) << 20) | (fill_nibble<false>(false, 0, 0xe) << 24) | (fill_nibble<false>(false, 0, 0xf) << 28),
|
||||
|
||||
(fill_nibble<false>(false, 1, 0x0) << 0) | (fill_nibble<false>(false, 1, 0x1) << 4) | (fill_nibble<false>(false, 1, 0x2) << 8) | (fill_nibble<false>(false, 1, 0x3) << 12) |
|
||||
(fill_nibble<false>(false, 1, 0x4) << 16) | (fill_nibble<false>(false, 1, 0x5) << 20) | (fill_nibble<false>(false, 1, 0x6) << 24) | (fill_nibble<false>(false, 1, 0x7) << 28),
|
||||
|
||||
(fill_nibble<false>(false, 1, 0x8) << 0) | (fill_nibble<false>(false, 1, 0x9) << 4) | (fill_nibble<false>(false, 1, 0xa) << 8) | (fill_nibble<false>(false, 1, 0xb) << 12) |
|
||||
(fill_nibble<false>(false, 1, 0xc) << 16) | (fill_nibble<false>(false, 1, 0xd) << 20) | (fill_nibble<false>(false, 1, 0xe) << 24) | (fill_nibble<false>(false, 1, 0xf) << 28),
|
||||
|
||||
(fill_nibble<false>(true, 0, 0x0) << 0) | (fill_nibble<false>(true, 0, 0x1) << 4) | (fill_nibble<false>(true, 0, 0x2) << 8) | (fill_nibble<false>(true, 0, 0x3) << 12) |
|
||||
(fill_nibble<false>(true, 0, 0x4) << 16) | (fill_nibble<false>(true, 0, 0x5) << 20) | (fill_nibble<false>(true, 0, 0x6) << 24) | (fill_nibble<false>(true, 0, 0x7) << 28),
|
||||
|
||||
(fill_nibble<false>(true, 0, 0x8) << 0) | (fill_nibble<false>(true, 0, 0x9) << 4) | (fill_nibble<false>(true, 0, 0xa) << 8) | (fill_nibble<false>(true, 0, 0xb) << 12) |
|
||||
(fill_nibble<false>(true, 0, 0xc) << 16) | (fill_nibble<false>(true, 0, 0xd) << 20) | (fill_nibble<false>(true, 0, 0xe) << 24) | (fill_nibble<false>(true, 0, 0xf) << 28),
|
||||
|
||||
(fill_nibble<false>(true, 1, 0x0) << 0) | (fill_nibble<false>(true, 1, 0x1) << 4) | (fill_nibble<false>(true, 1, 0x2) << 8) | (fill_nibble<false>(true, 1, 0x3) << 12) |
|
||||
(fill_nibble<false>(true, 1, 0x4) << 16) | (fill_nibble<false>(true, 1, 0x5) << 20) | (fill_nibble<false>(true, 1, 0x6) << 24) | (fill_nibble<false>(true, 1, 0x7) << 28),
|
||||
|
||||
(fill_nibble<false>(true, 1, 0x8) << 0) | (fill_nibble<false>(true, 1, 0x9) << 4) | (fill_nibble<false>(true, 1, 0xa) << 8) | (fill_nibble<false>(true, 1, 0xb) << 12) |
|
||||
(fill_nibble<false>(true, 1, 0xc) << 16) | (fill_nibble<false>(true, 1, 0xd) << 20) | (fill_nibble<false>(true, 1, 0xe) << 24) | (fill_nibble<false>(true, 1, 0xf) << 28),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template <bool record_bus>
|
||||
void Blitter<record_bus>::set_control(int index, uint16_t value) {
|
||||
if(index) {
|
||||
line_mode_ = (value & 0x0001);
|
||||
one_dot_ = value & 0x0002;
|
||||
line_direction_ = (value >> 2) & 7;
|
||||
line_sign_ = (value & 0x0040) ? -1 : 1;
|
||||
|
||||
direction_ = one_dot_ ? uint32_t(-1) : uint32_t(1);
|
||||
exclusive_fill_ = (value & 0x0010);
|
||||
inclusive_fill_ = !exclusive_fill_ && (value & 0x0008); // Exclusive fill takes precedence. Probably? TODO: verify.
|
||||
fill_carry_ = (value & 0x0004);
|
||||
} else {
|
||||
minterms_ = value & 0xff;
|
||||
sequencer_.set_control(value >> 8);
|
||||
}
|
||||
shifts_[index] = value >> 12;
|
||||
LOG("Set control " << index << " to " << PADHEX(4) << value);
|
||||
}
|
||||
|
||||
template <bool record_bus>
|
||||
void Blitter<record_bus>::set_first_word_mask(uint16_t value) {
|
||||
LOG("Set first word mask: " << PADHEX(4) << value);
|
||||
a_mask_[0] = value;
|
||||
}
|
||||
|
||||
template <bool record_bus>
|
||||
void Blitter<record_bus>::set_last_word_mask(uint16_t value) {
|
||||
LOG("Set last word mask: " << PADHEX(4) << value);
|
||||
a_mask_[1] = value;
|
||||
}
|
||||
|
||||
template <bool record_bus>
|
||||
void Blitter<record_bus>::set_size(uint16_t value) {
|
||||
// width_ = (width_ & ~0x3f) | (value & 0x3f);
|
||||
// height_ = (height_ & ~0x3ff) | (value >> 6);
|
||||
width_ = value & 0x3f;
|
||||
if(!width_) width_ = 0x40;
|
||||
height_ = value >> 6;
|
||||
if(!height_) height_ = 1024;
|
||||
LOG("Set size to " << std::dec << width_ << ", " << height_);
|
||||
|
||||
// Current assumption: writing this register informs the
|
||||
// blitter that it should treat itself as about to start a new line.
|
||||
}
|
||||
|
||||
template <bool record_bus>
|
||||
void Blitter<record_bus>::set_minterms(uint16_t value) {
|
||||
LOG("Set minterms " << PADHEX(4) << value);
|
||||
minterms_ = value & 0xff;
|
||||
}
|
||||
|
||||
//template <bool record_bus>
|
||||
//void Blitter<record_bus>::set_vertical_size([[maybe_unused]] uint16_t value) {
|
||||
// LOG("Set vertical size " << PADHEX(4) << value);
|
||||
// // TODO. This is ECS only, I think. Ditto set_horizontal_size.
|
||||
//}
|
||||
//
|
||||
//template <bool record_bus>
|
||||
//void Blitter<record_bus>::set_horizontal_size([[maybe_unused]] uint16_t value) {
|
||||
// LOG("Set horizontal size " << PADHEX(4) << value);
|
||||
//}
|
||||
|
||||
template <bool record_bus>
|
||||
void Blitter<record_bus>::set_data(int channel, uint16_t value) {
|
||||
LOG("Set data " << channel << " to " << PADHEX(4) << value);
|
||||
|
||||
// Ugh, backed myself into a corner. TODO: clean.
|
||||
switch(channel) {
|
||||
case 0: a_data_ = value; break;
|
||||
case 1: b_data_ = value; break;
|
||||
case 2: c_data_ = value; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
template <bool record_bus>
|
||||
uint16_t Blitter<record_bus>::get_status() {
|
||||
const uint16_t result =
|
||||
(not_zero_flag_ ? 0x0000 : 0x2000) | (height_ ? 0x4000 : 0x0000);
|
||||
LOG("Returned status of " << result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Due to the pipeline, writes are delayed by one slot — the first write will occur
|
||||
// after the second set of inputs has been fetched, and every sequence with writes enabled
|
||||
// will end with an additional write.
|
||||
//
|
||||
// USE Code
|
||||
// in Active
|
||||
// BLTCON0 Channels Cycle Sequence
|
||||
// --------- -------- --------------
|
||||
// F A B C D A0 B0 C0 - A1 B1 C1 D0 A2 B2 C2 D1 D2
|
||||
// E A B C A0 B0 C0 A1 B1 C1 A2 B2 C2
|
||||
// D A B D A0 B0 - A1 B1 D0 A2 B2 D1 - D2
|
||||
// C A B A0 B0 - A1 B1 - A2 B2
|
||||
// B A C D A0 C0 - A1 C1 D0 A2 C2 D1 - D2
|
||||
// A A C A0 C0 A1 C1 A2 C2
|
||||
// 9 A D A0 - A1 D0 A2 D1 - D2
|
||||
// 8 A A0 - A1 - A2
|
||||
// 7 B C D B0 C0 - - B1 C1 D0 - B2 C2 D1 - D2
|
||||
// 6 B C B0 C0 - B1 C1 - B2 C2
|
||||
// 5 B D B0 - - B1 D0 - B2 D1 - D2
|
||||
// 4 B B0 - - B1 - - B2
|
||||
// 3 C D C0 - - C1 D0 - C2 D1 - D2
|
||||
// 2 C C0 - C1 - C2
|
||||
// 1 D D0 - D1 - D2
|
||||
// 0 none - - - -
|
||||
//
|
||||
//
|
||||
// Table 6-2: Typical Blitter Cycle Sequence
|
||||
|
||||
template <bool record_bus>
|
||||
void Blitter<record_bus>::add_modulos() {
|
||||
pointer_[0] += modulos_[0] * sequencer_.channel_enabled<0>()* direction_;
|
||||
pointer_[1] += modulos_[1] * sequencer_.channel_enabled<1>() * direction_;
|
||||
pointer_[2] += modulos_[2] * sequencer_.channel_enabled<2>() * direction_;
|
||||
pointer_[3] += modulos_[3] * sequencer_.channel_enabled<3>() * direction_;
|
||||
}
|
||||
|
||||
template <bool record_bus>
|
||||
template <bool complete_immediately>
|
||||
bool Blitter<record_bus>::advance_dma() {
|
||||
if(!height_) return false;
|
||||
|
||||
// TODO: eliminate @c complete_immediately and this workaround.
|
||||
// See commentary in Chipset.cpp.
|
||||
if constexpr (complete_immediately) {
|
||||
|
||||
// HACK! HACK!! HACK!!!
|
||||
//
|
||||
// This resolves an issue with loading the particular copy of Spindizzy Worlds
|
||||
// I am testing against.
|
||||
//
|
||||
// TODO: DO NOT PUBLISH THIS.
|
||||
//
|
||||
// This is committed solely so that I can continue researching the real, underlying
|
||||
// issue across machines. It would not be acceptable to me to ship this.
|
||||
// (and the printf is another reminder-to-self)
|
||||
if(width_ == 8 && height_ == 32) {
|
||||
printf("Accelerating %d x %d\n", width_, height_);
|
||||
|
||||
while(get_status() & 0x4000) {
|
||||
advance_dma<false>();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(line_mode_) {
|
||||
not_zero_flag_ = false;
|
||||
|
||||
// As-yet unimplemented:
|
||||
assert(b_data_ == 0xffff);
|
||||
|
||||
//
|
||||
// Line mode.
|
||||
//
|
||||
|
||||
// Bluffer's guide to line mode:
|
||||
//
|
||||
// In Bresenham terms, the following registers have been set up:
|
||||
//
|
||||
// [A modulo] = 4 * (dy - dx)
|
||||
// [B modulo] = 4 * dy
|
||||
// [A pointer] = 4 * dy - 2 * dx, with the sign flag in BLTCON1 indicating sign.
|
||||
//
|
||||
// [A data] = 0x8000
|
||||
// [Both masks] = 0xffff
|
||||
// [A shift] = x1 & 15
|
||||
//
|
||||
// [B data] = texture
|
||||
// [B shift] = bit at which to start the line texture (0 = LSB)
|
||||
//
|
||||
// [C and D pointers] = word containing the first pixel of the line
|
||||
// [C and D modulo] = width of the bitplane in bytes
|
||||
//
|
||||
// height = number of pixels
|
||||
//
|
||||
// If ONEDOT of BLTCON1 is set, plot only a single bit per horizontal row.
|
||||
//
|
||||
// BLTCON1 quadrants are (bits 2–4):
|
||||
//
|
||||
// 110 -> step in x, x positive, y negative
|
||||
// 111 -> step in x, x negative, y negative
|
||||
// 101 -> step in x, x negative, y positive
|
||||
// 100 -> step in x, x positive, y positive
|
||||
//
|
||||
// 001 -> step in y, x positive, y negative
|
||||
// 011 -> step in y, x negative, y negative
|
||||
// 010 -> step in y, x negative, y positive
|
||||
// 000 -> step in y, x positive, y positive
|
||||
//
|
||||
// So that's:
|
||||
//
|
||||
// * bit 4 = x [=1] or y [=0] major;
|
||||
// * bit 3 = 1 => major variable negative; otherwise positive;
|
||||
// * bit 2 = 1 => minor variable negative; otherwise positive.
|
||||
|
||||
//
|
||||
// Implementation below is heavily based on the documentation found
|
||||
// at https://github.com/niklasekstrom/blitter-subpixel-line/blob/master/Drawing%20lines%20using%20the%20Amiga%20blitter.pdf
|
||||
//
|
||||
|
||||
//
|
||||
// Caveat: I've no idea how the DMA access slots should be laid out for
|
||||
// line drawing.
|
||||
//
|
||||
|
||||
if(!busy_) {
|
||||
error_ = int16_t(pointer_[0] << 1) >> 1; // TODO: what happens if line_sign_ doesn't agree with this?
|
||||
draw_ = true;
|
||||
busy_ = true;
|
||||
has_c_data_ = false;
|
||||
}
|
||||
|
||||
bool did_output = false;
|
||||
if(draw_) {
|
||||
// TODO: patterned lines. Unclear what to do with the bit that comes out of b.
|
||||
// Probably extend it to a full word?
|
||||
|
||||
if(!has_c_data_) {
|
||||
has_c_data_ = true;
|
||||
c_data_ = ram_[pointer_[3] & ram_mask_];
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::ReadC, pointer_[3], c_data_);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint16_t output =
|
||||
apply_minterm<uint16_t>(a_data_ >> shifts_[0], b_data_, c_data_, minterms_);
|
||||
ram_[pointer_[3] & ram_mask_] = output;
|
||||
not_zero_flag_ |= output;
|
||||
draw_ &= !one_dot_;
|
||||
has_c_data_ = false;
|
||||
did_output = true;
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, pointer_[3], output);
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int LEFT = 1 << 0;
|
||||
constexpr int RIGHT = 1 << 1;
|
||||
constexpr int UP = 1 << 2;
|
||||
constexpr int DOWN = 1 << 3;
|
||||
int step = (line_direction_ & 4) ?
|
||||
((line_direction_ & 1) ? LEFT : RIGHT) :
|
||||
((line_direction_ & 1) ? UP : DOWN);
|
||||
|
||||
if(error_ < 0) {
|
||||
error_ += modulos_[1];
|
||||
} else {
|
||||
step |=
|
||||
(line_direction_ & 4) ?
|
||||
((line_direction_ & 2) ? UP : DOWN) :
|
||||
((line_direction_ & 2) ? LEFT : RIGHT);
|
||||
|
||||
error_ += modulos_[0];
|
||||
}
|
||||
|
||||
if(step & LEFT) {
|
||||
--shifts_[0];
|
||||
if(shifts_[0] == -1) {
|
||||
--pointer_[3];
|
||||
}
|
||||
} else if(step & RIGHT) {
|
||||
++shifts_[0];
|
||||
if(shifts_[0] == 16) {
|
||||
++pointer_[3];
|
||||
}
|
||||
}
|
||||
shifts_[0] &= 15;
|
||||
|
||||
if(step & UP) {
|
||||
pointer_[3] -= modulos_[2];
|
||||
draw_ = true;
|
||||
} else if(step & DOWN) {
|
||||
pointer_[3] += modulos_[2];
|
||||
draw_ = true;
|
||||
}
|
||||
|
||||
--height_;
|
||||
if(!height_) {
|
||||
busy_ = false;
|
||||
posit_interrupt(InterruptFlag::Blitter);
|
||||
}
|
||||
|
||||
return did_output;
|
||||
} else {
|
||||
// Copy mode.
|
||||
if(!busy_) {
|
||||
sequencer_.begin();
|
||||
a32_ = 0;
|
||||
b32_ = 0;
|
||||
|
||||
y_ = 0;
|
||||
x_ = 0;
|
||||
loop_index_ = -1;
|
||||
write_phase_ = WritePhase::Starting;
|
||||
not_zero_flag_ = false;
|
||||
busy_ = true;
|
||||
}
|
||||
|
||||
const auto next = sequencer_.next();
|
||||
|
||||
// If this is the start of a new iteration, check for end of line,
|
||||
// or of blit, and pick an appropriate mask for A based on location.
|
||||
if(next.second != loop_index_) {
|
||||
transient_a_mask_ = x_ ? 0xffff : a_mask_[0];
|
||||
|
||||
// Check whether an entire row was completed in the previous iteration.
|
||||
// If so then add modulos. Though this won't capture the move off the
|
||||
// final line, so that's handled elsewhere.
|
||||
if(!x_ && y_) {
|
||||
add_modulos();
|
||||
}
|
||||
|
||||
++x_;
|
||||
if(x_ == width_) {
|
||||
transient_a_mask_ &= a_mask_[1];
|
||||
x_ = 0;
|
||||
++y_;
|
||||
if(y_ == height_) {
|
||||
sequencer_.complete();
|
||||
}
|
||||
}
|
||||
++loop_index_;
|
||||
}
|
||||
|
||||
using Channel = BlitterSequencer::Channel;
|
||||
switch(next.first) {
|
||||
case Channel::A:
|
||||
a_data_ = ram_[pointer_[0] & ram_mask_];
|
||||
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::ReadA, pointer_[0], a_data_);
|
||||
}
|
||||
pointer_[0] += direction_;
|
||||
return true;
|
||||
case Channel::B:
|
||||
b_data_ = ram_[pointer_[1] & ram_mask_];
|
||||
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::ReadB, pointer_[1], b_data_);
|
||||
}
|
||||
pointer_[1] += direction_;
|
||||
return true;
|
||||
case Channel::C:
|
||||
c_data_ = ram_[pointer_[2] & ram_mask_];
|
||||
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::ReadC, pointer_[2], c_data_);
|
||||
}
|
||||
pointer_[2] += direction_;
|
||||
return true;
|
||||
case Channel::FlushPipeline:
|
||||
add_modulos();
|
||||
posit_interrupt(InterruptFlag::Blitter);
|
||||
height_ = 0;
|
||||
busy_ = false;
|
||||
|
||||
if(write_phase_ == WritePhase::Full) {
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, write_address_, write_value_);
|
||||
}
|
||||
ram_[write_address_ & ram_mask_] = write_value_;
|
||||
write_phase_ = WritePhase::Starting;
|
||||
}
|
||||
return true;
|
||||
|
||||
case Channel::None:
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::SkippedSlot);
|
||||
}
|
||||
return false;
|
||||
|
||||
case Channel::Write: break;
|
||||
}
|
||||
|
||||
a32_ = (a32_ << 16) | (a_data_ & transient_a_mask_);
|
||||
b32_ = (b32_ << 16) | b_data_;
|
||||
|
||||
uint16_t a, b;
|
||||
|
||||
// The barrel shifter shifts to the right in ascending address mode,
|
||||
// but to the left otherwise.
|
||||
if(!one_dot_) {
|
||||
a = uint16_t(a32_ >> shifts_[0]);
|
||||
b = uint16_t(b32_ >> shifts_[1]);
|
||||
} else {
|
||||
// TODO: there must be a neater solution than this.
|
||||
a = uint16_t(
|
||||
(a32_ << shifts_[0]) |
|
||||
(a32_ >> (32 - shifts_[0]))
|
||||
);
|
||||
|
||||
b = uint16_t(
|
||||
(b32_ << shifts_[1]) |
|
||||
(b32_ >> (32 - shifts_[1]))
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t output =
|
||||
apply_minterm<uint16_t>(
|
||||
a,
|
||||
b,
|
||||
c_data_,
|
||||
minterms_);
|
||||
|
||||
if(exclusive_fill_ || inclusive_fill_) {
|
||||
// Use the fill tables nibble-by-nibble to figure out the filled word.
|
||||
uint16_t fill_output = 0;
|
||||
int ongoing_carry = fill_carry_;
|
||||
const int type_mask = exclusive_fill_ ? (1 << 5) : 0;
|
||||
for(int c = 0; c < 16; c += 4) {
|
||||
const int total_index = (output & 0xf) | (ongoing_carry << 4) | type_mask;
|
||||
fill_output |= ((fill_values[total_index >> 3] >> ((total_index & 7) * 4)) & 0xf) << c;
|
||||
ongoing_carry = (fill_carries[total_index >> 5] >> (total_index & 31)) & 1;
|
||||
output >>= 4;
|
||||
}
|
||||
|
||||
output = fill_output;
|
||||
fill_carry_ = ongoing_carry;
|
||||
}
|
||||
|
||||
not_zero_flag_ |= output;
|
||||
|
||||
switch(write_phase_) {
|
||||
case WritePhase::Full:
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::WriteFromPipeline, write_address_, write_value_);
|
||||
}
|
||||
ram_[write_address_ & ram_mask_] = write_value_;
|
||||
[[fallthrough]];
|
||||
|
||||
case WritePhase::Starting:
|
||||
write_phase_ = WritePhase::Full;
|
||||
write_address_ = pointer_[3];
|
||||
write_value_ = output;
|
||||
|
||||
if constexpr (record_bus) {
|
||||
transactions_.emplace_back(Transaction::Type::AddToPipeline, write_address_, write_value_);
|
||||
}
|
||||
pointer_[3] += direction_;
|
||||
return true;
|
||||
|
||||
default: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <bool record_bus>
|
||||
std::vector<typename Blitter<record_bus>::Transaction> Blitter<record_bus>::get_and_reset_transactions() {
|
||||
std::vector<Transaction> result;
|
||||
std::swap(result, transactions_);
|
||||
return result;
|
||||
}
|
||||
|
||||
template class Amiga::Blitter<false>;
|
||||
template class Amiga::Blitter<true>;
|
||||
template bool Amiga::Blitter<true>::advance_dma<true>();
|
||||
template bool Amiga::Blitter<true>::advance_dma<false>();
|
||||
template bool Amiga::Blitter<false>::advance_dma<true>();
|
||||
template bool Amiga::Blitter<false>::advance_dma<false>();
|
||||
134
Machines/Amiga/Blitter.hpp
Normal file
134
Machines/Amiga/Blitter.hpp
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// Blitter.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Blitter_hpp
|
||||
#define Blitter_hpp
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "BlitterSequencer.hpp"
|
||||
#include "DMADevice.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
/*!
|
||||
If @c record_bus is @c true then all bus interactions will be recorded
|
||||
and can subsequently be retrieved. This is included for testing purposes.
|
||||
*/
|
||||
template <bool record_bus = false> class Blitter: public DMADevice<4, 4> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
template <int id, int shift> void set_pointer(uint16_t value) {
|
||||
if(get_status() & 0x4000) {
|
||||
printf(">>>");
|
||||
}
|
||||
DMADevice<4, 4>::set_pointer<id, shift>(value);
|
||||
}
|
||||
|
||||
// Various setters; it's assumed that address decoding is handled externally.
|
||||
//
|
||||
// In all cases where a channel is identified numerically, it's taken that
|
||||
// 0 = A, 1 = B, 2 = C, 3 = D.
|
||||
void set_control(int index, uint16_t value);
|
||||
void set_first_word_mask(uint16_t value);
|
||||
void set_last_word_mask(uint16_t value);
|
||||
|
||||
void set_size(uint16_t value);
|
||||
void set_minterms(uint16_t value);
|
||||
// void set_vertical_size(uint16_t value);
|
||||
// void set_horizontal_size(uint16_t value);
|
||||
void set_data(int channel, uint16_t value);
|
||||
|
||||
uint16_t get_status();
|
||||
|
||||
template <bool complete_immediately> bool advance_dma();
|
||||
|
||||
struct Transaction {
|
||||
enum class Type {
|
||||
SkippedSlot,
|
||||
ReadA,
|
||||
ReadB,
|
||||
ReadC,
|
||||
AddToPipeline,
|
||||
WriteFromPipeline
|
||||
} type = Type::SkippedSlot;
|
||||
|
||||
uint32_t address = 0;
|
||||
uint16_t value = 0;
|
||||
|
||||
Transaction() {}
|
||||
Transaction(Type type) : type(type) {}
|
||||
Transaction(Type type, uint32_t address, uint16_t value) : type(type), address(address), value(value) {}
|
||||
|
||||
std::string to_string() const {
|
||||
std::string result;
|
||||
|
||||
switch(type) {
|
||||
case Type::SkippedSlot: result = "SkippedSlot"; break;
|
||||
case Type::ReadA: result = "ReadA"; break;
|
||||
case Type::ReadB: result = "ReadB"; break;
|
||||
case Type::ReadC: result = "ReadC"; break;
|
||||
case Type::AddToPipeline: result = "AddToPipeline"; break;
|
||||
case Type::WriteFromPipeline: result = "WriteFromPipeline"; break;
|
||||
}
|
||||
|
||||
result += " address:" + std::to_string(address) + " value:" + std::to_string(value);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
std::vector<Transaction> get_and_reset_transactions();
|
||||
|
||||
private:
|
||||
int width_ = 0, height_ = 0;
|
||||
int shifts_[2]{};
|
||||
uint16_t a_mask_[2] = {0xffff, 0xffff};
|
||||
|
||||
bool line_mode_ = false;
|
||||
bool one_dot_ = false;
|
||||
int line_direction_ = 0;
|
||||
int line_sign_ = 1;
|
||||
|
||||
uint32_t direction_ = 1;
|
||||
bool inclusive_fill_ = false;
|
||||
bool exclusive_fill_ = false;
|
||||
bool fill_carry_ = false;
|
||||
|
||||
uint8_t minterms_ = 0;
|
||||
uint32_t a32_ = 0, b32_ = 0;
|
||||
uint16_t a_data_ = 0, b_data_ = 0, c_data_ = 0;
|
||||
|
||||
bool not_zero_flag_ = false;
|
||||
|
||||
BlitterSequencer sequencer_;
|
||||
uint32_t write_address_ = 0xffff'ffff;
|
||||
uint16_t write_value_ = 0;
|
||||
enum WritePhase {
|
||||
Starting, Full
|
||||
} write_phase_;
|
||||
int y_, x_;
|
||||
uint16_t transient_a_mask_;
|
||||
bool busy_ = false;
|
||||
int loop_index_ = -1;
|
||||
|
||||
int error_ = 0;
|
||||
bool draw_ = false;
|
||||
bool has_c_data_ = false;
|
||||
|
||||
void add_modulos();
|
||||
std::vector<Transaction> transactions_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* Blitter_hpp */
|
||||
167
Machines/Amiga/BlitterSequencer.hpp
Normal file
167
Machines/Amiga/BlitterSequencer.hpp
Normal file
@@ -0,0 +1,167 @@
|
||||
//
|
||||
// BlitterSequencer.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/08/2022.
|
||||
// Copyright © 2022 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef BlitterSequencer_hpp
|
||||
#define BlitterSequencer_hpp
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
/*!
|
||||
Statefully provides the next access the Blitter should make.
|
||||
|
||||
TODO: determine the actual logic here, rather than
|
||||
relying on tables.
|
||||
*/
|
||||
class BlitterSequencer {
|
||||
public:
|
||||
enum class Channel {
|
||||
/// Tells the caller to calculate and load a new piece of output
|
||||
/// into the output pipeline.
|
||||
///
|
||||
/// If any inputs are enabled then a one-slot output pipeline applies:
|
||||
/// output will rest in the pipeline for one write phase before being written.
|
||||
Write,
|
||||
/// Indicates that a write should occur if anything is in the pipeline, otherwise
|
||||
/// no activity should occur.
|
||||
FlushPipeline,
|
||||
/// The caller should read from channel C.
|
||||
C,
|
||||
/// The caller should read from channel B.
|
||||
B,
|
||||
/// The caller should read from channel A.
|
||||
A,
|
||||
/// Indicates an unused DMA slot.
|
||||
None
|
||||
};
|
||||
|
||||
/// Sets the current control value, which indicates which
|
||||
/// channels are enabled.
|
||||
void set_control(int control) {
|
||||
control_ = control & 0xf;
|
||||
index_ = 0; // TODO: this probably isn't accurate; case caught is a change
|
||||
// of control values during a blit.
|
||||
}
|
||||
|
||||
/// Indicates that blitting should conclude after this step, i.e.
|
||||
/// whatever is being fetched now is part of the final set of input data;
|
||||
/// this is safe to call following a fetch request on any channel.
|
||||
void complete() {
|
||||
next_phase_ =
|
||||
(control_ == 0x9 || control_ == 0xb || control_ == 0xd) ?
|
||||
Phase::PauseAndComplete : Phase::Complete;
|
||||
}
|
||||
|
||||
/// Begins a blit operation.
|
||||
void begin() {
|
||||
phase_ = next_phase_ = Phase::Ongoing;
|
||||
index_ = loop_ = 0;
|
||||
}
|
||||
|
||||
/// Provides the next channel to fetch from, or that a write is required,
|
||||
/// along with a count of complete channel iterations so far completed.
|
||||
std::pair<Channel, int> next() {
|
||||
switch(phase_) {
|
||||
default: break;
|
||||
|
||||
case Phase::Complete:
|
||||
return std::make_pair(Channel::FlushPipeline, loop_);
|
||||
|
||||
case Phase::PauseAndComplete:
|
||||
phase_ = Phase::Complete;
|
||||
return std::make_pair(Channel::None, loop_);
|
||||
}
|
||||
|
||||
Channel next = Channel::None;
|
||||
|
||||
switch(control_) {
|
||||
default: break;
|
||||
|
||||
case 0: next = next_channel(pattern0); break;
|
||||
case 1: next = next_channel(pattern1); break;
|
||||
case 2: next = next_channel(pattern2); break;
|
||||
case 3: next = next_channel(pattern3); break;
|
||||
case 4: next = next_channel(pattern4); break;
|
||||
case 5: next = next_channel(pattern5); break;
|
||||
case 6: next = next_channel(pattern6); break;
|
||||
case 7: next = next_channel(pattern7); break;
|
||||
case 8: next = next_channel(pattern8); break;
|
||||
case 9: next = next_channel(pattern9); break;
|
||||
case 10: next = next_channel(patternA); break;
|
||||
case 11: next = next_channel(patternB); break;
|
||||
case 12: next = next_channel(patternC); break;
|
||||
case 13: next = next_channel(patternD); break;
|
||||
case 14: next = next_channel(patternE); break;
|
||||
case 15: next = next_channel(patternF); break;
|
||||
}
|
||||
|
||||
return std::make_pair(next, loop_);
|
||||
}
|
||||
|
||||
template <int channel> bool channel_enabled() {
|
||||
return control_ & (8 >> channel);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr std::array<Channel, 1> pattern0 = { Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern1 = { Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern2 = { Channel::C, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern3 = { Channel::C, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern4 = { Channel::B, Channel::None, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern5 = { Channel::B, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 3> pattern6 = { Channel::B, Channel::C, Channel::None };
|
||||
static constexpr std::array<Channel, 4> pattern7 = { Channel::B, Channel::C, Channel::Write, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern8 = { Channel::A, Channel::None };
|
||||
static constexpr std::array<Channel, 2> pattern9 = { Channel::A, Channel::Write };
|
||||
static constexpr std::array<Channel, 2> patternA = { Channel::A, Channel::C };
|
||||
static constexpr std::array<Channel, 3> patternB = { Channel::A, Channel::C, Channel::Write };
|
||||
static constexpr std::array<Channel, 3> patternC = { Channel::A, Channel::B, Channel::None };
|
||||
static constexpr std::array<Channel, 3> patternD = { Channel::A, Channel::B, Channel::Write };
|
||||
static constexpr std::array<Channel, 3> patternE = { Channel::A, Channel::B, Channel::C };
|
||||
static constexpr std::array<Channel, 4> patternF = { Channel::A, Channel::B, Channel::C, Channel::Write };
|
||||
template <typename ArrayT> Channel next_channel(const ArrayT &list) {
|
||||
loop_ += index_ / list.size();
|
||||
index_ %= list.size();
|
||||
const Channel result = list[index_];
|
||||
++index_;
|
||||
if(index_ == list.size()) {
|
||||
phase_ = next_phase_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Current control flags, i.e. which channels are enabled.
|
||||
int control_ = 0;
|
||||
|
||||
// Index into the pattern table for this blit.
|
||||
size_t index_ = 0;
|
||||
|
||||
// Number of times the entire pattern table has been completed.
|
||||
int loop_ = 0;
|
||||
|
||||
enum class Phase {
|
||||
/// Return the next thing in the pattern table and advance.
|
||||
/// If looping from the end of the pattern table to the start,
|
||||
/// set phase_ to next_phase_.
|
||||
Ongoing,
|
||||
/// Return a Channel::None and advancce to phase_ = Phase::Complete.
|
||||
PauseAndComplete,
|
||||
/// Return Channel::Write indefinitely.
|
||||
Complete
|
||||
};
|
||||
|
||||
// Current sequencer pahse.
|
||||
Phase phase_ = Phase::Complete;
|
||||
// Phase to assume at the end of this iteration of the sequence table.
|
||||
Phase next_phase_ = Phase::Complete;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* BlitterSequencer_hpp */
|
||||
1300
Machines/Amiga/Chipset.cpp
Normal file
1300
Machines/Amiga/Chipset.cpp
Normal file
File diff suppressed because it is too large
Load Diff
372
Machines/Amiga/Chipset.hpp
Normal file
372
Machines/Amiga/Chipset.hpp
Normal file
@@ -0,0 +1,372 @@
|
||||
//
|
||||
// Chipset.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Chipset_hpp
|
||||
#define Chipset_hpp
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../Activity/Source.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
#include "../../ClockReceiver/JustInTime.hpp"
|
||||
#include "../../Components/6526/6526.hpp"
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../Processors/68000Mk2/68000Mk2.hpp"
|
||||
#include "../../Storage/Disk/Controller/DiskController.hpp"
|
||||
#include "../../Storage/Disk/Drive.hpp"
|
||||
|
||||
#include "Audio.hpp"
|
||||
#include "Bitplanes.hpp"
|
||||
#include "Blitter.hpp"
|
||||
#include "Copper.hpp"
|
||||
#include "DMADevice.hpp"
|
||||
#include "Flags.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "MouseJoystick.hpp"
|
||||
#include "MemoryMap.hpp"
|
||||
#include "Sprites.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Chipset: private ClockingHint::Observer {
|
||||
public:
|
||||
Chipset(MemoryMap &memory_map, int input_clock_rate);
|
||||
|
||||
struct Changes {
|
||||
int interrupt_level = 0;
|
||||
HalfCycles duration;
|
||||
|
||||
Changes &operator += (const Changes &rhs) {
|
||||
duration += rhs.duration;
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// Advances the stated amount of time.
|
||||
Changes run_for(HalfCycles);
|
||||
|
||||
/// Advances to the end of the next available CPU slot.
|
||||
Changes run_until_after_cpu_slot();
|
||||
|
||||
/// Performs the provided microcycle, which the caller guarantees to be a memory access.
|
||||
void perform(const CPU::MC68000Mk2::Microcycle &);
|
||||
|
||||
/// Sets the current state of the CIA interrupt lines.
|
||||
void set_cia_interrupts(bool cia_a, bool cia_b);
|
||||
|
||||
/// Provides the chipset's current interrupt level.
|
||||
int get_interrupt_level() {
|
||||
return interrupt_level_;
|
||||
}
|
||||
|
||||
/// Inserts the disks provided.
|
||||
/// @returns @c true if anything was inserted; @c false otherwise.
|
||||
bool insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks);
|
||||
|
||||
// The standard CRT set.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
// Activity observation.
|
||||
void set_activity_observer(Activity::Observer *observer) {
|
||||
cia_a_handler_.set_activity_observer(observer);
|
||||
disk_controller_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
// Keyboard and mouse exposure.
|
||||
Keyboard &get_keyboard() {
|
||||
return keyboard_;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
// Synchronisation.
|
||||
void flush();
|
||||
|
||||
// Input for receiving collected bitplanes.
|
||||
void post_bitplanes(const BitplaneData &data);
|
||||
|
||||
// Obtains the source of audio output.
|
||||
Outputs::Speaker::Speaker *get_speaker() {
|
||||
return audio_.get_speaker();
|
||||
}
|
||||
|
||||
private:
|
||||
friend class DMADeviceBase;
|
||||
|
||||
// MARK: - Register read/write functions.
|
||||
uint16_t read(uint32_t address, bool allow_conversion = true);
|
||||
void write(uint32_t address, uint16_t value, bool allow_conversion = true);
|
||||
static constexpr uint32_t ChipsetAddressMask = 0x1fe;
|
||||
friend class Copper;
|
||||
|
||||
// MARK: - E Clock and keyboard dividers.
|
||||
|
||||
HalfCycles cia_divider_;
|
||||
HalfCycles keyboard_divider_;
|
||||
|
||||
// MARK: - Interrupts.
|
||||
|
||||
uint16_t interrupt_enable_ = 0;
|
||||
uint16_t interrupt_requests_ = 0;
|
||||
int interrupt_level_ = 0;
|
||||
|
||||
void update_interrupts();
|
||||
void posit_interrupt(InterruptFlag::FlagT);
|
||||
|
||||
// MARK: - Scheduler.
|
||||
|
||||
template <bool stop_on_cpu> Changes run(HalfCycles duration = HalfCycles::max());
|
||||
template <bool stop_on_cpu> int advance_slots(int, int);
|
||||
template <int cycle, bool stop_if_cpu> bool perform_cycle();
|
||||
template <int cycle> void output();
|
||||
void output_pixels(int cycles_until_sync);
|
||||
void apply_ham(uint8_t);
|
||||
|
||||
// MARK: - DMA Control, Scheduler and Blitter.
|
||||
|
||||
uint16_t dma_control_ = 0;
|
||||
Blitter<false> blitter_;
|
||||
|
||||
// MARK: - Sprites and collision flags.
|
||||
|
||||
std::array<Sprite, 8> sprites_;
|
||||
std::array<TwoSpriteShifter, 4> sprite_shifters_;
|
||||
uint16_t collisions_ = 0, collisions_flags_= 0;
|
||||
|
||||
uint32_t playfield_collision_mask_ = 0, playfield_collision_complement_ = 0;
|
||||
|
||||
// MARK: - Raster position and state.
|
||||
|
||||
// Definitions related to PAL/NTSC.
|
||||
// (Default values are PAL).
|
||||
int line_length_ = 227;
|
||||
int short_field_height_ = 312;
|
||||
int vertical_blank_height_ = 25; // PAL = 25, NTSC = 20
|
||||
|
||||
// Current raster position.
|
||||
int line_cycle_ = 0, y_ = 0;
|
||||
|
||||
// Parameters affecting bitplane collection and output.
|
||||
uint16_t display_window_start_[2] = {0, 0};
|
||||
uint16_t display_window_stop_[2] = {0, 0};
|
||||
uint16_t fetch_window_[2] = {0, 0};
|
||||
|
||||
// Ephemeral bitplane collection state.
|
||||
bool fetch_vertical_ = false;
|
||||
bool display_horizontal_ = false;
|
||||
bool did_fetch_ = false;
|
||||
|
||||
int horizontal_offset_ = 0;
|
||||
enum HorizontalFetch {
|
||||
Started, WillRequestStop, StopRequested, Stopped
|
||||
} horizontal_fetch_ = HorizontalFetch::Stopped;
|
||||
|
||||
// Output state.
|
||||
uint16_t border_colour_ = 0;
|
||||
bool is_border_ = true;
|
||||
int zone_duration_ = 0;
|
||||
uint16_t *pixels_ = nullptr;
|
||||
uint16_t last_colour_ = 0; // Retained for HAM mode.
|
||||
void flush_output();
|
||||
|
||||
Bitplanes bitplanes_;
|
||||
|
||||
BitplaneData next_bitplanes_, previous_bitplanes_;
|
||||
bool has_next_bitplanes_ = false;
|
||||
|
||||
int odd_priority_ = 0, even_priority_ = 0;
|
||||
bool even_over_odd_ = false;
|
||||
bool hold_and_modify_ = false;
|
||||
bool dual_playfields_ = false;
|
||||
bool interlace_ = false;
|
||||
bool is_long_field_ = false;
|
||||
|
||||
BitplaneShifter bitplane_pixels_;
|
||||
|
||||
int odd_delay_ = 0, even_delay_ = 0;
|
||||
bool is_high_res_ = false;
|
||||
|
||||
// MARK: - Copper.
|
||||
|
||||
Copper copper_;
|
||||
|
||||
// MARK: - Audio.
|
||||
|
||||
Audio audio_;
|
||||
|
||||
// MARK: - Serial port.
|
||||
|
||||
class SerialPort {
|
||||
public:
|
||||
void set_control(uint16_t);
|
||||
void set_data(uint16_t);
|
||||
uint16_t get_status();
|
||||
|
||||
private:
|
||||
uint16_t value = 0, reload = 0;
|
||||
uint16_t shift = 0, receive_shift = 0;
|
||||
uint16_t status;
|
||||
} serial_;
|
||||
|
||||
// MARK: - Pixel output.
|
||||
|
||||
Outputs::CRT::CRT crt_;
|
||||
uint16_t palette_[32]{};
|
||||
uint16_t swizzled_palette_[64]{};
|
||||
|
||||
// MARK: - Mouse.
|
||||
private:
|
||||
Mouse mouse_;
|
||||
|
||||
public:
|
||||
Inputs::Mouse &get_mouse() {
|
||||
return mouse_;
|
||||
}
|
||||
|
||||
// MARK: - Joystick.
|
||||
private:
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
Joystick &joystick(size_t index) const {
|
||||
return *static_cast<Joystick *>(joysticks_[index].get());
|
||||
}
|
||||
|
||||
// MARK: - CIAs.
|
||||
private:
|
||||
class DiskController;
|
||||
|
||||
class CIAAHandler: public MOS::MOS6526::PortHandler {
|
||||
public:
|
||||
CIAAHandler(MemoryMap &map, DiskController &controller, Mouse &mouse);
|
||||
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
|
||||
uint8_t get_port_input(MOS::MOS6526::Port port);
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
// TEMPORARY.
|
||||
// TODO: generalise mice and joysticks.
|
||||
// This is a hack. A TEMPORARY HACK.
|
||||
void set_joystick(Joystick *joystick) {
|
||||
joystick_ = joystick;
|
||||
}
|
||||
|
||||
private:
|
||||
MemoryMap &map_;
|
||||
DiskController &controller_;
|
||||
Mouse &mouse_;
|
||||
Joystick *joystick_ = nullptr;
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
inline static const std::string led_name = "Power";
|
||||
} cia_a_handler_;
|
||||
|
||||
class CIABHandler: public MOS::MOS6526::PortHandler {
|
||||
public:
|
||||
CIABHandler(DiskController &controller);
|
||||
void set_port_output(MOS::MOS6526::Port port, uint8_t value);
|
||||
uint8_t get_port_input(MOS::MOS6526::Port);
|
||||
|
||||
private:
|
||||
DiskController &controller_;
|
||||
} cia_b_handler_;
|
||||
|
||||
public:
|
||||
using CIAA = MOS::MOS6526::MOS6526<CIAAHandler, MOS::MOS6526::Personality::P8250>;
|
||||
using CIAB = MOS::MOS6526::MOS6526<CIABHandler, MOS::MOS6526::Personality::P8250>;
|
||||
|
||||
// CIAs are provided for direct access; it's up to the caller properly
|
||||
// to distinguish relevant accesses.
|
||||
CIAA cia_a;
|
||||
CIAB cia_b;
|
||||
|
||||
private:
|
||||
// MARK: - Disk drives.
|
||||
|
||||
class DiskDMA: public DMADevice<1> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
void set_length(uint16_t);
|
||||
void set_control(uint16_t);
|
||||
bool advance_dma();
|
||||
|
||||
void enqueue(uint16_t value, bool matches_sync);
|
||||
|
||||
private:
|
||||
uint16_t length_;
|
||||
bool dma_enable_ = false;
|
||||
bool write_ = false;
|
||||
uint16_t last_set_length_ = 0;
|
||||
bool sync_with_word_ = false;
|
||||
|
||||
std::array<uint16_t, 4> buffer_;
|
||||
size_t buffer_read_ = 0, buffer_write_ = 0;
|
||||
|
||||
enum class State {
|
||||
Inactive,
|
||||
WaitingForSync,
|
||||
Reading,
|
||||
} state_ = State::Inactive;
|
||||
} disk_;
|
||||
|
||||
class DiskController: public Storage::Disk::Controller {
|
||||
public:
|
||||
DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia);
|
||||
|
||||
void set_mtr_sel_side_dir_step(uint8_t);
|
||||
uint8_t get_rdy_trk0_wpro_chng();
|
||||
|
||||
void run_for(Cycles duration) {
|
||||
Storage::Disk::Controller::run_for(duration);
|
||||
}
|
||||
|
||||
bool insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive);
|
||||
void set_activity_observer(Activity::Observer *);
|
||||
|
||||
void set_sync_word(uint16_t);
|
||||
void set_control(uint16_t);
|
||||
|
||||
private:
|
||||
void process_input_bit(int value) final;
|
||||
void process_index_hole() final;
|
||||
|
||||
// Implement the Amiga's drive ID shift registers
|
||||
// directly in the controller for now.
|
||||
uint32_t drive_ids_[4]{};
|
||||
uint32_t previous_select_ = 0;
|
||||
|
||||
uint16_t data_ = 0;
|
||||
int bit_count_ = 0;
|
||||
uint16_t sync_word_ = 0x4489; // TODO: confirm or deny guess.
|
||||
bool sync_with_word_ = false;
|
||||
|
||||
Chipset &chipset_;
|
||||
DiskDMA &disk_dma_;
|
||||
CIAB &cia_;
|
||||
|
||||
} disk_controller_;
|
||||
friend DiskController;
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;
|
||||
bool disk_controller_is_sleeping_ = false;
|
||||
uint16_t paula_disk_control_ = 0;
|
||||
|
||||
// MARK: - Keyboard.
|
||||
|
||||
Keyboard keyboard_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Chipset_hpp */
|
||||
147
Machines/Amiga/Copper.cpp
Normal file
147
Machines/Amiga/Copper.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// Copper.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/09/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
#define LOG_PREFIX "[Copper] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
#include "Chipset.hpp"
|
||||
#include "Copper.hpp"
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
namespace {
|
||||
|
||||
bool satisfies_raster(uint16_t position, uint16_t blitter_status, uint16_t *instruction) {
|
||||
// Return immediately if: (i) wait-for-Blitter is not disabled; and (ii) the Blitter is busy.
|
||||
if(!(instruction[1] & 0x8000) && (blitter_status & 0x4000)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, test the raster position against the instruction's value and mask.
|
||||
const uint16_t mask = 0x8000 | (instruction[1] & 0x7ffe);
|
||||
return (position & mask) >= (instruction[0] & mask);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// Quick notes on the Copper:
|
||||
//
|
||||
// There are three instructions: move, wait and skip. All are two words in length.
|
||||
//
|
||||
// Move writes a value to one of the Chipset registers; it is encoded as:
|
||||
//
|
||||
// First word:
|
||||
// b0: 0
|
||||
// b1–b8: register address
|
||||
// b9+: unused ("should be set to 0")
|
||||
//
|
||||
// Second word:
|
||||
// b0–b15: value to move.
|
||||
//
|
||||
//
|
||||
// Wait waits until the raster gets to at least a certain position, and
|
||||
// optionally until the Blitter has finished. It is encoded as:
|
||||
//
|
||||
// First word:
|
||||
// b0: 1
|
||||
// b1–b7: horizontal beam position
|
||||
// b8+: vertical beam position
|
||||
//
|
||||
// Second word:
|
||||
// b0: 0
|
||||
// b1–b7: horizontal beam comparison mask
|
||||
// b8–b14: vertical beam comparison mask
|
||||
// b15: 1 => don't also wait for the Blitter to be finished; 0 => wait.
|
||||
//
|
||||
//
|
||||
// Skip skips the next instruction if the raster has already reached a certain
|
||||
// position, and optionally only if the Blitter has finished, and only if the
|
||||
// next instruction is a move.
|
||||
//
|
||||
// First word:
|
||||
// b0: 1
|
||||
// b1–b7: horizontal beam position
|
||||
// b8+: vertical beam position
|
||||
//
|
||||
// Second word:
|
||||
// b0: 1
|
||||
// b1–b7: horizontal beam comparison mask
|
||||
// b8–b14: vertical beam comparison mask
|
||||
// b15: 1 => don't also test whether the Blitter is finished; 0 => test.
|
||||
//
|
||||
bool Copper::advance_dma(uint16_t position, uint16_t blitter_status) {
|
||||
switch(state_) {
|
||||
default: return false;
|
||||
|
||||
case State::Waiting:
|
||||
if(satisfies_raster(position, blitter_status, instruction_)) {
|
||||
LOG("Unblocked waiting for " << PADHEX(4) << instruction_[0] << " at " << PADHEX(4) << position << " with mask " << PADHEX(4) << (instruction_[1] & 0x7ffe));
|
||||
state_ = State::FetchFirstWord;
|
||||
}
|
||||
return false;
|
||||
|
||||
case State::FetchFirstWord:
|
||||
instruction_[0] = ram_[address_ & ram_mask_];
|
||||
++address_;
|
||||
state_ = State::FetchSecondWord;
|
||||
LOG("First word fetch at " << PADHEX(4) << position);
|
||||
break;
|
||||
|
||||
case State::FetchSecondWord: {
|
||||
// Get and reset the should-skip-next flag.
|
||||
const bool should_skip_move = skip_next_;
|
||||
skip_next_ = false;
|
||||
|
||||
// Read in the second instruction word.
|
||||
instruction_[1] = ram_[address_ & ram_mask_];
|
||||
++address_;
|
||||
LOG("Second word fetch at " << PADHEX(4) << position);
|
||||
|
||||
// Check for a MOVE.
|
||||
if(!(instruction_[0] & 1)) {
|
||||
if(!should_skip_move) {
|
||||
// Stop if this move would be a privilege violation.
|
||||
instruction_[0] &= 0x1fe;
|
||||
if((instruction_[0] < 0x10) || (instruction_[0] < 0x20 && !(control_&1))) {
|
||||
LOG("Invalid MOVE to " << PADHEX(4) << instruction_[0] << "; stopping");
|
||||
state_ = State::Stopped;
|
||||
break;
|
||||
}
|
||||
|
||||
chipset_.write(instruction_[0], instruction_[1]);
|
||||
}
|
||||
|
||||
// Roll onto the next command.
|
||||
state_ = State::FetchFirstWord;
|
||||
break;
|
||||
}
|
||||
|
||||
// Got to here => this is a WAIT or a SKIP.
|
||||
|
||||
if(!(instruction_[1] & 1)) {
|
||||
// A WAIT. The wait-for-start-of-next PAL wait of
|
||||
// $FFDF,$FFFE seems to suggest evaluation will happen
|
||||
// in the next cycle rather than this one.
|
||||
state_ = State::Waiting;
|
||||
break;
|
||||
}
|
||||
|
||||
// Neither a WAIT nor a MOVE => a SKIP.
|
||||
|
||||
skip_next_ = satisfies_raster(position, blitter_status, instruction_);
|
||||
state_ = State::FetchFirstWord;
|
||||
} break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
54
Machines/Amiga/Copper.hpp
Normal file
54
Machines/Amiga/Copper.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copper.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/09/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Copper_h
|
||||
#define Copper_h
|
||||
|
||||
#include "DMADevice.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Copper: public DMADevice<2> {
|
||||
public:
|
||||
using DMADevice<2>::DMADevice;
|
||||
|
||||
/// Offers a DMA slot to the Copper, specifying the current beam position and Blitter status.
|
||||
///
|
||||
/// @returns @c true if the slot was used; @c false otherwise.
|
||||
bool advance_dma(uint16_t position, uint16_t blitter_status);
|
||||
|
||||
/// Forces a reload of address @c id (i.e. 0 or 1) and restarts the Copper.
|
||||
template <int id> void reload() {
|
||||
address_ = pointer_[id];
|
||||
state_ = State::FetchFirstWord;
|
||||
}
|
||||
|
||||
/// Sets the Copper control word.
|
||||
void set_control(uint16_t c) {
|
||||
control_ = c;
|
||||
}
|
||||
|
||||
/// Forces the Copper into the stopped state.
|
||||
void stop() {
|
||||
state_ = State::Stopped;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t address_ = 0;
|
||||
uint16_t control_ = 0;
|
||||
|
||||
enum class State {
|
||||
FetchFirstWord, FetchSecondWord, Waiting, Stopped,
|
||||
} state_ = State::Stopped;
|
||||
bool skip_next_ = false;
|
||||
uint16_t instruction_[2]{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Copper_h */
|
||||
74
Machines/Amiga/DMADevice.hpp
Normal file
74
Machines/Amiga/DMADevice.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// DMADevice.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/09/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef DMADevice_hpp
|
||||
#define DMADevice_hpp
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "Flags.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Chipset;
|
||||
|
||||
class DMADeviceBase {
|
||||
public:
|
||||
DMADeviceBase(Chipset &chipset, uint16_t *ram, size_t word_size) :
|
||||
chipset_(chipset), ram_(ram), ram_mask_(uint32_t(word_size - 1)) {}
|
||||
|
||||
void posit_interrupt(Amiga::InterruptFlag::FlagT);
|
||||
|
||||
protected:
|
||||
Chipset &chipset_;
|
||||
uint16_t *const ram_ = nullptr;
|
||||
const uint32_t ram_mask_ = 0;
|
||||
};
|
||||
|
||||
template <size_t num_addresses, size_t num_modulos = 0> class DMADevice: public DMADeviceBase {
|
||||
public:
|
||||
using DMADeviceBase::DMADeviceBase;
|
||||
|
||||
/// Writes the word @c value to the address register @c id, shifting it by @c shift (0 or 16) first.
|
||||
template <int id, int shift> void set_pointer(uint16_t value) {
|
||||
static_assert(id < num_addresses);
|
||||
static_assert(shift == 0 || shift == 16);
|
||||
|
||||
byte_pointer_[id] = (byte_pointer_[id] & (0xffff'0000 >> shift)) | uint32_t(value << shift);
|
||||
pointer_[id] = byte_pointer_[id] >> 1;
|
||||
}
|
||||
|
||||
/// Writes the word @c value to the modulo register @c id, shifting it by @c shift (0 or 16) first.
|
||||
template <int id> void set_modulo(uint16_t value) {
|
||||
static_assert(id < num_modulos);
|
||||
|
||||
// Convert by sign extension.
|
||||
modulos_[id] = uint32_t(int16_t(value) >> 1);
|
||||
}
|
||||
|
||||
template <int id, int shift> uint16_t get_pointer() {
|
||||
// Restore the original least-significant bit.
|
||||
const uint32_t source = (pointer_[id] << 1) | (byte_pointer_[id] & 1);
|
||||
return uint16_t(source >> shift);
|
||||
}
|
||||
|
||||
protected:
|
||||
// These are shifted right one to provide word-indexing pointers;
|
||||
// subclasses should use e.g. ram_[pointer_[0] & ram_mask_] directly.
|
||||
std::array<uint32_t, num_addresses> pointer_{};
|
||||
std::array<uint32_t, num_modulos> modulos_{};
|
||||
|
||||
private:
|
||||
std::array<uint32_t, num_addresses> byte_pointer_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* DMADevice_hpp */
|
||||
263
Machines/Amiga/Disk.cpp
Normal file
263
Machines/Amiga/Disk.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
//
|
||||
// Disk.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Chipset.hpp"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define NDEBUG
|
||||
#endif
|
||||
|
||||
#define LOG_PREFIX "[Disk] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
// MARK: - Disk DMA.
|
||||
|
||||
void Chipset::DiskDMA::enqueue(uint16_t value, bool matches_sync) {
|
||||
if(matches_sync && state_ == State::WaitingForSync) {
|
||||
state_ = State::Reading;
|
||||
return;
|
||||
}
|
||||
|
||||
if(state_ == State::Reading) {
|
||||
buffer_[buffer_write_ & 3] = value;
|
||||
if(buffer_write_ == buffer_read_ + 4) {
|
||||
++buffer_read_;
|
||||
}
|
||||
++buffer_write_;
|
||||
}
|
||||
}
|
||||
|
||||
void Chipset::DiskDMA::set_control(uint16_t control) {
|
||||
sync_with_word_ = control & 0x400;
|
||||
}
|
||||
|
||||
void Chipset::DiskDMA::set_length(uint16_t value) {
|
||||
if(value == last_set_length_) {
|
||||
dma_enable_ = value & 0x8000;
|
||||
write_ = value & 0x4000;
|
||||
length_ = value & 0x3fff;
|
||||
buffer_read_ = buffer_write_ = 0;
|
||||
|
||||
if(dma_enable_) {
|
||||
LOG("Disk DMA " << (write_ ? "write" : "read") << " of " << length_ << " to " << PADHEX(8) << pointer_[0]);
|
||||
}
|
||||
|
||||
state_ = sync_with_word_ ? State::WaitingForSync : State::Reading;
|
||||
}
|
||||
|
||||
last_set_length_ = value;
|
||||
}
|
||||
|
||||
bool Chipset::DiskDMA::advance_dma() {
|
||||
if(!dma_enable_) return false;
|
||||
|
||||
if(!write_) {
|
||||
if(length_ && buffer_read_ != buffer_write_) {
|
||||
ram_[pointer_[0] & ram_mask_] = buffer_[buffer_read_ & 3];
|
||||
++pointer_[0];
|
||||
++buffer_read_;
|
||||
--length_;
|
||||
|
||||
if(!length_) {
|
||||
chipset_.posit_interrupt(InterruptFlag::DiskBlock);
|
||||
state_ = State::Inactive;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// MARK: - Disk Controller.
|
||||
|
||||
Chipset::DiskController::DiskController(Cycles clock_rate, Chipset &chipset, DiskDMA &disk_dma, CIAB &cia) :
|
||||
Storage::Disk::Controller(clock_rate),
|
||||
chipset_(chipset),
|
||||
disk_dma_(disk_dma),
|
||||
cia_(cia) {
|
||||
|
||||
// Add four drives.
|
||||
for(int c = 0; c < 4; c++) {
|
||||
emplace_drive(clock_rate.as<int>(), 300, 2, Storage::Disk::Drive::ReadyType::IBMRDY);
|
||||
}
|
||||
}
|
||||
|
||||
void Chipset::DiskController::process_input_bit(int value) {
|
||||
data_ = uint16_t((data_ << 1) | value);
|
||||
++bit_count_;
|
||||
|
||||
const bool sync_matches = data_ == sync_word_;
|
||||
if(sync_matches) {
|
||||
chipset_.posit_interrupt(InterruptFlag::DiskSyncMatch);
|
||||
|
||||
if(sync_with_word_) {
|
||||
bit_count_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(!(bit_count_ & 15)) {
|
||||
disk_dma_.enqueue(data_, sync_matches);
|
||||
}
|
||||
}
|
||||
|
||||
void Chipset::DiskController::set_sync_word(uint16_t value) {
|
||||
LOG("Set disk sync word to " << PADHEX(4) << value);
|
||||
sync_word_ = value;
|
||||
}
|
||||
|
||||
void Chipset::DiskController::set_control(uint16_t control) {
|
||||
// b13 and b14: precompensation length specifier
|
||||
// b12: 0 => GCR precompensation; 1 => MFM.
|
||||
// b10: 1 => enable use of word sync; 0 => disable.
|
||||
// b9: 1 => sync on MSB (Disk II style, presumably?); 0 => don't.
|
||||
// b8: 1 => 2µs per bit; 0 => 4µs.
|
||||
|
||||
sync_with_word_ = control & 0x400;
|
||||
|
||||
Storage::Time bit_length;
|
||||
bit_length.length = 1;
|
||||
bit_length.clock_rate = (control & 0x100) ? 500000 : 250000;
|
||||
set_expected_bit_length(bit_length);
|
||||
|
||||
LOG((sync_with_word_ ? "Will" : "Won't") << " sync with word; bit length is " << ((control & 0x100) ? "short" : "long"));
|
||||
}
|
||||
|
||||
void Chipset::DiskController::process_index_hole() {
|
||||
// Pulse the CIA flag input.
|
||||
//
|
||||
// TODO: rectify once drives do an actual index pulse, with length.
|
||||
cia_.set_flag_input(true);
|
||||
cia_.set_flag_input(false);
|
||||
|
||||
// Resync word output. Experimental!!
|
||||
bit_count_ = 0;
|
||||
}
|
||||
|
||||
void Chipset::DiskController::set_mtr_sel_side_dir_step(uint8_t value) {
|
||||
// b7: /MTR
|
||||
// b6: /SEL3
|
||||
// b5: /SEL2
|
||||
// b4: /SEL1
|
||||
// b3: /SEL0
|
||||
// b2: /SIDE
|
||||
// b1: DIR
|
||||
// b0: /STEP
|
||||
|
||||
// Select active drive.
|
||||
set_drive(((value >> 3) & 0x0f) ^ 0x0f);
|
||||
|
||||
// "[The MTR] signal is nonstandard on the Amiga system.
|
||||
// Each drive will latch the motor signal at the time its
|
||||
// select signal turns on." — The Hardware Reference Manual.
|
||||
const auto difference = int(previous_select_ ^ value);
|
||||
previous_select_ = value;
|
||||
|
||||
// Check for changes in the SEL line per drive.
|
||||
const bool motor_on = !(value & 0x80);
|
||||
const int side = (value & 0x04) ? 0 : 1;
|
||||
const bool did_step = difference & value & 0x01;
|
||||
const auto direction = Storage::Disk::HeadPosition(
|
||||
(value & 0x02) ? -1 : 1
|
||||
);
|
||||
|
||||
for(int c = 0; c < 4; c++) {
|
||||
auto &drive = get_drive(size_t(c));
|
||||
const int select_mask = 0x08 << c;
|
||||
const bool is_selected = !(value & select_mask);
|
||||
|
||||
// Both the motor state and the ID shifter are affected upon
|
||||
// changes in drive selection only.
|
||||
if(difference & select_mask) {
|
||||
// If transitioning to inactive, shift the drive ID value;
|
||||
// if transitioning to active, possibly reset the drive
|
||||
// ID and definitely latch the new motor state.
|
||||
if(!is_selected) {
|
||||
drive_ids_[c] <<= 1;
|
||||
LOG("Shifted drive ID shift register for drive " << +c << " to " << PADHEX(4) << std::bitset<16>{drive_ids_[c]});
|
||||
} else {
|
||||
// Motor transition on -> off => reload register.
|
||||
if(!motor_on && drive.get_motor_on()) {
|
||||
// NB:
|
||||
// 0xffff'ffff = 3.5" drive;
|
||||
// 0x5555'5555 = 5.25" drive;
|
||||
// 0x0000'0000 = no drive.
|
||||
drive_ids_[c] = 0xffff'ffff;
|
||||
LOG("Reloaded drive ID shift register for drive " << +c);
|
||||
}
|
||||
|
||||
// Also latch the new motor state.
|
||||
drive.set_motor_on(motor_on);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the new side.
|
||||
drive.set_head(side);
|
||||
|
||||
// Possibly step.
|
||||
if(did_step && is_selected) {
|
||||
LOG("Stepped drive " << +c << " by " << std::dec << +direction.as_int());
|
||||
drive.step(direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Chipset::DiskController::get_rdy_trk0_wpro_chng() {
|
||||
// b5: /RDY
|
||||
// b4: /TRK0
|
||||
// b3: /WPRO
|
||||
// b2: /CHNG
|
||||
|
||||
// My interpretation:
|
||||
//
|
||||
// RDY isn't RDY, it's a shift value as described above, combined with the motor state.
|
||||
// CHNG is what is normally RDY.
|
||||
|
||||
const uint32_t combined_id =
|
||||
((previous_select_ & 0x40) ? 0 : drive_ids_[3]) |
|
||||
((previous_select_ & 0x20) ? 0 : drive_ids_[2]) |
|
||||
((previous_select_ & 0x10) ? 0 : drive_ids_[1]) |
|
||||
((previous_select_ & 0x08) ? 0 : drive_ids_[0]);
|
||||
|
||||
auto &drive = get_drive();
|
||||
const uint8_t active_high =
|
||||
((combined_id & 0x8000) >> 10) |
|
||||
(drive.get_motor_on() ? 0x20 : 0x00) |
|
||||
(drive.get_is_ready() ? 0x00 : 0x04) |
|
||||
(drive.get_is_track_zero() ? 0x10 : 0x00) |
|
||||
(drive.get_is_read_only() ? 0x08 : 0x00);
|
||||
|
||||
return ~active_high;
|
||||
}
|
||||
|
||||
void Chipset::DiskController::set_activity_observer(Activity::Observer *observer) {
|
||||
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
|
||||
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
|
||||
});
|
||||
}
|
||||
|
||||
bool Chipset::DiskController::insert(const std::shared_ptr<Storage::Disk::Disk> &disk, size_t drive) {
|
||||
if(drive >= 4) return false;
|
||||
get_drive(drive).set_disk(disk);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Chipset::insert(const std::vector<std::shared_ptr<Storage::Disk::Disk>> &disks) {
|
||||
bool inserted = false;
|
||||
|
||||
size_t target = 0;
|
||||
for(const auto &disk: disks) {
|
||||
inserted |= disk_controller_.insert(disk, target);
|
||||
++target;
|
||||
}
|
||||
|
||||
return inserted;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user