mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
974 Commits
2021-04-19
...
2022-03-26
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3dd2db815 | ||
|
|
290dd3993b | ||
|
|
ee6470708b | ||
|
|
61f25926b5 | ||
|
|
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 | ||
|
|
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 | ||
|
|
77befb7f8e | ||
|
|
86c6248b48 | ||
|
|
f2af8ff25d | ||
|
|
7d8894415c | ||
|
|
f8380d2d4c | ||
|
|
5cc25d0846 | ||
|
|
1502c4530e | ||
|
|
c1df4d1c0b | ||
|
|
1f9e41e9cb | ||
|
|
e402e690b0 | ||
|
|
6a15bb15ca | ||
|
|
3255fc91fa | ||
|
|
7f2610c4fc | ||
|
|
79bd3eb6ae | ||
|
|
b11dd6950c | ||
|
|
98bd6fc240 | ||
|
|
8be053fd35 | ||
|
|
99fee22a9f | ||
|
|
084d002353 | ||
|
|
dcbc9847a3 | ||
|
|
db3c158215 | ||
|
|
25e2bd307a | ||
|
|
b9f78f5d33 | ||
|
|
b4ec9d70da | ||
|
|
738999a8b7 | ||
|
|
dd91d793d9 | ||
|
|
1f0bf1b32d | ||
|
|
8e51e8eb77 | ||
|
|
6210605bc7 | ||
|
|
0245b040b0 | ||
|
|
34c1cc5693 | ||
|
|
8795719c18 | ||
|
|
6bbbf43341 | ||
|
|
f0ef45f0ca | ||
|
|
ee6039bfa5 | ||
|
|
ef58ce6277 | ||
|
|
15de5e98c4 | ||
|
|
38848ca2db | ||
|
|
77c627e822 | ||
|
|
c640132699 | ||
|
|
60b09d9bb0 | ||
|
|
57dd38aef2 | ||
|
|
460a6cb6fe | ||
|
|
fdb676da4e | ||
|
|
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 | ||
|
|
038ed0551e | ||
|
|
cfaf4a8a65 | ||
|
|
22dd8a8847 | ||
|
|
b2ae8e7a4a | ||
|
|
3e2bac8129 | ||
|
|
50b9d0e86d | ||
|
|
a030d9935e | ||
|
|
c425dec4d5 | ||
|
|
67d53601d5 | ||
|
|
622cca0acf | ||
|
|
48999c03a5 | ||
|
|
377cc7bdcd | ||
|
|
a5d0976c2d | ||
|
|
ae05010255 | ||
|
|
66cacbd0e0 | ||
|
|
b1616be4b8 | ||
|
|
a0a9a72d8f | ||
|
|
0cfc7f732c | ||
|
|
f7de6f790c | ||
|
|
d1f3b5ed80 | ||
|
|
7925dcc5a2 | ||
|
|
6ade36bf09 | ||
|
|
c52945aab5 | ||
|
|
2b0a4055f7 | ||
|
|
7cb16a3fc5 | ||
|
|
0b80c1988b | ||
|
|
eab9bc1503 | ||
|
|
5bfedff8d1 | ||
|
|
c8638c0ffb | ||
|
|
8a95b91e2a | ||
|
|
c226be612f | ||
|
|
c8699d9770 | ||
|
|
a0799e14cc | ||
|
|
dea6048849 | ||
|
|
813e252539 | ||
|
|
b41e29a83b | ||
|
|
d35c7ad127 | ||
|
|
ea63415d0e | ||
|
|
52ea3b741c | ||
|
|
2731ca8c92 | ||
|
|
af1ade9433 | ||
|
|
fc248951cc | ||
|
|
84547ee1c1 | ||
|
|
a42848c62f | ||
|
|
c7b5d69431 | ||
|
|
81374b70b5 | ||
|
|
47a530fd5c | ||
|
|
58451d7c0c | ||
|
|
5c8f8c76fe | ||
|
|
ae1d1bdb5b | ||
|
|
33cc1154a2 | ||
|
|
4bc0b75c30 | ||
|
|
eb8ec1efb1 | ||
|
|
616f8efc47 | ||
|
|
29e4369420 | ||
|
|
bd7f7bc8d7 | ||
|
|
e689ca92c4 | ||
|
|
4ef3005072 | ||
|
|
174c837767 | ||
|
|
486bb911a9 | ||
|
|
754221d697 | ||
|
|
3c36c90729 | ||
|
|
3d1d15a25b | ||
|
|
000d99f26c | ||
|
|
524e2abc8c | ||
|
|
00bab98e09 | ||
|
|
6d98349be1 | ||
|
|
d24d153c08 | ||
|
|
b01561712c | ||
|
|
324edcb391 | ||
|
|
6e62e4e296 | ||
|
|
f81ecbf4a0 | ||
|
|
4370456323 | ||
|
|
a424ed7c00 | ||
|
|
a2065f59a1 | ||
|
|
c1bd7f5c67 | ||
|
|
5810a1a98e | ||
|
|
a4c011e3c0 | ||
|
|
337fd15dc0 | ||
|
|
9bc94f4536 | ||
|
|
3f4cf35384 | ||
|
|
4dd7f2cc09 | ||
|
|
1b29cc34c4 | ||
|
|
53c3c1f5ab | ||
|
|
6225abd751 | ||
|
|
c6fcd9a1eb | ||
|
|
30fbb6ea53 | ||
|
|
0e49258546 | ||
|
|
264b8dfb28 | ||
|
|
6a15b8f695 | ||
|
|
5167d256cc | ||
|
|
16bd826491 | ||
|
|
55af8fa5d9 | ||
|
|
1ec8ff20af | ||
|
|
99a65d3297 | ||
|
|
94907b51aa | ||
|
|
0085265d13 | ||
|
|
8e0893bd42 | ||
|
|
704dc9bdcb | ||
|
|
7a673a2448 | ||
|
|
33e2a4b21c | ||
|
|
3e6b804896 | ||
|
|
e98165a657 | ||
|
|
2a7727d12b | ||
|
|
c20e8f4062 | ||
|
|
4ca9db7d49 | ||
|
|
4add48cffb | ||
|
|
adbfb009f8 | ||
|
|
43ceca8711 | ||
|
|
3ef28a4f03 | ||
|
|
adcd580d5b | ||
|
|
5715c9183f | ||
|
|
ceb62ac7f9 | ||
|
|
bda0756620 | ||
|
|
6b47fb38c6 | ||
|
|
38bf8a06a7 | ||
|
|
196651d9aa | ||
|
|
6b46212a4e | ||
|
|
2a6fff2008 | ||
|
|
c5944efe50 | ||
|
|
f384370b18 | ||
|
|
0c09275a9f | ||
|
|
278671cdb9 | ||
|
|
964d2d4fa4 | ||
|
|
f371221dba | ||
|
|
27b0579ec6 | ||
|
|
283092cfbc | ||
|
|
614953a222 | ||
|
|
4fffb3cf19 | ||
|
|
850aa2b23a | ||
|
|
d715e5fd1d | ||
|
|
7826a26c7b | ||
|
|
dc0a82cf9a | ||
|
|
2e60c81bd6 | ||
|
|
763b9ba0ec | ||
|
|
bae8bb0c00 | ||
|
|
bcf483fb7e | ||
|
|
a5b7d819a7 | ||
|
|
fe07a0b1d8 | ||
|
|
d9231e5d4a | ||
|
|
b7aa1a1c84 | ||
|
|
32e144115d | ||
|
|
177cc96f49 | ||
|
|
51d98ef9ab | ||
|
|
2327c48cc4 | ||
|
|
742d44a532 | ||
|
|
52b96db2b9 | ||
|
|
0b9de78c38 | ||
|
|
2c28cb8c57 | ||
|
|
483fe82e9d | ||
|
|
29492d6138 | ||
|
|
19310e32c4 | ||
|
|
c04a395499 | ||
|
|
1c424833a9 | ||
|
|
a46ff5590d | ||
|
|
ab059b63fd | ||
|
|
3d8fc9952d | ||
|
|
8ce8fbd977 | ||
|
|
7f08218b28 | ||
|
|
2c139ad931 | ||
|
|
1119779c8b | ||
|
|
5351ac560f | ||
|
|
49f0ab0f15 | ||
|
|
a5c57e777e | ||
|
|
3c59042388 | ||
|
|
919e211bc4 | ||
|
|
daa0737ce4 | ||
|
|
36805cb120 | ||
|
|
7de69e9874 | ||
|
|
b93575bbcc | ||
|
|
116e0f0105 | ||
|
|
e4a650aaff | ||
|
|
b5312b9ba0 | ||
|
|
6afee7bb9b | ||
|
|
5729e6e13a | ||
|
|
2f53b105bb | ||
|
|
b698056f78 | ||
|
|
95c906f03d | ||
|
|
be19fa9dde | ||
|
|
81e9ba5608 | ||
|
|
f2d7b9f6a9 | ||
|
|
1ea034310a | ||
|
|
bdcab447f9 | ||
|
|
10bf6744aa | ||
|
|
895d98e266 | ||
|
|
903e343895 | ||
|
|
f8b7c59616 | ||
|
|
fcd267a3f9 | ||
|
|
f8bb66d2a0 | ||
|
|
90782d3c27 | ||
|
|
f2336d2efc | ||
|
|
c2d093fa3c | ||
|
|
1a97cc8a91 | ||
|
|
c34a548fa0 | ||
|
|
d1b89392a2 | ||
|
|
ed734754e5 | ||
|
|
520c3c9218 | ||
|
|
9230cf1726 | ||
|
|
6e616972a5 | ||
|
|
f98888824d | ||
|
|
6c8b23e708 | ||
|
|
2c2bb3765f | ||
|
|
0d165740ea | ||
|
|
88f0f2b623 | ||
|
|
0afa143375 | ||
|
|
8319aca351 | ||
|
|
a66734883a | ||
|
|
d2ab0dd839 | ||
|
|
2574407afb | ||
|
|
83a54fd6d2 | ||
|
|
e062780968 | ||
|
|
3acd0be1f7 | ||
|
|
69c0734975 | ||
|
|
c1678d7be7 | ||
|
|
117f9a9794 | ||
|
|
b49cc407c6 | ||
|
|
954386f1cc | ||
|
|
d7ff6bd04d | ||
|
|
6025516f9f | ||
|
|
d8b9cdf7a2 | ||
|
|
09dbff39f2 | ||
|
|
2fe15a6168 | ||
|
|
07dc26f8fa | ||
|
|
a08d65b1ff | ||
|
|
199621db08 | ||
|
|
0e1e8c7faa | ||
|
|
42a98e1676 | ||
|
|
23e26e0333 | ||
|
|
fadb04f3f3 | ||
|
|
4968ccf46d | ||
|
|
1dcac304d3 | ||
|
|
1651efe4fc | ||
|
|
8f24aed43e | ||
|
|
a381374e31 | ||
|
|
9411c37d23 | ||
|
|
6af6f21868 | ||
|
|
9a0022cfcb | ||
|
|
266310d9c2 | ||
|
|
fbf1adef05 | ||
|
|
311ddfb05a | ||
|
|
2fd8a8aa66 | ||
|
|
0c3e9dca28 | ||
|
|
c331d15429 | ||
|
|
4414e96710 | ||
|
|
7161783a4f | ||
|
|
cbac48da86 | ||
|
|
d9142d5427 | ||
|
|
e5e988b28f | ||
|
|
e94e051c87 | ||
|
|
5fc91effb5 | ||
|
|
6c9dacbe89 | ||
|
|
6a7eb832cc | ||
|
|
60cf8486bb | ||
|
|
90b8163d54 | ||
|
|
a1e4389f63 | ||
|
|
440b11708b | ||
|
|
f90dce5c54 | ||
|
|
606c7709cf | ||
|
|
1d1e6d1820 | ||
|
|
3eb4dd74a2 | ||
|
|
853914480c | ||
|
|
fe04410681 | ||
|
|
1f686c4e6b | ||
|
|
2a2ac1227b | ||
|
|
b5340c8f74 | ||
|
|
196c4dcdd9 | ||
|
|
c5a86f0ef7 | ||
|
|
88f2a2940b | ||
|
|
26b019a4d4 | ||
|
|
5f7b3ae313 | ||
|
|
61c127ed2e | ||
|
|
333981e2a7 | ||
|
|
423fbc9ac7 | ||
|
|
1c1719e561 | ||
|
|
56c30e1651 | ||
|
|
1ea4130035 | ||
|
|
57713d63fa | ||
|
|
d18a537509 | ||
|
|
8e0a6df03b | ||
|
|
95a52a9f62 | ||
|
|
ae2993625c | ||
|
|
0982141442 | ||
|
|
85fab2abc4 | ||
|
|
de3b37799c | ||
|
|
70851f3b2d | ||
|
|
462bbf2e40 | ||
|
|
778b9ef683 | ||
|
|
96e7eb1bed | ||
|
|
05671f3553 | ||
|
|
6e4832f999 | ||
|
|
54e3332673 | ||
|
|
6c559d7556 | ||
|
|
9165a85484 | ||
|
|
98ada2588a | ||
|
|
43f686c22d | ||
|
|
4a2673d757 | ||
|
|
f27e331462 | ||
|
|
dd64aef910 | ||
|
|
95971f39f1 | ||
|
|
83beb3c0e6 | ||
|
|
76335e5cf2 | ||
|
|
4494320238 | ||
|
|
5acd97c860 | ||
|
|
b0f551c307 | ||
|
|
b6b3d845a3 | ||
|
|
505d84f336 | ||
|
|
1d5144b912 | ||
|
|
deff91e460 | ||
|
|
afd8dc0915 | ||
|
|
fbee74e1fe | ||
|
|
ccd82591aa | ||
|
|
64931e476d | ||
|
|
604a715a49 | ||
|
|
24757ef20c | ||
|
|
e36cc9e777 | ||
|
|
2e999889bd | ||
|
|
f4db4c3a73 | ||
|
|
d923fe72c0 | ||
|
|
f05cdd5e34 | ||
|
|
f9954619d4 | ||
|
|
0aa8c3c40d | ||
|
|
a30eeaab6a | ||
|
|
3858e79579 | ||
|
|
b4a5fa33b0 | ||
|
|
2a6e9c5e8a | ||
|
|
488c2aed51 | ||
|
|
5483f979dc | ||
|
|
ea11f3826a | ||
|
|
ceae81a332 | ||
|
|
50ea56e908 | ||
|
|
bfb2f79cff | ||
|
|
8268e8ee4c | ||
|
|
cb31e22f59 | ||
|
|
6752f4fd73 | ||
|
|
22c31e4f55 | ||
|
|
c2ff64c1e0 | ||
|
|
4db792591a | ||
|
|
1290a8e32b | ||
|
|
8ae38991b0 | ||
|
|
6d40549c0c | ||
|
|
93d5c9a3c7 | ||
|
|
9af6c0b37a | ||
|
|
7e3528c692 | ||
|
|
41f2fc51be | ||
|
|
11228dc265 | ||
|
|
ef50967793 | ||
|
|
5f6c08b7e0 | ||
|
|
6cb23ec5be | ||
|
|
1bae70bcf8 | ||
|
|
9820591ba4 | ||
|
|
77071b3c69 | ||
|
|
335e839b31 | ||
|
|
6fe947b8b9 | ||
|
|
22b29e77a7 | ||
|
|
4858cfce6b | ||
|
|
8da3e91f5e | ||
|
|
012235bfeb | ||
|
|
052e284c33 | ||
|
|
32e3dd71b1 | ||
|
|
95f4272919 | ||
|
|
00679b6135 | ||
|
|
2c18bb4508 | ||
|
|
0cf1c9040a | ||
|
|
9196341482 | ||
|
|
685140a4c2 | ||
|
|
1465b0ee4d | ||
|
|
0bf6b765d3 | ||
|
|
4774676e2a | ||
|
|
9c29655da2 | ||
|
|
c8ab18f2b6 | ||
|
|
8ebce466db | ||
|
|
1b39b17125 | ||
|
|
5a46853075 | ||
|
|
48ad4d4c4c | ||
|
|
056a036712 | ||
|
|
70eaa79108 | ||
|
|
20c814a4dd | ||
|
|
6a052e1900 | ||
|
|
cecdf8584a | ||
|
|
4758bc8615 | ||
|
|
c906dc3c0a | ||
|
|
d1dcb41b6f | ||
|
|
96ac86a757 | ||
|
|
4919786825 | ||
|
|
24b4185714 | ||
|
|
ad10d0037a | ||
|
|
b6554c8255 | ||
|
|
01dc83d0d6 | ||
|
|
2fd08789ab | ||
|
|
bc9e529995 | ||
|
|
708c24cc57 | ||
|
|
7fb3048257 | ||
|
|
9319f0525a | ||
|
|
b7a62e0121 | ||
|
|
bd5dd9b9a3 | ||
|
|
3348167c46 | ||
|
|
700c505974 | ||
|
|
d403036d86 | ||
|
|
5e08d7db39 | ||
|
|
c34cb310a8 | ||
|
|
8d86aa69bc | ||
|
|
cc41ccc5f1 | ||
|
|
e6252fe0ed | ||
|
|
03577de675 | ||
|
|
205518ba75 | ||
|
|
2510064218 | ||
|
|
0ef2806970 | ||
|
|
d80f03e369 | ||
|
|
fd271d920b | ||
|
|
2bbf8bc9fa | ||
|
|
9b65d56ed0 | ||
|
|
a5098a60ec | ||
|
|
0ebd900e40 | ||
|
|
7aeb17ac92 | ||
|
|
cc78bfb229 | ||
|
|
485c2a866c | ||
|
|
5b419ca5bf | ||
|
|
14ae579fca | ||
|
|
1c2ea0d7fe | ||
|
|
e7a9ae18a1 | ||
|
|
d61f478a39 | ||
|
|
9cc747b3e2 | ||
|
|
2f223f7db2 | ||
|
|
17f11a3be3 | ||
|
|
37dcf61130 | ||
|
|
856ebfacca | ||
|
|
9731fdd33b | ||
|
|
5ea605ccf7 | ||
|
|
d0c789ff9a | ||
|
|
9baa861742 | ||
|
|
30a1a53c97 | ||
|
|
bdb1b7e77c | ||
|
|
9293bcbc88 | ||
|
|
c481f475e7 | ||
|
|
ef01471e17 | ||
|
|
73c8157197 | ||
|
|
af1dc2d3b2 |
@@ -23,10 +23,19 @@ namespace Activity {
|
||||
*/
|
||||
class Observer {
|
||||
public:
|
||||
/// Provides hints as to the sort of information presented on an LED.
|
||||
enum LEDPresentation: uint8_t {
|
||||
/// This LED informs the user of some sort of persistent state, e.g. scroll lock.
|
||||
/// If this flag is absent then the LED describes an ephemeral state, such as media access.
|
||||
Persistent = (1 << 0),
|
||||
};
|
||||
|
||||
/// Announces to the receiver that there is an LED of name @c name.
|
||||
virtual void register_led([[maybe_unused]] const std::string &name) {}
|
||||
virtual void register_led([[maybe_unused]] const std::string &name, [[maybe_unused]] uint8_t presentation = 0) {}
|
||||
|
||||
/// Announces to the receiver that there is a drive of name @c name.
|
||||
///
|
||||
/// If a drive has the same name as an LED, that LED goes with this drive.
|
||||
virtual void register_drive([[maybe_unused]] const std::string &name) {}
|
||||
|
||||
/// Informs the receiver of the new state of the LED with name @c name.
|
||||
|
||||
@@ -17,8 +17,10 @@ enum class Machine {
|
||||
AppleIIgs,
|
||||
Atari2600,
|
||||
AtariST,
|
||||
Amiga,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
Macintosh,
|
||||
MasterSystem,
|
||||
MSX,
|
||||
|
||||
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 */
|
||||
@@ -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) {
|
||||
|
||||
84
Analyser/Static/Enterprise/StaticAnalyser.cpp
Normal file
84
Analyser/Static/Enterprise/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/06/2021.
|
||||
// Copyright 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/FAT.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
|
||||
return std::equal(
|
||||
lhs.begin(), lhs.end(),
|
||||
rhs.begin(), rhs.end(),
|
||||
[] (char l, char r) {
|
||||
return tolower(l) == tolower(r);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// Otherwise, assume a return will happen.
|
||||
Analyser::Static::TargetList targets;
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
auto *const target = new Target;
|
||||
target->media = media;
|
||||
|
||||
// Always require a BASIC.
|
||||
target->basic_version = Target::BASICVersion::Any;
|
||||
|
||||
// Inspect any supplied disks.
|
||||
if(!media.disks.empty()) {
|
||||
// DOS will be needed.
|
||||
target->dos = Target::DOS::EXDOS;
|
||||
|
||||
// Grab the volume information, which includes the root directory.
|
||||
auto volume = Storage::Disk::FAT::GetVolume(media.disks.front());
|
||||
if(volume) {
|
||||
// If there's an EXDOS.INI then this disk should be able to boot itself.
|
||||
// If not but if there's only one visible .COM or .BAS, automatically load
|
||||
// that. Otherwise, issue a :DIR.
|
||||
using File = Storage::Disk::FAT::File;
|
||||
const File *selected_file = nullptr;
|
||||
bool has_exdos_ini = false;
|
||||
bool did_pick_file = false;
|
||||
for(const auto &file: (*volume).root_directory) {
|
||||
if(insensitive_equal(file.name, "exdos") && insensitive_equal(file.extension, "ini")) {
|
||||
has_exdos_ini = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!(file.attributes & File::Attribute::Hidden) &&
|
||||
(insensitive_equal(file.extension, "com") || insensitive_equal(file.extension, "bas"))
|
||||
) {
|
||||
did_pick_file = !selected_file;
|
||||
selected_file = &file;
|
||||
}
|
||||
}
|
||||
|
||||
if(!has_exdos_ini) {
|
||||
if(did_pick_file) {
|
||||
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"\n";
|
||||
} else {
|
||||
target->loading_command = ":dir\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
}
|
||||
27
Analyser/Static/Enterprise/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Enterprise/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/06/2021.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Enterprise_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */
|
||||
57
Analyser/Static/Enterprise/Target.hpp
Normal file
57
Analyser/Static/Enterprise/Target.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Enterprise_Target_h
|
||||
#define Analyser_Static_Enterprise_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
|
||||
ReflectableEnum(EXOSVersion, v10, v20, v21, v23, Any);
|
||||
ReflectableEnum(BASICVersion, v10, v11, v21, Any, None);
|
||||
ReflectableEnum(DOS, EXDOS, None);
|
||||
ReflectableEnum(Speed, FourMHz, SixMHz);
|
||||
|
||||
Model model = Model::Enterprise128;
|
||||
EXOSVersion exos_version = EXOSVersion::Any;
|
||||
BASICVersion basic_version = BASICVersion::None;
|
||||
DOS dos = DOS::None;
|
||||
Speed speed = Speed::FourMHz;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Enterprise) {
|
||||
if(needs_declare()) {
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(EXOSVersion);
|
||||
AnnounceEnum(BASICVersion);
|
||||
AnnounceEnum(DOS);
|
||||
AnnounceEnum(Speed);
|
||||
|
||||
DeclareField(model);
|
||||
DeclareField(exos_version);
|
||||
DeclareField(basic_version);
|
||||
DeclareField(dos);
|
||||
DeclareField(speed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_Target_h */
|
||||
@@ -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"
|
||||
@@ -23,6 +24,7 @@
|
||||
#include "Coleco/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "Enterprise/StaticAnalyser.hpp"
|
||||
#include "Macintosh/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
@@ -37,15 +39,16 @@
|
||||
// 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/MSA.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
@@ -55,8 +58,14 @@
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// State Snapshots
|
||||
#include "../../Storage/State/SNA.hpp"
|
||||
#include "../../Storage/State/SZX.hpp"
|
||||
#include "../../Storage/State/Z80.hpp"
|
||||
|
||||
// Tapes
|
||||
#include "../../Storage/Tape/Formats/CAS.hpp"
|
||||
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
|
||||
@@ -73,15 +82,23 @@
|
||||
|
||||
using namespace Analyser::Static;
|
||||
|
||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||
Media result;
|
||||
namespace {
|
||||
|
||||
std::string get_extension(const std::string &name) {
|
||||
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
|
||||
// test as to file format.
|
||||
std::string::size_type final_dot = file_name.find_last_of(".");
|
||||
if(final_dot == std::string::npos) return result;
|
||||
std::string extension = file_name.substr(final_dot + 1);
|
||||
std::string::size_type final_dot = name.find_last_of(".");
|
||||
if(final_dot == std::string::npos) return name;
|
||||
std::string extension = name.substr(final_dot + 1);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
return extension;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||
Media result;
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
#define InsertInstance(list, instance, platforms) \
|
||||
list.emplace_back(instance);\
|
||||
@@ -113,7 +130,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
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
|
||||
@@ -131,8 +149,9 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // DSK (Amstrad CPC, etc)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
|
||||
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( "hfe",
|
||||
@@ -142,6 +161,7 @@ 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("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
|
||||
@@ -199,14 +219,35 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
|
||||
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetList targets;
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
// Check whether the file directly identifies a target; if so then just return that.
|
||||
#define Format(ext, class) \
|
||||
if(extension == ext) { \
|
||||
try { \
|
||||
auto target = Storage::State::class::load(file_name); \
|
||||
if(target) { \
|
||||
targets.push_back(std::move(target)); \
|
||||
return targets; \
|
||||
} \
|
||||
} catch(...) {} \
|
||||
}
|
||||
|
||||
Format("sna", SNA);
|
||||
Format("szx", SZX);
|
||||
Format("z80", Z80);
|
||||
|
||||
#undef TryInsert
|
||||
|
||||
// Otherwise:
|
||||
//
|
||||
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
|
||||
// union of all platforms this file might be a target for.
|
||||
TargetPlatform::IntType potential_platforms = 0;
|
||||
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||
|
||||
// Hand off to platform-specific determination of whether these things are actually compatible and,
|
||||
// if so, how to load them.
|
||||
// Hand off to platform-specific determination of whether these
|
||||
// things are actually compatible and, if so, how to load them.
|
||||
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
|
||||
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
|
||||
@@ -215,11 +256,13 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
Append(AmstradCPC);
|
||||
Append(AppleII);
|
||||
Append(AppleIIgs);
|
||||
Append(Amiga);
|
||||
Append(Atari2600);
|
||||
Append(AtariST);
|
||||
Append(Coleco);
|
||||
Append(Commodore);
|
||||
Append(DiskII);
|
||||
Append(Enterprise);
|
||||
Append(Macintosh);
|
||||
Append(MSX);
|
||||
Append(Oric);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -23,8 +24,10 @@
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
|
||||
struct State;
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges.
|
||||
A list of disks, tapes and cartridges, and possibly a state snapshot.
|
||||
*/
|
||||
struct Media {
|
||||
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
|
||||
@@ -48,13 +51,16 @@ struct Media {
|
||||
};
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
|
||||
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
||||
Describes a machine and possibly its state; conventionally subclassed to add other machine-specific configuration fields and any
|
||||
necessary instructions on how to launch any software provided, plus a measure of confidence in this target's correctness.
|
||||
*/
|
||||
struct Target {
|
||||
Target(Machine machine) : machine(machine) {}
|
||||
virtual ~Target() {}
|
||||
|
||||
// This field is entirely optional.
|
||||
std::unique_ptr<Reflection::Struct> state;
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
float confidence = 0.0f;
|
||||
|
||||
@@ -27,7 +27,7 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
||||
Plus3,
|
||||
);
|
||||
|
||||
Model model = Model::Plus2a;
|
||||
Model model = Model::Plus2;
|
||||
bool should_hold_enter = false;
|
||||
|
||||
Target(): Analyser::Static::Target(Machine::ZXSpectrum) {
|
||||
|
||||
@@ -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_; }
|
||||
@@ -213,7 +217,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
|
||||
*/
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
@@ -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,6 +9,7 @@
|
||||
#ifndef JustInTime_h
|
||||
#define JustInTime_h
|
||||
|
||||
#include "ClockReceiver.hpp"
|
||||
#include "../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "ClockingHintSource.hpp"
|
||||
#include "ForceInline.hpp"
|
||||
@@ -103,9 +104,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
}
|
||||
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
time_until_event_ -= rhs;
|
||||
time_until_event_ -= rhs * multiplier;
|
||||
if(time_until_event_ <= LocalTimeScale(0)) {
|
||||
time_overrun_ = time_until_event_;
|
||||
time_overrun_ = time_until_event_ / divider;
|
||||
flush();
|
||||
update_sequence_point();
|
||||
return true;
|
||||
@@ -145,13 +146,21 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, in the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
|
||||
// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_;
|
||||
}
|
||||
return TargetTimeScale(time_since_update_.as_integral() / divider);
|
||||
}
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
|
||||
/// converted to the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_ + offset;
|
||||
}
|
||||
return TargetTimeScale((time_since_update_ + offset).as_integral() / divider);
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
///
|
||||
/// This does not affect this actor's record of when the next sequence point will occur.
|
||||
@@ -185,7 +194,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
|
||||
/// supports sequence points; @c LocalTimeScale() otherwise.
|
||||
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
|
||||
return time_until_event_;
|
||||
return time_until_event_ / divider;
|
||||
}
|
||||
|
||||
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
|
||||
@@ -196,10 +205,43 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
return rhs >= time_until_event_;
|
||||
}
|
||||
|
||||
/// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly
|
||||
/// after @c duration, if that delay were to occur in @c offset units of time from now.
|
||||
[[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const {
|
||||
// A 1:1 mapping is easy.
|
||||
if constexpr (multiplier == 1 && divider == 1) {
|
||||
return duration;
|
||||
}
|
||||
|
||||
// Work out when this query is placed, and the time to which it relates
|
||||
const auto base = time_since_update_ + offset * divider;
|
||||
const auto target = base + duration * divider;
|
||||
|
||||
// Figure out the number of whole input steps that is required to get
|
||||
// past target, and subtract the number of whole input steps necessary
|
||||
// to get to base.
|
||||
const auto steps_to_base = base.as_integral() / multiplier;
|
||||
const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier;
|
||||
|
||||
return LocalTimeScale(steps_to_target - steps_to_base);
|
||||
}
|
||||
|
||||
/// Updates this template's record of the next sequence point.
|
||||
void update_sequence_point() {
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
time_until_event_ = object_.get_next_sequence_point();
|
||||
// Keep a fast path where no conversions will be applied; if conversions are
|
||||
// going to be applied then do a direct max -> max translation rather than
|
||||
// allowing the arithmetic to overflow.
|
||||
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
|
||||
time_until_event_ = object_.get_next_sequence_point();
|
||||
} else {
|
||||
const auto time = object_.get_next_sequence_point();
|
||||
if(time == TargetTimeScale::max()) {
|
||||
time_until_event_ = LocalTimeScale::max();
|
||||
} else {
|
||||
time_until_event_ = time * divider;
|
||||
}
|
||||
}
|
||||
assert(time_until_event_ > LocalTimeScale(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,10 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto test_type1_type;
|
||||
|
||||
begin_type1_spin_up:
|
||||
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
|
||||
if((command_&0x08) || get_drive().get_motor_on()) {
|
||||
set_motor_on(true);
|
||||
goto test_type1_type;
|
||||
}
|
||||
SPIN_UP();
|
||||
|
||||
test_type1_type:
|
||||
@@ -387,7 +390,10 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
if((command_&0x08) && has_motor_on_line()) goto test_type2_delay;
|
||||
if(!has_motor_on_line() && !has_head_load_line()) goto test_type2_delay;
|
||||
if(!has_motor_on_line() && !has_head_load_line()) {
|
||||
if(has_motor_on_line()) set_motor_on(true);
|
||||
goto test_type2_delay;
|
||||
}
|
||||
|
||||
if(has_motor_on_line()) goto begin_type2_spin_up;
|
||||
goto begin_type2_load_head;
|
||||
|
||||
@@ -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 */
|
||||
@@ -435,7 +435,7 @@ template <class BusHandler> class MOS6560 {
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
AudioGenerator audio_generator_;
|
||||
Outputs::Speaker::LowpassSpeaker<AudioGenerator> speaker_;
|
||||
Outputs::Speaker::PullLowpass<AudioGenerator> speaker_;
|
||||
|
||||
Cycles cycles_since_speaker_update_;
|
||||
void update_audio() {
|
||||
|
||||
@@ -129,8 +129,8 @@ ClockingHint::Preference ACIA::preferred_clocking() const {
|
||||
// because it's unclear when the interrupt might come.
|
||||
if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
|
||||
|
||||
// No clocking required then.
|
||||
return ClockingHint::Preference::None;
|
||||
// Real-time clocking not required then.
|
||||
return ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
bool ACIA::get_interrupt_line() const {
|
||||
@@ -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();
|
||||
|
||||
@@ -208,7 +208,7 @@ void MFP68901::run_for(HalfCycles time) {
|
||||
}
|
||||
|
||||
HalfCycles MFP68901::get_next_sequence_point() {
|
||||
return HalfCycles(-1);
|
||||
return HalfCycles::max();
|
||||
}
|
||||
|
||||
// MARK: - Timers
|
||||
|
||||
@@ -750,7 +750,7 @@ HalfCycles TMS9918::get_next_sequence_point() {
|
||||
if(next_line_interrupt_row == -1) {
|
||||
return generate_interrupts_ ?
|
||||
half_cycles_before_internal_cycles(time_until_frame_interrupt) :
|
||||
HalfCycles(-1);
|
||||
HalfCycles::max();
|
||||
}
|
||||
|
||||
// Figure out the number of internal cycles until the next line interrupt, which is the amount
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
namespace GI {
|
||||
namespace AY38910 {
|
||||
|
||||
@@ -162,6 +164,8 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
|
||||
uint8_t a_left_ = 255, a_right_ = 255;
|
||||
uint8_t b_left_ = 255, b_right_ = 255;
|
||||
uint8_t c_left_ = 255, c_right_ = 255;
|
||||
|
||||
friend struct State;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -192,6 +196,29 @@ struct Utility {
|
||||
|
||||
};
|
||||
|
||||
struct State: public Reflection::StructImpl<State> {
|
||||
uint8_t registers[16]{};
|
||||
uint8_t selected_register = 0;
|
||||
|
||||
// TODO: all audio-production thread state.
|
||||
|
||||
State() {
|
||||
if(needs_declare()) {
|
||||
DeclareField(registers);
|
||||
DeclareField(selected_register);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename AY> void apply(AY &target) {
|
||||
// Establish emulator-thread state
|
||||
for(uint8_t c = 0; c < 16; c++) {
|
||||
target.select_register(c);
|
||||
target.set_register_value(registers[c]);
|
||||
}
|
||||
target.select_register(selected_register);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
@@ -137,10 +140,15 @@ void DiskII::decide_clocking_preference() {
|
||||
|
||||
// If in read mode, clocking is either:
|
||||
//
|
||||
// just-in-time, if drives are running or the shift register has any 1s in it or a flux event hasn't yet passed; or
|
||||
// none, given that drives are not running, the shift register has already emptied and there's no flux about to be received.
|
||||
// just-in-time, if drives are running or the shift register has any 1s in it and shifting may occur, or a flux event hasn't yet passed; or
|
||||
// none, given that drives are not running, the shift register has already emptied or stopped and there's no flux about to be received.
|
||||
if(!(inputs_ & ~input_flux)) {
|
||||
clocking_preference_ = (!motor_is_enabled_ && !shift_register_ && (inputs_&input_flux)) ? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
const bool is_stuck_at_nop =
|
||||
!flux_duration_ && state_machine_[(state_ & 0xf0) | inputs_ | ((shift_register_&0x80) >> 6)] == state_ && (state_ &0xf) == 0x8;
|
||||
|
||||
clocking_preference_ =
|
||||
(drive_is_sleeping_[0] && drive_is_sleeping_[1] && (!shift_register_ || is_stuck_at_nop) && (inputs_&input_flux))
|
||||
? ClockingHint::Preference::None : ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
// If in writing mode, clocking is real time.
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
using namespace Concurrency;
|
||||
|
||||
AsyncTaskQueue::AsyncTaskQueue()
|
||||
#ifndef __APPLE__
|
||||
#ifndef USE_GCD
|
||||
: should_destruct_(false)
|
||||
#endif
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
|
||||
#else
|
||||
thread_ = std::make_unique<std::thread>([this]() {
|
||||
@@ -44,7 +44,7 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
}
|
||||
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
flush();
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
serial_dispatch_queue_ = nullptr;
|
||||
@@ -57,7 +57,7 @@ AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||
#else
|
||||
std::lock_guard lock(queue_mutex_);
|
||||
@@ -67,7 +67,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::flush() {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||
#else
|
||||
auto flush_mutex = std::make_shared<std::mutex>();
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
#include <dispatch/dispatch.h>
|
||||
#define USE_GCD
|
||||
#endif
|
||||
|
||||
namespace Concurrency {
|
||||
@@ -47,7 +48,7 @@ class AsyncTaskQueue {
|
||||
void flush();
|
||||
|
||||
private:
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_queue_t serial_dispatch_queue_;
|
||||
#else
|
||||
std::unique_ptr<std::thread> thread_;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifndef Joystick_hpp
|
||||
#define Joystick_hpp
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Inputs {
|
||||
@@ -35,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;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
using namespace Inputs;
|
||||
|
||||
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef CachingExecutor_hpp
|
||||
#define CachingExecutor_hpp
|
||||
|
||||
#include "Sizes.hpp"
|
||||
#include "../Numeric/Sizes.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef Disassembler_hpp
|
||||
#define Disassembler_hpp
|
||||
|
||||
#include "Sizes.hpp"
|
||||
#include "../Numeric/Sizes.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
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 */
|
||||
262
Machines/Amiga/Amiga.cpp
Normal file
262
Machines/Amiga/Amiga.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
//
|
||||
// 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/68000/68000.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::MC68000::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.
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
HalfCycles perform_bus_operation(const CPU::MC68000::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().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;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
chipset_.flush();
|
||||
}
|
||||
|
||||
private:
|
||||
CPU::MC68000::Processor<ConcreteMachine, 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 {
|
||||
return chipset_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
// MARK: - MachineTypes::TimedMachine.
|
||||
|
||||
void run_for(const Cycles cycles) {
|
||||
mc68000_.run_for(cycles);
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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 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 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;
|
||||
}
|
||||
}
|
||||
96
Machines/Amiga/Bitplanes.hpp
Normal file
96
Machines/Amiga/Bitplanes.hpp
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// 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) and 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 formed as 00[bitplane 5][bitplane 4]...[bitplane 0].
|
||||
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 */
|
||||
400
Machines/Amiga/Blitter.cpp
Normal file
400
Machines/Amiga/Blitter.cpp
Normal file
@@ -0,0 +1,400 @@
|
||||
//
|
||||
// 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),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void Blitter::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;
|
||||
channel_enables_[3] = value & 0x100;
|
||||
channel_enables_[2] = value & 0x200;
|
||||
channel_enables_[1] = value & 0x400;
|
||||
channel_enables_[0] = value & 0x800;
|
||||
}
|
||||
shifts_[index] = value >> 12;
|
||||
LOG("Set control " << index << " to " << PADHEX(4) << value);
|
||||
}
|
||||
|
||||
void Blitter::set_first_word_mask(uint16_t value) {
|
||||
LOG("Set first word mask: " << PADHEX(4) << value);
|
||||
a_mask_[0] = value;
|
||||
}
|
||||
|
||||
void Blitter::set_last_word_mask(uint16_t value) {
|
||||
LOG("Set last word mask: " << PADHEX(4) << value);
|
||||
a_mask_[1] = value;
|
||||
}
|
||||
|
||||
void Blitter::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.
|
||||
}
|
||||
|
||||
void Blitter::set_minterms(uint16_t value) {
|
||||
LOG("Set minterms " << PADHEX(4) << value);
|
||||
minterms_ = value & 0xff;
|
||||
}
|
||||
|
||||
//void Blitter::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.
|
||||
//}
|
||||
//
|
||||
//void Blitter::set_horizontal_size([[maybe_unused]] uint16_t value) {
|
||||
// LOG("Set horizontal size " << PADHEX(4) << value);
|
||||
//}
|
||||
|
||||
void Blitter::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;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t Blitter::get_status() {
|
||||
const uint16_t result =
|
||||
(not_zero_flag_ ? 0x0000 : 0x2000) | (height_ ? 0x4000 : 0x0000);
|
||||
LOG("Returned status of " << result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Blitter::advance_dma() {
|
||||
if(!height_) return false;
|
||||
|
||||
not_zero_flag_ = false;
|
||||
if(line_mode_) {
|
||||
// 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
|
||||
//
|
||||
|
||||
int error = int16_t(pointer_[0] << 1) >> 1; // TODO: what happens if line_sign_ doesn't agree with this?
|
||||
bool draw_ = true;
|
||||
while(height_--) {
|
||||
|
||||
if(draw_) {
|
||||
// TODO: patterned lines. Unclear what to do with the bit that comes out of b.
|
||||
// Probably extend it to a full word?
|
||||
c_data_ = ram_[pointer_[3] & ram_mask_];
|
||||
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_;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Copy mode.
|
||||
|
||||
// Quick hack: do the entire action atomically.
|
||||
a32_ = 0;
|
||||
b32_ = 0;
|
||||
|
||||
for(int y = 0; y < height_; y++) {
|
||||
bool fill_carry = fill_carry_;
|
||||
|
||||
for(int x = 0; x < width_; x++) {
|
||||
uint16_t a_mask = 0xffff;
|
||||
if(x == 0) a_mask &= a_mask_[0];
|
||||
if(x == width_ - 1) a_mask &= a_mask_[1];
|
||||
|
||||
if(channel_enables_[0]) {
|
||||
a_data_ = ram_[pointer_[0] & ram_mask_];
|
||||
pointer_[0] += direction_;
|
||||
}
|
||||
a32_ = (a32_ << 16) | (a_data_ & a_mask);
|
||||
|
||||
if(channel_enables_[1]) {
|
||||
b_data_ = ram_[pointer_[1] & ram_mask_];
|
||||
pointer_[1] += direction_;
|
||||
}
|
||||
b32_ = (b32_ << 16) | b_data_;
|
||||
|
||||
if(channel_enables_[2]) {
|
||||
c_data_ = ram_[pointer_[2] & ram_mask_];
|
||||
pointer_[2] += direction_;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if(channel_enables_[3]) {
|
||||
ram_[pointer_[3] & ram_mask_] = output;
|
||||
pointer_[3] += direction_;
|
||||
}
|
||||
}
|
||||
|
||||
pointer_[0] += modulos_[0] * channel_enables_[0] * direction_;
|
||||
pointer_[1] += modulos_[1] * channel_enables_[1] * direction_;
|
||||
pointer_[2] += modulos_[2] * channel_enables_[2] * direction_;
|
||||
pointer_[3] += modulos_[3] * channel_enables_[3] * direction_;
|
||||
}
|
||||
}
|
||||
|
||||
posit_interrupt(InterruptFlag::Blitter);
|
||||
height_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
69
Machines/Amiga/Blitter.hpp
Normal file
69
Machines/Amiga/Blitter.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// 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 "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "DMADevice.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Blitter: public DMADevice<4, 4> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
// 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();
|
||||
|
||||
bool advance_dma();
|
||||
|
||||
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;
|
||||
|
||||
bool channel_enables_[4]{};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif /* Blitter_hpp */
|
||||
1276
Machines/Amiga/Chipset.cpp
Normal file
1276
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/68000/68000.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::MC68000::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);
|
||||
|
||||
// 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 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 */
|
||||
143
Machines/Amiga/Copper.cpp
Normal file
143
Machines/Amiga/Copper.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// 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) {
|
||||
const uint16_t mask = 0x8000 | (instruction[1] & 0x7ffe);
|
||||
return
|
||||
(position & mask) >= (instruction[0] & mask) &&
|
||||
(!(blitter_status & 0x4000) || (instruction[1] & 0x8000));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// 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);
|
||||
|
||||
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;
|
||||
}
|
||||
49
Machines/Amiga/Flags.hpp
Normal file
49
Machines/Amiga/Flags.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// Flags.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 13/10/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Flags_hpp
|
||||
#define Flags_hpp
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
enum class InterruptFlag: uint16_t {
|
||||
SerialPortTransmit = 1 << 0,
|
||||
DiskBlock = 1 << 1,
|
||||
Software = 1 << 2,
|
||||
IOPortsAndTimers = 1 << 3, // i.e. CIA A.
|
||||
Copper = 1 << 4,
|
||||
VerticalBlank = 1 << 5,
|
||||
Blitter = 1 << 6,
|
||||
AudioChannel0 = 1 << 7,
|
||||
AudioChannel1 = 1 << 8,
|
||||
AudioChannel2 = 1 << 9,
|
||||
AudioChannel3 = 1 << 10,
|
||||
SerialPortReceive = 1 << 11,
|
||||
DiskSyncMatch = 1 << 12,
|
||||
External = 1 << 13, // i.e. CIA B.
|
||||
};
|
||||
|
||||
enum class DMAFlag: uint16_t {
|
||||
AudioChannel0 = 1 << 0,
|
||||
AudioChannel1 = 1 << 1,
|
||||
AudioChannel2 = 1 << 2,
|
||||
AudioChannel3 = 1 << 3,
|
||||
Disk = 1 << 4,
|
||||
Sprites = 1 << 5,
|
||||
Blitter = 1 << 6,
|
||||
Copper = 1 << 7,
|
||||
Bitplane = 1 << 8,
|
||||
AllBelow = 1 << 9,
|
||||
BlitterPriority = 1 << 10,
|
||||
BlitterZero = 1 << 13,
|
||||
BlitterBusy = 1 << 14,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* Flags_hpp */
|
||||
188
Machines/Amiga/Keyboard.cpp
Normal file
188
Machines/Amiga/Keyboard.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
// Notes to self:
|
||||
//
|
||||
//
|
||||
// Before
|
||||
// the transmission starts, both KCLK and KDAT are high. The keyboard starts
|
||||
// the transmission by putting out the first data bit (on KDAT), followed by
|
||||
// a pulse on KCLK (low then high); then it puts out the second data bit and
|
||||
// pulses KCLK until all eight data bits have been sent.
|
||||
//
|
||||
// When the computer has received the eighth bit, it must pulse KDAT low for
|
||||
// at least 1 (one) microsecond, as a handshake signal to the keyboard. The
|
||||
// keyboard must be able to detect pulses greater than or equal
|
||||
// to 1 microsecond. Software MUST pulse the line low for 85 microseconds to
|
||||
// ensure compatibility with all keyboard models.
|
||||
//
|
||||
//
|
||||
// If the handshake pulse does not arrive within
|
||||
// 143 ms of the last clock of the transmission, the keyboard will assume
|
||||
// that the computer is still waiting for the rest of the transmission and is
|
||||
// therefore out of sync. The keyboard will then attempt to restore sync by
|
||||
// going into "resync mode." In this mode, the keyboard clocks out a 1 and
|
||||
// waits for a handshake pulse. If none arrives within 143 ms, it clocks out
|
||||
// another 1 and waits again.
|
||||
//
|
||||
// The keyboard Hard Resets the Amiga by pulling KCLK low and starting a 500
|
||||
// millisecond timer. When one or more of the keys is released and 500
|
||||
// milliseconds have passed, the keyboard will release KCLK.
|
||||
//
|
||||
// The usual sequence of events will therefore be: power-up; synchronize;
|
||||
// transmit "initiate power-up key stream" ($FD); transmit "terminate key
|
||||
// stream" ($FE).
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
Keyboard::Keyboard(Serial::Line<true> &output) : output_(output) {
|
||||
output_.set_writer_clock_rate(HalfCycles(1'000'000)); // Use µs.
|
||||
}
|
||||
|
||||
/*uint8_t Keyboard::update(uint8_t input) {
|
||||
// If a bit transmission is ongoing, continue that, up to and including
|
||||
// the handshake. If no handshake comes, set a macro state of synchronising.
|
||||
switch(shift_state_) {
|
||||
case ShiftState::Shifting:
|
||||
// The keyboard processor sets the KDAT line about 20 microseconds before it
|
||||
// pulls KCLK low. KCLK stays low for about 20 microseconds, then goes high
|
||||
// again. The processor waits another 20 microseconds before changing KDAT.
|
||||
switch(bit_phase_) {
|
||||
default: break;
|
||||
case 0: lines_ = Lines::Clock | (shift_sequence_ & 1); break;
|
||||
case 20: lines_ = (shift_sequence_ & 1); break;
|
||||
case 40: lines_ = Lines::Clock | (shift_sequence_ & 1); break;
|
||||
}
|
||||
bit_phase_ = (bit_phase_ + 1) % 60;
|
||||
|
||||
if(!bit_phase_) {
|
||||
--bits_remaining_;
|
||||
shift_sequence_ >>= 1;
|
||||
if(!bits_remaining_) {
|
||||
shift_state_ = ShiftState::AwaitingHandshake;
|
||||
}
|
||||
}
|
||||
return lines_;
|
||||
|
||||
case ShiftState::AwaitingHandshake:
|
||||
if(!(input & Lines::Data)) {
|
||||
shift_state_ = ShiftState::Idle;
|
||||
}
|
||||
++bit_phase_;
|
||||
if(bit_phase_ == 143) {
|
||||
// shift_state_ = ShiftState::Synchronising;
|
||||
}
|
||||
return lines_;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
switch(state_) {
|
||||
case State::Startup:
|
||||
bit_phase_ = 0;
|
||||
shift_sequence_ = 0xff;
|
||||
shift_state_ = ShiftState::Shifting;
|
||||
break;
|
||||
}
|
||||
|
||||
return lines_;
|
||||
}*/
|
||||
|
||||
void Keyboard::set_key_state(uint16_t key, bool is_pressed) {
|
||||
if(pressed_[key] == is_pressed) {
|
||||
return;
|
||||
}
|
||||
pressed_[key] = is_pressed;
|
||||
output_.write<false>(
|
||||
HalfCycles(60),
|
||||
uint8_t(((key << 1) | (is_pressed ? 0 : 1)) ^ 0xff)
|
||||
);
|
||||
}
|
||||
|
||||
void Keyboard::clear_all_keys() {
|
||||
for(uint16_t c = 0; c < uint16_t(pressed_.size()); c++) {
|
||||
if(pressed_[c]) set_key_state(c, false);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - KeyboardMapper.
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
|
||||
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return uint16_t(Key::dest)
|
||||
#define DIRECTBIND(source) BIND(source, source)
|
||||
switch(key) {
|
||||
default: break;
|
||||
|
||||
DIRECTBIND(Escape);
|
||||
DIRECTBIND(Delete);
|
||||
|
||||
DIRECTBIND(F1); DIRECTBIND(F2); DIRECTBIND(F3); DIRECTBIND(F4); DIRECTBIND(F5);
|
||||
DIRECTBIND(F6); DIRECTBIND(F7); DIRECTBIND(F8); DIRECTBIND(F9); DIRECTBIND(F10);
|
||||
|
||||
BIND(BackTick, Tilde);
|
||||
DIRECTBIND(k1); DIRECTBIND(k2); DIRECTBIND(k3); DIRECTBIND(k4); DIRECTBIND(k5);
|
||||
DIRECTBIND(k6); DIRECTBIND(k7); DIRECTBIND(k8); DIRECTBIND(k9); DIRECTBIND(k0);
|
||||
|
||||
DIRECTBIND(Hyphen);
|
||||
DIRECTBIND(Equals);
|
||||
DIRECTBIND(Backslash);
|
||||
DIRECTBIND(Backspace);
|
||||
DIRECTBIND(Tab);
|
||||
DIRECTBIND(CapsLock);
|
||||
|
||||
BIND(LeftControl, Control);
|
||||
BIND(RightControl, Control);
|
||||
DIRECTBIND(LeftShift);
|
||||
DIRECTBIND(RightShift);
|
||||
BIND(LeftOption, Alt);
|
||||
BIND(RightOption, Alt);
|
||||
BIND(LeftMeta, LeftAmiga);
|
||||
BIND(RightMeta, RightAmiga);
|
||||
|
||||
DIRECTBIND(Q); DIRECTBIND(W); DIRECTBIND(E); DIRECTBIND(R); DIRECTBIND(T);
|
||||
DIRECTBIND(Y); DIRECTBIND(U); DIRECTBIND(I); DIRECTBIND(O); DIRECTBIND(P);
|
||||
DIRECTBIND(A); DIRECTBIND(S); DIRECTBIND(D); DIRECTBIND(F); DIRECTBIND(G);
|
||||
DIRECTBIND(H); DIRECTBIND(J); DIRECTBIND(K); DIRECTBIND(L); DIRECTBIND(Z);
|
||||
DIRECTBIND(X); DIRECTBIND(C); DIRECTBIND(V); DIRECTBIND(B); DIRECTBIND(N);
|
||||
DIRECTBIND(M);
|
||||
|
||||
DIRECTBIND(OpenSquareBracket);
|
||||
DIRECTBIND(CloseSquareBracket);
|
||||
|
||||
DIRECTBIND(Help);
|
||||
BIND(Insert, Help);
|
||||
BIND(Home, Help);
|
||||
BIND(End, Help);
|
||||
BIND(Enter, Return);
|
||||
DIRECTBIND(Semicolon);
|
||||
DIRECTBIND(Quote);
|
||||
DIRECTBIND(Comma);
|
||||
DIRECTBIND(FullStop);
|
||||
DIRECTBIND(ForwardSlash);
|
||||
|
||||
DIRECTBIND(Space);
|
||||
DIRECTBIND(Up);
|
||||
DIRECTBIND(Down);
|
||||
DIRECTBIND(Left);
|
||||
DIRECTBIND(Right);
|
||||
|
||||
DIRECTBIND(Keypad0); DIRECTBIND(Keypad1); DIRECTBIND(Keypad2);
|
||||
DIRECTBIND(Keypad3); DIRECTBIND(Keypad4); DIRECTBIND(Keypad5);
|
||||
DIRECTBIND(Keypad6); DIRECTBIND(Keypad7); DIRECTBIND(Keypad8);
|
||||
DIRECTBIND(Keypad9);
|
||||
|
||||
DIRECTBIND(KeypadDecimalPoint);
|
||||
DIRECTBIND(KeypadMinus);
|
||||
DIRECTBIND(KeypadEnter);
|
||||
}
|
||||
#undef DIRECTBIND
|
||||
#undef BIND
|
||||
return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
|
||||
}
|
||||
121
Machines/Amiga/Keyboard.hpp
Normal file
121
Machines/Amiga/Keyboard.hpp
Normal file
@@ -0,0 +1,121 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 29/07/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Amiga_Keyboard_hpp
|
||||
#define Machines_Amiga_Keyboard_hpp
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../../Components/Serial/Line.hpp"
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
enum class Key: uint16_t {
|
||||
Escape = 0x45,
|
||||
Delete = 0x46,
|
||||
|
||||
F1 = 0x50, F2 = 0x51, F3 = 0x52, F4 = 0x53, F5 = 0x54,
|
||||
F6 = 0x55, F7 = 0x56, F8 = 0x57, F9 = 0x58, F10 = 0x59,
|
||||
|
||||
Tilde = 0x00,
|
||||
k1 = 0x01, k2 = 0x02, k3 = 0x03, k4 = 0x04, k5 = 0x05,
|
||||
k6 = 0x06, k7 = 0x07, k8 = 0x08, k9 = 0x09, k0 = 0x0a,
|
||||
|
||||
Hyphen = 0x0b,
|
||||
Equals = 0x0c,
|
||||
Backslash = 0x0d,
|
||||
Backspace = 0x41,
|
||||
Tab = 0x42,
|
||||
Control = 0x63,
|
||||
CapsLock = 0x62,
|
||||
LeftShift = 0x60,
|
||||
RightShift = 0x61,
|
||||
|
||||
Q = 0x10, W = 0x11, E = 0x12, R = 0x13, T = 0x14,
|
||||
Y = 0x15, U = 0x16, I = 0x17, O = 0x18, P = 0x19,
|
||||
A = 0x20, S = 0x21, D = 0x22, F = 0x23, G = 0x24,
|
||||
H = 0x25, J = 0x26, K = 0x27, L = 0x28, Z = 0x31,
|
||||
X = 0x32, C = 0x33, V = 0x34, B = 0x35, N = 0x36,
|
||||
M = 0x37,
|
||||
|
||||
OpenSquareBracket = 0x1a,
|
||||
CloseSquareBracket = 0x1b,
|
||||
Help = 0x5f,
|
||||
Return = 0x44,
|
||||
Semicolon = 0x29,
|
||||
Quote = 0x2a,
|
||||
Comma = 0x38,
|
||||
FullStop = 0x39,
|
||||
ForwardSlash = 0x3a,
|
||||
Alt = 0x64,
|
||||
LeftAmiga = 0x66,
|
||||
RightAmiga = 0x67,
|
||||
Space = 0x40,
|
||||
|
||||
Up = 0x4c, Left = 0x4f, Right = 0x4e, Down = 0x4d,
|
||||
|
||||
Keypad7 = 0x3d, Keypad8 = 0x3e, Keypad9 = 0x3f,
|
||||
Keypad4 = 0x2d, Keypad5 = 0x2e, Keypad6 = 0x2f,
|
||||
Keypad1 = 0x1d, Keypad2 = 0x1e, Keypad3 = 0x1f,
|
||||
Keypad0 = 0x0f, KeypadDecimalPoint = 0x3c,
|
||||
KeypadMinus = 0x4a, KeypadEnter = 0x43,
|
||||
KeypadOpenBracket = 0x5a,
|
||||
KeypadCloseBracket = 0x5b,
|
||||
KeypadDivide = 0x5c,
|
||||
KeypadMultiply = 0x5d,
|
||||
KeypadPlus = 0x5e,
|
||||
};
|
||||
|
||||
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
|
||||
};
|
||||
|
||||
class Keyboard {
|
||||
public:
|
||||
Keyboard(Serial::Line<true> &output);
|
||||
|
||||
// enum Lines: uint8_t {
|
||||
// Data = (1 << 0),
|
||||
// Clock = (1 << 1),
|
||||
// };
|
||||
//
|
||||
// uint8_t update(uint8_t);
|
||||
|
||||
void set_key_state(uint16_t, bool);
|
||||
void clear_all_keys();
|
||||
|
||||
void run_for(HalfCycles duration) {
|
||||
output_.advance_writer(duration);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class ShiftState {
|
||||
Shifting,
|
||||
AwaitingHandshake,
|
||||
Idle,
|
||||
} shift_state_ = ShiftState::Idle;
|
||||
|
||||
enum class State {
|
||||
Startup,
|
||||
} state_ = State::Startup;
|
||||
|
||||
int bit_phase_ = 0;
|
||||
uint32_t shift_sequence_ = 0;
|
||||
int bits_remaining_ = 0;
|
||||
|
||||
uint8_t lines_ = 0;
|
||||
|
||||
Serial::Line<true> &output_;
|
||||
std::array<bool, 128> pressed_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Machines_Amiga_Keyboard_hpp */
|
||||
196
Machines/Amiga/MemoryMap.hpp
Normal file
196
Machines/Amiga/MemoryMap.hpp
Normal file
@@ -0,0 +1,196 @@
|
||||
//
|
||||
// MemoryMap.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/10/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MemoryMap_hpp
|
||||
#define MemoryMap_hpp
|
||||
|
||||
#include "../../Analyser/Static/Amiga/Target.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class MemoryMap {
|
||||
private:
|
||||
static constexpr auto PermitRead = CPU::MC68000::Microcycle::PermitRead;
|
||||
static constexpr auto PermitWrite = CPU::MC68000::Microcycle::PermitWrite;
|
||||
static constexpr auto PermitReadWrite = PermitRead | PermitWrite;
|
||||
|
||||
public:
|
||||
std::array<uint8_t, 512*1024> kickstart{0xff};
|
||||
std::vector<uint8_t> chip_ram{};
|
||||
|
||||
struct MemoryRegion {
|
||||
uint8_t *contents = nullptr;
|
||||
unsigned int read_write_mask = 0;
|
||||
} regions[64]; // i.e. top six bits are used as an index.
|
||||
|
||||
using FastRAM = Analyser::Static::Amiga::Target::FastRAM;
|
||||
using ChipRAM = Analyser::Static::Amiga::Target::ChipRAM;
|
||||
MemoryMap(ChipRAM chip_ram_size, FastRAM fast_ram_size) {
|
||||
// Address spaces that matter:
|
||||
//
|
||||
// 00'0000 – 08'0000: chip RAM. [or overlayed KickStart]
|
||||
// – 10'0000: extended chip ram for ECS.
|
||||
// – 20'0000: slow RAM and further chip RAM.
|
||||
// – a0'0000: auto-config space (/fast RAM).
|
||||
// ...
|
||||
// bf'd000 – c0'0000: 8250s.
|
||||
// c0'0000 – d8'0000: pseudo-fast RAM.
|
||||
// ...
|
||||
// dc'0000 – dd'0000: optional real-time clock.
|
||||
// df'f000 - e0'0000: custom chip registers.
|
||||
// ...
|
||||
// f0'0000 — : 512kb Kickstart (or possibly just an extra 512kb reserved for hypothetical 1mb Kickstart?).
|
||||
// f8'0000 — : 256kb Kickstart if 2.04 or higher.
|
||||
// fc'0000 – : 256kb Kickstart otherwise.
|
||||
set_region(0xfc'0000, 0x1'00'0000, kickstart.data(), PermitRead);
|
||||
|
||||
switch(chip_ram_size) {
|
||||
default:
|
||||
case ChipRAM::FiveHundredAndTwelveKilobytes:
|
||||
chip_ram.resize(512 * 1024);
|
||||
break;
|
||||
case ChipRAM::OneMegabyte:
|
||||
chip_ram.resize(1 * 1024 * 1024);
|
||||
break;
|
||||
case ChipRAM::TwoMegabytes:
|
||||
chip_ram.resize(2 * 1024 * 1024);
|
||||
break;
|
||||
}
|
||||
|
||||
switch(fast_ram_size) {
|
||||
default:
|
||||
fast_autoconf_visible_ = false;
|
||||
break;
|
||||
case FastRAM::OneMegabyte:
|
||||
fast_ram_.resize(1 * 1024 * 1024);
|
||||
fast_ram_size_ = 5;
|
||||
break;
|
||||
case FastRAM::TwoMegabytes:
|
||||
fast_ram_.resize(2 * 1024 * 1024);
|
||||
fast_ram_size_ = 6;
|
||||
break;
|
||||
case FastRAM::FourMegabytes:
|
||||
fast_ram_.resize(4 * 1024 * 1024);
|
||||
fast_ram_size_ = 7;
|
||||
break;
|
||||
case FastRAM::EightMegabytes:
|
||||
fast_ram_.resize(8 * 1024 * 1024);
|
||||
fast_ram_size_ = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
set_overlay(true);
|
||||
}
|
||||
|
||||
void set_overlay(bool enabled) {
|
||||
if(overlay_ == enabled) {
|
||||
return;
|
||||
}
|
||||
overlay_ = enabled;
|
||||
|
||||
set_region(0x00'0000, uint32_t(chip_ram.size()), chip_ram.data(), PermitReadWrite);
|
||||
if(enabled) {
|
||||
set_region(0x00'0000, 0x08'0000, kickstart.data(), PermitRead);
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs the provided microcycle, which the caller guarantees to be a memory access,
|
||||
/// and in the Zorro register range.
|
||||
bool perform(const CPU::MC68000::Microcycle &cycle) {
|
||||
if(!fast_autoconf_visible_) return false;
|
||||
|
||||
const uint32_t register_address = *cycle.address & 0xfe;
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
// Re: Autoconf:
|
||||
//
|
||||
// "All read registers physically return only the top 4 bits of data, on D31-D28";
|
||||
// (this is from Zorro III documentation; I'm assuming it to be D15–D11 for the
|
||||
// 68000's 16-bit bus);
|
||||
//
|
||||
// "Every AUTOCONFIG register is logically considered to be 8 bits wide; the
|
||||
// 8 bits actually being nybbles from two paired addresses."
|
||||
|
||||
uint8_t value = 0xf;
|
||||
switch(register_address) {
|
||||
default: break;
|
||||
|
||||
case 0x00: // er_Type (high)
|
||||
value =
|
||||
0xc | // Zoro II-style PIC.
|
||||
0x2; // Memory will be linked into the free pool
|
||||
break;
|
||||
case 0x02: // er_Type (low)
|
||||
value = fast_ram_size_;
|
||||
break;
|
||||
|
||||
// er_Manufacturer
|
||||
//
|
||||
// On the manufacturer number: this is supposed to be assigned
|
||||
// by Commodore. TODO: find and crib a real fast RAM number, if it matters.
|
||||
//
|
||||
// (0xffff seems to be invalid, so _something_ needs to be supplied)
|
||||
case 0x10: case 0x12:
|
||||
value = 0xa; // Manufacturer's number, high byte.
|
||||
break;
|
||||
case 0x14: case 0x16:
|
||||
value = 0xb; // Manufacturer's number, low byte.
|
||||
break;
|
||||
}
|
||||
|
||||
// Shove the value into the top of the data bus.
|
||||
cycle.set_value16(uint16_t(0x0fff | (value << 12)));
|
||||
} else {
|
||||
fast_autoconf_visible_ &= !(register_address >= 0x4c && register_address < 0x50);
|
||||
|
||||
switch(register_address) {
|
||||
default: break;
|
||||
|
||||
case 0x48: { // ec_BaseAddress (A23–A16)
|
||||
const auto address = uint32_t(cycle.value8_high()) << 16;
|
||||
set_region(address, uint32_t(address + fast_ram_.size()), fast_ram_.data(), PermitRead | PermitWrite);
|
||||
fast_autoconf_visible_ = false;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> fast_ram_{};
|
||||
uint8_t fast_ram_size_ = 0;
|
||||
|
||||
bool fast_autoconf_visible_ = true;
|
||||
bool overlay_ = false;
|
||||
|
||||
void set_region(uint32_t start, uint32_t end, uint8_t *base, unsigned int read_write_mask) {
|
||||
[[maybe_unused]] constexpr uint32_t precision_loss_mask = uint32_t(~0xfc'0000);
|
||||
assert(!(start & precision_loss_mask));
|
||||
assert(!((end - (1 << 18)) & precision_loss_mask));
|
||||
assert(end > start);
|
||||
|
||||
if(base) base -= start;
|
||||
for(decltype(start) c = start >> 18; c < end >> 18; c++) {
|
||||
regions[c].contents = base;
|
||||
regions[c].read_write_mask = read_write_mask;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
#endif /* MemoryMap_hpp */
|
||||
387
Machines/Amiga/Minterms.hpp
Normal file
387
Machines/Amiga/Minterms.hpp
Normal file
@@ -0,0 +1,387 @@
|
||||
//
|
||||
// Minterms.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/09/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Minterms_hpp
|
||||
#define Minterms_hpp
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
/// @returns the result of applying the Amiga-format @c minterm to inputs @c a, @c b and @c c.
|
||||
template <typename IntT> IntT apply_minterm(IntT a, IntT b, IntT c, int minterm) {
|
||||
|
||||
// Quick implementation notes:
|
||||
//
|
||||
// This was created very lazily. I tried to enter as many logical combinations of
|
||||
// a, b and c as I could think of and had a test program match them up to the
|
||||
// Amiga minterm IDs, prioritising by simplicity.
|
||||
//
|
||||
// That got me most of the way; starting from the point indicated below I had run
|
||||
// out of good ideas and automatically generated the rest.
|
||||
//
|
||||
switch(minterm) {
|
||||
default:
|
||||
case 0x00: return IntT(0);
|
||||
case 0xff: return IntT(~0);
|
||||
|
||||
|
||||
case 0xf0: return a;
|
||||
case 0xcc: return b;
|
||||
case 0xaa: return c;
|
||||
|
||||
case 0x0f: return ~a;
|
||||
case 0x33: return ~b;
|
||||
case 0x55: return ~c;
|
||||
|
||||
case 0xfc: return a | b;
|
||||
case 0xfa: return a | c;
|
||||
case 0xee: return b | c;
|
||||
case 0xfe: return a | b | c;
|
||||
|
||||
case 0xf3: return a | ~b;
|
||||
case 0xf5: return a | ~c;
|
||||
case 0xdd: return b | ~c;
|
||||
|
||||
case 0xfd: return a | b | ~c;
|
||||
case 0xfb: return a | ~b | c;
|
||||
case 0xf7: return a | ~b | ~c;
|
||||
|
||||
case 0xcf: return ~a | b;
|
||||
case 0xaf: return ~a | c;
|
||||
case 0xbb: return ~b | c;
|
||||
|
||||
case 0xef: return ~a | b | c;
|
||||
case 0xdf: return ~a | b | ~c;
|
||||
case 0x7f: return ~a | ~b | ~c;
|
||||
|
||||
|
||||
case 0x3c: return a ^ b;
|
||||
case 0x5a: return a ^ c;
|
||||
case 0x66: return b ^ c;
|
||||
case 0x96: return a ^ b ^ c;
|
||||
|
||||
case 0xc3: return ~a ^ b;
|
||||
case 0xa5: return ~a ^ c;
|
||||
case 0x99: return ~b ^ c;
|
||||
case 0x69: return ~a ^ b ^ c;
|
||||
|
||||
|
||||
case 0xc0: return a & b;
|
||||
case 0xa0: return a & c;
|
||||
case 0x88: return b & c;
|
||||
case 0x80: return a & b & c;
|
||||
|
||||
case 0x30: return a & ~b;
|
||||
case 0x50: return a & ~c;
|
||||
case 0x44: return b & ~c;
|
||||
|
||||
case 0x0c: return ~a & b;
|
||||
case 0x0a: return ~a & c;
|
||||
case 0x22: return ~b & c;
|
||||
|
||||
case 0x40: return a & b & ~c;
|
||||
case 0x20: return a & ~b & c;
|
||||
case 0x08: return ~a & b & c;
|
||||
|
||||
case 0x10: return a & ~b & ~c;
|
||||
case 0x04: return ~a & b & ~c;
|
||||
case 0x02: return ~a & ~b & c;
|
||||
|
||||
case 0x03: return ~a & ~b;
|
||||
case 0x05: return ~a & ~c;
|
||||
case 0x11: return ~b & ~c;
|
||||
case 0x01: return ~a & ~b & ~c;
|
||||
|
||||
case 0x70: return a & ~(b & c);
|
||||
case 0x4c: return b & ~(a & c);
|
||||
case 0x2a: return c & ~(a & b);
|
||||
|
||||
case 0x07: return ~a & ~(b & c);
|
||||
case 0x13: return ~b & ~(a & c);
|
||||
case 0x15: return ~c & ~(a & b);
|
||||
|
||||
|
||||
case 0xe0: return a & (b | c);
|
||||
case 0xc8: return b & (a | c);
|
||||
case 0xa8: return c & (a | b);
|
||||
|
||||
case 0x0e: return ~a & (b | c);
|
||||
case 0x32: return ~b & (a | c);
|
||||
case 0x54: return ~c & (a | b);
|
||||
|
||||
case 0x60: return a & (b ^ c);
|
||||
case 0x48: return b & (a ^ c);
|
||||
case 0x28: return c & (a ^ b);
|
||||
|
||||
case 0x06: return ~a & (b ^ c);
|
||||
case 0x12: return ~b & (a ^ c);
|
||||
case 0x14: return ~c & (a ^ b);
|
||||
|
||||
case 0x90: return a & ~(b ^ c);
|
||||
case 0x84: return b & ~(a ^ c);
|
||||
case 0x82: return c & ~(a ^ b);
|
||||
|
||||
case 0x09: return ~a & ~(b ^ c);
|
||||
case 0x21: return ~b & ~(a ^ c);
|
||||
case 0x41: return ~c & ~(a ^ b);
|
||||
|
||||
case 0xb0: return a & (~b | c);
|
||||
case 0xd0: return a & (b | ~c);
|
||||
case 0x0b: return ~a & (~b | c);
|
||||
case 0x0d: return ~a & (b | ~c);
|
||||
|
||||
|
||||
case 0xf6: return a | (b ^ c);
|
||||
case 0xde: return b | (a ^ c);
|
||||
case 0xbe: return c | (a ^ b);
|
||||
|
||||
case 0x6f: return ~a | (b ^ c);
|
||||
case 0x7b: return ~b | (a ^ c);
|
||||
case 0x7d: return ~c | (a ^ b);
|
||||
|
||||
case 0x9f: return ~a | ~(b ^ c);
|
||||
case 0xb7: return ~b | ~(a ^ c);
|
||||
case 0xd7: return ~c | ~(a ^ b);
|
||||
|
||||
case 0xf8: return a | (b & c);
|
||||
case 0xec: return b | (a & c);
|
||||
case 0xea: return c | (a & b);
|
||||
|
||||
case 0x8f: return ~a | (b & c);
|
||||
case 0xb3: return ~b | (a & c);
|
||||
case 0xd5: return ~c | (a & b);
|
||||
|
||||
case 0xf1: return a | ~(b | c);
|
||||
case 0xcd: return b | ~(a | c);
|
||||
case 0xab: return c | ~(a | b);
|
||||
|
||||
case 0x1f: return ~a | ~(b | c);
|
||||
case 0x37: return ~b | ~(a | c);
|
||||
case 0x57: return ~c | ~(a | b);
|
||||
|
||||
case 0x8c: return b & (~a | c);
|
||||
case 0x8a: return c & (~a | b);
|
||||
|
||||
case 0xc4: return b & (a | ~c);
|
||||
case 0xa2: return c & (a | ~b);
|
||||
|
||||
|
||||
case 0x78: return a ^ (b & c);
|
||||
case 0x6c: return b ^ (a & c);
|
||||
case 0x6a: return c ^ (a & b);
|
||||
|
||||
case 0x87: return ~a ^ (b & c);
|
||||
case 0x93: return ~b ^ (a & c);
|
||||
case 0x95: return ~c ^ (a & b);
|
||||
|
||||
|
||||
case 0x1e: return a ^ (b | c);
|
||||
case 0x36: return b ^ (a | c);
|
||||
case 0x56: return c ^ (a | b);
|
||||
|
||||
case 0x2d: return a ^ (b | ~c);
|
||||
case 0x4b: return a ^ (~b | c);
|
||||
case 0xe1: return a ^ ~(b | c);
|
||||
|
||||
case 0x39: return b ^ (a | ~c);
|
||||
case 0x63: return b ^ (~a | c);
|
||||
case 0xc9: return b ^ ~(a | c);
|
||||
|
||||
case 0x59: return c ^ (a | ~b);
|
||||
case 0x65: return c ^ (~a | b);
|
||||
case 0xa9: return c ^ ~(a | b);
|
||||
|
||||
|
||||
case 0x24: return (a ^ b) & (b ^ c);
|
||||
case 0x18: return (a ^ b) & (a ^ c);
|
||||
case 0x42: return (a ^ c) & (b ^ c);
|
||||
|
||||
case 0xa6: return (a & b) ^ (b ^ c);
|
||||
case 0xc6: return (a & c) ^ (b ^ c);
|
||||
|
||||
case 0x5c: return (a | b) ^ (a & c);
|
||||
case 0x74: return (a | b) ^ (b & c);
|
||||
case 0x72: return (a | c) ^ (b & c);
|
||||
case 0x4e: return (b | c) ^ (a & c);
|
||||
|
||||
case 0x58: return (a | b) & (a ^ c);
|
||||
case 0x62: return (a | c) & (b ^ c);
|
||||
|
||||
case 0x7e: return (a ^ b) | (a ^ c);
|
||||
|
||||
case 0xca: return (a & b) | (~a & c);
|
||||
case 0xac: return (~a & b) | (a & c);
|
||||
case 0xa3: return (~a & ~b) | (a & c);
|
||||
|
||||
|
||||
case 0xf4: return a | ((a ^ b) & (b ^ c));
|
||||
case 0xf2: return a | ((a ^ c) & (b ^ c));
|
||||
case 0xdc: return b | ((a ^ b) & (a ^ c));
|
||||
case 0xce: return b | ((a ^ c) & (b ^ c));
|
||||
case 0xae: return c | ((a ^ b) & (b ^ c));
|
||||
case 0xba: return c | ((a ^ b) & (a ^ c));
|
||||
|
||||
case 0x2f: return ~a | ((a ^ b) & (b ^ c));
|
||||
case 0x4f: return ~a | ((a ^ c) & (b ^ c));
|
||||
case 0x3b: return ~b | ((a ^ b) & (a ^ c));
|
||||
case 0x73: return ~b | ((a ^ c) & (b ^ c));
|
||||
case 0x75: return ~c | ((a ^ b) & (b ^ c));
|
||||
case 0x5d: return ~c | ((a ^ b) & (a ^ c));
|
||||
|
||||
case 0x3f: return ~a | ~b | ((a ^ b) & (b ^ c));
|
||||
case 0x77: return ~b | ~c | ((a ^ b) & (b ^ c));
|
||||
|
||||
case 0x27: return ~(a | b) | ((a ^ b) & (b ^ c));
|
||||
case 0x47: return ~(a | c) | ((a ^ c) & (b ^ c));
|
||||
case 0x53: return ~(b | c) | ((a ^ c) & (b ^ c));
|
||||
case 0x43: return ~(a | b | c) | ((a ^ c) & (b ^ c));
|
||||
|
||||
|
||||
case 0x7a: return (a & ~b) | (a ^ c);
|
||||
case 0x76: return (a & ~b) | (b ^ c);
|
||||
case 0x7c: return (a & ~c) | (a ^ b);
|
||||
|
||||
case 0x5e: return (~a & b) | (a ^ c);
|
||||
case 0x6e: return (~a & b) | (b ^ c);
|
||||
case 0x3e: return (~a & c) | (a ^ b);
|
||||
|
||||
case 0xad: return (~a & b) | ~(a ^ c);
|
||||
case 0xb5: return (a & ~b) | ~(a ^ c);
|
||||
case 0xcb: return (~a & c) | ~(a ^ b);
|
||||
case 0xd3: return (a & ~c) | ~(a ^ b);
|
||||
|
||||
case 0x9b: return (~a & c) | ~(b ^ c);
|
||||
case 0xd9: return (a & ~c) | ~(b ^ c);
|
||||
case 0x9d: return (~a & b) | ~(b ^ c);
|
||||
case 0xb9: return (a & ~b) | ~(b ^ c);
|
||||
|
||||
case 0x9e: return (~a & b) | (a ^ b ^ c);
|
||||
case 0xb6: return (a & ~b) | (a ^ b ^ c);
|
||||
case 0xd6: return (a & ~c) | (a ^ b ^ c);
|
||||
case 0xbf: return ~(a & b) | (a ^ b ^ c);
|
||||
|
||||
case 0x6d: return (~a & b) | ~(a ^ b ^ c);
|
||||
case 0x79: return (a & ~b) | ~(a ^ b ^ c);
|
||||
case 0x6b: return (~a & c) | ~(a ^ b ^ c);
|
||||
case 0xe9: return (b & c) | ~(a ^ b ^ c);
|
||||
|
||||
case 0xb8: return (a & ~b) | (c & b);
|
||||
case 0xd8: return (a & ~c) | (b & c);
|
||||
case 0xe4: return (b & ~c) | (a & c);
|
||||
case 0xe2: return (c & ~b) | (a & b);
|
||||
|
||||
|
||||
case 0x2c: return (~a & b) | ((a ^ b) & (b ^ c));
|
||||
case 0x34: return (a & ~b) | ((a ^ b) & (b ^ c));
|
||||
case 0x4a: return (~a & c) | ((a ^ c) & (b ^ c));
|
||||
case 0x52: return (a & ~c) | ((a ^ c) & (b ^ c));
|
||||
case 0x5f: return ~(a & c) | ((a ^ c) & (b ^ c));
|
||||
|
||||
|
||||
case 0x16: return (a & ~(c | b)) | (c & ~(b | a)) | (b & ~(a | c));
|
||||
case 0x81: return (a ^ ~(c | b)) & (c ^ ~(b | a)) & (b ^ ~(a | c));
|
||||
|
||||
|
||||
case 0x2e: return (~a & (b | c)) | (~b & c);
|
||||
case 0x3a: return (~b & (a | c)) | (~a & c);
|
||||
|
||||
case 0x8b: return (~a & ~b) | (c & b);
|
||||
case 0x8d: return (~a & ~c) | (b & c);
|
||||
case 0xb1: return (~b & ~c) | (a & c);
|
||||
case 0xd1: return (~c & ~b) | (a & b);
|
||||
|
||||
|
||||
case 0x98: return (a & ~(c | b)) | (b & c);
|
||||
case 0x8e: return (~a & (c | b)) | (b & c);
|
||||
|
||||
case 0x46: return (~a | b) & (b ^ c);
|
||||
|
||||
case 0xe6: return ((~a | b) & (b ^ c)) ^ (a & c);
|
||||
case 0xc2: return ((a | ~b) & (b ^ c)) ^ (a & c);
|
||||
|
||||
case 0x85: return (~a | b) & ~(a ^ c);
|
||||
case 0x83: return (~a | c) & ~(a ^ b);
|
||||
case 0x89: return (~a | c) & ~(b ^ c);
|
||||
|
||||
case 0xa1: return (a | ~b) & ~(a ^ c);
|
||||
case 0x91: return (a | ~b) & ~(b ^ c);
|
||||
case 0xc1: return (a | ~c) & ~(a ^ b);
|
||||
|
||||
case 0x94: return (a | b) & (a ^ b ^ c);
|
||||
case 0x86: return (b | c) & (a ^ b ^ c);
|
||||
case 0x92: return (a | c) & (a ^ b ^ c);
|
||||
|
||||
case 0x68: return (a | b) & ~(a ^ b ^ c);
|
||||
case 0x61: return (a | ~b) & ~(a ^ b ^ c);
|
||||
case 0x49: return (~a | b) & ~(a ^ b ^ c);
|
||||
case 0x29: return (~a | c) & ~(a ^ b ^ c);
|
||||
|
||||
case 0x64: return (a & ~b & c) | (b & ~c);
|
||||
|
||||
//
|
||||
// From here downwards functions were found automatically.
|
||||
// Neater versions likely exist of many of the functions below.
|
||||
//
|
||||
|
||||
case 0xe8: return (a & b) | ((b | a) & c);
|
||||
case 0xd4: return (a & b) | ((b | a) & ~c);
|
||||
case 0xb2: return (a & ~b) | ((~b | a) & c);
|
||||
case 0x17: return (~a & ~b) | ((~b | ~a) & ~c);
|
||||
case 0x1b: return (~a & ~b) | (~b & ~c) | (~a & c);
|
||||
case 0x1d: return (~a & b) | ((~b | ~a) & ~c);
|
||||
case 0x2b: return (~a & ~b) | ((~b | ~a) & c);
|
||||
case 0x35: return (a & ~b) | ((~b | ~a) & ~c);
|
||||
case 0x4d: return (~a & b) | ((b | ~a) & ~c);
|
||||
case 0x71: return (a & ~b) | ((~b | a) & ~c);
|
||||
case 0xbd: return (~a & b) | (~b & ~c) | (a & c);
|
||||
case 0xc5: return (a & b) | ((b | ~a) & ~c);
|
||||
case 0xdb: return (a & b) | (~b & ~c) | (~a & c);
|
||||
case 0xe7: return (~a & ~b) | (b & ~c) | (a & c);
|
||||
|
||||
|
||||
case 0x1c: return (~a & b) | (a & ~b & ~c);
|
||||
case 0x23: return (~a & ~b) | (a & ~b & c);
|
||||
case 0x31: return (a & ~b) | (~a & ~b & ~c);
|
||||
case 0x38: return (a & ~b) | (~a & b & c);
|
||||
case 0x1a: return (~a & c) | (a & ~b & ~c);
|
||||
case 0x25: return (~a & ~c) | (a & ~b & c);
|
||||
case 0x45: return (~a & ~c) | (a & b & ~c);
|
||||
case 0x51: return (a & ~c) | (~a & ~b & ~c);
|
||||
case 0xa4: return (a & c) | (~a & b & ~c);
|
||||
case 0x19: return (~b & ~c) | (~a & b & c);
|
||||
case 0x26: return (~b & c) | (~a & b & ~c);
|
||||
|
||||
case 0xc7: return (a & b) | (~a & (~b | ~c));
|
||||
case 0x3d: return (a & ~b) | (~a & (b | ~c));
|
||||
case 0xbc: return (~a & b) | (a & (~b | c));
|
||||
case 0xe3: return (~a & ~b) | (a & (b | c));
|
||||
case 0xa7: return (a & c) | (~a & (~b | ~c));
|
||||
case 0x5b: return (a & ~c) | (~a & (~b | c));
|
||||
case 0xda: return (~a & c) | (a & (b | ~c));
|
||||
case 0xe5: return (~a & ~c) | (a & (b | c));
|
||||
|
||||
case 0x67: return (~a & ~b) | ((~a | b) & ~c) | (~b & c);
|
||||
case 0x97: return (~a & ~b) | ((~a | ~b) & ~c) | (a & b & c);
|
||||
|
||||
case 0xb4: return (a & ~b) | (a & c) | (~a & b & ~c);
|
||||
case 0x9c: return (~a & b) | (b & c) | (a & ~b & ~c);
|
||||
|
||||
case 0xd2: return ((~c | b) & a) | (~a & ~b & c);
|
||||
case 0x9a: return ((~a | b) & c) | (a & ~b & ~c);
|
||||
|
||||
case 0xf9: return a | (~b & ~c) | (b & c);
|
||||
case 0xed: return b | (~a & ~c) | (a & c);
|
||||
case 0xeb: return c | (~a & ~b) | (a & b);
|
||||
}
|
||||
|
||||
// Should be unreachable.
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* Minterms_hpp */
|
||||
124
Machines/Amiga/MouseJoystick.cpp
Normal file
124
Machines/Amiga/MouseJoystick.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
//
|
||||
// MouseJoystick.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MouseJoystick.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
// MARK: - Mouse.
|
||||
|
||||
int Mouse::get_number_of_buttons() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
void Mouse::set_button_pressed(int button, bool is_set) {
|
||||
switch(button) {
|
||||
case 0:
|
||||
cia_state_ = (cia_state_ &~ 0x40) | (is_set ? 0 : 0x40);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Mouse::get_cia_button() const {
|
||||
return cia_state_;
|
||||
}
|
||||
|
||||
void Mouse::reset_all_buttons() {
|
||||
cia_state_ = 0xff;
|
||||
}
|
||||
|
||||
void Mouse::move(int x, int y) {
|
||||
position_[0] += x;
|
||||
position_[1] += y;
|
||||
}
|
||||
|
||||
uint16_t Mouse::get_position() {
|
||||
// The Amiga hardware retains only eight bits of position
|
||||
// for the mouse; its software polls frequently and maps
|
||||
// changes into a larger space.
|
||||
//
|
||||
// On modern computers with 5k+ displays and trackpads, it
|
||||
// proved empirically possible to overflow the hardware
|
||||
// counters more quickly than software would poll.
|
||||
//
|
||||
// Therefore the approach taken for mapping mouse motion
|
||||
// into the Amiga is to do it in steps of no greater than
|
||||
// [-128, +127], as per the below.
|
||||
const int pending[] = {
|
||||
position_[0], position_[1]
|
||||
};
|
||||
|
||||
const int8_t change[] = {
|
||||
int8_t(std::clamp(pending[0], -128, 127)),
|
||||
int8_t(std::clamp(pending[1], -128, 127))
|
||||
};
|
||||
|
||||
position_[0] -= change[0];
|
||||
position_[1] -= change[1];
|
||||
declared_position_[0] += change[0];
|
||||
declared_position_[1] += change[1];
|
||||
|
||||
return uint16_t(
|
||||
(declared_position_[1] << 8) |
|
||||
declared_position_[0]
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: - Joystick.
|
||||
|
||||
// TODO: add second fire button.
|
||||
|
||||
Joystick::Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
Input(Input::Fire, 0),
|
||||
}) {}
|
||||
|
||||
void Joystick::did_set_input(const Input &input, bool is_active) {
|
||||
// Accumulate state.
|
||||
inputs_[input.type] = is_active;
|
||||
|
||||
// Determine what that does to the two position bits.
|
||||
const auto low =
|
||||
(inputs_[Input::Type::Down] ^ inputs_[Input::Type::Right]) |
|
||||
(inputs_[Input::Type::Right] << 1);
|
||||
const auto high =
|
||||
(inputs_[Input::Type::Up] ^ inputs_[Input::Type::Left]) |
|
||||
(inputs_[Input::Type::Left] << 1);
|
||||
|
||||
// Ripple upwards if that affects the mouse position counters.
|
||||
const uint8_t previous_low = position_ & 3;
|
||||
uint8_t low_upper = (position_ >> 2) & 0x3f;
|
||||
const uint8_t previous_high = (position_ >> 8) & 3;
|
||||
uint8_t high_upper = (position_ >> 10) & 0x3f;
|
||||
|
||||
if(!low && previous_low == 3) ++low_upper;
|
||||
if(!previous_low && low == 3) --low_upper;
|
||||
if(!high && previous_high == 3) ++high_upper;
|
||||
if(!previous_high && high == 3) --high_upper;
|
||||
|
||||
position_ = uint16_t(
|
||||
low | ((low_upper & 0x3f) << 2) |
|
||||
(high << 8) | ((high_upper & 0x3f) << 10)
|
||||
);
|
||||
}
|
||||
|
||||
uint16_t Joystick::get_position() {
|
||||
return position_;
|
||||
}
|
||||
|
||||
uint8_t Joystick::get_cia_button() const {
|
||||
return inputs_[Input::Type::Fire] ? 0xbf : 0xff;
|
||||
}
|
||||
57
Machines/Amiga/MouseJoystick.hpp
Normal file
57
Machines/Amiga/MouseJoystick.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// MouseJoystick.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MouseJoystick_hpp
|
||||
#define MouseJoystick_hpp
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
#include "../../Inputs/Joystick.hpp"
|
||||
#include "../../Inputs/Mouse.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
struct MouseJoystickInput {
|
||||
virtual uint16_t get_position() = 0;
|
||||
virtual uint8_t get_cia_button() const = 0;
|
||||
};
|
||||
|
||||
class Mouse: public Inputs::Mouse, public MouseJoystickInput {
|
||||
public:
|
||||
uint16_t get_position() final;
|
||||
uint8_t get_cia_button() const final;
|
||||
|
||||
private:
|
||||
int get_number_of_buttons() final;
|
||||
void set_button_pressed(int, bool) final;
|
||||
void reset_all_buttons() final;
|
||||
void move(int, int) final;
|
||||
|
||||
uint8_t declared_position_[2]{};
|
||||
uint8_t cia_state_ = 0xff;
|
||||
std::array<std::atomic<int>, 2> position_{};
|
||||
};
|
||||
|
||||
class Joystick: public Inputs::ConcreteJoystick, public MouseJoystickInput {
|
||||
public:
|
||||
Joystick();
|
||||
|
||||
uint16_t get_position() final;
|
||||
uint8_t get_cia_button() const final;
|
||||
|
||||
private:
|
||||
void did_set_input(const Input &input, bool is_active) final;
|
||||
|
||||
bool inputs_[Joystick::Input::Type::Max]{};
|
||||
uint16_t position_ = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* MouseJoystick_hpp */
|
||||
115
Machines/Amiga/Sprites.cpp
Normal file
115
Machines/Amiga/Sprites.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
//
|
||||
// Sprites.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Sprites.hpp"
|
||||
|
||||
using namespace Amiga;
|
||||
|
||||
namespace {
|
||||
|
||||
/// Expands @c source from b15 ... b0 to 000b15 ... 000b0.
|
||||
constexpr uint64_t expand_sprite_word(uint16_t source) {
|
||||
uint64_t result = source;
|
||||
result = (result | (result << 24)) & 0x0000'00ff'0000'00ff;
|
||||
result = (result | (result << 12)) & 0x000f'000f'000f'000f;
|
||||
result = (result | (result << 6)) & 0x0303'0303'0303'0303;
|
||||
result = (result | (result << 3)) & 0x1111'1111'1111'1111;
|
||||
return result;
|
||||
}
|
||||
|
||||
// A very small selection of test cases.
|
||||
static_assert(expand_sprite_word(0xffff) == 0x11'11'11'11'11'11'11'11);
|
||||
static_assert(expand_sprite_word(0x5555) == 0x01'01'01'01'01'01'01'01);
|
||||
static_assert(expand_sprite_word(0xaaaa) == 0x10'10'10'10'10'10'10'10);
|
||||
static_assert(expand_sprite_word(0x0000) == 0x00'00'00'00'00'00'00'00);
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Sprites.
|
||||
|
||||
void Sprite::set_start_position(uint16_t value) {
|
||||
v_start_ = (v_start_ & 0xff00) | (value >> 8);
|
||||
h_start = uint16_t((h_start & 0x0001) | ((value & 0xff) << 1));
|
||||
}
|
||||
|
||||
void Sprite::set_stop_and_control(uint16_t value) {
|
||||
h_start = uint16_t((h_start & 0x01fe) | (value & 0x01));
|
||||
v_stop_ = uint16_t((value >> 8) | ((value & 0x02) << 7));
|
||||
v_start_ = uint16_t((v_start_ & 0x00ff) | ((value & 0x04) << 6));
|
||||
attached = value & 0x80;
|
||||
|
||||
// Disarm the sprite, but expect graphics next from DMA.
|
||||
visible = false;
|
||||
dma_state_ = DMAState::FetchImage;
|
||||
}
|
||||
|
||||
void Sprite::set_image_data(int slot, uint16_t value) {
|
||||
data[slot] = value;
|
||||
visible |= slot == 0;
|
||||
}
|
||||
|
||||
void Sprite::advance_line(int y, bool is_end_of_blank) {
|
||||
if(dma_state_ == DMAState::FetchImage && y == v_start_) {
|
||||
visible = true;
|
||||
}
|
||||
if(is_end_of_blank || y == v_stop_) {
|
||||
dma_state_ = DMAState::FetchControl;
|
||||
visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Sprite::advance_dma(int offset) {
|
||||
if(!visible) return false;
|
||||
|
||||
// Fetch another word.
|
||||
const uint16_t next_word = ram_[pointer_[0] & ram_mask_];
|
||||
++pointer_[0];
|
||||
|
||||
// Put the fetched word somewhere appropriate and update the DMA state.
|
||||
switch(dma_state_) {
|
||||
// i.e. stopped.
|
||||
default: return false;
|
||||
|
||||
case DMAState::FetchControl:
|
||||
if(offset) {
|
||||
set_stop_and_control(next_word);
|
||||
} else {
|
||||
set_start_position(next_word);
|
||||
}
|
||||
return true;
|
||||
|
||||
case DMAState::FetchImage:
|
||||
set_image_data(1 - bool(offset), next_word);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <int sprite> void TwoSpriteShifter::load(
|
||||
uint16_t lsb,
|
||||
uint16_t msb,
|
||||
int delay) {
|
||||
constexpr int sprite_shift = sprite << 1;
|
||||
const int delay_shift = delay << 2;
|
||||
|
||||
// Clear out any current sprite pixels; this is a reload.
|
||||
data_ &= 0xcccc'cccc'cccc'ccccull >> (sprite_shift + delay_shift);
|
||||
|
||||
// Map LSB and MSB up to 64-bits and load into the shifter.
|
||||
const uint64_t new_data =
|
||||
(
|
||||
expand_sprite_word(lsb) |
|
||||
(expand_sprite_word(msb) << 1)
|
||||
) << sprite_shift;
|
||||
|
||||
data_ |= new_data >> delay_shift;
|
||||
overflow_ |= uint8_t((new_data << 8) >> delay_shift);
|
||||
}
|
||||
|
||||
template void TwoSpriteShifter::load<0>(uint16_t, uint16_t, int);
|
||||
template void TwoSpriteShifter::load<1>(uint16_t, uint16_t, int);
|
||||
76
Machines/Amiga/Sprites.hpp
Normal file
76
Machines/Amiga/Sprites.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// Sprites.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 26/11/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Sprites_hpp
|
||||
#define Sprites_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "DMADevice.hpp"
|
||||
|
||||
namespace Amiga {
|
||||
|
||||
class Sprite: public DMADevice<1> {
|
||||
public:
|
||||
using DMADevice::DMADevice;
|
||||
|
||||
void set_start_position(uint16_t value);
|
||||
void set_stop_and_control(uint16_t value);
|
||||
void set_image_data(int slot, uint16_t value);
|
||||
|
||||
void advance_line(int y, bool is_end_of_blank);
|
||||
bool advance_dma(int offset);
|
||||
|
||||
uint16_t data[2]{};
|
||||
bool attached = false;
|
||||
bool visible = false;
|
||||
uint16_t h_start = 0;
|
||||
|
||||
private:
|
||||
uint16_t v_start_ = 0, v_stop_ = 0;
|
||||
|
||||
enum class DMAState {
|
||||
FetchControl,
|
||||
FetchImage
|
||||
} dma_state_ = DMAState::FetchControl;
|
||||
};
|
||||
|
||||
class TwoSpriteShifter {
|
||||
public:
|
||||
/// Installs new pixel data for @c sprite (either 0 or 1),
|
||||
/// with @c delay being either 0 or 1 to indicate whether
|
||||
/// output should begin now or in one pixel's time.
|
||||
template <int sprite> void load(
|
||||
uint16_t lsb,
|
||||
uint16_t msb,
|
||||
int delay);
|
||||
|
||||
/// Shifts two pixels.
|
||||
void shift() {
|
||||
data_ <<= 8;
|
||||
data_ |= overflow_;
|
||||
overflow_ = 0;
|
||||
}
|
||||
|
||||
/// @returns The next two pixels to output, formulated as
|
||||
/// abcd efgh where ab and ef are two pixels of the first sprite
|
||||
/// and cd and gh are two pixels of the second. In each case the
|
||||
/// more significant two are output first.
|
||||
uint8_t get() {
|
||||
return uint8_t(data_ >> 56);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t data_;
|
||||
uint8_t overflow_;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif /* Sprites_hpp */
|
||||
@@ -158,7 +158,7 @@ class AYDeferrer {
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910<true> ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<true>> speaker_;
|
||||
Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<true>> speaker_;
|
||||
HalfCycles cycles_since_update_;
|
||||
};
|
||||
|
||||
@@ -787,45 +787,44 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
ay_.ay().set_port_handler(&key_state_);
|
||||
|
||||
// construct the list of necessary ROMs
|
||||
const std::string machine_name = "AmstradCPC";
|
||||
std::vector<ROMMachine::ROM> required_roms = {
|
||||
ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd)
|
||||
};
|
||||
std::string model_number;
|
||||
uint32_t crcs[2];
|
||||
bool has_amsdos = false;
|
||||
ROM::Name firmware, basic;
|
||||
|
||||
using Model = Analyser::Static::AmstradCPC::Target::Model;
|
||||
switch(target.model) {
|
||||
case Model::CPC464:
|
||||
firmware = ROM::Name::CPC464Firmware;
|
||||
basic = ROM::Name::CPC464BASIC;
|
||||
break;
|
||||
case Model::CPC664:
|
||||
firmware = ROM::Name::CPC664Firmware;
|
||||
basic = ROM::Name::CPC664BASIC;
|
||||
has_amsdos = true;
|
||||
break;
|
||||
default:
|
||||
model_number = "6128";
|
||||
has_128k_ = true;
|
||||
crcs[0] = 0x0219bb74;
|
||||
crcs[1] = 0xca6af63d;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
|
||||
model_number = "464";
|
||||
has_128k_ = false;
|
||||
crcs[0] = 0x815752df;
|
||||
crcs[1] = 0x7d9a3bac;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
|
||||
model_number = "664";
|
||||
has_128k_ = false;
|
||||
crcs[0] = 0x3f5a6dc4;
|
||||
crcs[1] = 0x32fee492;
|
||||
firmware = ROM::Name::CPC6128Firmware;
|
||||
basic = ROM::Name::CPC6128BASIC;
|
||||
has_amsdos = true;
|
||||
break;
|
||||
}
|
||||
required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]);
|
||||
required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]);
|
||||
|
||||
// fetch and verify the ROMs
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
auto &data = roms[index];
|
||||
if(!data) throw ROMMachine::Error::MissingROMs;
|
||||
roms_[index] = std::move(*data);
|
||||
roms_[index].resize(16384);
|
||||
ROM::Request request = ROM::Request(firmware) && ROM::Request(basic);
|
||||
if(has_amsdos) {
|
||||
request = request && ROM::Request(ROM::Name::AMSDOS);
|
||||
}
|
||||
|
||||
// Fetch and verify the ROMs.
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
if(has_amsdos) {
|
||||
roms_[ROMType::AMSDOS] = roms.find(ROM::Name::AMSDOS)->second;
|
||||
}
|
||||
roms_[ROMType::OS] = roms.find(firmware)->second;
|
||||
roms_[ROMType::BASIC] = roms.find(basic)->second;
|
||||
|
||||
// Establish default memory map
|
||||
upper_rom_is_paged_ = true;
|
||||
upper_rom_ = ROMType::BASIC;
|
||||
@@ -840,6 +839,9 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
read_pointers_[2] = write_pointers_[2];
|
||||
read_pointers_[3] = roms_[upper_rom_].data();
|
||||
|
||||
// Set total RAM available.
|
||||
has_128k_ = target.model == Model::CPC6128;
|
||||
|
||||
// Type whatever is required.
|
||||
if(!target.loading_command.empty()) {
|
||||
type_string(target.loading_command);
|
||||
@@ -1121,7 +1123,7 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
}
|
||||
|
||||
HalfCycles get_typer_frequency() const final {
|
||||
return Cycles(80'000); // Perform one key transition per frame.
|
||||
return Cycles(160'000); // Perform one key transition per frame and a half.
|
||||
}
|
||||
|
||||
// See header; sets a key as either pressed or released.
|
||||
@@ -1250,20 +1252,20 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
HalfCycles crtc_counter_;
|
||||
HalfCycles half_cycles_since_ay_update_;
|
||||
|
||||
bool fdc_is_sleeping_;
|
||||
bool tape_player_is_sleeping_;
|
||||
bool has_128k_;
|
||||
bool fdc_is_sleeping_ = false;
|
||||
bool tape_player_is_sleeping_ = false;
|
||||
bool has_128k_ = false;
|
||||
|
||||
enum ROMType: int {
|
||||
AMSDOS = 0, OS = 1, BASIC = 2
|
||||
};
|
||||
std::vector<uint8_t> roms_[3];
|
||||
bool upper_rom_is_paged_;
|
||||
bool upper_rom_is_paged_ = false;
|
||||
ROMType upper_rom_;
|
||||
|
||||
uint8_t *ram_pages_[4];
|
||||
const uint8_t *read_pointers_[4];
|
||||
uint8_t *write_pointers_[4];
|
||||
uint8_t *ram_pages_[4]{};
|
||||
const uint8_t *read_pointers_[4]{};
|
||||
uint8_t *write_pointers_[4]{};
|
||||
|
||||
KeyboardState key_state_;
|
||||
AmstradCPC::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
@@ -151,7 +151,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
return table_lookup_sequence_for_character(key_sequences, character);
|
||||
}
|
||||
|
||||
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {
|
||||
|
||||
@@ -40,7 +40,7 @@ struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMappe
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
const uint16_t *sequence_for_character(char character) const override;
|
||||
|
||||
bool needs_pause_after_reset_all_keys() const override { return false; }
|
||||
bool needs_pause_after_reset_all_keys() const override { return true; }
|
||||
bool needs_pause_after_key(uint16_t key) const override;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ void Keyboard::perform_command(const Command &command) {
|
||||
switch(command.type) {
|
||||
case Command::Type::Reset:
|
||||
modifiers_ = 0xffff;
|
||||
[[fallthrough]];
|
||||
case Command::Type::Flush: {
|
||||
std::lock_guard lock_guard(keys_mutex_);
|
||||
pending_events_.clear();
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "Mouse.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Apple::ADB;
|
||||
|
||||
Mouse::Mouse(Bus &bus) : ReactiveDevice(bus, 3) {}
|
||||
@@ -20,8 +22,8 @@ void Mouse::perform_command(const Command &command) {
|
||||
const int buttons = button_flags_;
|
||||
|
||||
// Clamp deltas.
|
||||
delta_x = std::max(std::min(delta_x, int16_t(127)), int16_t(-128));
|
||||
delta_y = std::max(std::min(delta_y, int16_t(127)), int16_t(-128));
|
||||
delta_x = std::clamp(delta_x, int16_t(-128), int16_t(127));
|
||||
delta_y = std::clamp(delta_y, int16_t(-128), int16_t(127));
|
||||
|
||||
// Figure out what that would look like, and don't respond if there's
|
||||
// no change to report.
|
||||
|
||||
@@ -97,7 +97,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
Outputs::Speaker::LowpassSpeaker<Audio::Toggle> speaker_;
|
||||
Outputs::Speaker::PullLowpass<Audio::Toggle> speaker_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
|
||||
// MARK: - Cards
|
||||
@@ -257,7 +257,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
Keyboard(Processor *m6502) : m6502_(m6502) {}
|
||||
|
||||
void reset_all_keys() final {
|
||||
open_apple_is_pressed = closed_apple_is_pressed = key_is_down = false;
|
||||
open_apple_is_pressed = closed_apple_is_pressed = control_is_pressed = shift_is_pressed = key_is_down = false;
|
||||
}
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed) final {
|
||||
@@ -267,19 +267,49 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
case Key::Right: value = 0x15; break;
|
||||
case Key::Down: value = 0x0a; break;
|
||||
case Key::Up: value = 0x0b; break;
|
||||
case Key::Backspace: value = 0x7f; break;
|
||||
case Key::Backspace:
|
||||
if(is_iie()) {
|
||||
value = 0x7f;
|
||||
break;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case Key::Enter: value = 0x0d; break;
|
||||
case Key::Tab: value = '\t'; break;
|
||||
case Key::Tab:
|
||||
if (is_iie()) {
|
||||
value = '\t';
|
||||
break;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
case Key::Escape: value = 0x1b; break;
|
||||
case Key::Space: value = 0x20; break;
|
||||
|
||||
case Key::LeftOption:
|
||||
case Key::RightMeta:
|
||||
open_apple_is_pressed = is_pressed;
|
||||
return true;
|
||||
if (is_iie()) {
|
||||
open_apple_is_pressed = is_pressed;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case Key::RightOption:
|
||||
case Key::LeftMeta:
|
||||
closed_apple_is_pressed = is_pressed;
|
||||
if (is_iie()) {
|
||||
closed_apple_is_pressed = is_pressed;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
case Key::LeftControl:
|
||||
control_is_pressed = is_pressed;
|
||||
return true;
|
||||
|
||||
case Key::LeftShift:
|
||||
case Key::RightShift:
|
||||
shift_is_pressed = is_pressed;
|
||||
return true;
|
||||
|
||||
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
|
||||
@@ -306,6 +336,30 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
// Prior to the IIe, the keyboard could produce uppercase only.
|
||||
if(!is_iie()) value = char(toupper(value));
|
||||
|
||||
if(control_is_pressed && isalpha(value)) value &= 0xbf;
|
||||
|
||||
// TODO: properly map IIe keys
|
||||
if(!is_iie() && shift_is_pressed) {
|
||||
switch(value) {
|
||||
case 0x27: value = 0x22; break; // ' -> "
|
||||
case 0x2c: value = 0x3c; break; // , -> <
|
||||
case 0x2e: value = 0x3e; break; // . -> >
|
||||
case 0x2f: value = 0x3f; break; // / -> ?
|
||||
case 0x30: value = 0x29; break; // 0 -> )
|
||||
case 0x31: value = 0x21; break; // 1 -> !
|
||||
case 0x32: value = 0x40; break; // 2 -> @
|
||||
case 0x33: value = 0x23; break; // 3 -> #
|
||||
case 0x34: value = 0x24; break; // 4 -> $
|
||||
case 0x35: value = 0x25; break; // 5 -> %
|
||||
case 0x36: value = 0x5e; break; // 6 -> ^
|
||||
case 0x37: value = 0x26; break; // 7 -> &
|
||||
case 0x38: value = 0x2a; break; // 8 -> *
|
||||
case 0x39: value = 0x28; break; // 9 -> (
|
||||
case 0x3b: value = 0x3a; break; // ; -> :
|
||||
case 0x3d: value = 0x2b; break; // = -> +
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -313,7 +367,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
keyboard_input = uint8_t(value | 0x80);
|
||||
key_is_down = true;
|
||||
} else {
|
||||
if((keyboard_input & 0x7f) == value) {
|
||||
if((keyboard_input & 0x3f) == value) {
|
||||
key_is_down = false;
|
||||
}
|
||||
}
|
||||
@@ -329,6 +383,8 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
bool shift_is_pressed = false;
|
||||
bool control_is_pressed = false;
|
||||
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
|
||||
bool open_apple_is_pressed = false;
|
||||
bool closed_apple_is_pressed = false;
|
||||
@@ -377,49 +433,55 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
// Pick the required ROMs.
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
const std::string machine_name = "AppleII";
|
||||
std::vector<ROMMachine::ROM> rom_descriptions;
|
||||
size_t rom_size = 12*1024;
|
||||
ROM::Name character, system;
|
||||
|
||||
switch(target.model) {
|
||||
default:
|
||||
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
|
||||
rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
|
||||
character = ROM::Name::AppleIICharacter;
|
||||
system = ROM::Name::AppleIIOriginal;
|
||||
break;
|
||||
case Target::Model::IIplus:
|
||||
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
|
||||
character = ROM::Name::AppleIICharacter;
|
||||
system = ROM::Name::AppleIIPlus;
|
||||
break;
|
||||
case Target::Model::IIe:
|
||||
rom_size += 3840;
|
||||
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::IIe));
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
|
||||
character = ROM::Name::AppleIIeCharacter;
|
||||
system = ROM::Name::AppleIIe;
|
||||
break;
|
||||
case Target::Model::EnhancedIIe:
|
||||
rom_size += 3840;
|
||||
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::EnhancedIIe));
|
||||
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
|
||||
character = ROM::Name::AppleIIEnhancedECharacter;
|
||||
system = ROM::Name::AppleIIEnhancedE;
|
||||
break;
|
||||
}
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
|
||||
// Try to install a Disk II card now, before checking the ROM list,
|
||||
// to make sure that Disk II dependencies have been communicated.
|
||||
if(target.disk_controller != Target::DiskController::None) {
|
||||
ROM::Request request = ROM::Request(character) && ROM::Request(system);
|
||||
|
||||
// Add the necessary Disk II requests if appropriate.
|
||||
const bool has_disk_controller = target.disk_controller != Target::DiskController::None;
|
||||
const bool is_sixteen_sector = target.disk_controller == Target::DiskController::SixteenSector;
|
||||
if(has_disk_controller) {
|
||||
// Apple recommended slot 6 for the (first) Disk II.
|
||||
install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
|
||||
request = request && DiskIICard::rom_request(is_sixteen_sector);
|
||||
}
|
||||
|
||||
// Now, check and move the ROMs.
|
||||
if(!roms[0] || !roms[1]) {
|
||||
// Request, validate and install ROMs.
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
rom_ = std::move(*roms[1]);
|
||||
if(rom_.size() > rom_size) {
|
||||
rom_.erase(rom_.begin(), rom_.end() - off_t(rom_size));
|
||||
if(has_disk_controller) {
|
||||
install_card(6, new Apple::II::DiskIICard(roms, is_sixteen_sector));
|
||||
}
|
||||
|
||||
video_.set_character_rom(*roms[0]);
|
||||
rom_ = std::move(roms.find(system)->second);
|
||||
// The IIe and Enhanced IIe ROMs often distributed are oversized; trim if necessary.
|
||||
if(system == ROM::Name::AppleIIe || system == ROM::Name::AppleIIEnhancedE) {
|
||||
if(rom_.size() > 16128) {
|
||||
rom_.erase(rom_.begin(), rom_.end() - off_t(16128));
|
||||
}
|
||||
}
|
||||
video_.set_character_rom(roms.find(character)->second);
|
||||
|
||||
// Set up the default memory blocks. On a II or II+ these values will never change.
|
||||
// On a IIe they'll be affected by selection of auxiliary RAM.
|
||||
@@ -761,7 +823,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
|
||||
bool prefers_logical_input() final {
|
||||
return true;
|
||||
return is_iie();
|
||||
}
|
||||
|
||||
Inputs::Keyboard &get_keyboard() final {
|
||||
@@ -781,12 +843,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
std::unique_ptr<Reflection::Struct> get_options() final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
options->use_square_pixels = video_.get_use_square_pixels();
|
||||
return options;
|
||||
}
|
||||
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
|
||||
const auto options = dynamic_cast<Options *>(str.get());
|
||||
set_video_signal_configurable(options->output);
|
||||
video_.set_use_square_pixels(options->use_square_pixels);
|
||||
}
|
||||
|
||||
// MARK: MediaTarget
|
||||
|
||||
@@ -30,8 +30,11 @@ class Machine {
|
||||
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
|
||||
friend Configurable::DisplayOption<Options>;
|
||||
public:
|
||||
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
|
||||
bool use_square_pixels = false;
|
||||
|
||||
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(use_square_pixels);
|
||||
declare_display_option();
|
||||
limit_enum(&output, Configurable::Display::CompositeMonochrome, Configurable::Display::CompositeColour, -1);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ template <typename Machine> class AuxiliaryMemorySwitches {
|
||||
bool read_auxiliary_memory = false;
|
||||
bool write_auxiliary_memory = false;
|
||||
|
||||
bool internal_CX_rom = false;
|
||||
bool internal_CX_rom = true;
|
||||
bool slot_C3_rom = false;
|
||||
bool internal_C8_rom = false;
|
||||
|
||||
|
||||
@@ -10,27 +10,34 @@
|
||||
|
||||
using namespace Apple::II;
|
||||
|
||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
|
||||
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
|
||||
ROM::Request DiskIICard::rom_request(bool is_16_sector) {
|
||||
if(is_16_sector) {
|
||||
roms = rom_fetcher({
|
||||
{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6},
|
||||
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
|
||||
});
|
||||
return ROM::Request(ROM::Name::DiskIIBoot16Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
|
||||
} else {
|
||||
roms = rom_fetcher({
|
||||
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
|
||||
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
|
||||
// {"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
|
||||
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
|
||||
});
|
||||
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
|
||||
return ROM::Request(ROM::Name::DiskIIBoot13Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
|
||||
}
|
||||
if(!roms[0] || !roms[1]) {
|
||||
}
|
||||
|
||||
|
||||
DiskIICard::DiskIICard(ROM::Map &map, bool is_16_sector) : diskii_(2045454) {
|
||||
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
|
||||
ROM::Map::iterator state_machine, boot;
|
||||
if(is_16_sector) {
|
||||
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
|
||||
boot = map.find(ROM::Name::DiskIIBoot16Sector);
|
||||
} else {
|
||||
// TODO: see above re: 13-sector state machine.
|
||||
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
|
||||
boot = map.find(ROM::Name::DiskIIBoot13Sector);
|
||||
}
|
||||
|
||||
if(state_machine == map.end() || boot == map.end()) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
boot_ = std::move(*roms[0]);
|
||||
diskii_.set_state_machine(*roms[1]);
|
||||
boot_ = std::move(boot->second);
|
||||
diskii_.set_state_machine(state_machine->second);
|
||||
set_select_constraints(None);
|
||||
diskii_.set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace II {
|
||||
|
||||
class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
|
||||
static ROM::Request rom_request(bool is_16_sector);
|
||||
DiskIICard(ROM::Map &, bool is_16_sector);
|
||||
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
|
||||
void run_for(Cycles cycles, int stretches) final;
|
||||
|
||||
@@ -15,9 +15,8 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
||||
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
|
||||
is_iie_(is_iie) {
|
||||
|
||||
// Show only the centre 75% of the TV frame.
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.118f, 0.122f, 0.77f, 0.77f));
|
||||
set_use_square_pixels(use_square_pixels_);
|
||||
|
||||
// TODO: there seems to be some sort of bug whereby switching modes can cause
|
||||
// a signal discontinuity that knocks phase out of whack. So it isn't safe to
|
||||
@@ -26,6 +25,41 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
||||
// crt_.set_immediate_default_phase(0.5f);
|
||||
}
|
||||
|
||||
void VideoBase::set_use_square_pixels(bool use_square_pixels) {
|
||||
use_square_pixels_ = use_square_pixels;
|
||||
|
||||
// HYPER-UGLY HACK. See correlated hack in the Macintosh.
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.122f, 0.75f, 0.77f));
|
||||
#else
|
||||
if(use_square_pixels) {
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.09f, 0.75f, 0.77f));
|
||||
} else {
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.12f, 0.75f, 0.77f));
|
||||
}
|
||||
#endif
|
||||
|
||||
if(use_square_pixels) {
|
||||
// From what I can make out, many contemporary Apple II monitors were
|
||||
// calibrated slightly to stretch the Apple II's display slightly wider
|
||||
// than it should be per the NTSC standards, for approximately square
|
||||
// pixels. This reproduces that.
|
||||
|
||||
// 243 lines and 52µs are visible.
|
||||
// i.e. to be square, 1 pixel should be: (1/243 * 52) * (3/4) = 156/972 = 39/243 µs
|
||||
// On an Apple II each pixel is actually 1/7µs.
|
||||
// Therefore the adjusted aspect ratio should be (4/3) * (39/243)/(1/7) = (4/3) * 273/243 = 1092/729 = 343/243 ~= 1.412
|
||||
crt_.set_aspect_ratio(343.0f / 243.0f);
|
||||
} else {
|
||||
// Standard NTSC aspect ratio.
|
||||
crt_.set_aspect_ratio(4.0f / 3.0f);
|
||||
}
|
||||
}
|
||||
bool VideoBase::get_use_square_pixels() {
|
||||
return use_square_pixels_;
|
||||
}
|
||||
|
||||
|
||||
void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
@@ -51,8 +51,14 @@ class VideoBase: public VideoSwitches<Cycles> {
|
||||
/// Gets the type of output.
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/// Sets whether the current CRT should be recalibrated away from normative NTSC
|
||||
/// to produce square pixels in 40-column text mode.
|
||||
void set_use_square_pixels(bool);
|
||||
bool get_use_square_pixels();
|
||||
|
||||
protected:
|
||||
Outputs::CRT::CRT crt_;
|
||||
bool use_square_pixels_ = false;
|
||||
|
||||
// State affecting output video stream generation.
|
||||
uint8_t *pixel_pointer_ = nullptr;
|
||||
@@ -393,7 +399,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
|
||||
// Supply the real phase value if this is an Apple build.
|
||||
// TODO: eliminate UGLY HACK.
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
constexpr int phase = 224;
|
||||
#else
|
||||
constexpr int phase = 0;
|
||||
|
||||
@@ -228,36 +228,6 @@ template <typename TimeUnit> class VideoSwitches {
|
||||
return external_.annunciator_3;
|
||||
}
|
||||
|
||||
enum class CharacterROM {
|
||||
/// The ROM that shipped with both the Apple II and the II+.
|
||||
II,
|
||||
/// The ROM that shipped with the original IIe.
|
||||
IIe,
|
||||
/// The ROM that shipped with the Enhanced IIe.
|
||||
EnhancedIIe,
|
||||
/// The ROM that shipped with the IIgs.
|
||||
IIgs
|
||||
};
|
||||
|
||||
/// @returns A file-level description of @c rom.
|
||||
static ROMMachine::ROM rom_description(CharacterROM rom) {
|
||||
const std::string machine_name = "AppleII";
|
||||
switch(rom) {
|
||||
case CharacterROM::II:
|
||||
return ROMMachine::ROM(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
|
||||
|
||||
case CharacterROM::IIe:
|
||||
return ROMMachine::ROM(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
|
||||
|
||||
default: // To appease GCC.
|
||||
case CharacterROM::EnhancedIIe:
|
||||
return ROMMachine::ROM(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
|
||||
|
||||
case CharacterROM::IIgs:
|
||||
return ROMMachine::ROM(machine_name, "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the character ROM for this video output.
|
||||
void set_character_rom(const std::vector<uint8_t> &rom) {
|
||||
character_rom_ = rom;
|
||||
|
||||
@@ -38,11 +38,40 @@
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
|
||||
//
|
||||
// HEAVY WARNING: THIS IS INCOMPLETE AND VERY PROVISIONAL.
|
||||
//
|
||||
// You'll notice lots of random bits of debugging code sitting around but commented out.
|
||||
// Most of this will go when this machine is complete. Please look past the gross ugliness
|
||||
// of this code's intermediate state if you are able.
|
||||
//
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int CLOCK_RATE = 14'318'180;
|
||||
|
||||
class MemManagerChecker {
|
||||
// This is the first result that came up when searching for valid Apple IIgs BRAM states;
|
||||
// I'm unclear on its provenance.
|
||||
constexpr uint8_t default_bram[] = {
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0d, 0x06, 0x02, 0x01, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x07, 0x06, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x06, 0x06, 0x00, 0x05, 0x06,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
|
||||
0x07, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
|
||||
0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x96, 0x57, 0x3c,
|
||||
};
|
||||
|
||||
/*class MemManagerChecker {
|
||||
int handle_total_ = 0;
|
||||
bool dump_bank(const Apple::IIgs::MemoryMap &memory, const char *name, uint32_t address, bool print, uint32_t must_contain = 0xffffffff) {
|
||||
const auto handles = memory.regions[memory.region_map[0xe117]].read;
|
||||
@@ -149,7 +178,7 @@ class MemManagerChecker {
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
};*/
|
||||
|
||||
}
|
||||
|
||||
@@ -187,33 +216,26 @@ class ConcreteMachine:
|
||||
|
||||
set_clock_rate(double(CLOCK_RATE));
|
||||
speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider));
|
||||
clock_.ClockStorage::set_data(std::begin(default_bram), std::end(default_bram));
|
||||
|
||||
using Target = Analyser::Static::AppleIIgs::Target;
|
||||
std::vector<ROMMachine::ROM> rom_descriptions;
|
||||
const std::string machine_name = "AppleIIgs";
|
||||
ROM::Name system;
|
||||
switch(target.model) {
|
||||
case Target::Model::ROM00:
|
||||
/* TODO */
|
||||
case Target::Model::ROM01:
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0);
|
||||
break;
|
||||
|
||||
case Target::Model::ROM03:
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29);
|
||||
break;
|
||||
case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break;
|
||||
case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break;
|
||||
default: system = ROM::Name::AppleIIgsROM03; break;
|
||||
}
|
||||
rom_descriptions.push_back(video_->rom_description(Video::Video::CharacterROM::EnhancedIIe));
|
||||
constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter;
|
||||
constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03;
|
||||
|
||||
// TODO: pick a different ADB ROM for earlier machine revisions?
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0);
|
||||
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
if(!roms[0] || !roms[1] || !roms[2]) {
|
||||
ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
rom_ = *roms[0];
|
||||
video_->set_character_rom(*roms[1]);
|
||||
adb_glu_->set_microcontroller_rom(*roms[2]);
|
||||
rom_ = roms.find(system)->second;
|
||||
video_->set_character_rom(roms.find(characters)->second);
|
||||
adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second);
|
||||
|
||||
// Run only the currently-interesting self test.
|
||||
// rom_[0x36402] = 2;
|
||||
@@ -285,6 +307,11 @@ class ConcreteMachine:
|
||||
// std::srand(23);
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
// Prior to ROM03 there's no power-on bit.
|
||||
if(target.model != Target::Model::ROM03) {
|
||||
speed_register_ &= ~0x40;
|
||||
}
|
||||
|
||||
// Sync up initial values.
|
||||
memory_.set_speed_register(speed_register_ ^ 0x80);
|
||||
|
||||
@@ -356,6 +383,23 @@ class ConcreteMachine:
|
||||
static bool log = false;
|
||||
bool is_1Mhz = false;
|
||||
|
||||
// if(operation == CPU::WDC65816::BusOperation::ReadOpcode) {
|
||||
// if(address == 0xfe00d5) {
|
||||
// printf("");
|
||||
// }
|
||||
//
|
||||
// printf("%06x a:%04x x:%04x y:%04x s:%04x d:%04x b:%04x\n",
|
||||
// address,
|
||||
// m65816_.get_value_of_register(CPU::WDC65816::Register::A),
|
||||
// m65816_.get_value_of_register(CPU::WDC65816::Register::X),
|
||||
// m65816_.get_value_of_register(CPU::WDC65816::Register::Y),
|
||||
//// m65816_.get_value_of_register(CPU::WDC65816::Register::Flags),
|
||||
// m65816_.get_value_of_register(CPU::WDC65816::Register::StackPointer),
|
||||
// m65816_.get_value_of_register(CPU::WDC65816::Register::Direct),
|
||||
// m65816_.get_value_of_register(CPU::WDC65816::Register::DataBank)
|
||||
// );
|
||||
// }
|
||||
|
||||
if(operation == CPU::WDC65816::BusOperation::ReadVector && !(memory_.get_shadow_register()&0x40)) {
|
||||
// I think vector pulls always go to ROM?
|
||||
// That's slightly implied in the documentation, and doing so makes GS/OS boot, so...
|
||||
@@ -944,9 +988,9 @@ class ConcreteMachine:
|
||||
// }
|
||||
|
||||
if(operation == CPU::WDC65816::BusOperation::ReadOpcode) {
|
||||
if(total > 482342960 && total < 482352960 && address == 0xe10000) {
|
||||
printf("entry: %llu\n", static_cast<unsigned long long>(total));
|
||||
}
|
||||
// if(total > 482342960 && total < 482352960 && address == 0xe10000) {
|
||||
// printf("entry: %llu\n", static_cast<unsigned long long>(total));
|
||||
// }
|
||||
|
||||
// log |= address == 0xfc144f;
|
||||
// log &= !((address < 0xfc144f) || (address >= 0xfc1490));
|
||||
@@ -1107,7 +1151,7 @@ class ConcreteMachine:
|
||||
Audio::Toggle audio_toggle_;
|
||||
using AudioSource = Outputs::Speaker::CompoundSource<Apple::IIgs::Sound::GLU, Audio::Toggle>;
|
||||
AudioSource mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<AudioSource> speaker_;
|
||||
Outputs::Speaker::PullLowpass<AudioSource> speaker_;
|
||||
Cycles cycles_since_audio_update_;
|
||||
Cycles cycles_until_audio_event_;
|
||||
static constexpr int audio_divider = 16;
|
||||
@@ -1155,3 +1199,4 @@ Machine *Machine::AppleIIgs(const Analyser::Static::Target *target, const ROMMac
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
|
||||
@@ -241,7 +241,7 @@ class MemoryMap {
|
||||
friend AuxiliaryMemorySwitches;
|
||||
friend LanguageCardSwitches;
|
||||
|
||||
uint8_t shadow_register_ = 0x08;
|
||||
uint8_t shadow_register_ = 0x00;
|
||||
uint8_t speed_register_ = 0x00;
|
||||
|
||||
// MARK: - Memory banking.
|
||||
|
||||
@@ -182,7 +182,7 @@ void Video::advance(Cycles cycles) {
|
||||
if(column_start != FinalColumn) {
|
||||
output_row(row_start, column_start, FinalColumn);
|
||||
}
|
||||
for(int row = row_start+1; row < row_end; row++) {
|
||||
for(int row = row_start+1; row != row_end; row = (row + 1)%Lines) {
|
||||
output_row(row, 0, FinalColumn);
|
||||
}
|
||||
if(column_end) {
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Macintosh {
|
||||
struct DeferredAudio {
|
||||
Concurrency::DeferringAsyncTaskQueue queue;
|
||||
Audio audio;
|
||||
Outputs::Speaker::LowpassSpeaker<Audio> speaker;
|
||||
Outputs::Speaker::PullLowpass<Audio> speaker;
|
||||
HalfCycles time_since_update;
|
||||
|
||||
DeferredAudio() : audio(queue), speaker(audio) {}
|
||||
|
||||
@@ -21,19 +21,19 @@ namespace {
|
||||
pulse widths from the values stored into the PWM buffer.
|
||||
*/
|
||||
template<uint8_t value> constexpr uint8_t lfsr() {
|
||||
if constexpr (value == 0x20 || !value) return 0;
|
||||
return 1+lfsr<(((value ^ (value >> 1))&1) << 5) | (value >> 1)>();
|
||||
if constexpr (value == 0x20 || !value) return 0;
|
||||
return 1+lfsr<(((value ^ (value >> 1))&1) << 5) | (value >> 1)>();
|
||||
}
|
||||
|
||||
constexpr uint8_t pwm_lookup[] = {
|
||||
lfsr<0>(), lfsr<1>(), lfsr<2>(), lfsr<3>(), lfsr<4>(), lfsr<5>(), lfsr<6>(), lfsr<7>(),
|
||||
lfsr<8>(), lfsr<9>(), lfsr<10>(), lfsr<11>(), lfsr<12>(), lfsr<13>(), lfsr<14>(), lfsr<15>(),
|
||||
lfsr<16>(), lfsr<17>(), lfsr<18>(), lfsr<19>(), lfsr<20>(), lfsr<21>(), lfsr<22>(), lfsr<23>(),
|
||||
lfsr<24>(), lfsr<25>(), lfsr<26>(), lfsr<27>(), lfsr<28>(), lfsr<29>(), lfsr<30>(), lfsr<31>(),
|
||||
lfsr<32>(), lfsr<33>(), lfsr<34>(), lfsr<35>(), lfsr<36>(), lfsr<37>(), lfsr<38>(), lfsr<39>(),
|
||||
lfsr<40>(), lfsr<41>(), lfsr<42>(), lfsr<43>(), lfsr<44>(), lfsr<45>(), lfsr<46>(), lfsr<47>(),
|
||||
lfsr<48>(), lfsr<49>(), lfsr<50>(), lfsr<51>(), lfsr<52>(), lfsr<53>(), lfsr<54>(), lfsr<55>(),
|
||||
lfsr<56>(), lfsr<57>(), lfsr<58>(), lfsr<59>(), lfsr<60>(), lfsr<61>(), lfsr<62>(), lfsr<63>(),
|
||||
lfsr<0>(), lfsr<1>(), lfsr<2>(), lfsr<3>(), lfsr<4>(), lfsr<5>(), lfsr<6>(), lfsr<7>(),
|
||||
lfsr<8>(), lfsr<9>(), lfsr<10>(), lfsr<11>(), lfsr<12>(), lfsr<13>(), lfsr<14>(), lfsr<15>(),
|
||||
lfsr<16>(), lfsr<17>(), lfsr<18>(), lfsr<19>(), lfsr<20>(), lfsr<21>(), lfsr<22>(), lfsr<23>(),
|
||||
lfsr<24>(), lfsr<25>(), lfsr<26>(), lfsr<27>(), lfsr<28>(), lfsr<29>(), lfsr<30>(), lfsr<31>(),
|
||||
lfsr<32>(), lfsr<33>(), lfsr<34>(), lfsr<35>(), lfsr<36>(), lfsr<37>(), lfsr<38>(), lfsr<39>(),
|
||||
lfsr<40>(), lfsr<41>(), lfsr<42>(), lfsr<43>(), lfsr<44>(), lfsr<45>(), lfsr<46>(), lfsr<47>(),
|
||||
lfsr<48>(), lfsr<49>(), lfsr<50>(), lfsr<51>(), lfsr<52>(), lfsr<53>(), lfsr<54>(), lfsr<55>(),
|
||||
lfsr<56>(), lfsr<57>(), lfsr<58>(), lfsr<59>(), lfsr<60>(), lfsr<61>(), lfsr<62>(), lfsr<63>(),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ class Keyboard {
|
||||
/// so this may not be valid prior to Mode::PerformingCommand.
|
||||
int command_ = 0;
|
||||
/// Populated during PerformingCommand as the response to the most-recently-received command, this
|
||||
/// is then shifted out to teh host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
|
||||
/// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
|
||||
/// but not afterwards.
|
||||
int response_ = 0;
|
||||
|
||||
|
||||
@@ -49,6 +49,15 @@ namespace {
|
||||
|
||||
constexpr int CLOCK_RATE = 7833600;
|
||||
|
||||
|
||||
// Former default PRAM:
|
||||
//
|
||||
// 0xa8, 0x00, 0x00, 0x00,
|
||||
// 0xcc, 0x0a, 0xcc, 0x0a,
|
||||
// 0x00, 0x00, 0x00, 0x00,
|
||||
// 0x00, 0x02, 0x63, 0x00,
|
||||
// 0x03, 0x88, 0x00, 0x4c
|
||||
|
||||
}
|
||||
|
||||
namespace Apple {
|
||||
@@ -96,25 +105,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||
const std::string machine_name = "Macintosh";
|
||||
uint32_t ram_size, rom_size;
|
||||
std::vector<ROMMachine::ROM> rom_descriptions;
|
||||
ROM::Name rom_name;
|
||||
switch(model) {
|
||||
default:
|
||||
case Model::Mac128k:
|
||||
ram_size = 128*1024;
|
||||
rom_size = 64*1024;
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28);
|
||||
rom_name = ROM::Name::Macintosh128k;
|
||||
break;
|
||||
case Model::Mac512k:
|
||||
ram_size = 512*1024;
|
||||
rom_size = 64*1024;
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d);
|
||||
rom_name = ROM::Name::Macintosh512k;
|
||||
break;
|
||||
case Model::Mac512ke:
|
||||
case Model::MacPlus: {
|
||||
ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024;
|
||||
rom_size = 128*1024;
|
||||
const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
|
||||
rom_name = ROM::Name::MacintoshPlus;
|
||||
} break;
|
||||
}
|
||||
ram_mask_ = ram_size - 1;
|
||||
@@ -123,12 +131,12 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1);
|
||||
|
||||
// Grab a copy of the ROM and convert it into big-endian data.
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
if(!roms[0]) {
|
||||
ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
roms[0]->resize(rom_size);
|
||||
Memory::PackBigEndian16(*roms[0], rom_);
|
||||
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
|
||||
|
||||
// Randomise memory contents.
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
@@ -35,7 +35,7 @@ Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulato
|
||||
// The [newer] Metal scan target has no such issue. So assume that Apple => Metal,
|
||||
// and set a visible area to work around the OpenGL issue if required.
|
||||
// TODO: eliminate UGLY HACK.
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, 10.0f / 368.0f, 0.82f, 344.0f / 368.0f));
|
||||
#else
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
|
||||
|
||||
@@ -41,7 +41,7 @@ class Bus {
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
TIASound tia_sound_;
|
||||
Outputs::Speaker::LowpassSpeaker<TIASound> speaker_;
|
||||
Outputs::Speaker::PullLowpass<TIASound> speaker_;
|
||||
|
||||
// joystick state
|
||||
uint8_t tia_input_value_[2] = {0xff, 0xff};
|
||||
|
||||
@@ -105,7 +105,7 @@ class Pitfall2: public BusExtender {
|
||||
|
||||
int table_position = 0;
|
||||
for(int c = 0; c < 3; c++) {
|
||||
audio_channel_[c] = (audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]);
|
||||
audio_channel_[c] = uint8_t((audio_channel_[c] + cycles_to_run_for) % (1 + top_[5 + c]));
|
||||
if((featcher_address_[5 + c] & 0x1000) && ((top_[5 + c] - audio_channel_[c]) > bottom_[5 + c])) {
|
||||
table_position |= 0x4 >> c;
|
||||
}
|
||||
|
||||
@@ -74,15 +74,13 @@ class ConcreteMachine:
|
||||
video_->set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size());
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
std::vector<ROMMachine::ROM> rom_descriptions = {
|
||||
{"AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64}
|
||||
// {"AtariST", "the UK TOS 1.04 ROM", "tos104.img", 192*1024, 0xa50d1d43}
|
||||
};
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
if(!roms[0]) {
|
||||
constexpr ROM::Name rom_name = ROM::Name::AtariSTTOS100;
|
||||
ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
Memory::PackBigEndian16(*roms[0], rom_);
|
||||
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
|
||||
|
||||
// Set up basic memory map.
|
||||
memory_map_[0] = BusDevice::MostlyRAM;
|
||||
@@ -178,7 +176,7 @@ class ConcreteMachine:
|
||||
|
||||
// An interrupt acknowledge, perhaps?
|
||||
if(cycle.operation & Microcycle::InterruptAcknowledge) {
|
||||
// Current implementation: everything other than 6 (i.e. the MFP is autovectored.
|
||||
// Current implementation: everything other than 6 (i.e. the MFP) is autovectored.
|
||||
const int interrupt_level = cycle.word_address()&7;
|
||||
if(interrupt_level != 6) {
|
||||
video_interrupts_pending_ &= ~interrupt_level;
|
||||
@@ -485,7 +483,7 @@ class ConcreteMachine:
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Outputs::Speaker::LowpassSpeaker<GI::AY38910::AY38910<false>> speaker_;
|
||||
Outputs::Speaker::PullLowpass<GI::AY38910::AY38910<false>> speaker_;
|
||||
HalfCycles cycles_since_audio_update_;
|
||||
|
||||
JustInTimeActor<DMAController> dma_;
|
||||
|
||||
@@ -252,7 +252,7 @@ void DMAController::set_component_prefers_clocking(ClockingHint::Source *, Clock
|
||||
}
|
||||
|
||||
ClockingHint::Preference DMAController::preferred_clocking() const {
|
||||
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
|
||||
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::JustInTime : ClockingHint::Preference::RealTime;
|
||||
}
|
||||
|
||||
void DMAController::set_activity_observer(Activity::Observer *observer) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
using namespace Atari::ST;
|
||||
|
||||
IntelligentKeyboard::IntelligentKeyboard(Serial::Line &input, Serial::Line &output) : output_line_(output) {
|
||||
IntelligentKeyboard::IntelligentKeyboard(Serial::Line<false> &input, Serial::Line<false> &output) : output_line_(output) {
|
||||
input.set_read_delegate(this, Storage::Time(2, 15625));
|
||||
output_line_.set_writer_clock_rate(15625);
|
||||
|
||||
@@ -24,7 +24,7 @@ IntelligentKeyboard::IntelligentKeyboard(Serial::Line &input, Serial::Line &outp
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
}
|
||||
|
||||
bool IntelligentKeyboard::serial_line_did_produce_bit(Serial::Line *, int bit) {
|
||||
bool IntelligentKeyboard::serial_line_did_produce_bit(Serial::Line<false> *, int bit) {
|
||||
// Shift.
|
||||
command_ = (command_ >> 1) | (bit << 9);
|
||||
|
||||
@@ -68,8 +68,8 @@ void IntelligentKeyboard::run_for(HalfCycles duration) {
|
||||
mouse_position_[1] += mouse_y_multiplier_ * scaled_movement[1];
|
||||
|
||||
// Clamp to range.
|
||||
mouse_position_[0] = std::min(std::max(mouse_position_[0], 0), mouse_range_[0]);
|
||||
mouse_position_[1] = std::min(std::max(mouse_position_[1], 0), mouse_range_[1]);
|
||||
mouse_position_[0] = std::clamp(mouse_position_[0], 0, mouse_range_[0]);
|
||||
mouse_position_[1] = std::clamp(mouse_position_[1], 0, mouse_range_[1]);
|
||||
|
||||
mouse_movement_[0] -= scaled_movement[0] * mouse_scale_[0];
|
||||
mouse_movement_[1] -= scaled_movement[1] * mouse_scale_[1];
|
||||
@@ -157,7 +157,7 @@ void IntelligentKeyboard::dispatch_command(uint8_t command) {
|
||||
// If not, exit. If so, perform and drop out of the switch.
|
||||
switch(command_sequence_.front()) {
|
||||
default:
|
||||
printf("Unrecognised IKBD command %02x\n", command);
|
||||
LOG("Unrecognised IKBD command " << PADHEX(2) << +command);
|
||||
break;
|
||||
|
||||
case 0x80:
|
||||
|
||||
@@ -53,11 +53,11 @@ static_assert(uint16_t(Key::KeypadEnter) == 0x72, "KeypadEnter should have key c
|
||||
keyboard input and output and mouse handling.
|
||||
*/
|
||||
class IntelligentKeyboard:
|
||||
public Serial::Line::ReadDelegate,
|
||||
public Serial::Line<false>::ReadDelegate,
|
||||
public ClockingHint::Source,
|
||||
public Inputs::Mouse {
|
||||
public:
|
||||
IntelligentKeyboard(Serial::Line &input, Serial::Line &output);
|
||||
IntelligentKeyboard(Serial::Line<false> &input, Serial::Line<false> &output);
|
||||
ClockingHint::Preference preferred_clocking() const final;
|
||||
void run_for(HalfCycles duration);
|
||||
|
||||
@@ -78,10 +78,10 @@ class IntelligentKeyboard:
|
||||
// MARK: - Serial line state.
|
||||
int bit_count_ = 0;
|
||||
int command_ = 0;
|
||||
Serial::Line &output_line_;
|
||||
Serial::Line<false> &output_line_;
|
||||
|
||||
void output_bytes(std::initializer_list<uint8_t> value);
|
||||
bool serial_line_did_produce_bit(Serial::Line *, int bit) final;
|
||||
bool serial_line_did_produce_bit(Serial::Line<false> *, int bit) final;
|
||||
|
||||
// MARK: - Command dispatch.
|
||||
std::vector<uint8_t> command_sequence_;
|
||||
|
||||
@@ -127,15 +127,13 @@ class ConcreteMachine:
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
|
||||
const auto roms = rom_fetcher(
|
||||
{ {"ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3} });
|
||||
|
||||
if(!roms[0]) {
|
||||
constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS;
|
||||
const ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
bios_ = *roms[0];
|
||||
bios_.resize(8192);
|
||||
bios_ = roms.find(rom_name)->second;
|
||||
|
||||
if(!target.media.cartridges.empty()) {
|
||||
const auto &segment = target.media.cartridges.front()->get_segments().front();
|
||||
@@ -383,7 +381,7 @@ class ConcreteMachine:
|
||||
TI::SN76489 sn76489_;
|
||||
GI::AY38910::AY38910<false> ay_;
|
||||
Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
|
||||
Outputs::Speaker::PullLowpass<Outputs::Speaker::CompoundSource<TI::SN76489, GI::AY38910::AY38910<false>>> speaker_;
|
||||
|
||||
std::vector<uint8_t> bios_;
|
||||
std::vector<uint8_t> cartridge_;
|
||||
|
||||
@@ -41,7 +41,8 @@ namespace C1540 {
|
||||
*/
|
||||
class Machine final: public MachineBase {
|
||||
public:
|
||||
Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
static ROM::Request rom_request(Personality personality);
|
||||
Machine(Personality personality, const ROM::Map &roms);
|
||||
|
||||
/*!
|
||||
Sets the serial bus to which this drive should attach itself.
|
||||
|
||||
@@ -16,7 +16,15 @@
|
||||
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
ROM::Request Machine::rom_request(Personality personality) {
|
||||
switch(personality) {
|
||||
default:
|
||||
case Personality::C1540: return ROM::Request(ROM::Name::Commodore1540);
|
||||
case Personality::C1541: return ROM::Request(ROM::Name::Commodore1541);
|
||||
}
|
||||
}
|
||||
|
||||
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
|
||||
Storage::Disk::Controller(1000000),
|
||||
m6502_(*this),
|
||||
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
||||
@@ -39,28 +47,22 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &
|
||||
emplace_drive(1000000, 300, 2);
|
||||
set_drive(1);
|
||||
|
||||
std::string device_name;
|
||||
uint32_t crc = 0;
|
||||
ROM::Name rom_name;
|
||||
switch(personality) {
|
||||
case Personality::C1540:
|
||||
device_name = "1540";
|
||||
crc = 0x718d42b1;
|
||||
break;
|
||||
case Personality::C1541:
|
||||
device_name = "1541";
|
||||
crc = 0xfb760019;
|
||||
break;
|
||||
default:
|
||||
case Personality::C1540: rom_name = ROM::Name::Commodore1540; break;
|
||||
case Personality::C1541: rom_name = ROM::Name::Commodore1541; break;
|
||||
}
|
||||
|
||||
auto roms = rom_fetcher({ {"Commodore1540", "the " + device_name + " ROM", device_name + ".bin", 16*1024, crc} });
|
||||
if(!roms[0]) {
|
||||
auto rom = roms.find(rom_name);
|
||||
if(rom == roms.end()) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
|
||||
std::memcpy(rom_, roms.find(rom_name)->second.data(), std::min(sizeof(rom_), roms.find(rom_name)->second.size()));
|
||||
}
|
||||
|
||||
Machine::Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
MachineBase(personality, rom_fetcher) {}
|
||||
Machine::Machine(Personality personality, const ROM::Map &roms) :
|
||||
MachineBase(personality, roms) {}
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
|
||||
@@ -127,7 +127,7 @@ class MachineBase:
|
||||
public Storage::Disk::Controller {
|
||||
|
||||
public:
|
||||
MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
MachineBase(Personality personality, const ROM::Map &roms);
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
|
||||
@@ -151,5 +151,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
return table_lookup_sequence_for_character(key_sequences, character);
|
||||
}
|
||||
|
||||
@@ -316,53 +316,48 @@ class ConcreteMachine:
|
||||
// Install a joystick.
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
|
||||
const std::string machine_name = "Vic20";
|
||||
std::vector<ROMMachine::ROM> rom_names = {
|
||||
{machine_name, "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1}
|
||||
};
|
||||
ROM::Request request(ROM::Name::Vic20BASIC);
|
||||
ROM::Name kernel, character;
|
||||
switch(target.region) {
|
||||
default:
|
||||
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
|
||||
rom_names.emplace_back(machine_name, "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4);
|
||||
character = ROM::Name::Vic20EnglishCharacters;
|
||||
kernel = ROM::Name::Vic20EnglishPALKernel;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::American:
|
||||
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
|
||||
rom_names.emplace_back(machine_name, "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174);
|
||||
character = ROM::Name::Vic20EnglishCharacters;
|
||||
kernel = ROM::Name::Vic20EnglishNTSCKernel;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Danish:
|
||||
rom_names.emplace_back(machine_name, "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454);
|
||||
rom_names.emplace_back(machine_name, "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16);
|
||||
character = ROM::Name::Vic20DanishCharacters;
|
||||
kernel = ROM::Name::Vic20DanishKernel;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Japanese:
|
||||
rom_names.emplace_back(machine_name, "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4b);
|
||||
rom_names.emplace_back(machine_name, "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7);
|
||||
character = ROM::Name::Vic20JapaneseCharacters;
|
||||
kernel = ROM::Name::Vic20JapaneseKernel;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Swedish:
|
||||
rom_names.emplace_back(machine_name, "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551d);
|
||||
rom_names.emplace_back(machine_name, "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662);
|
||||
character = ROM::Name::Vic20SwedishCharacters;
|
||||
kernel = ROM::Name::Vic20SwedishKernel;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto roms = rom_fetcher(rom_names);
|
||||
if(target.has_c1540) {
|
||||
request = request && Commodore::C1540::Machine::rom_request(Commodore::C1540::Personality::C1540);
|
||||
}
|
||||
request = request && ROM::Request(character) && ROM::Request(kernel);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
if(!rom) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
basic_rom_ = std::move(*roms[0]);
|
||||
character_rom_ = std::move(*roms[1]);
|
||||
kernel_rom_ = std::move(*roms[2]);
|
||||
|
||||
// Characters ROMs should be 4kb.
|
||||
character_rom_.resize(4096);
|
||||
// Kernel ROMs and the BASIC ROM should be 8kb.
|
||||
kernel_rom_.resize(8192);
|
||||
basic_rom_ = std::move(roms.find(ROM::Name::Vic20BASIC)->second);
|
||||
character_rom_ = std::move(roms.find(character)->second);
|
||||
kernel_rom_ = std::move(roms.find(kernel)->second);
|
||||
|
||||
if(target.has_c1540) {
|
||||
// construct the 1540
|
||||
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, rom_fetcher);
|
||||
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, roms);
|
||||
|
||||
// attach it to the serial bus
|
||||
c1540_->set_serial_bus(serial_bus_);
|
||||
|
||||
@@ -69,37 +69,25 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
|
||||
speaker_.set_high_frequency_cutoff(6000);
|
||||
|
||||
const std::string machine_name = "Electron";
|
||||
std::vector<ROMMachine::ROM> required_roms = {
|
||||
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
|
||||
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
|
||||
};
|
||||
const size_t pres_adfs_rom_position = required_roms.size();
|
||||
::ROM::Request request = ::ROM::Request(::ROM::Name::AcornBASICII) && ::ROM::Request(::ROM::Name::AcornElectronMOS100);
|
||||
if(target.has_pres_adfs) {
|
||||
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993);
|
||||
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e);
|
||||
request = request && ::ROM::Request(::ROM::Name::PRESADFSSlot1) && ::ROM::Request(::ROM::Name::PRESADFSSlot2);
|
||||
}
|
||||
const size_t acorn_adfs_rom_position = required_roms.size();
|
||||
if(target.has_acorn_adfs) {
|
||||
required_roms.emplace_back(machine_name, "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6);
|
||||
request = request && ::ROM::Request(::ROM::Name::AcornADFS);
|
||||
}
|
||||
const size_t dfs_rom_position = required_roms.size();
|
||||
if(target.has_dfs) {
|
||||
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
|
||||
request = request && ::ROM::Request(::ROM::Name::Acorn1770DFS);
|
||||
}
|
||||
const size_t ap6_rom_position = required_roms.size();
|
||||
if(target.has_ap6_rom) {
|
||||
required_roms.emplace_back(machine_name, "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfc);
|
||||
request = request && ::ROM::Request(::ROM::Name::PRESAdvancedPlus6);
|
||||
}
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
if(!rom) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
set_rom(ROM::BASIC, *roms[0], false);
|
||||
set_rom(ROM::OS, *roms[1], false);
|
||||
set_rom(ROM::BASIC, roms.find(::ROM::Name::AcornBASICII)->second, false);
|
||||
set_rom(ROM::OS, roms.find(::ROM::Name::AcornElectronMOS100)->second, false);
|
||||
|
||||
/*
|
||||
ROM slot mapping applied:
|
||||
@@ -115,19 +103,18 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
plus3_ = std::make_unique<Plus3>();
|
||||
|
||||
if(target.has_dfs) {
|
||||
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
|
||||
set_rom(ROM::Slot0, roms.find(::ROM::Name::Acorn1770DFS)->second, true);
|
||||
}
|
||||
if(target.has_pres_adfs) {
|
||||
set_rom(ROM::Slot4, *roms[pres_adfs_rom_position], true);
|
||||
set_rom(ROM::Slot5, *roms[pres_adfs_rom_position+1], true);
|
||||
set_rom(ROM::Slot4, roms.find(::ROM::Name::PRESADFSSlot1)->second, true);
|
||||
set_rom(ROM::Slot5, roms.find(::ROM::Name::PRESADFSSlot2)->second, true);
|
||||
}
|
||||
if(target.has_acorn_adfs) {
|
||||
set_rom(ROM::Slot6, *roms[acorn_adfs_rom_position], true);
|
||||
set_rom(ROM::Slot6, roms.find(::ROM::Name::AcornADFS)->second, true);
|
||||
}
|
||||
}
|
||||
|
||||
if(target.has_ap6_rom) {
|
||||
set_rom(ROM::Slot15, *roms[ap6_rom_position], true);
|
||||
set_rom(ROM::Slot15, roms.find(::ROM::Name::PRESAdvancedPlus6)->second, true);
|
||||
}
|
||||
|
||||
if(target.has_sideways_ram) {
|
||||
@@ -620,7 +607,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
activity_observer_ = observer;
|
||||
if(activity_observer_) {
|
||||
activity_observer_->register_led(caps_led);
|
||||
activity_observer_->register_led(caps_led, Activity::Observer::LEDPresentation::Persistent);
|
||||
activity_observer_->set_led_status(caps_led, caps_led_state_);
|
||||
}
|
||||
|
||||
@@ -781,7 +768,7 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
SoundGenerator sound_generator_;
|
||||
Outputs::Speaker::LowpassSpeaker<SoundGenerator> speaker_;
|
||||
Outputs::Speaker::PullLowpass<SoundGenerator> speaker_;
|
||||
|
||||
bool speaker_is_enabled_ = false;
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
return table_lookup_sequence_for_character(key_sequences, character);
|
||||
}
|
||||
|
||||
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user